```
feat(scheduler): 添加定时任务功能并集成前端配置界面 - 引入 node-cron 依赖以支持定时任务调度 - 新增定时任务相关 API 接口:获取配置、更新配置、查询状态、手动触发任务 - 前端新增“定时任务”标签页,支持 Cron 表达式配置与友好时间展示 - 支持通过 Web 界面启用/禁用定时任务、设置执行计划和金额阈值 - 定时任务可自动采集数据并发送邮件报告,无需重启服务即可生效新配置 - 优化配置保存逻辑,避免敏感信息泄露 ```
This commit is contained in:
@@ -4,6 +4,7 @@ import axios from 'axios';
|
||||
import * as cheerio from 'cheerio';
|
||||
import iconv from 'iconv-lite';
|
||||
import { sendReportEmail } from './emailService.js';
|
||||
import { initScheduler, runTaskNow, reloadScheduler, getSchedulerStatus } from './scheduler.js';
|
||||
|
||||
const app = express();
|
||||
const PORT = 3000;
|
||||
@@ -732,6 +733,99 @@ app.post('/api/test-pdf', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// 获取配置
|
||||
app.get('/api/config', async (req, res) => {
|
||||
try {
|
||||
const { readFileSync } = await import('fs');
|
||||
const { join } = await import('path');
|
||||
const { fileURLToPath } = await import('url');
|
||||
const { dirname } = await import('path');
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const configPath = join(__dirname, '..', 'config.json');
|
||||
|
||||
const configContent = readFileSync(configPath, 'utf-8');
|
||||
const config = JSON.parse(configContent);
|
||||
|
||||
// 不返回敏感信息(密码)
|
||||
if (config.email && config.email.smtpPass) {
|
||||
config.email.smtpPass = '***已配置***';
|
||||
}
|
||||
|
||||
res.json({ success: true, data: config });
|
||||
} catch (error) {
|
||||
res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// 更新配置
|
||||
app.post('/api/config', async (req, res) => {
|
||||
try {
|
||||
const { writeFileSync, readFileSync } = await import('fs');
|
||||
const { join } = await import('path');
|
||||
const { fileURLToPath } = await import('url');
|
||||
const { dirname } = await import('path');
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const configPath = join(__dirname, '..', 'config.json');
|
||||
|
||||
const newConfig = req.body;
|
||||
|
||||
// 如果密码字段是占位符,保留原密码
|
||||
if (newConfig.email && newConfig.email.smtpPass === '***已配置***') {
|
||||
const oldConfigContent = readFileSync(configPath, 'utf-8');
|
||||
const oldConfig = JSON.parse(oldConfigContent);
|
||||
newConfig.email.smtpPass = oldConfig.email.smtpPass;
|
||||
}
|
||||
|
||||
// 保存配置
|
||||
writeFileSync(configPath, JSON.stringify(newConfig, null, 2), 'utf-8');
|
||||
|
||||
// 重新加载定时任务(如果定时任务配置有变化)
|
||||
reloadScheduler();
|
||||
|
||||
res.json({ success: true, message: '配置已保存并重新加载定时任务' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// 获取定时任务状态
|
||||
app.get('/api/scheduler/status', async (req, res) => {
|
||||
try {
|
||||
const status = getSchedulerStatus();
|
||||
res.json({ success: true, data: status });
|
||||
} catch (error) {
|
||||
res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// 手动触发定时任务的API(用于测试)
|
||||
app.post('/api/run-scheduled-task', async (req, res) => {
|
||||
try {
|
||||
console.log('手动触发定时任务...');
|
||||
// 在后台执行任务,不阻塞响应
|
||||
runTaskNow().catch(err => {
|
||||
console.error('定时任务执行失败:', err);
|
||||
});
|
||||
res.json({
|
||||
success: true,
|
||||
message: '定时任务已触发,正在后台执行...'
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server running at http://localhost:${PORT}`);
|
||||
|
||||
// 启动定时任务
|
||||
console.log('正在初始化定时任务...');
|
||||
initScheduler();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user