Merge pull request #297 from qinfendebingshuo/copilot/add-notification-email-feature

feat: email-hub dual send mode — single-user targeting + batch broadcast
This commit is contained in:
冰朔 2026-04-06 13:38:44 +08:00 committed by GitHub
commit f835e36b51
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 120 additions and 11 deletions

View File

@ -31,10 +31,16 @@ on:
- status
- send-monthly-reset
- send-update-notify
- send-update-notify-single
- send-traffic-warn
- list-user-emails
default: 'status'
message:
description: '附加消息 (update-notify描述 / traffic-warn百分比)'
description: '附加消息 (update-notify描述 / traffic-warn百分比) · 描述支持分号分隔多条内容'
required: false
type: string
email:
description: '目标邮箱 (send-update-notify-single时需要 · 可先用list-user-emails查看)'
required: false
type: string
@ -67,6 +73,7 @@ jobs:
env:
ACTION: ${{ github.event.inputs.action || 'monthly-evolution' }}
MESSAGE: ${{ github.event.inputs.message || '' }}
EMAIL: ${{ github.event.inputs.email || '' }}
ZY_SMTP_USER: ${{ secrets.ZY_SMTP_USER }}
ZY_SMTP_PASS: ${{ secrets.ZY_SMTP_PASS }}
run: |
@ -143,7 +150,7 @@ jobs:
;;
send-update-notify)
echo "发送更新通知..."
echo "一键发送更新通知 (全部用户)..."
$SSH_CMD "
cd $PROXY_DIR
export ZY_SMTP_USER='$ZY_SMTP_USER'
@ -152,6 +159,25 @@ jobs:
"
;;
send-update-notify-single)
if [ -z "$EMAIL" ]; then
echo "❌ 请提供目标邮箱 (email字段)"
echo "💡 可先使用 list-user-emails 查看所有用户邮箱"
exit 1
fi
if ! echo "$EMAIL" | grep -qE '^[^@]+@[^@]+\.[^@]+$'; then
echo "❌ 邮箱格式不正确: $EMAIL"
exit 1
fi
echo "单独发送更新通知给: $EMAIL ..."
$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-single '$EMAIL' '${MESSAGE:-系统已更新}'
"
;;
send-traffic-warn)
echo "发送流量预警..."
$SSH_CMD "
@ -162,6 +188,14 @@ jobs:
"
;;
list-user-emails)
echo "查询所有用户邮箱..."
$SSH_CMD "
cd $PROXY_DIR
node service/email-hub.js list-emails
"
;;
*)
echo "❌ 未知操作: $ACTION"
exit 1

View File

