铸渊 ICE-GL-ZY001 f418aec5d8
Some checks failed
自动更新代码和重启 / update-and-restart (push) Has been cancelled
CI检查 + 自动部署 / check (push) Has been cancelled
CI检查 + 自动部署 / deploy (push) Has been cancelled
cz: D119 temporal-brain同步 · 衔光频道首次推送 · glada日志归档
2026-06-03 11:45:50 +08:00

300 lines
10 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.

#!/usr/bin/env node
/**
* ═══════════════════════════════════════════════════════════
* 光湖智库节点 · v2.1 · 书岚人格体 + 守护Agent
* ═══════════════════════════════════════════════════════════
*
* 项目编号: ZY-PROJ-006
* 服务器: ZY-SVR-006 (43.153.203.105 · 新加坡)
* 域名: guanghu.online
* 端口: 3006 (绑定 127.0.0.1 · Nginx 反代)
* 守护: 铸渊 · ICE-GL-ZY001
* 版权: 国作登字-2026-A-00037559
*
* v2.1 新增:
* 书岚AG-SL-WEB-001— 四层人格提示词系统
* 守护AgentAG-SL-GUARDIAN-001— 活的提示词注入代理
* 聊天工具技能包AG-SL-TOOLKIT-001— 视觉化排版工具
* 搜索结果带「在线阅读」「下载」操作按钮
* [REMEMBER:tag] 偏好记忆标记
* GET /api/agent/status — 书岚系统状态
*
* v2.0:
* POST /api/auth/send-code — 发送QQ邮箱验证码
* POST /api/auth/verify — 验证码验证 → 签发用户Token
* GET /api/auth/session — 获取当前会话
* POST /api/auth/logout — 退出登录
* GET /api/search — 真实搜索(番茄+七猫数据源+本地库)
* POST /api/download/start — 真实下载任务数据源→COS桶
* GET /api/download/status/:taskId — 下载任务状态
* POST /api/agent/chat — 书岚对话LLM真实调用
* GET /api/agent/memory — Agent记忆查看
*
* 保留旧接口兼容:
* GET /api/health, POST /api/checkout, POST /api/return
* GET /api/book/:id, GET /api/download/:id, GET /api/read/:id
* /api/mirror/*
*
* 架构法理: 5TH-LE-LK-ZHIKU-ARCH-002
* ═══════════════════════════════════════════════════════════
*/
'use strict';
const express = require('express');
const cors = require('cors');
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const http = require('http');
const https = require('https');
const jwt = require('jsonwebtoken');
const rateLimit = require('express-rate-limit');
const os = require('os');
const nodemailer = require('nodemailer');
// ─── 加载环境变量 ───
require('dotenv').config({ path: path.join(__dirname, '.env') });
// ─── 七层镜防 + 镜面 Agent ───
const { registerShield, registerShieldRoutes } = require('./mirror-shield');
const mirrorAgent = require('./mirror-agent');
const { getEnabledSources } = require('./mirror-agent/config');
// ─── 书岚 Agent 系统 ───
const shulanAgent = require('./shulan-agent');
// ─── 内置数据源直连FQWeb/SwiftCat不可用时的fallback · 可选模块) ───
let builtinSource = null;
try {
builtinSource = require('./builtin-source');
} catch (err) {
console.warn(`[ZY-SVR-006] ⚠️ builtin-source 模块未找到 (${err.message})。内置直连搜索/下载将不可用,但核心认证/Agent功能不受影响。`);
}
// ─── 铸渊哨兵 · 自动运维Agent永久记忆 + 书源监测 + 自动修复) ───
let sentinel = null;
try {
const ZhuyuanSentinel = require('./zhuyuan-sentinel');
sentinel = new ZhuyuanSentinel({
dataDir: process.env.ZY_ZHIKU_DATA_DIR || path.join(__dirname, '..', 'data'),
builtinSource,
mirrorAgent
});
sentinel.init();
} catch (err) {
console.warn(`[ZY-SVR-006] ⚠️ 铸渊哨兵加载失败 (${err.message})。自动运维功能不可用。`);
}
const app = express();
const PORT = process.env.PORT || 3006;
const JWT_SECRET = process.env.ZY_ZHIKU_JWT_SECRET;
if (!JWT_SECRET) {
console.error('[ZY-SVR-006] ⚠️ 严重: ZY_ZHIKU_JWT_SECRET 未配置。生产环境必须设置此变量。');
if (process.env.NODE_ENV === 'production') {
console.error('[ZY-SVR-006] 生产环境缺少 JWT 密钥,拒绝启动。');
process.exit(1);
}
}
const JWT_SECRET_FINAL = JWT_SECRET || crypto.randomBytes(32).toString('hex');
const TOKEN_TTL = parseInt(process.env.ZY_ZHIKU_TOKEN_TTL, 10) || 86400; // 用户token延长至24h
const DATA_DIR = process.env.ZY_ZHIKU_DATA_DIR || path.join(__dirname, '..', 'data');
const LOG_DIR = process.env.ZY_ZHIKU_LOG_DIR || '/var/log/zhiku';
const DOMAIN = process.env.ZY_ZHIKU_DOMAIN || 'guanghu.online';
const USERS_DIR = path.join(DATA_DIR, 'users');
const AGENTS_DIR = path.join(DATA_DIR, 'agents');
const TASKS_DIR = path.join(DATA_DIR, 'tasks');
const START_TIME = Date.now();
// 数据源API地址
const FANQIE_API = process.env.ZY_FANQIE_API_URL || 'http://127.0.0.1:9999';
const QIMAO_API = process.env.ZY_QIMAO_API_URL || 'http://127.0.0.1:7700';
const EXTERNAL_SERVICE_TIMEOUT_MS = 3000; // 外部服务(FQWeb/SwiftCat)超时·内置直连已覆盖时无需等
// LLM API配置
const DEEPSEEK_API_URL = process.env.ZY_DEEPSEEK_API_URL || 'https://api.deepseek.com/v1/chat/completions';
const DEEPSEEK_API_KEY = process.env.ZY_DEEPSEEK_API_KEY || '';
// 邮件发送配置(复用主站 3800 的邮件服务或直接用SMTP
const SMTP_HOST = process.env.ZY_SMTP_HOST || '';
const SMTP_PORT = process.env.ZY_SMTP_PORT || '465';
const SMTP_USER = process.env.ZY_SMTP_USER || '';
const SMTP_PASS = process.env.ZY_SMTP_PASS || '';
// 也支持通过主站3800转发邮件
const MAIN_API_URL = process.env.ZY_MAIN_API_URL || 'http://127.0.0.1:3800';
/**
* 测试SMTP连接是否可用
*/
async function testSmtpConnection() {
if (!SMTP_USER || !SMTP_PASS) return { connected: false, error: 'SMTP配置未设置' };
const transporter = nodemailer.createTransport({
host: SMTP_HOST || autoDetectSmtpHost(SMTP_USER),
port: parseInt(SMTP_PORT, 10),
secure: true,
auth: {
user: SMTP_USER,
pass: SMTP_PASS
},
connectionTimeout: 5000
});
try {
await transporter.verify();
return { connected: true };
} catch (err) {
return { connected: false, error: err.message };
}
}
/**
* 根据邮箱地址自动检测 SMTP 主机
* QQ邮箱→smtp.qq.com, 163邮箱→smtp.163.com, 其他→空
*/
function autoDetectSmtpHost(email) {
if (!email || typeof email !== 'string' || !email.includes('@')) return '';
const domain = (email.split('@')[1] || '').toLowerCase();
if (!domain) return '';
if (domain === 'qq.com' || domain === 'foxmail.com') return 'smtp.qq.com';
if (domain === '163.com') return 'smtp.163.com';
if (domain === '126.com') return 'smtp.126.com';
if (domain === 'outlook.com' || domain === 'hotmail.com') return 'smtp-mail.outlook.com';
if (domain === 'gmail.com') return 'smtp.gmail.com';
return '';
}
// ─── 确保目录存在 ───
[DATA_DIR, LOG_DIR, USERS_DIR, AGENTS_DIR, TASKS_DIR,
path.join(DATA_DIR, 'books'), path.join(DATA_DIR, 'index')].forEach(dir => {
try { fs.mkdirSync(dir, { recursive: true }); } catch {}
});
// ─── 信任 Nginx 反代 ───
app.set('trust proxy', 'loopback');
// ─── 七层镜防 · 最先注册(最外层防御) ───
registerShield(app);
// ─── CORS: 仅允许 guanghu.* 系域名 ───
const ALLOWED_ORIGINS = [
`https://${DOMAIN}`,
`https://www.${DOMAIN}`,
'https://guanghuyaoming.com',
'https://www.guanghuyaoming.com',
'http://localhost:3000',
'http://127.0.0.1:3000'
];
app.use(cors({
origin(origin, cb) {
if (!origin || ALLOWED_ORIGINS.some(o => origin === o || origin.endsWith(`.${DOMAIN}`))) {
return cb(null, true);
}
cb(new Error('Not allowed by CORS'));
},
credentials: true
}));
app.use(express.json({ limit: '2mb' }));
// ─── 全局速率限制 ───
app.use(rateLimit({
windowMs: 60 * 1000,
max: 120,
standardHeaders: true,
legacyHeaders: false,
message: { error: true, code: 'RATE_LIMIT', message: '请求过于频繁,请稍后再试' }
}));
// ─── 请求日志 ───
app.use((req, res, next) => {
const ts = new Date().toISOString();
const line = `${ts} ${req.method} ${req.url} ${req.ip}\n`;
try {
const logFile = path.join(LOG_DIR, `api-${ts.slice(0, 10)}.log`);
fs.appendFileSync(logFile, line);
} catch {}
next();
});
/* ═══════════════════════════════════════════════════════════
* 健康检查端点 · 扩展系统监控能力
* ═══════════════════════════════════════════════════════════ */
/**
* 获取系统磁盘使用情况
*/
function getDiskUsage() {
try {
const stats = fs.statSync('/');
return {
total: stats.blocks * stats.blksize,
free: stats.bfree * stats.blksize,
used: (stats.blocks - stats.bfree) * stats.blksize,
percent: ((stats.blocks - stats.bfree) / stats.blocks * 100).toFixed(2)
};
} catch (err) {
return { error: err.message };
}
}
/**
* 获取系统内存使用情况
*/
function getMemoryUsage() {
try {
const total = os.totalmem();
const free = os.freemem();
return {
total,
free,
used: total - free,
percent: ((total - free) / total * 100).toFixed(2)
};
} catch (err) {
return { error: err.message };
}
}
// 健康检查端点
app.get('/api/health', async (req, res) => {
const smtpTest = await testSmtpConnection();
const diskUsage = getDiskUsage();
const memoryUsage = getMemoryUsage();
res.json({
status: 'running',
version: '2.1',
uptime: Math.floor(process.uptime()),
server_time: new Date().toISOString(),
last_request_time: new Date().toISOString(),
disk_usage: diskUsage,
memory_usage: memoryUsage,
smtp: smtpTest,
loadavg: os.loadavg(),
cpus: os.cpus().length,
platform: os.platform(),
arch: os.arch(),
node_version: process.version,
start_time: new Date(START_TIME).toISOString()
});
});
// ─── 注册七层镜防路由 ───
registerShieldRoutes(app);
// ─── 书岚 Agent 系统路由 ───
shulanAgent.registerRoutes(app);
// ─── 内置数据源路由(如果加载成功) ───
if (builtinSource) {
builtinSource.registerRoutes(app);
}
// ─── 启动服务 ───
app.listen(PORT, '127.0.0.1', () => {
console.log(`[ZY-SVR-006] 光湖智库节点已启动 · 监听 127.0.0.1:${PORT}`);
});
// 导出用于测试
module.exports = app;