feat: ∞版本 Phase 1 — 邮件中枢 + 自主进化引擎 + 智能分流增强 + 部署配置 + 定时工作流

Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/c5b8b0a2-c3fb-437f-bd6f-07549f2c5709

Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-04-06 04:26:30 +00:00 committed by GitHub
parent bbd3f49ce5
commit c10556b6d3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 1411 additions and 12 deletions

View File

@ -31,6 +31,7 @@ on:
- send-subscription-v3
- deploy-v3
- switch-v3
- deploy-infinity
default: 'status'
email:
description: '用户邮箱 (add-user/remove-user/send-subscription/send-subscription-v3时需要)'
@ -60,7 +61,8 @@ jobs:
github.event.inputs.action == 'status' ||
github.event.inputs.action == 'restart' ||
github.event.inputs.action == 'deploy-v3' ||
github.event.inputs.action == 'switch-v3'
github.event.inputs.action == 'switch-v3' ||
github.event.inputs.action == 'deploy-infinity'
steps:
- name: '📥 检出代码'

166
.github/workflows/infinity-evolution.yml vendored Normal file
View File

@ -0,0 +1,166 @@
# ═══════════════════════════════════════════════
# 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001
# 📜 Copyright: 国作登字-2026-A-00037559
# ═══════════════════════════════════════════════
# ♾️ 光湖语言世界 · 自主进化定时工作流
#
# 定时触发∞版本的关键任务:
# - 每月1号: 月度进化 (LLM分析 + 流量重置邮件)
# - 每周五: 用户反馈处理 (Phase 2)
# - 手动触发: 任意∞版本操作
#
# 注: 大部分定时任务由服务器上的PM2进程 (auto-evolution.js)
# 自动执行。此工作流作为补充/备份触发机制。
# ═══════════════════════════════════════════════
name: '♾️ 光湖∞ · 自主进化'
on:
schedule:
# 每月1号 UTC 16:00 = CST 00:00 (月度进化)
- cron: '0 16 1 * *'
workflow_dispatch:
inputs:
action:
description: '操作类型'
required: true
type: choice
options:
- monthly-evolution
- deploy-infinity
- status
- send-monthly-reset
- send-update-notify
- send-traffic-warn
default: 'status'
message:
description: '附加消息 (update-notify描述 / traffic-warn百分比)'
required: false
type: string
permissions:
contents: read
concurrency:
group: infinity-evolution
cancel-in-progress: false
jobs:
# ═══ §1 ∞版本部署/管理 ═══
infinity-action:
name: '♾️ ${{ github.event.inputs.action || ''monthly-evolution'' }}'
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: '📥 检出代码'
uses: actions/checkout@v4
- name: '🔑 配置SSH'
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ZY_BRAIN_KEY }}" > ~/.ssh/brain_key
chmod 600 ~/.ssh/brain_key
ssh-keyscan -H ${{ secrets.ZY_BRAIN_HOST }} >> ~/.ssh/known_hosts 2>/dev/null || true
- name: '♾️ 执行∞操作'
env:
ACTION: ${{ github.event.inputs.action || 'monthly-evolution' }}
MESSAGE: ${{ github.event.inputs.message || '' }}
ZY_SMTP_USER: ${{ secrets.ZY_SMTP_USER }}
ZY_SMTP_PASS: ${{ secrets.ZY_SMTP_PASS }}
run: |
SSH_CMD="ssh -i ~/.ssh/brain_key -o StrictHostKeyChecking=no ${{ secrets.ZY_BRAIN_USER }}@${{ secrets.ZY_BRAIN_HOST }}"
PROXY_DIR="/opt/zhuyuan-brain/proxy"
echo "════════════════════════════════════════"
echo "♾️ 光湖∞ · 自主进化 · action=$ACTION"
echo "════════════════════════════════════════"
case "$ACTION" in
deploy-infinity)
echo "部署∞版本..."
# 先同步最新代码到服务器
scp -i ~/.ssh/brain_key -o StrictHostKeyChecking=no \
server/proxy/service/auto-evolution.js \
server/proxy/service/email-hub.js \
server/proxy/service/protocol-mirror.js \
server/proxy/ecosystem.brain-proxy-v3.config.js \
${{ secrets.ZY_BRAIN_USER }}@${{ secrets.ZY_BRAIN_HOST }}:/tmp/
$SSH_CMD "
cp /tmp/auto-evolution.js $PROXY_DIR/service/
cp /tmp/email-hub.js $PROXY_DIR/service/
cp /tmp/protocol-mirror.js $PROXY_DIR/service/
cp /tmp/ecosystem.brain-proxy-v3.config.js $PROXY_DIR/
mkdir -p $PROXY_DIR/data/evolution-reports
cd $PROXY_DIR && pm2 startOrRestart ecosystem.brain-proxy-v3.config.js --update-env && pm2 save
echo '✅ ∞版本已部署'
pm2 list
"
;;
monthly-evolution)
echo "触发月度进化..."
$SSH_CMD "
cd $PROXY_DIR
export ZY_SMTP_USER='$ZY_SMTP_USER'
export ZY_SMTP_PASS='$ZY_SMTP_PASS'
node service/email-hub.js monthly-reset 2>&1 || echo '⚠️ 月度邮件发送遇到问题'
echo '✅ 月度进化已触发'
"
;;
status)
echo "查询∞版本状态..."
$SSH_CMD "
cd $PROXY_DIR
echo '── PM2进程 ──'
pm2 list 2>/dev/null || echo '⚠️ PM2不可用'
echo ''
echo '── 进化状态 ──'
cat data/auto-evolution-status.json 2>/dev/null || echo '(未初始化)'
echo ''
echo '── 镜像状态 ──'
cat data/protocol-mirror-status.json 2>/dev/null || echo '(未初始化)'
echo ''
echo '── 流量池 ──'
cat data/pool-quota-status.json 2>/dev/null || echo '(未初始化)'
"
;;
send-monthly-reset)
echo "发送月初重置邮件..."
$SSH_CMD "
cd $PROXY_DIR
export ZY_SMTP_USER='$ZY_SMTP_USER'
export ZY_SMTP_PASS='$ZY_SMTP_PASS'
node service/email-hub.js monthly-reset
"
;;
send-update-notify)
echo "发送更新通知..."
$SSH_CMD "
cd $PROXY_DIR
export ZY_SMTP_USER='$ZY_SMTP_USER'
export ZY_SMTP_PASS='$ZY_SMTP_PASS'
node service/email-hub.js update-notify '${MESSAGE:-系统已更新}'
"
;;
send-traffic-warn)
echo "发送流量预警..."
$SSH_CMD "
cd $PROXY_DIR
export ZY_SMTP_USER='$ZY_SMTP_USER'
export ZY_SMTP_PASS='$ZY_SMTP_PASS'
node service/email-hub.js traffic-warn '${MESSAGE:-70}'
"
;;
*)
echo "❌ 未知操作: $ACTION"
exit 1
;;
esac

View File

