feat: add VIP gating on cloud sync write endpoints + daily expiry reminder cron

- Add requireVip middleware to POST/PUT/DELETE /api/user-equity routes
- Extract sendExpiryReminders() from route handler for reuse
- Add node-cron job at 21:30 Asia/Shanghai daily for expiry reminders

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Developer
2026-05-15 22:22:08 +08:00
parent 21f9824a24
commit f9a4d50b09
5 changed files with 854 additions and 84 deletions
+49 -3
View File
@@ -2,6 +2,7 @@ const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const morgan = require('morgan');
const path = require('path');
require('dotenv').config();
const connectDB = require('./config/database');
@@ -21,11 +22,23 @@ const settingsRoutes = require('./routes/settings');
const subscribeRoutes = require('./routes/subscribe');
const wechatMessageRoutes = require('./routes/wechatMessage');
const app = express();
const cron = require('node-cron');
const app = express();
connectDB();
app.use(helmet());
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
scriptSrcAttr: ["'unsafe-inline'"],
styleSrc: ["'self'", "'unsafe-inline'", "https:"],
imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'", "https:"],
},
},
}));
app.use(cors());
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
@@ -34,6 +47,9 @@ if (process.env.NODE_ENV === 'development') {
app.use(morgan('dev'));
}
app.use('/uploads', express.static(path.join(__dirname, '../public/uploads')));
app.use('/admin', express.static(path.join(__dirname, '../public/admin')));
app.get('/health', (req, res) => {
res.json({
status: 'ok',
@@ -56,6 +72,25 @@ app.use('/api/subscribe', subscribeRoutes);
app.use('/wechat-message', wechatMessageRoutes);
const adminRoutes = require('./routes/admin');
app.use('/api/admin', adminRoutes);
const { sendExpiryReminders } = require('./routes/subscribe');
cron.schedule('30 21 * * *', async () => {
console.log(`[${new Date().toISOString()}] ⏰ 到期提醒定时任务开始执行...`);
try {
const results = await sendExpiryReminders();
console.log(`[${new Date().toISOString()}] ✅ 到期提醒执行完成: 共${results.total}条, 发送成功${results.sent}条, 失败${results.failed}`);
if (results.errors.length > 0) {
console.error('失败详情:', JSON.stringify(results.errors.slice(0, 10)));
}
} catch (error) {
console.error(`[${new Date().toISOString()}] ❌ 到期提醒执行失败:`, error.message);
}
}, {
timezone: 'Asia/Shanghai'
});
app.use(notFound);
app.use(errorHandler);
@@ -64,7 +99,18 @@ const PORT = process.env.PORT || 3000;
app.listen(PORT, '0.0.0.0', () => {
console.log(`🚀 权益小助手后端服务运行在端口 ${PORT}`);
console.log(`📊 环境: ${process.env.NODE_ENV || 'development'}`);
console.log(`🌐 访问地址: https://api.dxz99wyr.cn`);
console.log(`🌐 访问地址: https://api-miniapp.dxz99wyr.cn`);
});
process.on('uncaughtException', (error) => {
console.error('💥 未捕获异常 - 进程即将退出:', error.message);
console.error('Stack:', error.stack);
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
console.error('💥 未处理的Promise拒绝:', reason);
if (reason && reason.stack) console.error('Stack:', reason.stack);
});
module.exports = app;