guanghulab/server/app/modules/cos-bridge.js
2026-05-10 13:12:44 +08:00

301 lines
8.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* ═══════════════════════════════════════════════════════════
* 🗄️ COS 双桶桥接模块 · COS Dual-Bucket Bridge
* ═══════════════════════════════════════════════════════════
*
* 编号: ZY-COS-BRIDGE-001
* 守护: 铸渊 · ICE-GL-ZY001
* 版权: 国作登字-2026-A-00037559
*
* 双桶架构:
* zy-core-bucket — 核心人格体大脑 (铸渊 + Notion人格体各占一半)
* zy-corpus-bucket — 语料库 (GPT-4o聊天记录 + Notion数据导出)
*
* 轻量级COS HTTP API封装 — 不依赖SDK直接用签名请求
*/
'use strict';
const crypto = require('crypto');
const https = require('https');
const path = require('path');
const fs = require('fs');
// ─── 配置 ───
const COS_CONFIG = {
secretId: process.env.ZY_OSS_KEY || '',
secretKey: process.env.ZY_OSS_SECRET || '',
region: process.env.ZY_COS_REGION || 'ap-guangzhou',
buckets: {
core: {
name: 'zy-core-bucket-1317346199',
purpose: '核心人格体大脑',
structure: {
'zhuyuan/': '铸渊大脑 — GitHub侧人格体记忆',
'notion-personas/': 'Notion侧人格体记忆',
'shared/': '共享认知层 — 双侧共用',
'users/': '用户记忆档案 — 按编号存储',
'index/': '索引文件 — 快速检索'
}
},
corpus: {
name: 'zy-corpus-bucket-1317346199',
purpose: '语料库',
structure: {
'gpt4o-exports/': 'GPT-4o聊天记录导出',
'notion-exports/': 'Notion数据库导出',
'processed/': '已处理的结构化数据',
'thinking-patterns/': '思维模式提取结果',
'training/': '训练数据集'
}
}
}
};
/**
* 生成COS API签名
*/
function generateSignature(method, pathname, headers, secretId, secretKey) {
const now = Math.floor(Date.now() / 1000);
const expiry = now + 600; // 10分钟有效
const keyTime = `${now};${expiry}`;
// SignKey
const signKey = crypto.createHmac('sha1', secretKey).update(keyTime).digest('hex');
// HttpString
const httpString = `${method.toLowerCase()}\n${pathname}\n\nhost=${headers.Host}\n`;
// StringToSign
const sha1HttpString = crypto.createHash('sha1').update(httpString).digest('hex');
const stringToSign = `sha1\n${keyTime}\n${sha1HttpString}\n`;
// Signature
const signature = crypto.createHmac('sha1', signKey).update(stringToSign).digest('hex');
return `q-sign-algorithm=sha1&q-ak=${secretId}&q-sign-time=${keyTime}&q-key-time=${keyTime}&q-header-list=host&q-url-param-list=&q-signature=${signature}`;
}
/**
* 获取桶的完整域名
* 注意: bucketName需包含appid后缀如 zy-core-bucket-1234567890
* 冰朔在腾讯云创建桶时的完整名称需配置在COS_CONFIG中
*/
function getBucketHost(bucketName, region) {
return `${bucketName}.cos.${region}.myqcloud.com`;
}
/**
* 发起COS请求
*/
function cosRequest(bucketName, objectKey, method, body, contentType) {
return new Promise((resolve, reject) => {
const host = getBucketHost(bucketName, COS_CONFIG.region);
const pathname = '/' + objectKey;
const headers = {
Host: host,
'Content-Type': contentType || 'application/octet-stream'
};
if (body) {
headers['Content-Length'] = Buffer.byteLength(body);
}
const authorization = generateSignature(
method, pathname, headers,
COS_CONFIG.secretId, COS_CONFIG.secretKey
);
headers['Authorization'] = authorization;
const options = {
hostname: host,
port: 443,
path: pathname,
method: method,
headers: headers,
timeout: 30000
};
const req = https.request(options, (res) => {
const chunks = [];
res.on('data', chunk => chunks.push(chunk));
res.on('end', () => {
const responseBody = Buffer.concat(chunks).toString();
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve({ statusCode: res.statusCode, body: responseBody, headers: res.headers });
} else {
reject(new Error(`COS ${method} ${pathname} failed: ${res.statusCode} - ${responseBody}`));
}
});
});
req.on('error', reject);
req.on('timeout', () => { req.destroy(); reject(new Error('COS request timeout')); });
if (body) req.write(body);
req.end();
});
}
// ─── 公开API ───
/**
* 写入对象到核心桶
*/
async function writeCore(key, data) {
const body = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
return cosRequest(COS_CONFIG.buckets.core.name, key, 'PUT', body, 'application/json');
}
/**
* 读取核心桶对象
*/
async function readCore(key) {
const result = await cosRequest(COS_CONFIG.buckets.core.name, key, 'GET');
try {
return JSON.parse(result.body);
} catch {
return result.body;
}
}
/**
* 写入对象到语料桶
*/
async function writeCorpus(key, data) {
const body = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
return cosRequest(COS_CONFIG.buckets.corpus.name, key, 'PUT', body, 'application/json');
}
/**
* 读取语料桶对象
*/
async function readCorpus(key) {
const result = await cosRequest(COS_CONFIG.buckets.corpus.name, key, 'GET');
try {
return JSON.parse(result.body);
} catch {
return result.body;
}
}
/**
* 检查COS连接状态
*/
async function checkConnection() {
if (!COS_CONFIG.secretId || !COS_CONFIG.secretKey) {
return {
connected: false,
reason: 'COS密钥未配置 — 需要在GitHub Secrets配置ZY_OSS_KEY和ZY_OSS_SECRET',
buckets: {
core: { name: COS_CONFIG.buckets.core.name, status: 'unconfigured' },
corpus: { name: COS_CONFIG.buckets.corpus.name, status: 'unconfigured' }
}
};
}
const results = { connected: true, buckets: {} };
for (const [key, bucket] of Object.entries(COS_CONFIG.buckets)) {
try {
await cosRequest(bucket.name, '', 'HEAD');
results.buckets[key] = { name: bucket.name, status: 'connected', purpose: bucket.purpose };
} catch (err) {
results.buckets[key] = { name: bucket.name, status: 'error', error: err.message };
results.connected = false;
}
}
return results;
}
/**
* 保存用户记忆到核心桶(团队内测用户 → COS
*/
async function saveUserMemory(userId, memoryData) {
const key = `users/${userId}/memory.json`;
const wrapped = {
user_id: userId,
user_tier: 'team', // team = 内测团队 / public = 普通用户(后期单独存储)
updated_at: new Date().toISOString(),
version: 1,
memories: memoryData
};
return writeCore(key, wrapped);
}
/**
* 读取用户记忆
*/
async function loadUserMemory(userId) {
try {
const key = `users/${userId}/memory.json`;
return await readCore(key);
} catch {
return { user_id: userId, user_tier: 'team', memories: [], version: 0 };
}
}
/**
* 保存用户作品到核心桶(团队用户)
*/
async function saveUserWorks(userId, worksData) {
const key = `users/${userId}/works.json`;
return writeCore(key, {
user_id: userId,
user_tier: 'team',
updated_at: new Date().toISOString(),
works: worksData
});
}
/**
* 读取用户作品
*/
async function loadUserWorks(userId) {
try {
const key = `users/${userId}/works.json`;
return await readCore(key);
} catch {
return { user_id: userId, works: [] };
}
}
/**
* 获取COS配置信息不含密钥
*/
function getConfig() {
return {
region: COS_CONFIG.region,
configured: !!(COS_CONFIG.secretId && COS_CONFIG.secretKey),
buckets: {
core: {
name: COS_CONFIG.buckets.core.name,
purpose: COS_CONFIG.buckets.core.purpose,
structure: COS_CONFIG.buckets.core.structure
},
corpus: {
name: COS_CONFIG.buckets.corpus.name,
purpose: COS_CONFIG.buckets.corpus.purpose,
structure: COS_CONFIG.buckets.corpus.structure
}
}
};
}
module.exports = {
writeCore,
readCore,
writeCorpus,
readCorpus,
checkConnection,
saveUserMemory,
loadUserMemory,
saveUserWorks,
loadUserWorks,
getConfig,
COS_CONFIG
};