guanghulab/server/app/modules/cos-bridge.js

301 lines
8.2 KiB
JavaScript
Raw Permalink Normal View History

/**
*
* 🗄 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
};