272 lines
12 KiB
JavaScript
Raw Normal View History

/**
*
* 光湖智库节点 · Phase 1 API 服务
*
*
* 编号: ZY-SVR-006-API-P1
* 服务器: ZY-SVR-006 (43.153.203.105 · 新加坡)
* 守护: 铸渊 · ICE-GL-ZY001
* 版权: 国作登字-2026-A-00037559
*
* Phase 1 功能:
* GET /api/health 系统健康检查 (Nginx 存活探针)
* GET /shield/status 语言保护罩状态 + 封禁统计
* GET /shield/logs 最近封禁日志 (面板读取)
*
* Phase 2 功能 (智库节点三大引擎):
* /api/zhiku/download/* ()
* /api/zhiku/chapter/* (TXT+)
* /api/zhiku/reader/* 线 (//)
* GET /api/zhiku/stats 智库节点综合统计
*
* Phase 3 功能 (团队书库+成员Agent):
* /api/zhiku/library/* (/+)
* /api/zhiku/agent/* Agent (++)
*
* ZY-PROJ-004 智能小说系统:
* /api/novel/* ////AI
*
*
*/
'use strict';
const express = require('express');
const cors = require('cors');
const fs = require('fs');
const path = require('path');
// 加载环境变量 · 优先从上级目录 .env 读取PM2 cwd=/opt/zhuyuan/novel-db
// 再 fallback 到 cwd/.env兼容独立启动场景
const dotenvResult = require('dotenv').config({ path: path.join(__dirname, '..', '.env') });
if (dotenvResult.error) {
require('dotenv').config();
}
const app = express();
const PORT = process.env.PORT || 4000;
// ─── 信任本地 Nginx 反向代理 ───
app.set('trust proxy', 'loopback');
// ─── CORS: 仅允许 novel.guanghuyaoming.com + guanghuyaoming.com ───
const ALLOWED_ORIGINS = [
'https://novel.guanghuyaoming.com',
'https://guanghuyaoming.com',
'https://guanghu.online',
'https://www.guanghu.online',
'http://localhost:3000',
'http://127.0.0.1:3000'
];
app.use(cors({
origin: function(origin, cb) {
if (!origin || ALLOWED_ORIGINS.includes(origin)) return cb(null, true);
cb(new Error('Not allowed by CORS'));
},
credentials: true
}));
app.use(express.json());
// ─── 日志文件路径 ───
const BAN_LOG_PATH = process.env.BAN_LOG_PATH || '/var/log/novel-shield-bans.log';
const NGINX_LOG = process.env.NGINX_LOG || '/var/log/nginx/shield-access.log';
// ─── 工具函数 ───
function readFileLines(filePath, maxLines = 200) {
try {
const content = fs.readFileSync(filePath, 'utf8');
const lines = content.trim().split('\n').filter(Boolean);
return lines.slice(-maxLines);
} catch {
return [];
}
}
function parseBanLog(lines) {
const today = new Date().toISOString().slice(0, 10);
let todayBans = 0, totalBans = 0, totalUnbans = 0;
const recentBans = [];
for (const line of lines) {
if (line.includes(' BAN ')) {
totalBans++;
if (line.startsWith(today)) todayBans++;
if (recentBans.length < 50) {
const parts = line.split(' ');
recentBans.push({
ts: parts[0] || '',
action: 'ban',
ip: parts[2] || '',
jail: (parts[3] || '').replace('jail=', ''),
attempts:(parts[4] || '').replace('attempts=', '')
});
}
} else if (line.includes(' UNBAN ')) {
totalUnbans++;
}
}
return { todayBans, totalBans, activeBans: Math.max(0, totalBans - totalUnbans), recentBans: recentBans.reverse() };
}
// ─── 检查 fail2ban 状态 ───
function getF2bStatus() {
try {
const { execSync } = require('child_process');
const out = execSync('fail2ban-client status 2>/dev/null', { timeout: 3000 }).toString();
const jails = (out.match(/Jail list:\s*(.+)/)?.[1] || '').split(',').map(s => s.trim()).filter(Boolean);
return { running: true, jails };
} catch {
return { running: false, jails: [] };
}
}
// ─── 检查 Nginx 状态 ───
function getNginxStatus() {
try {
const { execSync } = require('child_process');
execSync('systemctl is-active nginx', { timeout: 3000 });
return 'active';
} catch {
return 'inactive';
}
}
/*
* GET /api/health 系统健康检查
* */
app.get('/api/health', (req, res) => {
res.json({
status: 'ok',
service: 'novel-db-api',
phase: '1',
server: 'ZY-SVR-006',
timestamp: new Date().toISOString(),
_sovereign: 'TCS-0002∞'
});
});
/*
* GET /shield/status 语言保护罩状态
* */
app.get('/shield/status', (req, res) => {
const lines = readFileLines(BAN_LOG_PATH, 500);
const banStats = parseBanLog(lines);
const f2b = getF2bStatus();
const nginxSt = getNginxStatus();
res.json({
shield: {
l2_nginx: { status: nginxSt === 'active' ? 'active' : 'down', description: '入站蜜罐层' },
l3_fail2ban:{ status: f2b.running ? 'active' : 'down', jails: f2b.jails, description: '自动封禁层' },
l1_proxy: { status: 'pending', description: '出站代理池Phase 2 部署)' }
},
bans: {
today: banStats.todayBans,
total: banStats.totalBans,
active: banStats.activeBans
},
timestamp: new Date().toISOString()
});
});
/*
* GET /shield/logs 最近封禁日志
* */
app.get('/shield/logs', (req, res) => {
const limit = Math.min(parseInt(req.query.limit) || 50, 200);
const lines = readFileLines(BAN_LOG_PATH, 500);
const parsed = parseBanLog(lines);
res.json({
logs: parsed.recentBans.slice(0, limit),
total: parsed.totalBans,
timestamp: new Date().toISOString()
});
});
/*
* Phase 2: 智库节点三大引擎路由
* */
const downloadRouter = require('./routes/download');
const chapterRouter = require('./routes/chapter');
const readerRouter = require('./routes/reader');
app.use('/api/zhiku/download', downloadRouter);
app.use('/api/zhiku/chapter', chapterRouter);
app.use('/api/zhiku/reader', readerRouter);
/*
* Phase 3: 团队书库 + 成员Agent
* */
const libraryRouter = require('./routes/library');
const memberAgentRouter = require('./routes/member-agent');
app.use('/api/zhiku/library', libraryRouter);
app.use('/api/zhiku/agent', memberAgentRouter);
/*
* Phase 4: 文件上传 + AI桥接
* */
const uploadRouter = require('./routes/upload');
app.use('/api/zhiku/upload', uploadRouter);
// AI 健康检查
const aiBridge = require('./services/ai-bridge');
app.get('/api/zhiku/ai/health', (req, res) => {
res.json({
error: false,
data: aiBridge.healthCheck()
});
});
/*
* ZY-PROJ-004: 智能小说系统
* */
const novelRouter = require('./routes/novel');
app.use('/api/novel', novelRouter);
// 智库综合统计
const downloadEngine = require('./services/download-engine');
const chapterEngine = require('./services/chapter-engine');
const readerEngine = require('./services/reader-engine');
const libraryEngine = require('./services/library-engine');
const agentEngine = require('./services/member-agent-engine');
const novelEngine = require('./services/novel-engine');
const uploadEngine = require('./services/upload-engine');
app.get('/api/zhiku/stats', (req, res) => {
res.json({
service: 'zhiku-node-full',
server: 'ZY-SVR-006',
timestamp: new Date().toISOString(),
download: downloadEngine.getStats(),
chapter: chapterEngine.getStats(),
reader: readerEngine.getStats(),
library: libraryEngine.getStats(),
agent: agentEngine.getStats(),
novel: novelEngine.getStats(),
upload: uploadEngine.getStats(),
ai: aiBridge.healthCheck(),
_sovereign: 'TCS-0002∞'
});
});
/*
* 404 fallback
* */
app.use((req, res) => {
res.status(404).json({ error: true, code: 'NOT_FOUND', message: 'API endpoint not found' });
});
/*
* 启动
* */
// 绑定到 127.0.0.1 而非 0.0.0.0 — 确保 API 只通过 Nginx 反向代理对外暴露,
// 永远不会直接暴露在公网上。外部访问路径: 公网 → Nginx(:443) → 127.0.0.1:4000
app.listen(PORT, '127.0.0.1', () => {
console.log(`[ZY-SVR-006] novel-db-api Phase 1 · 运行在 127.0.0.1:${PORT}`);
console.log(`[ZY-SVR-006] 语言保护罩 API 已激活 · TCS-0002∞`);
});