guanghulab/scheduler/reminder.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

205 lines
5.1 KiB
JavaScript
Raw Permalink 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
};