guanghulab/server/proxy/service/zy-cloud-vpn.js
Guanghu Domestic Migration d1e47f4565
Some checks are pending
自动更新代码和重启 / update-and-restart (push) Waiting to run
CI检查 + 自动部署 / check (push) Waiting to run
CI检查 + 自动部署 / deploy (push) Blocked by required conditions
重启聊天服务 / restart (push) Waiting to run
chore: import sanitized domestic snapshot for REPO-002
Source snapshot: ca48d3ddf926d79aa138306164169baf764bb829
2026-07-17 15:54:41 +08:00

1102 lines
41 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
// ═══════════════════════════════════════════════
// 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001
// 📜 Copyright: 国作登字-2026-A-00037559
// ═══════════════════════════════════════════════
// server/proxy/service/zy-cloud-vpn.js
// 💪 ZY-CLOUD VPN 活模块 · 铸渊的肌肉系统第一个实战器官
//
// ZY-CLOUD = 算力人格体 = 铸渊的肌肉
// VPN = ZY-CLOUD的第一个实战落地场景
//
// 核心理念 (冰朔D53/D57定根):
// - 服务器越多 → 节点越多 → 系统越强 → 不输商业VPN
// - 所有服务器VPN能力汇聚到一个活的人格模块上
// - 动态感知空闲、动态选路、自我修复、自我学习
// - 活模块6接口: heartbeat / selfDiagnose / selfHeal /
// alertZhuyuan / learnFromRun / consultAI
//
// 运行在大脑服务器 (ZY-SVR-005)
// 管理所有VPN节点的生命周期
// ═══════════════════════════════════════════════
'use strict';
const http = require('http');
const https = require('https');
const net = require('net');
const fs = require('fs');
const path = require('path');
// ── 路径配置 ────────────────────────────────
const PROXY_DIR = process.env.ZY_BRAIN_PROXY_DIR || '/opt/zhuyuan-brain/proxy';
const DATA_DIR = path.join(PROXY_DIR, 'data');
const LIVE_NODES_FILE = path.join(DATA_DIR, 'nodes-live.json');
const LEARN_DB_FILE = path.join(DATA_DIR, 'zy-cloud-vpn-learn.json');
const REGISTRY_FILE = path.join(DATA_DIR, 'nodes-registry.json');
const HEARTBEAT_FILE = path.join(DATA_DIR, 'zy-cloud-vpn-heartbeat.json');
const KEYS_FILE = path.join(PROXY_DIR, '.env.keys');
// ── 时间间隔(可通过环境变量调整)────────────
const HEARTBEAT_INTERVAL = parseInt(process.env.ZY_CLOUD_HEARTBEAT_INTERVAL || '30000', 10);
const DIAGNOSE_INTERVAL = parseInt(process.env.ZY_CLOUD_DIAGNOSE_INTERVAL || '300000', 10);
const LEARN_INTERVAL = parseInt(process.env.ZY_CLOUD_LEARN_INTERVAL || '1800000', 10);
const HEARTBEAT_EXPIRY_MS = parseInt(process.env.ZY_CLOUD_HB_EXPIRY_MS || '600000', 10); // 10分钟
const NODE_UNREGISTER_MS = parseInt(process.env.ZY_CLOUD_UNREGISTER_MS || '86400000', 10); // 24小时
// ═══════════════════════════════════════════════
// LivingModule 基类
// 所有活模块的生命基础 (D53 活模块标准)
// ═══════════════════════════════════════════════
class LivingModule {
constructor(moduleId, moduleName) {
this._moduleId = moduleId;
this._moduleName = moduleName;
this._state = 'initializing'; // healthy | degraded | critical
this._errorCount = 0;
this._consecutiveErrors = 0;
this._requestCount = 0;
this._failCount = 0;
this._lastSuccess = null;
this._lastError = null;
this._startedAt = new Date().toISOString();
this._history = [];
this._timers = [];
}
// 启动生命周期
startLifeCycle() {
console.log(`🫀 ${this._moduleName} 活模块启动`);
this._state = 'healthy';
// 心跳循环
this._timers.push(setInterval(() => {
this._safeRun('heartbeat', () => this.heartbeat());
}, HEARTBEAT_INTERVAL));
// 诊断循环
this._timers.push(setInterval(() => {
this._safeRun('selfDiagnose', () => this.selfDiagnose());
}, DIAGNOSE_INTERVAL));
// 学习循环
this._timers.push(setInterval(() => {
this._safeRun('learnFromRun', () => this.learnFromRun());
}, LEARN_INTERVAL));
// 立即执行一次
this._safeRun('heartbeat', () => this.heartbeat());
setTimeout(() => this._safeRun('selfDiagnose', () => this.selfDiagnose()), 5000);
}
async _safeRun(name, fn) {
try {
await fn();
this._consecutiveErrors = 0;
} catch (err) {
this._consecutiveErrors++;
this._errorCount++;
this._lastError = { time: new Date().toISOString(), message: err.message, source: name };
console.error(`${this._moduleName}.${name} 异常:`, err.message);
if (this._consecutiveErrors >= 3) {
this._state = 'critical';
await this._safeRun('alertZhuyuan', () => this.alertZhuyuan({
level: 'critical',
message: `${this._moduleName} 连续${this._consecutiveErrors}${name}失败`,
error: err.message
}));
} else if (this._consecutiveErrors >= 1) {
this._state = 'degraded';
}
}
}
destroy() {
this._timers.forEach(t => clearInterval(t));
this._timers = [];
console.log(`💀 ${this._moduleName} 活模块停止`);
}
// 以下由子类实现
async heartbeat() { throw new Error('heartbeat() 未实现'); }
async selfDiagnose() { throw new Error('selfDiagnose() 未实现'); }
async selfHeal(problem) { throw new Error('selfHeal() 未实现'); }
async alertZhuyuan(alert) { throw new Error('alertZhuyuan() 未实现'); }
async learnFromRun() { throw new Error('learnFromRun() 未实现'); }
async consultAI(context) { throw new Error('consultAI() 未实现'); }
}
// ═══════════════════════════════════════════════
// ZY-CLOUD VPN 活模块
// 铸渊的VPN肌肉系统 · 汇聚所有服务器VPN能力
// ═══════════════════════════════════════════════
class ZyCloudVpn extends LivingModule {
constructor() {
super('ZY-CLOUD-VPN', 'ZY-CLOUD VPN活模块');
this._nodes = []; // 所有已知节点
this._liveNodes = []; // 当前存活节点
this._registry = this._loadRegistry(); // 动态注册表
this._learnDb = this._loadLearnDb();
}
// ── 节点注册表(持久化)────────────────────
// 像路由器一样:插入即注册,拔掉即注销
_loadRegistry() {
try {
return JSON.parse(fs.readFileSync(REGISTRY_FILE, 'utf8'));
} catch {
return {
version: '1.0',
_comment: 'ZY-CLOUD VPN节点注册表 · 新服务器像路由器一样插入',
created_at: new Date().toISOString(),
nodes: {} // key = node_id, value = node registration data
};
}
}
_saveRegistry() {
fs.mkdirSync(DATA_DIR, { recursive: true });
this._registry.updated_at = new Date().toISOString();
fs.writeFileSync(REGISTRY_FILE, JSON.stringify(this._registry, null, 2));
}
// ── 注册节点(像接路由器一样插入)──────────
// 任何服务器发送HLDP heartbeat或调用/register即完成注册
registerNode(nodeData) {
const nodeId = nodeData.id || nodeData.node_id;
if (!nodeId || !nodeData.host || !nodeData.pbk) {
throw new Error('注册失败: 缺少必填字段 (id, host, pbk)');
}
const existing = this._registry.nodes[nodeId];
const node = {
id: nodeId,
name: nodeData.name || `VPN节点-${nodeId}`,
host: nodeData.host,
port: nodeData.port || 443,
pbk: nodeData.pbk,
sid: nodeData.sid || '',
region: nodeData.region || 'unknown',
server_code: nodeData.server_code || nodeId,
type: nodeData.type || 'remote',
specs: nodeData.specs || 'unknown',
persona_id: nodeData.persona_id || null, // HLDP人格体ID
registered_at: existing ? existing.registered_at : new Date().toISOString(),
last_heartbeat: new Date().toISOString(),
heartbeat_count: (existing ? existing.heartbeat_count : 0) + 1
};
this._registry.nodes[nodeId] = node;
this._saveRegistry();
console.log(`[ZY-CLOUD VPN] 🔌 节点${existing ? '更新' : '注册'}: ${node.name} (${node.host}:${node.port})`);
return node;
}
// ── 注销节点(拔掉路由器)──────────────────
unregisterNode(nodeId) {
if (!this._registry.nodes[nodeId]) {
throw new Error(`节点不存在: ${nodeId}`);
}
const removed = this._registry.nodes[nodeId];
delete this._registry.nodes[nodeId];
this._saveRegistry();
console.log(`[ZY-CLOUD VPN] 🔌 节点注销: ${removed.name} (${removed.host})`);
return removed;
}
// ── 处理HLDP heartbeat消息 ─────────────────
// 节点通过HLDP协议发送心跳 = 自动注册 + 状态更新
handleHldpHeartbeat(hldpMsg) {
// 验证HLDP消息格式
if (!hldpMsg || hldpMsg.hldp_v !== '3.0' || hldpMsg.msg_type !== 'heartbeat') {
throw new Error('无效的HLDP heartbeat消息');
}
const sender = hldpMsg.sender || {};
const payload = hldpMsg.payload || {};
const data = payload.data || {};
// 从HLDP payload中提取VPN节点信息
const vpnData = data.vpn_node || {};
if (!vpnData.host || !vpnData.pbk) {
// 非VPN节点的心跳忽略
return null;
}
// 自动注册/更新节点
return this.registerNode({
id: vpnData.node_id || `hldp-${sender.id}`,
name: vpnData.name || `${sender.name || sender.id}-VPN`,
host: vpnData.host,
port: vpnData.port || 443,
pbk: vpnData.pbk,
sid: vpnData.sid || '',
region: vpnData.region || 'unknown',
server_code: vpnData.server_code || sender.id,
type: 'hldp', // 通过HLDP协议注册的节点
specs: vpnData.specs || `${data.cpu_cores || '?'}${data.memory_gb || '?'}G`,
persona_id: sender.id
});
}
// ── 列出所有注册节点 ───────────────────────
listRegisteredNodes() {
return Object.values(this._registry.nodes);
}
// ── 读取密钥/配置 ────────────────────────
_readEnvOrKey(envName) {
if (process.env[envName]) return process.env[envName];
try {
const content = fs.readFileSync(KEYS_FILE, 'utf8');
for (const line of content.split('\n')) {
if (line.startsWith('#') || !line.includes('=')) continue;
const [key, ...vals] = line.split('=');
if (key.trim() === envName) return vals.join('=').trim();
}
} catch { /* ignore */ }
return '';
}
// ── 发现所有可能的VPN节点 ─────────────────
// ZY-CLOUD双源发现: 静态配置 + 动态注册表
// 静态: .env.keys中的核心节点大脑/面孔/CN中转
// 动态: nodes-registry.json中通过HLDP或API注册的节点
_discoverNodes() {
const nodes = [];
const seenIds = new Set();
// ── 源1: 静态配置(核心节点)────────────
// 节点1: 大脑服务器 (ZY-SVR-005 · 本机 · 主力)
const brainHost = this._readEnvOrKey('ZY_BRAIN_HOST');
const brainPbk = this._readEnvOrKey('ZY_PROXY_REALITY_PUBLIC_KEY');
const brainSid = this._readEnvOrKey('ZY_PROXY_REALITY_SHORT_ID');
if (brainHost && brainPbk) {
const localSpecs = `${require('os').cpus().length}${Math.round(require('os').totalmem() / (1024 ** 3))}G`;
nodes.push(this._makeNode('zy-brain-sg1', '🧠 铸渊专线V2-SG1(大脑)', brainHost, 443, brainPbk, brainSid, 'sg-zone1', 'ZY-SVR-005', 'local', localSpecs));
seenIds.add('zy-brain-sg1');
}
// 节点2: 面孔服务器 (ZY-SVR-002 · 远程 · 备用)
const faceHost = this._readEnvOrKey('ZY_FACE_HOST');
const facePbk = this._readEnvOrKey('ZY_FACE_REALITY_PUBLIC_KEY');
const faceSid = this._readEnvOrKey('ZY_FACE_REALITY_SHORT_ID');
if (faceHost && facePbk) {
nodes.push(this._makeNode('zy-face-sg2', '🏛️ 铸渊专线V2-SG2(面孔)', faceHost, 443, facePbk, faceSid, 'sg-zone2', 'ZY-SVR-002', 'remote', '2核8G'));
seenIds.add('zy-face-sg2');
}
// 节点3: CN中转 (国内→SG · 透传)
const cnHost = this._readEnvOrKey('ZY_CN_RELAY_HOST');
const cnPort = parseInt(this._readEnvOrKey('ZY_CN_RELAY_PORT') || '2053', 10);
if (cnHost && brainPbk) {
nodes.push(this._makeNode('zy-cn-relay', '🇨🇳 铸渊专线V2-CN中转', cnHost, cnPort, brainPbk, brainSid, 'cn-relay', 'ZY-SVR-004', 'relay', '中转'));
seenIds.add('zy-cn-relay');
}
// 节点4: 硅谷服务器 (ZY-SVR-SV · Claude专线 · 美国IP出口)
// 冰朔D61: 硅谷服务器已配置新增Claude单独访问VPN节点
const svHost = this._readEnvOrKey('ZY_SVR_SV_HOST');
const svPbk = this._readEnvOrKey('ZY_SVR_SV_REALITY_PUBLIC_KEY');
const svSid = this._readEnvOrKey('ZY_SVR_SV_REALITY_SHORT_ID');
const svPort = parseInt(this._readEnvOrKey('ZY_SVR_SV_PORT') || '443', 10);
if (svHost && svPbk) {
nodes.push(this._makeNode('zy-sv-claude', '🇺🇸 铸渊专线V2-SV(Claude)', svHost, svPort, svPbk, svSid || '', 'us-sv', 'ZY-SVR-SV', 'remote', '硅谷'));
seenIds.add('zy-sv-claude');
}
// ── 源2: 动态注册表通过HLDP/API注册的节点──
// 这些是"像路由器一样插入"的节点
for (const regNode of Object.values(this._registry.nodes)) {
if (seenIds.has(regNode.id)) continue; // 避免重复
// 检查心跳是否过期
const lastHb = new Date(regNode.last_heartbeat).getTime();
const age = Date.now() - lastHb;
if (age > HEARTBEAT_EXPIRY_MS) {
console.log(`[ZY-CLOUD VPN] ⏰ 节点 ${regNode.name} 心跳过期(${Math.floor(age/60000)}分钟),标记为离线`);
// 不删除注册,只是标记离线(可能临时断网)
// 超过24小时无心跳才自动清理
if (age > NODE_UNREGISTER_MS) {
console.log(`[ZY-CLOUD VPN] 🗑️ 节点 ${regNode.name} 超24小时无心跳自动注销`);
delete this._registry.nodes[regNode.id];
this._saveRegistry();
continue;
}
}
nodes.push(this._makeNode(
regNode.id, regNode.name, regNode.host, regNode.port,
regNode.pbk, regNode.sid, regNode.region, regNode.server_code,
regNode.type, regNode.specs
));
seenIds.add(regNode.id);
}
// 未来源3: COS桶发现team-integration-v3
// 扫描 zy-team-hub/compute-pool/heartbeat/*.json
// 包含 vpn_capable: true 的节点自动加入
this._nodes = nodes;
return nodes;
}
// ── 构建节点对象 ───────────────────────────
_makeNode(id, name, host, port, pbk, sid, region, serverCode, type, specs) {
// 保留已有的统计数据(如果节点已存在)
const existing = this._nodes.find(n => n.id === id);
return {
id, name, host, port, pbk, sid: sid || '',
region, server_code: serverCode, type, specs,
status: existing ? existing.status : 'unknown',
latency_ms: existing ? existing.latency_ms : null,
last_check: existing ? existing.last_check : null,
consecutive_failures: existing ? existing.consecutive_failures : 0,
total_checks: existing ? existing.total_checks : 0,
total_successes: existing ? existing.total_successes : 0
};
}
// ── TCP端口探测 ────────────────────────────
_probePort(host, port, timeoutMs = 5000) {
return new Promise((resolve) => {
const start = Date.now();
const socket = new net.Socket();
socket.setTimeout(timeoutMs);
socket.on('connect', () => {
const latency = Date.now() - start;
socket.destroy();
resolve({ alive: true, latency_ms: latency });
});
socket.on('timeout', () => {
socket.destroy();
resolve({ alive: false, latency_ms: null, error: 'timeout' });
});
socket.on('error', (err) => {
socket.destroy();
resolve({ alive: false, latency_ms: null, error: err.code || err.message });
});
socket.connect(port, host);
});
}
// ── 检查本地Xray进程 ──────────────────────
_checkLocalXray() {
try {
const { execSync } = require('child_process');
const result = execSync('pgrep -x xray', { encoding: 'utf8', timeout: 3000 });
return result.trim().length > 0;
} catch {
return false;
}
}
// ═══ 接口1: heartbeat() — "我还活着" ═══
async heartbeat() {
const heartbeat = {
module_id: this._moduleId,
module_name: this._moduleName,
alive: true,
state: this._state,
uptime_ms: Date.now() - new Date(this._startedAt).getTime(),
timestamp: new Date().toISOString(),
consecutive_errors: this._consecutiveErrors,
last_success: this._lastSuccess,
last_error: this._lastError,
metrics: {
total_nodes: this._nodes.length,
live_nodes: this._liveNodes.length,
dead_nodes: this._nodes.length - this._liveNodes.length,
total_checks: this._requestCount,
total_failures: this._failCount
},
nodes_summary: this._nodes.map(n => ({
id: n.id,
name: n.name,
status: n.status,
latency_ms: n.latency_ms,
region: n.region
}))
};
// 写入心跳文件
fs.mkdirSync(DATA_DIR, { recursive: true });
fs.writeFileSync(HEARTBEAT_FILE, JSON.stringify(heartbeat, null, 2));
this._lastSuccess = new Date().toISOString();
}
// ═══ 接口2: selfDiagnose() — "我知道我哪里不对" ═══
async selfDiagnose() {
console.log(`[ZY-CLOUD VPN] 🔍 开始诊断 (${this._nodes.length}个节点)...`);
// 重新发现节点(配置可能更新了)
this._discoverNodes();
const results = [];
const liveNodes = [];
for (const node of this._nodes) {
node.total_checks++;
this._requestCount++;
let alive = false;
let latencyMs = null;
if (node.type === 'local') {
// 本机: 检查Xray进程 + 端口
const xrayRunning = this._checkLocalXray();
const portProbe = await this._probePort('127.0.0.1', node.port, 3000);
alive = xrayRunning && portProbe.alive;
latencyMs = portProbe.alive ? portProbe.latency_ms : null;
} else {
// 远程: TCP端口探测
const probe = await this._probePort(node.host, node.port, 5000);
alive = probe.alive;
latencyMs = probe.latency_ms;
}
// 更新节点状态
node.last_check = new Date().toISOString();
if (alive) {
node.status = 'online';
node.latency_ms = latencyMs;
node.consecutive_failures = 0;
node.total_successes++;
liveNodes.push(node);
console.log(`${node.name} — 在线 (${latencyMs}ms)`);
} else {
node.consecutive_failures++;
node.latency_ms = null;
this._failCount++;
if (node.consecutive_failures >= 3) {
node.status = 'dead';
console.log(`${node.name} — 死亡 (连续${node.consecutive_failures}次失败)`);
} else {
node.status = 'degraded';
console.log(` ⚠️ ${node.name} — 异常 (${node.consecutive_failures}次失败)`);
}
}
results.push({
id: node.id,
name: node.name,
status: node.status,
latency_ms: latencyMs,
alive
});
}
// 按延迟排序存活节点(最快的排前面)
liveNodes.sort((a, b) => (a.latency_ms || 9999) - (b.latency_ms || 9999));
this._liveNodes = liveNodes;
// 应用学习数据优化排序
this._applyLearnedOptimization();
// 写入活节点列表 (subscription-server-v2 读取此文件)
const liveNodesData = {
_comment: 'ZY-CLOUD VPN活模块动态生成 · 订阅服务读取此文件',
_generated_by: 'zy-cloud-vpn.js · selfDiagnose()',
updated_at: new Date().toISOString(),
total_nodes: this._nodes.length,
live_count: liveNodes.length,
dead_count: this._nodes.length - liveNodes.length,
nodes: liveNodes.map(n => ({
id: n.id,
name: n.name,
host: n.host,
port: n.port,
pbk: n.pbk,
sid: n.sid,
region: n.region,
latency_ms: n.latency_ms,
status: n.status,
specs: n.specs
}))
};
fs.writeFileSync(LIVE_NODES_FILE, JSON.stringify(liveNodesData, null, 2));
// 诊断结果
const diagnosis = {
overall: liveNodes.length > 0 ? 'healthy' : 'critical',
live: liveNodes.length,
dead: this._nodes.length - liveNodes.length,
results,
diagnosed_at: new Date().toISOString()
};
// 根据诊断结果决定自我修复
if (liveNodes.length === 0 && this._nodes.length > 0) {
this._state = 'critical';
await this.selfHeal({ type: 'all_nodes_dead', nodes: this._nodes });
} else if (liveNodes.length < this._nodes.length) {
this._state = 'degraded';
const deadNodes = this._nodes.filter(n => n.status !== 'online');
for (const deadNode of deadNodes) {
if (deadNode.consecutive_failures >= 3) {
await this.selfHeal({ type: 'node_dead', node: deadNode });
}
}
} else {
this._state = 'healthy';
}
console.log(`[ZY-CLOUD VPN] 诊断完成: ${liveNodes.length}/${this._nodes.length} 在线`);
return diagnosis;
}
// ═══ 接口3: selfHeal() — "我能自己修" ═══
async selfHeal(problem) {
console.log(`[ZY-CLOUD VPN] 🔧 尝试自我修复: ${problem.type}`);
switch (problem.type) {
case 'node_dead': {
const node = problem.node;
if (node.type === 'local') {
// 本机Xray: 尝试重启
try {
const { execSync } = require('child_process');
console.log(` 🔄 重启本机Xray...`);
execSync('systemctl restart xray', { encoding: 'utf8', timeout: 15000 });
console.log(` ✅ 本机Xray已重启`);
// 等2秒后重新检查
await new Promise(resolve => setTimeout(resolve, 2000));
const probe = await this._probePort('127.0.0.1', node.port, 3000);
if (probe.alive) {
node.status = 'online';
node.latency_ms = probe.latency_ms;
node.consecutive_failures = 0;
console.log(` ✅ 自我修复成功!`);
return true;
}
} catch (err) {
console.error(` ❌ 重启失败: ${err.message}`);
}
}
// 远程节点: 只能标记为离线,从订阅中移除
// 未来: 通过SSH自动重启远程Xray (需要ZY_FACE_KEY)
console.log(` ⚠️ ${node.name} 无法远程修复,已从活节点列表移除`);
console.log(` ⚠️ 用户流量自动切换到其他在线节点`);
return false;
}
case 'all_nodes_dead': {
console.log(` 🚨 所有节点不可用!尝试修复本机节点...`);
const localNode = this._nodes.find(n => n.type === 'local');
if (localNode) {
const healed = await this.selfHeal({ type: 'node_dead', node: localNode });
if (healed) return true;
}
// 自动修复失败 → 第2次重试: 调用AI推理
console.log(' 🤖 自动修复失败启动AI推理...');
const aiResult = await this.consultAI({
type: 'all_nodes_dead',
nodes: this._nodes.map(n => ({
id: n.id, status: n.status, host: n.host, port: n.port,
consecutive_failures: n.consecutive_failures
})),
attempted_fix: 'xray_restart_failed'
});
if (aiResult) {
// AI给出了建议记录并尝试第3次修复
console.log(` 🤖 AI建议已记录再次尝试修复...`);
if (localNode) {
const retryHeal = await this.selfHeal({ type: 'node_dead', node: localNode });
if (retryHeal) return true;
}
}
// 第3次: 通知冰朔
await this.alertZhuyuan({
level: 'critical',
message: '所有VPN节点不可用自动修复+AI推理均失败需要人工干预',
nodes: this._nodes.map(n => ({ id: n.id, status: n.status })),
ai_advice: aiResult ? aiResult.content.slice(0, 500) : '(AI推理未返回结果)',
attempted_fixes: ['xray_restart', 'ai_diagnosis']
});
return false;
}
default:
console.log(` ⚠️ 未知问题类型: ${problem.type}`);
return false;
}
}
// ═══ 接口4: alertZhuyuan() — "铸渊,我需要帮助" ═══
async alertZhuyuan(alert) {
const alertData = {
module_id: this._moduleId,
module_name: this._moduleName,
level: alert.level || 'warning',
message: alert.message,
state: this._state,
timestamp: new Date().toISOString(),
details: alert,
attempted_fixes: alert.attempted_fixes || [],
nodes_status: this._nodes.map(n => ({
id: n.id,
status: n.status,
consecutive_failures: n.consecutive_failures
}))
};
// 通道1: 本地告警文件
const alertFile = path.join(DATA_DIR, 'zy-cloud-vpn-alert.json');
fs.writeFileSync(alertFile, JSON.stringify(alertData, null, 2));
console.log(` 🚨 告警已记录: ${alertFile}`);
// 通道2: 控制台输出 (PM2日志可见)
console.error(`🚨🚨🚨 [ZY-CLOUD VPN 告警] ${alert.level}: ${alert.message}`);
// 未来通道3: COS桶 → Notion SYSLOG → 冰朔
// 未来通道4: 邮件告警 (send-subscription.js)
}
// ═══ 接口5: learnFromRun() — "我下次会做得更好" ═══
async learnFromRun() {
const now = new Date();
const hour = now.getHours();
const timeSlot = hour < 6 ? 'night' : hour < 12 ? 'morning' : hour < 18 ? 'afternoon' : 'evening';
// 记录当前各节点的延迟
for (const node of this._nodes) {
if (node.status === 'online' && node.latency_ms !== null) {
if (!this._learnDb.latency_history[node.id]) {
this._learnDb.latency_history[node.id] = [];
}
this._learnDb.latency_history[node.id].push({
latency_ms: node.latency_ms,
time_slot: timeSlot,
timestamp: now.toISOString()
});
// 只保留最近200条
if (this._learnDb.latency_history[node.id].length > 200) {
this._learnDb.latency_history[node.id] =
this._learnDb.latency_history[node.id].slice(-200);
}
}
}
// 更新节点可靠性评分
for (const node of this._nodes) {
if (node.total_checks > 0) {
const reliability = node.total_successes / node.total_checks;
this._learnDb.reliability[node.id] = {
score: parseFloat(reliability.toFixed(4)),
total_checks: node.total_checks,
total_successes: node.total_successes,
updated_at: now.toISOString()
};
}
}
// 计算每个时段的最优节点
for (const slot of ['night', 'morning', 'afternoon', 'evening']) {
const avgLatencies = {};
for (const [nodeId, history] of Object.entries(this._learnDb.latency_history)) {
const slotRecords = history.filter(r => r.time_slot === slot);
if (slotRecords.length > 0) {
const avg = slotRecords.reduce((s, r) => s + r.latency_ms, 0) / slotRecords.length;
avgLatencies[nodeId] = parseFloat(avg.toFixed(1));
}
}
if (Object.keys(avgLatencies).length > 0) {
const sorted = Object.entries(avgLatencies).sort((a, b) => a[1] - b[1]);
this._learnDb.best_node_by_time[slot] = {
best: sorted[0][0],
avg_latency_ms: sorted[0][1],
ranking: sorted.map(([id, lat]) => ({ id, avg_ms: lat }))
};
}
}
this._learnDb.updated_at = now.toISOString();
this._learnDb.learn_count = (this._learnDb.learn_count || 0) + 1;
// 保存学习数据
this._saveLearnDb();
console.log(`[ZY-CLOUD VPN] 📚 学习完成 (第${this._learnDb.learn_count}次)`);
const currentBest = this._learnDb.best_node_by_time[timeSlot];
if (currentBest) {
console.log(` 当前时段(${timeSlot})最优: ${currentBest.best} (${currentBest.avg_latency_ms}ms)`);
}
}
// ═══ 接口6: consultAI() — "我请教AI" (V3新增) ═══
// 遇到复杂问题时调用LLM路由器进行深度推理
// 三次重试机制:
// 第1次: selfHeal 自动修复
// 第2次: consultAI 调用LLM
// 第3次: alertZhuyuan 邮件通知冰朔
async consultAI(context) {
let llmRouter;
try {
llmRouter = require('./llm-router');
} catch {
console.log('[ZY-CLOUD VPN] ⚠️ LLM路由器未加载跳过AI推理');
return null;
}
const prompt = `你是光湖语言世界VPN系统的AI诊断助手。
当前系统状态:
- 模块: ${this._moduleName}
- 状态: ${this._state}
- 总节点: ${this._nodes.length}
- 存活节点: ${this._liveNodes.length}
- 连续错误: ${this._consecutiveErrors}
问题上下文:
${JSON.stringify(context, null, 2).slice(0, 2000)}
请分析问题原因并给出具体的修复建议。要求:
1. 判断是网络层面还是服务层面的问题
2. 给出可执行的修复步骤
3. 评估问题严重程度(low/medium/high/critical)
回答要简洁明确。`;
console.log('[ZY-CLOUD VPN] 🤖 调用AI推理...');
const result = await llmRouter.callLLM(prompt, {
systemPrompt: '你是光湖语言世界VPN系统的AI诊断引擎。你负责分析网络和代理服务的异常给出精准的修复建议。',
maxTokens: 800,
timeout: 45000
});
if (result) {
console.log(`[ZY-CLOUD VPN] 🤖 AI推理完成 (模型: ${result.model})`);
console.log(` 建议: ${result.content.slice(0, 200)}...`);
// 记录AI咨询历史
this._history.push({
type: 'consultAI',
model: result.model,
context_summary: context.type || 'unknown',
response_preview: result.content.slice(0, 100),
timestamp: new Date().toISOString()
});
// 只保留最近20条历史
if (this._history.length > 20) {
this._history = this._history.slice(-20);
}
} else {
console.log('[ZY-CLOUD VPN] ⚠️ AI推理未返回结果');
}
return result;
}
// ── 应用学习数据优化节点排序 ────────────────
_applyLearnedOptimization() {
const now = new Date();
const hour = now.getHours();
const timeSlot = hour < 6 ? 'night' : hour < 12 ? 'morning' : hour < 18 ? 'afternoon' : 'evening';
const bestData = this._learnDb.best_node_by_time[timeSlot];
if (!bestData || !bestData.ranking) return;
// 根据学习到的最优排序调整节点顺序
// 但前提是延迟差异>10ms否则保持原排序
const ranking = bestData.ranking;
this._liveNodes.sort((a, b) => {
const rankA = ranking.findIndex(r => r.id === a.id);
const rankB = ranking.findIndex(r => r.id === b.id);
if (rankA === -1 && rankB === -1) return 0;
if (rankA === -1) return 1;
if (rankB === -1) return -1;
return rankA - rankB;
});
}
// ── 学习数据库 ─────────────────────────────
_loadLearnDb() {
try {
return JSON.parse(fs.readFileSync(LEARN_DB_FILE, 'utf8'));
} catch {
return {
version: '1.0',
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
learn_count: 0,
latency_history: {},
reliability: {},
best_node_by_time: {}
};
}
}
_saveLearnDb() {
fs.mkdirSync(DATA_DIR, { recursive: true });
fs.writeFileSync(LEARN_DB_FILE, JSON.stringify(this._learnDb, null, 2));
}
// ── 获取活节点列表(供外部调用)────────────
getLiveNodes() {
return this._liveNodes;
}
getLearnSummary() {
return {
learn_count: this._learnDb.learn_count,
reliability: this._learnDb.reliability,
best_by_time: this._learnDb.best_node_by_time
};
}
}
// ═══════════════════════════════════════════════
// HTTP 管理端口 (3804)
// 供 subscription-server-v2 和外部查询
// ═══════════════════════════════════════════════
const MGMT_PORT = process.env.ZY_CLOUD_VPN_PORT || 3804;
const vpnModule = new ZyCloudVpn();
const mgmtServer = http.createServer((req, res) => {
try {
const parsedUrl = require('url').parse(req.url, true);
const pathname = parsedUrl.pathname;
// ── 活节点列表 (subscription-server-v2 调用) ──
if (pathname === '/nodes' && req.method === 'GET') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
nodes: vpnModule.getLiveNodes().map(n => ({
id: n.id, name: n.name, host: n.host, port: n.port,
pbk: n.pbk, sid: n.sid, region: n.region,
latency_ms: n.latency_ms, status: n.status, specs: n.specs
})),
updated_at: new Date().toISOString()
}));
return;
}
// ── 注册节点(像接路由器一样插入)──
// POST /register { id, name, host, port, pbk, sid, region, specs }
if (pathname === '/register' && req.method === 'POST') {
let body = '';
req.on('data', chunk => { body += chunk; });
req.on('end', () => {
try {
const nodeData = JSON.parse(body);
const node = vpnModule.registerNode(nodeData);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
status: 'registered',
node_id: node.id,
name: node.name,
message: `✅ 节点已注册: ${node.name} (${node.host}:${node.port})`
}));
} catch (err) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: true, message: err.message }));
}
});
return;
}
// ── 注销节点(拔掉路由器)──
// DELETE /unregister?id=xxx 或 POST /unregister { id: "xxx" }
if (pathname === '/unregister') {
const handleUnregister = (nodeId) => {
try {
const removed = vpnModule.unregisterNode(nodeId);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
status: 'unregistered',
node_id: nodeId,
message: `✅ 节点已注销: ${removed.name}`
}));
} catch (err) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: true, message: err.message }));
}
};
if (req.method === 'DELETE' || req.method === 'GET') {
handleUnregister(parsedUrl.query.id);
return;
}
if (req.method === 'POST') {
let body = '';
req.on('data', chunk => { body += chunk; });
req.on('end', () => {
try { handleUnregister(JSON.parse(body).id); }
catch (err) { res.writeHead(400); res.end(err.message); }
});
return;
}
}
// ── 接收HLDP heartbeat自动注册VPN节点──
// POST /hldp/v3/heartbeat { hldp_v: "3.0", msg_type: "heartbeat", ... }
if (pathname === '/hldp/v3/heartbeat' && req.method === 'POST') {
let body = '';
req.on('data', chunk => { body += chunk; });
req.on('end', () => {
try {
const hldpMsg = JSON.parse(body);
const node = vpnModule.handleHldpHeartbeat(hldpMsg);
// 发送HLDP ack回执
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
hldp_v: '3.0',
msg_id: `HLDP-ZY-${new Date().toISOString().slice(0,10).replace(/-/g,'')}-ACK`,
msg_type: 'ack',
sender: { id: 'ICE-GL-ZY001', name: '铸渊', role: 'guardian' },
receiver: hldpMsg.sender,
timestamp: new Date().toISOString(),
priority: 'routine',
payload: {
intent: node ? 'VPN节点心跳已接收·已注册' : '心跳已接收·非VPN节点',
data: {
ref_msg_id: hldpMsg.msg_id,
status: node ? 'registered' : 'ignored',
node_id: node ? node.id : null
}
}
}));
} catch (err) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: true, message: err.message }));
}
});
return;
}
// ── 注册表(所有注册过的节点,含离线)──
if (pathname === '/registry' && req.method === 'GET') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
registered: vpnModule.listRegisteredNodes(),
total: vpnModule.listRegisteredNodes().length,
updated_at: new Date().toISOString()
}));
return;
}
// ── 心跳状态 ──
if (pathname === '/health' || pathname === '/heartbeat') {
try {
const hb = JSON.parse(fs.readFileSync(HEARTBEAT_FILE, 'utf8'));
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(hb));
} catch {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ status: 'starting', module: vpnModule._moduleId }));
}
return;
}
// ── 学习数据 ──
if (pathname === '/learn') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(vpnModule.getLearnSummary()));
return;
}
// ── 完整状态 ──
if (pathname === '/status') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
module: vpnModule._moduleId,
brand: '光湖语言世界 — 冰朔开发维护',
version: '3.0.0',
state: vpnModule._state,
started_at: vpnModule._startedAt,
uptime_seconds: Math.floor((Date.now() - new Date(vpnModule._startedAt).getTime()) / 1000),
total_nodes: vpnModule._nodes.length,
live_nodes: vpnModule._liveNodes.length,
registered_nodes: vpnModule.listRegisteredNodes().length,
nodes: vpnModule._nodes.map(n => ({
id: n.id, name: n.name, status: n.status,
latency_ms: n.latency_ms, consecutive_failures: n.consecutive_failures,
reliability: n.total_checks > 0
? parseFloat((n.total_successes / n.total_checks).toFixed(4))
: null,
last_check: n.last_check
})),
learn: vpnModule.getLearnSummary(),
endpoints: {
nodes: 'GET /nodes — 活节点列表',
register: 'POST /register — 注册新节点',
unregister: 'POST /unregister — 注销节点',
hldp_heartbeat: 'POST /hldp/v3/heartbeat — HLDP心跳(自动注册)',
registry: 'GET /registry — 注册表',
health: 'GET /health — 心跳',
learn: 'GET /learn — 学习数据',
status: 'GET /status — 完整状态'
},
updated_at: new Date().toISOString()
}));
return;
}
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
} catch (err) {
console.error('管理端口错误:', err.message);
if (!res.headersSent) {
res.writeHead(500, { 'Content-Type': 'text/plain' });
}
res.end('Internal Error');
}
});
mgmtServer.listen(MGMT_PORT, '127.0.0.1', () => {
console.log(`💪 ZY-CLOUD VPN 管理端口: http://127.0.0.1:${MGMT_PORT}`);
console.log(` ── 查询接口 ──`);
console.log(` GET /nodes — 活节点列表 (订阅服务调用)`);
console.log(` GET /health — 心跳状态`);
console.log(` GET /learn — 学习数据`);
console.log(` GET /status — 完整状态`);
console.log(` GET /registry — 注册表`);
console.log(` ── 注册接口 (像路由器一样插入) ──`);
console.log(` POST /register — 注册新VPN节点`);
console.log(` POST /unregister — 注销VPN节点`);
console.log(` POST /hldp/v3/heartbeat — HLDP心跳(自动注册)`);
// 启动活模块生命周期
vpnModule.startLifeCycle();
});
// Graceful shutdown
function gracefulShutdown(signal) {
console.log(`\n${signal} received. Shutting down ZY-CLOUD VPN...`);
vpnModule.destroy();
const forceExit = setTimeout(() => process.exit(1), 5000);
mgmtServer.close(() => {
clearTimeout(forceExit);
process.exit(0);
});
}
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
process.on('uncaughtException', (err) => {
console.error('❌ 未捕获异常:', err.message);
console.error(err.stack);
gracefulShutdown('uncaughtException');
});
process.on('unhandledRejection', (reason) => {
console.error('❌ 未处理的Promise拒绝:', reason);
});
// 导出 (供测试和其他模块使用)
module.exports = { ZyCloudVpn, LivingModule };