@ -645,6 +645,7 @@ deploy_v3() {
echo "V3运行在 /api/proxy-v3/ (端口3805)"
echo ""
echo "切换V2用户到V3: bash deploy-brain-proxy.sh switch-v3"
echo "升级到∞版本: bash deploy-brain-proxy.sh deploy-infinity"
echo "════════════════════════════════════════"
# V3健康检查
@ -753,6 +754,78 @@ switch_v3() {
echo "════════════════════════════════════════"
}
# ── deploy-infinity: 部署∞版本 ─────────────────
# 在V3基础上启动自主进化引擎和协议镜像引擎
deploy_infinity() {
echo "部署 ∞ 版本 (在V3基础上添加自主进化能力)..."
deploy_services
# 确保V3进程已在运行
if ! pm2 list 2>/dev/null | grep -q "zy-proxy-v3-sub"; then
echo " ⚠️ V3进程未运行先部署V3..."
deploy_v3
fi
# 加载密钥作为环境变量
if [ -f "$PROXY_DIR/.env.keys" ]; then
set -a
# shellcheck source=/dev/null
source "$PROXY_DIR/.env.keys"
set +a
fi
# 复制 ∞ 版本新增文件
echo " 复制∞版本模块..."
for f in auto-evolution.js email-hub.js protocol-mirror.js; do
if [ -f "$REPO_PROXY_DIR/service/$f" ]; then
cp "$REPO_PROXY_DIR/service/$f" "$PROXY_DIR/service/$f"
echo "$f"
else
echo " ⚠️ $f 不存在于仓库"
fi
done
# 复制更新后的PM2配置
cp "$REPO_PROXY_DIR/ecosystem.brain-proxy-v3.config.js" "$PROXY_DIR/ecosystem.brain-proxy-v3.config.js"
# 创建进化报告目录
mkdir -p "$PROXY_DIR/data/evolution-reports"
# 重启所有V3+∞进程 (PM2会根据新配置启动新增进程)
cd "$PROXY_DIR" || { echo "❌ 无法进入 $PROXY_DIR"; return 1; }
pm2 startOrRestart ecosystem.brain-proxy-v3.config.js --update-env
pm2 save
echo ""
echo "════════════════════════════════════════"
echo "♾️ ∞ 版本部署完成!"
echo ""
echo "新增进程:"
echo " zy-auto-evolution — 自主进化引擎 (调度中枢)"
echo " zy-protocol-mirror — 协议镜像引擎 (Xray自动更新)"
echo ""
echo "定时任务:"
echo " 每30分钟 — 协议版本检查"
echo " 每月1号 — 月度进化 + 流量重置邮件"
echo " 流量70% — 全用户预警邮件"
echo ""
echo "PM2进程列表:"
pm2 list 2>/dev/null || true
echo "════════════════════════════════════════"
# 健康检查
sleep 2
echo ""
echo "∞ 版本健康检查:"
for proc in zy-proxy-v3-sub zy-proxy-guardian zy-reverse-boost zy-auto-evolution zy-protocol-mirror; do
if pm2 show "$proc" 2>/dev/null | grep -q "online"; then
echo "$proc: 在线"
else
echo " ⚠️ $proc: 未就绪 (等待启动)"
fi
done
}
# ── 执行 ──────────────────────────────────────
case "$ACTION" in
install) install ;;
@ -764,8 +837,9 @@ case "$ACTION" in
list-users) list_users ;;
deploy-v3) deploy_v3 ;;
switch-v3) switch_v3 ;;
deploy-infinity) deploy_infinity ;;
*)
echo "用法: bash deploy-brain-proxy.sh {install|update|status|restart|add-user|remove-user|list-users|deploy-v3|switch-v3}"
echo "用法: bash deploy-brain-proxy.sh {install|update|status|restart|add-user|remove-user|list-users|deploy-v3|switch-v3|deploy-infinity}"
exit 1
;;
esac

View File

