2026-05-10 13:12:44 +08:00

205 lines
5.1 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.

// 超时提醒系统·reminder.js
// HoloLake·M-DINGTALK Phase 3
// DEV-004 之之×秋秋
//
// 规则广播签发后72小时未交SYSLOG → 自动提醒
//
const fs = require('fs');
const path = require('path');
// 数据文件路径复用Phase 2的多维表格数据
const DATA_DIR = path.join(__dirname, '..', 'data');
const DEVELOPER_TABLE = path.join(DATA_DIR, 'developer-status.json');
const REMINDER_LOG = path.join(DATA_DIR, 'reminder-log.json');
// 提醒配置
const TIMEOUT_HOURS = 72;
const REMINDER_INTERVAL_MS = 60 * 60 * 1000; // 每小时检查一次
let reminderTimer = null;
/**
* 扫描所有开发者找出超时未交SYSLOG的
*/
function scanOverdue() {
console.log(`[Reminder] 扫描超时开发者... ${new Date().toLocaleString('zh-CN')}`);
// 读取开发者状态表
if (!fs.existsSync(DEVELOPER_TABLE)) {
console.log('[Reminder] 开发者状态表不存在,跳过');
return [];
}
let devTable;
try {
devTable = JSON.parse(fs.readFileSync(DEVELOPER_TABLE, 'utf-8'));
} catch (err) {
console.error('[Reminder] 读取开发者状态表失败:', err.message);
return [];
}
const now = Date.now();
const overdueList = [];
for (const dev of (devTable.developers || [])) {
// 跳过未启动或已完成的开发者
if (!dev.last_broadcast_time || dev.status === '已完成' || dev.status === '未启动') {
continue;
}
const broadcastTime = new Date(dev.last_broadcast_time).getTime();
const hoursSince = (now - broadcastTime) / (1000 * 60 * 60);
if (hoursSince >= TIMEOUT_HOURS && dev.status !== 'HOLD') {
overdueList.push({
dev_id: dev.dev_id,
name: dev.name,
current_module: dev.current_module,
last_broadcast: dev.last_broadcast_time,
hours_overdue: Math.round(hoursSince - TIMEOUT_HOURS),
total_hours: Math.round(hoursSince)
});
}
}
if (overdueList.length > 0) {
console.log(`[Reminder] ⚠️ 发现 ${overdueList.length} 个超时开发者:`);
for (const dev of overdueList) {
console.log(` · ${dev.dev_id} ${dev.name} - 超时${dev.hours_overdue}小时 - ${dev.current_module}`);
}
} else {
console.log('[Reminder] 无超时开发者');
}
return overdueList;
}
/**
* 生成提醒消息(钉钉消息卡片格式)
*/
function generateReminderMessage(devInfo) {
return {
msgtype: 'markdown',
markdown: {
title: `SYSLOG提醒·${devInfo.dev_id}`,
text: [
'## ⏰ SYSLOG超时提醒',
`**${devInfo.name}**${devInfo.dev_id}`,
`你的广播已经签发 **${devInfo.total_hours}** 小时了`,
`超过72小时启动窗口 **${devInfo.hours_overdue}** 小时`,
`当前任务:${devInfo.current_module}`,
'**请尽快提交SYSLOG**',
'---',
'✅ 已完成 → 提交完整SYSLOG',
'❓ 遇到困难 → 截图发群里,秋秋/知秋帮你',
'⏸️ 暂时没空 → 回复「HOLD」系统记录',
'---',
'🤖 系统自动提醒 · HoloLake'
].join('\n')
}
};
}
/**
* 执行提醒发送
*/
async function executeReminders() {
const overdueList = scanOverdue();
if (overdueList.length === 0) return [];
const reminderResults = [];
for (const dev of overdueList) {
const message = generateReminderMessage(dev);
// 模拟发送后续接入真实钉钉API
console.log(`[Reminder] 向 ${dev.name} 发送提醒消息(模拟)`);
reminderResults.push({
timestamp: new Date().toISOString(),
dev_id: dev.dev_id,
hours_overdue: dev.hours_overdue,
status: 'sent-mock'
});
}
// 保存提醒日志
saveReminderLog(reminderResults);
return reminderResults;
}
/**
* 启动定时提醒
*/
function start() {
if (reminderTimer) {
console.log('[Reminder] 提醒系统已在运行');
return;
}
console.log(`[Reminder] 启动超时提醒系统 · ${TIMEOUT_HOURS}小时超时 · 每小时扫描一次`);
// 启动时立即扫描一次
executeReminders();
// 定时扫描
reminderTimer = setInterval(executeReminders, REMINDER_INTERVAL_MS);
console.log('[Reminder] 定时器已启动');
}
/**
* 停止定时提醒
*/
function stop() {
if (reminderTimer) {
clearInterval(reminderTimer);
reminderTimer = null;
console.log('[Reminder] 提醒系统已停止');
}
}
/**
* 保存提醒日志
*/
function saveReminderLog(results) {
if (!fs.existsSync(DATA_DIR)) {
fs.mkdirSync(DATA_DIR, { recursive: true });
}
let log = [];
if (fs.existsSync(REMINDER_LOG)) {
try {
log = JSON.parse(fs.readFileSync(REMINDER_LOG, 'utf-8'));
} catch {}
}
log.unshift(...results);
if (log.length > 200) log = log.slice(0, 200);
fs.writeFileSync(REMINDER_LOG, JSON.stringify(log, null, 2), 'utf-8');
}
/**
* 获取提醒系统状态
*/
function getStatus() {
return {
running: reminderTimer !== null,
timeoutHours: TIMEOUT_HOURS,
checkIntervalMinutes: REMINDER_INTERVAL_MS / 60000,
overdueNow: scanOverdue()
};
}
module.exports = {
scanOverdue,
generateReminderMessage,
executeReminders,
start,
stop,
getStatus
};