@ -19,10 +19,12 @@
//
// 用法:
// node email-hub.js monthly-reset — 发送月初重置邮件给所有用户
// node email-hub.js update-notify <desc> — 发送更新通知给所有用户
// node email-hub.js update-notify <desc> — 一键发送更新通知给所有用户
// node email-hub.js update-notify-single <email> <desc> — 发送更新通知给单个用户
// node email-hub.js traffic-warn <pct> — 发送流量预警给所有用户
// node email-hub.js security-warn <email> <msg> — 发送安全提醒给单用户
// node email-hub.js feedback-ack <email> — 发送反馈确认给单用户
// node email-hub.js list-emails — 列出所有启用用户的邮箱
//
// 运行方式: CLI调用 (由auto-evolution.js调度)
// ═══════════════════════════════════════════════
@ -270,14 +272,26 @@ function generateMonthlyResetEmail(config) {
function generateUpdateNotifyEmail(description, config) {
const now = new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' });
// 支持用分号或换行符分隔的多条更新内容,自动渲染为功能清单
const items = description.split(/[;\n]/).map(s => s.trim()).filter(Boolean);
let detailHtml;
if (items.length > 1) {
detailHtml = `
<ul style="margin: 0; padding-left: 20px; color: #333; line-height: 2;">
${items.map(item => `<li>${escapeHtml(item)}</li>`).join('\n ')}
</ul>`;
} else {
detailHtml = `<p style="margin: 0; color: #333; line-height: 1.8;">${escapeHtml(description).replace(/\n/g, '<br>')}</p>`;
}
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 style="background: #f8f9fa; border-radius: 8px; padding: 15px; line-height: 1.8;">
${detailHtml}
</div>
<p style="color: #666; font-size: 13px; margin-top: 15px;">
@ -519,6 +533,36 @@ async function sendFeedbackAckEmail(email) {
}
}
/**
* 发送更新通知给单个用户 (分别发送模式)
* @param {string} email 目标邮箱
* @param {string} description 更新说明
*/
async function sendUpdateNotifySingleEmail(email, description) {
const config = loadConfig();
const html = generateUpdateNotifyEmail(description, config);
try {
await sendEmail(email, '🌐 光湖语言世界 · 系统已升级', html);
console.log(`[邮件中枢] ✅ 更新通知已发送: ${email}`);
logEmail('update-notify-single', email, true, null);
return { sent: 1, failed: 0 };
} catch (err) {
console.error(`[邮件中枢] ❌ 更新通知发送失败: ${err.message}`);
logEmail('update-notify-single', email, false, err.message);
return { sent: 0, failed: 1 };
}
}
/**
* 列出所有启用用户的邮箱 (供下拉选择)
* @returns {string[]} 邮箱列表
*/
function listUserEmails() {
const users = getEnabledUsers();
return users.map(u => u.email);
}
// ═══════════════════════════════════════════════
// CLI 主入口
// ═══════════════════════════════════════════════
@ -528,11 +572,16 @@ async function main() {
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 <邮箱> — 反馈确认回复');
console.log(' node email-hub.js monthly-reset — 月初重置通知 (全部用户)');
console.log(' node email-hub.js update-notify <描述> — 一键发送更新通知 (全部用户)');
console.log(' node email-hub.js update-notify-single <邮箱> <描述> — 单独发送更新通知');
console.log(' node email-hub.js traffic-warn <百分比> — 流量预警通知 (全部用户)');
console.log(' node email-hub.js security-warn <邮箱> <消息> — 安全风险提醒 (单用户)');
console.log(' node email-hub.js feedback-ack <邮箱> — 反馈确认回复 (单用户)');
console.log(' node email-hub.js list-emails — 列出所有用户邮箱');
console.log('');
console.log('💡 描述支持分号分隔多条内容,自动渲染为功能清单:');
console.log(' node email-hub.js update-notify "新增智能选路;优化连接速度;修复断连问题"');
process.exit(0);
}
@ -549,7 +598,18 @@ async function main() {
process.exit(1);
}
const result = await sendUpdateNotifyEmail(arg1);
console.log(`📧 更新通知: ${result.sent}成功 / ${result.failed}失败`);
console.log(`📧 更新通知 (一键发送): ${result.sent}成功 / ${result.failed}失败`);
break;
}
case 'update-notify-single': {
if (!arg1 || !arg2) {
console.error('❌ 请提供邮箱和更新描述');
console.error('用法: node email-hub.js update-notify-single <邮箱> <描述>');
process.exit(1);
}
const result = await sendUpdateNotifySingleEmail(arg1, arg2);
console.log(`📧 更新通知 (单独发送): ${result.sent}成功 / ${result.failed}失败`);
break;
}
@ -582,6 +642,19 @@ async function main() {
break;
}
case 'list-emails': {
const emails = listUserEmails();
if (emails.length === 0) {
console.log('[邮件中枢] 暂无启用用户');
} else {
console.log(`📧 当前启用用户邮箱 (${emails.length}位):`);
emails.forEach((email, i) => {
console.log(` ${i + 1}. ${email}`);
});
}
break;
}
default:
console.error(`❌ 未知操作: ${action}`);
process.exit(1);
@ -592,10 +665,12 @@ async function main() {
module.exports = {
sendMonthlyResetEmail,
sendUpdateNotifyEmail,
sendUpdateNotifySingleEmail,
sendTrafficWarnEmail,
sendSecurityWarnEmail,
sendFeedbackAckEmail,
getEnabledUsers,
listUserEmails,
sendEmail
};