@ -1,20 +1,19 @@
// ═══════════════════════════════════════════════
// 光湖语言世界 V3 · PM2 大脑服务器代理配置
// 光湖语言世界 · PM2 大脑服务器代理配置
// 部署在 ZY-SVR-005 (43.156.237.110) · 大脑服务器
//
// V3独立于V2运行测试通过后切换Nginx即可
// V2进程 (ecosystem.brain-proxy.config.js) 继续运行
// ∞ 版本在V3基础上新增:
// - zy-auto-evolution: 自主进化引擎 (调度所有定时任务)
// - zy-protocol-mirror: 协议镜像引擎 (Xray自动更新)
//
// 切换方式:
// 测试中: /api/proxy-v3/ → 3805
// 切换后: /api/proxy-v2/ → 3805 (Nginx改一行)
// V2进程 (ecosystem.brain-proxy.config.js) 继续运行
// ═══════════════════════════════════════════════
module.exports = {
apps: [
{
name: 'zy-proxy-v3-sub',
version: '3.0.0',
version: '',
script: '/opt/zhuyuan-brain/proxy/service/subscription-server-v3.js',
instances: 1,
exec_mode: 'fork',
@ -30,7 +29,7 @@ module.exports = {
},
{
name: 'zy-proxy-guardian',
version: '3.0.0',
version: '',
script: '/opt/zhuyuan-brain/proxy/service/proxy-guardian.js',
instances: 1,
exec_mode: 'fork',
@ -47,7 +46,7 @@ module.exports = {
},
{
name: 'zy-reverse-boost',
version: '3.0.0',
version: '',
script: '/opt/zhuyuan-brain/proxy/service/reverse-boost-agent.js',
instances: 1,
exec_mode: 'fork',
@ -59,6 +58,40 @@ module.exports = {
log_file: '/opt/zhuyuan-brain/proxy/logs/reverse-boost.log',
error_file: '/opt/zhuyuan-brain/proxy/logs/reverse-boost-error.log',
time: true
},
{
name: 'zy-auto-evolution',
version: '∞',
script: '/opt/zhuyuan-brain/proxy/service/auto-evolution.js',
instances: 1,
exec_mode: 'fork',
env: {
NODE_ENV: 'production',
ZY_BRAIN_PROXY_DIR: '/opt/zhuyuan-brain/proxy',
ZY_PROXY_DATA_DIR: '/opt/zhuyuan-brain/proxy/data',
ZY_PROXY_LOG_DIR: '/opt/zhuyuan-brain/proxy/logs'
},
max_memory_restart: '64M',
log_file: '/opt/zhuyuan-brain/proxy/logs/auto-evolution.log',
error_file: '/opt/zhuyuan-brain/proxy/logs/auto-evolution-error.log',
time: true
},
{
name: 'zy-protocol-mirror',
version: '∞',
script: '/opt/zhuyuan-brain/proxy/service/protocol-mirror.js',
instances: 1,
exec_mode: 'fork',
env: {
NODE_ENV: 'production',
ZY_BRAIN_PROXY_DIR: '/opt/zhuyuan-brain/proxy',
ZY_PROXY_DATA_DIR: '/opt/zhuyuan-brain/proxy/data',
ZY_PROXY_LOG_DIR: '/opt/zhuyuan-brain/proxy/logs'
},
max_memory_restart: '64M',
log_file: '/opt/zhuyuan-brain/proxy/logs/protocol-mirror.log',
error_file: '/opt/zhuyuan-brain/proxy/logs/protocol-mirror-error.log',
time: true
}
]
};

View File

@ -0,0 +1,510 @@
#!/usr/bin/env node
// ═══════════════════════════════════════════════
// 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001
// 📜 Copyright: 国作登字-2026-A-00037559
// ═══════════════════════════════════════════════
// server/proxy/service/auto-evolution.js
// ♾️ 光湖语言世界 · 自主进化引擎
//
// ∞版本核心调度中枢 — 管理所有定时任务和自我进化生命周期
//
// 调度表 (Asia/Shanghai时区):
// - 每30分钟: 协议版本检查 (protocol-mirror.js)
// - 每月1号 00:00: 月度进化周期 (LLM分析 + 流量重置邮件)
// - 每周五 20:00: 处理用户反馈 (Phase 2)
// - 每周一 09:00: 推送反馈结果 (Phase 2)
// - 流量池70%: 触发全用户预警邮件
//
// 更新编排流水线:
// 备份 → 应用 → 验证 → 通知用户 → 失败3次升级告警
//
// 运行方式: PM2 managed (zy-auto-evolution)
// ═══════════════════════════════════════════════
'use strict';
const { execFileSync } = require('child_process');
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 EVOLUTION_FILE = path.join(DATA_DIR, 'auto-evolution-status.json');
const REPORTS_DIR = path.join(DATA_DIR, 'evolution-reports');
const POOL_STATUS_FILE = path.join(DATA_DIR, 'pool-quota-status.json');
const GUARDIAN_FILE = path.join(DATA_DIR, 'guardian-status.json');
const MIRROR_FILE = path.join(DATA_DIR, 'protocol-mirror-status.json');
const USERS_FILE = path.join(DATA_DIR, 'users.json');
const SCHEDULE_CHECK_INTERVAL = 5 * 60 * 1000; // 每5分钟检查调度
const PROTOCOL_CHECK_INTERVAL = 30 * 60 * 1000; // 每30分钟协议检查
const MAX_UPDATE_RETRIES = 3;
// ── 获取中国时间 ─────────────────────────────
function getChinaTime() {
// 返回中国标准时间
const now = new Date();
const utc = now.getTime() + now.getTimezoneOffset() * 60000;
return new Date(utc + 8 * 3600000); // UTC+8
}
function getChinaTimeStr() {
return getChinaTime().toISOString().replace('T', ' ').slice(0, 19) + ' CST';
}
// ── 读取进化状态 ─────────────────────────────
function readEvolutionStatus() {
try {
return JSON.parse(fs.readFileSync(EVOLUTION_FILE, 'utf8'));
} catch {
return {
version: '∞',
started_at: new Date().toISOString(),
last_schedule_check: null,
schedules: {
protocol_check: { interval_min: 30, last_run: null },
monthly_evolution: { schedule: '每月1号 00:00', last_run: null },
weekly_feedback: { schedule: '每周五 20:00', last_run: null },
weekly_response: { schedule: '每周一 09:00', last_run: null },
traffic_alert_70: { last_run: null }
},
evolution_count: 0,
updates_applied: 0,
updates_failed: 0,
last_monthly_report: null,
status: 'running'
};
}
}
// ── 保存进化状态 ─────────────────────────────
function saveEvolutionStatus(status) {
fs.mkdirSync(DATA_DIR, { recursive: true });
fs.writeFileSync(EVOLUTION_FILE, JSON.stringify(status, null, 2));
}
// ── 安全读取JSON ─────────────────────────────
function safeReadJSON(filePath) {
try {
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
} catch {
return null;
}
}
// ── 安全加载模块 ─────────────────────────────
function safeRequire(modulePath) {
try {
return require(modulePath);
} catch (err) {
console.error(`[自主进化] 模块加载失败: ${modulePath}${err.message}`);
return null;
}
}
// ═══════════════════════════════════════════════
// §1 协议版本检查 (每30分钟)
// ═══════════════════════════════════════════════
async function runProtocolCheck() {
console.log('[自主进化] ── 协议版本检查 ──');
const protocolMirror = safeRequire('./protocol-mirror');
if (!protocolMirror) {
console.log('[自主进化] 协议镜像模块不可用,跳过');
return;
}
try {
const result = await protocolMirror.checkForUpdates();
if (result && result.update_available) {
console.log(`[自主进化] 🆕 检测到Xray更新: ${result.installed_version}${result.latest_version}`);
await orchestrateUpdate({
type: 'xray-core',
from_version: result.installed_version,
to_version: result.latest_version,
source: 'protocol-mirror'
});
} else {
console.log('[自主进化] ✅ 协议版本已是最新');
}
} catch (err) {
console.error('[自主进化] 协议检查异常:', err.message);
}
}
// ═══════════════════════════════════════════════
// §2 月度进化周期 (每月1号 00:00)
// ═══════════════════════════════════════════════
async function monthlyEvolution() {
console.log('[自主进化] ════ 月度进化周期开始 ════');
const status = readEvolutionStatus();
// 1. 收集系统状态
const guardian = safeReadJSON(GUARDIAN_FILE) || {};
const mirror = safeReadJSON(MIRROR_FILE) || {};
const pool = safeReadJSON(POOL_STATUS_FILE) || {};
const users = safeReadJSON(USERS_FILE) || { users: [] };
const userCount = (users.users || []).filter(u => u.enabled !== false).length;
console.log(`[自主进化] 系统状态: ${userCount}活跃用户, 守护=${guardian.status || '未知'}, 镜像=${mirror.mirror_status || '未知'}`);
// 2. LLM 深度推理 — 系统健康分析
const llmRouter = safeRequire('./llm-router');
let llmAnalysis = null;
if (llmRouter) {
try {
const prompt = `你是光湖语言世界VPN系统的月度分析引擎。现在是每月1号请分析系统状态并给出建议。
当前状态:
- 活跃用户: ${userCount}
- 守护Agent状态: ${guardian.status || '未知'}自动修复${guardian.auto_fixes || 0}LLM咨询${guardian.llm_consultations || 0}
- 协议镜像: ${mirror.mirror_status || '未知'}已安装版本=${mirror.installed_version || '未知'}最新=${mirror.latest_version || '未知'}
- 上月流量池: 已用${pool.pool_used_gb ? pool.pool_used_gb.toFixed(1) : '?'}GB / ${pool.pool_total_gb || 2000}GB (${pool.pool_percentage || 0}%)
- 累计进化次数: ${status.evolution_count}
- 累计更新: 成功${status.updates_applied}失败${status.updates_failed}
请回答:
1. 当前系统是否健康(一句话)
2. 是否需要升级Xray或调整配置(/+理由)
3. 流量使用是否正常(一句话)
4. 本月优化建议 (最多3条)
请用JSON格式回复: {"healthy":bool,"need_upgrade":bool,"analysis":"...","suggestions":["..."]}`;
const result = await llmRouter.callLLM(prompt, {
systemPrompt: '你是光湖语言世界VPN系统的AI分析引擎。请以JSON格式简洁回复。',
maxTokens: 800,
timeout: 60000
});
if (result) {
llmAnalysis = result.content;
console.log(`[自主进化] LLM分析完成 (模型: ${result.model})`);
}
} catch (err) {
console.error('[自主进化] LLM月度分析失败:', err.message);
}
}
// 3. 发送月初重置邮件给所有用户
console.log('[自主进化] 发送月初重置邮件...');
try {
const emailHub = safeRequire('./email-hub');
if (emailHub) {
const result = await emailHub.sendMonthlyResetEmail();
console.log(`[自主进化] 月初邮件: ${result.sent}成功 / ${result.failed}失败`);
} else {
// 回退: 用send-subscription.js的alert命令
const sendScript = path.join(__dirname, 'send-subscription.js');
execFileSync('node', [sendScript, 'alert', '每月1号流量池已重置 · 本月可用2000GB'], {
encoding: 'utf8', timeout: 30000
});
console.log('[自主进化] 月初邮件: 已通过alert方式发送给管理员');
}
} catch (err) {
console.error('[自主进化] 月初邮件发送失败:', err.message);
}
// 4. 生成月度进化报告
const report = {
period: getChinaTime().toISOString().slice(0, 7),
generated_at: new Date().toISOString(),
user_count: userCount,
guardian_status: guardian.status || 'unknown',
mirror_status: mirror.mirror_status || 'unknown',
pool_last_month: {
used_gb: pool.pool_used_gb || 0,
total_gb: pool.pool_total_gb || 2000,
percentage: pool.pool_percentage || 0
},
llm_analysis: llmAnalysis,
evolution_count: status.evolution_count,
updates: { applied: status.updates_applied, failed: status.updates_failed }
};
fs.mkdirSync(REPORTS_DIR, { recursive: true });
const reportFile = path.join(REPORTS_DIR, `${report.period}.json`);
fs.writeFileSync(reportFile, JSON.stringify(report, null, 2));
console.log(`[自主进化] 月度报告已保存: ${reportFile}`);
// 5. 更新状态
status.evolution_count++;
status.last_monthly_report = report.period;
status.schedules.monthly_evolution.last_run = new Date().toISOString();
saveEvolutionStatus(status);
console.log('[自主进化] ════ 月度进化周期完成 ════');
return report;
}
// ═══════════════════════════════════════════════
// §3 更新编排 (检测到更新时触发)
// ═══════════════════════════════════════════════
async function orchestrateUpdate(updateInfo) {
console.log(`[自主进化] ═══ 开始更新编排: ${updateInfo.type} ═══`);
const status = readEvolutionStatus();
let retries = 0;
let success = false;
while (retries < MAX_UPDATE_RETRIES && !success) {
retries++;
console.log(`[自主进化] 尝试 ${retries}/${MAX_UPDATE_RETRIES}...`);
try {
// Phase 1: 备份
console.log('[自主进化] Phase 1: 备份当前状态...');
// protocol-mirror.js 内部会处理备份
// Phase 2: 应用更新
console.log('[自主进化] Phase 2: 应用更新...');
const protocolMirror = safeRequire('./protocol-mirror');
if (!protocolMirror) {
throw new Error('协议镜像模块不可用');
}
await protocolMirror.performUpdate();
// Phase 3: 验证
console.log('[自主进化] Phase 3: 验证更新...');
const mirrorStatus = safeReadJSON(MIRROR_FILE);
if (mirrorStatus && mirrorStatus.mirror_status === 'error') {
throw new Error('更新后镜像状态异常');
}
success = true;
status.updates_applied++;
console.log('[自主进化] ✅ 更新成功');
} catch (err) {
console.error(`[自主进化] ❌ 更新失败 (尝试${retries}): ${err.message}`);
if (retries < MAX_UPDATE_RETRIES) {
console.log('[自主进化] 等待30秒后重试...');
await new Promise(r => setTimeout(r, 30000));
}
}
}
if (success) {
// Phase 4: 通知用户
console.log('[自主进化] Phase 4: 通知所有用户...');
try {
const emailHub = safeRequire('./email-hub');
if (emailHub) {
const description = `Xray核心已从 ${updateInfo.from_version} 升级到 ${updateInfo.to_version}\n安全性和性能已优化,无需手动操作。\n刷新订阅即可获取最新配置。`;
await emailHub.sendUpdateNotifyEmail(description);
}
} catch (err) {
console.error('[自主进化] 更新通知邮件发送失败:', err.message);
}
} else {
// Phase 5: 3次失败 → 告警管理员
status.updates_failed++;
console.error('[自主进化] ⚠️ 更新3次均失败告警管理员...');
try {
const sendScript = path.join(__dirname, 'send-subscription.js');
const msg = `Xray更新失败(${MAX_UPDATE_RETRIES}次)\n类型: ${updateInfo.type}\n目标版本: ${updateInfo.to_version}\n请手动检查`;
execFileSync('node', [sendScript, 'alert', msg], {
encoding: 'utf8', timeout: 30000
});
} catch (err) {
console.error('[自主进化] 告警邮件也发送失败:', err.message);
}
}
saveEvolutionStatus(status);
console.log(`[自主进化] ═══ 更新编排完成 (${success ? '成功' : '失败'}) ═══`);
return success;
}
// ═══════════════════════════════════════════════
// §4 流量预警处理
// ═══════════════════════════════════════════════
async function handleTrafficAlert(percentage) {
console.log(`[自主进化] 流量预警触发: ${percentage}%`);
try {
const emailHub = safeRequire('./email-hub');
if (emailHub) {
await emailHub.sendTrafficWarnEmail(percentage);
} else {
const sendScript = path.join(__dirname, 'send-subscription.js');
execFileSync('node', [sendScript, 'alert', `流量池已使用${percentage}%,请注意用量`], {
encoding: 'utf8', timeout: 30000
});
}
} catch (err) {
console.error('[自主进化] 流量预警邮件发送失败:', err.message);
}
}
// ═══════════════════════════════════════════════
// §5 用户反馈处理 (Phase 2 预留)
// ═══════════════════════════════════════════════
async function processWeeklyFeedback() {
console.log('[自主进化] 📥 用户反馈处理 (Phase 2 — 待实现)');
// Phase 2: 从COS桶读取反馈 → LLM评估 → 生成处理结果
const status = readEvolutionStatus();
status.schedules.weekly_feedback.last_run = new Date().toISOString();
saveEvolutionStatus(status);
}
async function sendWeeklyResponse() {
console.log('[自主进化] 📤 反馈结果推送 (Phase 2 — 待实现)');
// Phase 2: 将处理结果推送给对应用户
const status = readEvolutionStatus();
status.schedules.weekly_response.last_run = new Date().toISOString();
saveEvolutionStatus(status);
}
// ═══════════════════════════════════════════════
// §6 流量池监测 (检查是否需要触发70%预警)
// ═══════════════════════════════════════════════
async function checkTrafficPoolAlert() {
const pool = safeReadJSON(POOL_STATUS_FILE);
if (!pool) return;
const status = readEvolutionStatus();
const pct = pool.pool_percentage || 0;
// 70%预警 (每月只触发一次)
if (pct >= 70) {
const lastRun = status.schedules.traffic_alert_70.last_run;
const currentPeriod = pool.period || new Date().toISOString().slice(0, 7);
// 检查本月是否已发过
if (!lastRun || !lastRun.startsWith(currentPeriod.replace('-', '').slice(0, 6))) {
await handleTrafficAlert(Math.round(pct));
status.schedules.traffic_alert_70.last_run = new Date().toISOString();
saveEvolutionStatus(status);
}
}
}
// ═══════════════════════════════════════════════
// §7 调度引擎 (每5分钟检查)
// ═══════════════════════════════════════════════
async function checkSchedule() {
const now = getChinaTime();
const status = readEvolutionStatus();
status.last_schedule_check = new Date().toISOString();
const hour = now.getHours();
const minute = now.getMinutes();
const day = now.getDate();
const dayOfWeek = now.getDay(); // 0=周日, 5=周五, 1=周一
// ── 协议版本检查 (每30分钟) ──
const lastProtocol = status.schedules.protocol_check.last_run;
if (!lastProtocol || (Date.now() - new Date(lastProtocol).getTime()) >= PROTOCOL_CHECK_INTERVAL) {
try {
await runProtocolCheck();
status.schedules.protocol_check.last_run = new Date().toISOString();
} catch (err) {
console.error('[自主进化] 协议检查调度异常:', err.message);
}
}
// ── 月度进化 (每月1号 00:00-00:04) ──
if (day === 1 && hour === 0 && minute < 5) {
const lastMonthly = status.schedules.monthly_evolution.last_run;
const today = now.toISOString().slice(0, 10);
if (!lastMonthly || !lastMonthly.startsWith(today.slice(0, 4))) {
// 进一步检查: 确保本月还没执行过
const lastRunDate = lastMonthly ? new Date(lastMonthly) : null;
const lastRunMonth = lastRunDate ? `${lastRunDate.getFullYear()}-${String(lastRunDate.getMonth() + 1).padStart(2, '0')}` : '';
const currentMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
if (lastRunMonth !== currentMonth) {
try {
await monthlyEvolution();
} catch (err) {
console.error('[自主进化] 月度进化调度异常:', err.message);
}
}
}
}
// ── 周五20:00 用户反馈处理 ──
if (dayOfWeek === 5 && hour === 20 && minute < 5) {
const lastFeedback = status.schedules.weekly_feedback.last_run;
const today = now.toISOString().slice(0, 10);
if (!lastFeedback || !lastFeedback.startsWith(today)) {
try {
await processWeeklyFeedback();
} catch (err) {
console.error('[自主进化] 反馈处理调度异常:', err.message);
}
}
}
// ── 周一09:00 推送反馈结果 ──
if (dayOfWeek === 1 && hour === 9 && minute < 5) {
const lastResponse = status.schedules.weekly_response.last_run;
const today = now.toISOString().slice(0, 10);
if (!lastResponse || !lastResponse.startsWith(today)) {
try {
await sendWeeklyResponse();
} catch (err) {
console.error('[自主进化] 反馈推送调度异常:', err.message);
}
}
}
// ── 流量池预警检测 ──
try {
await checkTrafficPoolAlert();
} catch (err) {
console.error('[自主进化] 流量预警检测异常:', err.message);
}
saveEvolutionStatus(status);
}
// ── 获取进化状态 (供外部查询) ─────────────────
function getEvolutionStatus() {
return readEvolutionStatus();
}
// ═══════════════════════════════════════════════
// 导出
// ═══════════════════════════════════════════════
module.exports = {
monthlyEvolution,
orchestrateUpdate,
handleTrafficAlert,
getEvolutionStatus,
runProtocolCheck
};
// ═══════════════════════════════════════════════
// 启动自主进化引擎
// ═══════════════════════════════════════════════
if (require.main === module) {
console.log('♾️ 光湖语言世界 · 自主进化引擎启动 (∞ 版本)');
console.log(` 调度间隔: ${SCHEDULE_CHECK_INTERVAL / 1000}`);
console.log(` 协议检查: 每${PROTOCOL_CHECK_INTERVAL / 60000}分钟`);
console.log(` 月度进化: 每月1号 00:00 CST`);
console.log(` 反馈处理: 每周五 20:00 CST (Phase 2)`);
console.log(` 反馈推送: 每周一 09:00 CST (Phase 2)`);
console.log(` 当前时间: ${getChinaTimeStr()}`);
console.log(` 数据目录: ${DATA_DIR}`);
// 初始化状态文件
const status = readEvolutionStatus();
status.started_at = new Date().toISOString();
status.status = 'running';
saveEvolutionStatus(status);
// 立即执行一次协议检查
runProtocolCheck().catch(err => {
console.error('[自主进化] 初始协议检查失败:', err.message);
});
// 定期调度检查
setInterval(() => {
checkSchedule().catch(err => {
console.error('[自主进化] 调度检查异常:', err.message);
});
}, SCHEDULE_CHECK_INTERVAL);
}

View File

@ -0,0 +1,590 @@
#!/usr/bin/env node
// ═══════════════════════════════════════════════
// 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001
// 📜 Copyright: 国作登字-2026-A-00037559
// ═══════════════════════════════════════════════
// server/proxy/service/email-hub.js
// 📧 光湖语言世界 · 邮件通信中枢
//
// ∞版本邮件中枢 — 统一管理所有用户邮件推送
//
// 邮件类型:
// 1. 月初重置通知 — 每月1号通知流量池已重置
// 2. 更新升级通知 — 系统更新后告知用户
// 3. 流量预警通知 — 70%/80%/90%/100% 阶梯告警
// 4. 安全风险提醒 — 单用户异常行为告知
// 5. 反馈确认回复 — 收到用户反馈后的自动确认
//
// 所有邮件底部附「意见反馈」链接
//
// 用法:
// node email-hub.js monthly-reset — 发送月初重置邮件给所有用户
// node email-hub.js update-notify <desc> — 发送更新通知给所有用户
// node email-hub.js traffic-warn <pct> — 发送流量预警给所有用户
// node email-hub.js security-warn <email> <msg> — 发送安全提醒给单用户
// node email-hub.js feedback-ack <email> — 发送反馈确认给单用户
//
// 运行方式: CLI调用 (由auto-evolution.js调度)
// ═══════════════════════════════════════════════
'use strict';
const tls = require('tls');
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 USERS_FILE = path.join(DATA_DIR, 'users.json');
const KEYS_FILE = process.env.ZY_PROXY_KEYS_FILE || path.join(PROXY_DIR, '.env.keys');
const POOL_STATUS_FILE = path.join(DATA_DIR, 'pool-quota-status.json');
const EMAIL_LOG_FILE = path.join(DATA_DIR, 'email-hub-log.json');
// ── 加载配置 ─────────────────────────────────
function loadConfig() {
const config = {
smtp_user: process.env.ZY_SMTP_USER || '',
smtp_pass: process.env.ZY_SMTP_PASS || '',
server_host: process.env.ZY_SERVER_HOST || ''
};
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('=');
const k = key.trim();
const v = vals.join('=').trim();
if (!v) continue;
if (k === 'ZY_SERVER_HOST' && !config.server_host) config.server_host = v;
if (k === 'ZY_SMTP_USER' && !config.smtp_user) config.smtp_user = v;
if (k === 'ZY_SMTP_PASS' && !config.smtp_pass) config.smtp_pass = v;
}
} catch { /* ignore */ }
return config;
}
// ── SMTP发送邮件 ─────────────────────────────
function detectSmtpHost(email) {
if (email.includes('@qq.com')) return 'smtp.qq.com';
if (email.includes('@163.com')) return 'smtp.163.com';
if (email.includes('@126.com')) return 'smtp.126.com';
if (email.includes('@gmail.com')) return 'smtp.gmail.com';
if (email.includes('@outlook.com') || email.includes('@hotmail.com')) return 'smtp.office365.com';
if (email.includes('@yeah.net')) return 'smtp.yeah.net';
return 'smtp.qq.com';
}
async function sendEmail(to, subject, htmlBody) {
const config = loadConfig();
if (!config.smtp_user || !config.smtp_pass) {
throw new Error('SMTP未配置 (需要ZY_SMTP_USER和ZY_SMTP_PASS)');
}
const smtpHost = detectSmtpHost(config.smtp_user);
const smtpPort = 465;
return new Promise((resolve, reject) => {
const socket = tls.connect(smtpPort, smtpHost, {}, () => {
let step = 0;
const from = config.smtp_user;
const commands = [
`EHLO zy-proxy\r\n`,
`AUTH LOGIN\r\n`,
`${Buffer.from(from).toString('base64')}\r\n`,
`${Buffer.from(config.smtp_pass).toString('base64')}\r\n`,
`MAIL FROM:<${from}>\r\n`,
`RCPT TO:<${to}>\r\n`,
`DATA\r\n`,
`From: =?UTF-8?B?${Buffer.from('光湖语言世界').toString('base64')}?= <${from}>\r\nTo: <${to}>\r\nSubject: =?UTF-8?B?${Buffer.from(subject).toString('base64')}?=\r\nContent-Type: text/html; charset=utf-8\r\nMIME-Version: 1.0\r\n\r\n${htmlBody}\r\n.\r\n`,
`QUIT\r\n`
];
socket.on('data', () => {
if (step < commands.length) {
socket.write(commands[step]);
step++;
}
if (step >= commands.length) {
resolve(true);
}
});
socket.on('error', (err) => { reject(err); });
});
socket.on('error', (err) => { reject(err); });
setTimeout(() => {
try { socket.destroy(); } catch { /* ignore */ }
reject(new Error('SMTP超时(30s)'));
}, 30000);
});
}
// ── 加载所有启用用户 ─────────────────────────
function getEnabledUsers() {
try {
const data = JSON.parse(fs.readFileSync(USERS_FILE, 'utf8'));
return (data.users || []).filter(u => u.enabled !== false);
} catch {
return [];
}
}
// ── 加载流量池状态 ────────────────────────────
function getPoolStatus() {
try {
return JSON.parse(fs.readFileSync(POOL_STATUS_FILE, 'utf8'));
} catch {
return { pool_total_gb: 2000, pool_used_gb: 0, pool_percentage: 0, period: '' };
}
}
// ── 记录邮件发送日志 ─────────────────────────
function logEmail(type, recipients, success, error) {
let log;
try {
log = JSON.parse(fs.readFileSync(EMAIL_LOG_FILE, 'utf8'));
} catch {
log = { entries: [] };
}
log.entries.push({
type,
recipients: Array.isArray(recipients) ? recipients.length : 1,
success,
error: error || null,
time: new Date().toISOString()
});
// 只保留最近100条
if (log.entries.length > 100) {
log.entries = log.entries.slice(-100);
}
fs.mkdirSync(DATA_DIR, { recursive: true });
fs.writeFileSync(EMAIL_LOG_FILE, JSON.stringify(log, null, 2));
}
// ── 反馈链接 (所有邮件底部附带) ───────────────
function getFeedbackFooter(config) {
const host = config.server_host || 'guanghulab.com';
return `
<hr style="border: none; border-top: 1px solid #eee; margin: 25px 0 15px;">
<div style="text-align: center; padding: 10px;">
<p style="color: #888; font-size: 12px; margin: 0 0 8px;">
有建议或问题欢迎反馈 👇
</p>
<a href="https://${host}/api/proxy-v3/feedback"
style="display: inline-block; background: #4a90d9; color: white; padding: 8px 20px; border-radius: 6px; text-decoration: none; font-size: 13px;">
📝 提交意见反馈
</a>
<p style="color: #aaa; font-size: 11px; margin: 10px 0 0;">
每周五 20:00 铸渊集中处理 · 每周一推送处理结果
</p>
</div>`;
}
// ── 通用邮件模板包装 ─────────────────────────
function wrapEmailTemplate(title, content, config) {
const now = new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' });
const feedbackFooter = getFeedbackFooter(config);
return `
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"></head>
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; background: #f8f9fa;">
<div style="background: white; border-radius: 12px; padding: 30px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);">
<h1 style="color: #1a1a2e; margin-bottom: 5px;">🌐 光湖语言世界</h1>
<p style="color: #666; margin-top: 0; font-size: 13px;">${title}</p>
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
${content}
${feedbackFooter}
<hr style="border: none; border-top: 1px solid #eee; margin: 15px 0;">
<p style="color: #aaa; font-size: 11px; text-align: center;">
光湖语言世界 · 版本 · ${now}<br>
国作登字-2026-A-00037559
</p>
</div>
</body>
</html>`;
}
// ═══════════════════════════════════════════════
// 📧 邮件类型 1: 月初重置通知
// ═══════════════════════════════════════════════
function generateMonthlyResetEmail(config) {
const now = new Date();
const month = `${now.getFullYear()}${now.getMonth() + 1}`;
const content = `
<div style="background: #d4edda; border: 1px solid #c3e6cb; border-radius: 8px; padding: 15px; margin: 15px 0;">
<strong style="color: #155724;"> ${month} 流量节点已重置</strong>
</div>
<h3 style="color: #333;">📊 本月配额</h3>
<table style="width: 100%; border-collapse: collapse;">
<tr><td style="padding: 8px; color: #666;">流量池</td><td style="padding: 8px; font-weight: bold;">2000 GB</td></tr>
<tr><td style="padding: 8px; color: #666;">已使用</td><td style="padding: 8px; color: #28a745; font-weight: bold;">0 GB ()</td></tr>
<tr><td style="padding: 8px; color: #666;">重置日期</td><td style="padding: 8px;">1</td></tr>
<tr><td style="padding: 8px; color: #666;">协议</td><td style="padding: 8px;">VLESS + Reality</td></tr>
</table>
<p style="color: #666; font-size: 13px; margin-top: 15px;">
💡 您无需任何操作刷新订阅即可继续使用<br>
流量池为所有用户共享请合理使用
</p>`;
return wrapEmailTemplate(`${month} · 流量重置通知`, content, config);
}
// ═══════════════════════════════════════════════
// 📧 邮件类型 2: 更新升级通知
// ═══════════════════════════════════════════════
function generateUpdateNotifyEmail(description, config) {
const now = new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' });
const content = `
<div style="background: #cce5ff; border: 1px solid #b8daff; border-radius: 8px; padding: 15px; margin: 15px 0;">
<strong style="color: #004085;">🔄 系统已完成升级</strong>
</div>
<h3 style="color: #333;">📋 本次更新内容</h3>
<div style="background: #f8f9fa; border-radius: 8px; padding: 15px; color: #333; line-height: 1.8;">
${escapeHtml(description).replace(/\n/g, '<br>')}
</div>
<p style="color: #666; font-size: 13px; margin-top: 15px;">
更新时间: ${now}<br>
💡 大部分更新只需刷新订阅即可生效如需重新下载订阅链接会另行通知
</p>`;
return wrapEmailTemplate('系统升级通知', content, config);
}
// ═══════════════════════════════════════════════
// 📧 邮件类型 3: 流量预警通知
// ═══════════════════════════════════════════════
function generateTrafficWarnEmail(percentage, poolStatus, config) {
const usedGB = poolStatus.pool_used_gb ? poolStatus.pool_used_gb.toFixed(1) : '?';
const totalGB = poolStatus.pool_total_gb || 2000;
const remainGB = (totalGB - parseFloat(usedGB)).toFixed(1);
let urgencyColor, urgencyBg, urgencyBorder, urgencyText;
if (percentage >= 100) {
urgencyColor = '#721c24'; urgencyBg = '#f8d7da'; urgencyBorder = '#f5c6cb';
urgencyText = '⛔ 流量池已耗尽!所有连接已暂停。';
} else if (percentage >= 90) {
urgencyColor = '#856404'; urgencyBg = '#fff3cd'; urgencyBorder = '#ffeaa7';
urgencyText = `⚠️ 流量池仅剩 ${remainGB}GB请节约使用`;
} else {
urgencyColor = '#0c5460'; urgencyBg = '#d1ecf1'; urgencyBorder = '#bee5eb';
urgencyText = `📊 流量池已使用 ${percentage}%,剩余 ${remainGB}GB`;
}
const content = `
<div style="background: ${urgencyBg}; border: 1px solid ${urgencyBorder}; border-radius: 8px; padding: 15px; margin: 15px 0;">
<strong style="color: ${urgencyColor};">${urgencyText}</strong>
</div>
<h3 style="color: #333;">📊 流量池状态</h3>
<table style="width: 100%; border-collapse: collapse;">
<tr><td style="padding: 8px; color: #666;">已使用</td><td style="padding: 8px; font-weight: bold;">${usedGB} GB</td></tr>
<tr><td style="padding: 8px; color: #666;">总配额</td><td style="padding: 8px;">${totalGB} GB</td></tr>
<tr><td style="padding: 8px; color: #666;">使用率</td><td style="padding: 8px; font-weight: bold; color: ${urgencyColor};">${percentage}%</td></tr>
<tr><td style="padding: 8px; color: #666;">剩余</td><td style="padding: 8px;">${remainGB} GB</td></tr>
</table>
<div style="background: #f0f0f0; border-radius: 8px; height: 20px; margin: 15px 0; overflow: hidden;">
<div style="background: ${percentage >= 90 ? '#dc3545' : percentage >= 70 ? '#ffc107' : '#28a745'}; height: 100%; width: ${Math.min(percentage, 100)}%; border-radius: 8px;"></div>
</div>
<p style="color: #888; font-size: 12px;">
流量池每月1日重置所有用户共享 ${totalGB}GB 月配额
</p>`;
return wrapEmailTemplate('流量预警通知', content, config);
}
// ═══════════════════════════════════════════════
// 📧 邮件类型 4: 安全风险提醒 (单用户)
// ═══════════════════════════════════════════════
function generateSecurityWarnEmail(message, config) {
const content = `
<div style="background: #fff3cd; border: 1px solid #ffc107; border-radius: 8px; padding: 15px; margin: 15px 0;">
<strong style="color: #856404;">🛡 安全提醒</strong>
</div>
<div style="background: #f8f9fa; border-radius: 8px; padding: 15px; color: #333; line-height: 1.8;">
${escapeHtml(message).replace(/\n/g, '<br>')}
</div>
<h3 style="color: #333;">💡 安全建议</h3>
<ul style="color: #666; line-height: 2;">
<li>避免同时使用多个VPN客户端</li>
<li>关闭不使用的VPN连接</li>
<li>确保订阅链接仅个人使用</li>
<li>如有异常请及时反馈</li>
</ul>`;
return wrapEmailTemplate('安全使用提醒', content, config);
}
// ═══════════════════════════════════════════════
// 📧 邮件类型 5: 反馈确认回复
// ═══════════════════════════════════════════════
function generateFeedbackAckEmail(config) {
const content = `
<div style="background: #d4edda; border: 1px solid #c3e6cb; border-radius: 8px; padding: 15px; margin: 15px 0;">
<strong style="color: #155724;"> 您的反馈已收到</strong>
</div>
<p style="color: #666; line-height: 1.8;">
感谢您的宝贵意见铸渊将在以下时间处理
</p>
<table style="width: 100%; border-collapse: collapse;">
<tr><td style="padding: 8px; color: #666;">📥 收集截止</td><td style="padding: 8px;"> 20:00</td></tr>
<tr><td style="padding: 8px; color: #666;">🔍 深度分析</td><td style="padding: 8px;"> (AI)</td></tr>
<tr><td style="padding: 8px; color: #666;">📤 结果推送</td><td style="padding: 8px;"> 09:00</td></tr>
</table>
<p style="color: #888; font-size: 12px; margin-top: 15px;">
: 并非所有需求都会被采纳铸渊会基于系统安全性架构完整性和整体规划进行评估
</p>`;
return wrapEmailTemplate('反馈已收到', content, config);
}
// ── HTML转义 ─────────────────────────────────
function escapeHtml(str) {
return String(str)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}
// ═══════════════════════════════════════════════
// 📧 批量发送函数
// ═══════════════════════════════════════════════
/**
* 给所有启用用户发送邮件
* @param {string} subject 邮件主题
* @param {Function} htmlGenerator 生成HTML的函数(接收config参数)
* @returns {Promise<{sent: number, failed: number, errors: string[]}>}
*/
async function sendToAllUsers(subject, htmlGenerator) {
const users = getEnabledUsers();
const config = loadConfig();
if (users.length === 0) {
console.log('[邮件中枢] 无启用用户,跳过发送');
return { sent: 0, failed: 0, errors: [] };
}
const html = htmlGenerator(config);
let sent = 0;
let failed = 0;
const errors = [];
console.log(`[邮件中枢] 开始批量发送 (${users.length}位用户): ${subject}`);
for (const user of users) {
try {
await sendEmail(user.email, subject, html);
sent++;
console.log(`${user.email}`);
// 间隔500ms避免SMTP限流
await new Promise(r => setTimeout(r, 500));
} catch (err) {
failed++;
errors.push(`${user.email}: ${err.message}`);
console.error(`${user.email}: ${err.message}`);
}
}
console.log(`[邮件中枢] 发送完成: ${sent}成功 / ${failed}失败`);
return { sent, failed, errors };
}
// ═══════════════════════════════════════════════
// 📧 公开API (供auto-evolution.js调用)
// ═══════════════════════════════════════════════
/**
* 发送月初重置通知给所有用户
*/
async function sendMonthlyResetEmail() {
const result = await sendToAllUsers(
'🌐 光湖语言世界 · 本月流量已重置',
(config) => generateMonthlyResetEmail(config)
);
logEmail('monthly-reset', result.sent + result.failed, result.sent, result.errors.join('; '));
return result;
}
/**
* 发送更新通知给所有用户
* @param {string} description 更新说明
*/
async function sendUpdateNotifyEmail(description) {
const result = await sendToAllUsers(
'🌐 光湖语言世界 · 系统已升级',
(config) => generateUpdateNotifyEmail(description, config)
);
logEmail('update-notify', result.sent + result.failed, result.sent, result.errors.join('; '));
return result;
}
/**
* 发送流量预警给所有用户
* @param {number} percentage 流量使用百分比
*/
async function sendTrafficWarnEmail(percentage) {
const poolStatus = getPoolStatus();
const result = await sendToAllUsers(
`🌐 光湖语言世界 · 流量预警 (${percentage}%)`,
(config) => generateTrafficWarnEmail(percentage, poolStatus, config)
);
logEmail('traffic-warn', result.sent + result.failed, result.sent, result.errors.join('; '));
return result;
}
/**
* 发送安全提醒给单个用户
* @param {string} email 目标邮箱
* @param {string} message 安全提醒内容
*/
async function sendSecurityWarnEmail(email, message) {
const config = loadConfig();
const html = generateSecurityWarnEmail(message, config);
try {
await sendEmail(email, '🛡️ 光湖语言世界 · 安全提醒', html);
console.log(`[邮件中枢] ✅ 安全提醒已发送: ${email}`);
logEmail('security-warn', email, true, null);
return { sent: 1, failed: 0 };
} catch (err) {
console.error(`[邮件中枢] ❌ 安全提醒发送失败: ${err.message}`);
logEmail('security-warn', email, false, err.message);
return { sent: 0, failed: 1 };
}
}
/**
* 发送反馈确认给单个用户
* @param {string} email 目标邮箱
*/
async function sendFeedbackAckEmail(email) {
const config = loadConfig();
const html = generateFeedbackAckEmail(config);
try {
await sendEmail(email, '🌐 光湖语言世界 · 反馈已收到', html);
console.log(`[邮件中枢] ✅ 反馈确认已发送: ${email}`);
logEmail('feedback-ack', email, true, null);
return { sent: 1, failed: 0 };
} catch (err) {
console.error(`[邮件中枢] ❌ 反馈确认发送失败: ${err.message}`);
logEmail('feedback-ack', email, false, err.message);
return { sent: 0, failed: 1 };
}
}
// ═══════════════════════════════════════════════
// CLI 主入口
// ═══════════════════════════════════════════════
async function main() {
const [,, action, arg1, arg2] = process.argv;
if (!action) {
console.log('📧 光湖语言世界 · 邮件通信中枢');
console.log('用法:');
console.log(' node email-hub.js monthly-reset — 月初重置通知');
console.log(' node email-hub.js update-notify <描述> — 更新升级通知');
console.log(' node email-hub.js traffic-warn <百分比> — 流量预警通知');
console.log(' node email-hub.js security-warn <邮箱> <消息> — 安全风险提醒');
console.log(' node email-hub.js feedback-ack <邮箱> — 反馈确认回复');
process.exit(0);
}
switch (action) {
case 'monthly-reset': {
const result = await sendMonthlyResetEmail();
console.log(`📧 月初重置通知: ${result.sent}成功 / ${result.failed}失败`);
break;
}
case 'update-notify': {
if (!arg1) {
console.error('❌ 请提供更新描述');
process.exit(1);
}
const result = await sendUpdateNotifyEmail(arg1);
console.log(`📧 更新通知: ${result.sent}成功 / ${result.failed}失败`);
break;
}
case 'traffic-warn': {
const pct = parseInt(arg1, 10);
if (isNaN(pct)) {
console.error('❌ 请提供流量使用百分比 (数字)');
process.exit(1);
}
const result = await sendTrafficWarnEmail(pct);
console.log(`📧 流量预警: ${result.sent}成功 / ${result.failed}失败`);
break;
}
case 'security-warn': {
if (!arg1 || !arg2) {
console.error('❌ 请提供邮箱和消息');
process.exit(1);
}
await sendSecurityWarnEmail(arg1, arg2);
break;
}
case 'feedback-ack': {
if (!arg1) {
console.error('❌ 请提供邮箱');
process.exit(1);
}
await sendFeedbackAckEmail(arg1);
break;
}
default:
console.error(`❌ 未知操作: ${action}`);
process.exit(1);
}
}
// 导出API (供其他模块调用)
module.exports = {
sendMonthlyResetEmail,
sendUpdateNotifyEmail,
sendTrafficWarnEmail,
sendSecurityWarnEmail,
sendFeedbackAckEmail,
getEnabledUsers,
sendEmail
};
// CLI直接运行
if (require.main === module) {
main().catch(err => {
console.error('❌ 邮件中枢异常:', err.message);
process.exit(1);
});
}

View File

@ -424,26 +424,36 @@ rules:
# Apple (直连)
- DOMAIN-SUFFIX,apple.com,DIRECT
- DOMAIN-SUFFIX,icloud.com,DIRECT
- DOMAIN-SUFFIX,mzstatic.com,DIRECT
- DOMAIN-SUFFIX,apple-cloudkit.com,DIRECT
# CDN静态资源 (直连 · 节省池带宽)
- DOMAIN-SUFFIX,cdn.jsdelivr.net,DIRECT
- DOMAIN-SUFFIX,cdnjs.cloudflare.com,DIRECT
- DOMAIN-SUFFIX,unpkg.com,DIRECT
- DOMAIN-SUFFIX,bootcdn.net,DIRECT
- DOMAIN-SUFFIX,staticfile.org,DIRECT
# 国内直连
# 国内直连 ( 智能分流 · 用户切回国内网时VPN休眠)
- DOMAIN-SUFFIX,cn,DIRECT
- DOMAIN-SUFFIX,taobao.com,DIRECT
- DOMAIN-SUFFIX,tmall.com,DIRECT
- DOMAIN-SUFFIX,alipay.com,DIRECT
- DOMAIN-SUFFIX,aliyun.com,DIRECT
- DOMAIN-SUFFIX,aliyuncs.com,DIRECT
- DOMAIN-SUFFIX,jd.com,DIRECT
- DOMAIN-SUFFIX,qq.com,DIRECT
- DOMAIN-SUFFIX,tencent.com,DIRECT
- DOMAIN-SUFFIX,weixin.qq.com,DIRECT
- DOMAIN-SUFFIX,wechat.com,DIRECT
- DOMAIN-SUFFIX,bilibili.com,DIRECT
- DOMAIN-SUFFIX,bilivideo.com,DIRECT
- DOMAIN-SUFFIX,baidu.com,DIRECT
- DOMAIN-SUFFIX,bdstatic.com,DIRECT
- DOMAIN-SUFFIX,zhihu.com,DIRECT
- DOMAIN-SUFFIX,douyin.com,DIRECT
- DOMAIN-SUFFIX,bytedance.com,DIRECT
- DOMAIN-SUFFIX,toutiao.com,DIRECT
- DOMAIN-SUFFIX,weibo.com,DIRECT
- DOMAIN-SUFFIX,163.com,DIRECT
- DOMAIN-SUFFIX,126.com,DIRECT
@ -454,6 +464,20 @@ rules:
- DOMAIN-SUFFIX,meituan.com,DIRECT
- DOMAIN-SUFFIX,dianping.com,DIRECT
- DOMAIN-SUFFIX,pinduoduo.com,DIRECT
- DOMAIN-SUFFIX,suning.com,DIRECT
- DOMAIN-SUFFIX,csdn.net,DIRECT
- DOMAIN-SUFFIX,cnblogs.com,DIRECT
- DOMAIN-SUFFIX,gitee.com,DIRECT
- DOMAIN-SUFFIX,jianshu.com,DIRECT
- DOMAIN-SUFFIX,douban.com,DIRECT
- DOMAIN-SUFFIX,kuaishou.com,DIRECT
- DOMAIN-SUFFIX,ctrip.com,DIRECT
- DOMAIN-SUFFIX,ele.me,DIRECT
- DOMAIN-SUFFIX,netease.com,DIRECT
- DOMAIN-SUFFIX,iqiyi.com,DIRECT
- DOMAIN-SUFFIX,youku.com,DIRECT
- DOMAIN-SUFFIX,dingtalk.com,DIRECT
- DOMAIN-SUFFIX,feishu.cn,DIRECT
# 局域网直连
- IP-CIDR,192.168.0.0/16,DIRECT