feat: add expiry reminder subscription API (POST /subscribe/expiry-save) and send reminders endpoint

This commit is contained in:
Developer
2026-05-13 20:16:14 +08:00
parent 9c52975b5a
commit 67e7c251a6
3 changed files with 142 additions and 0 deletions
+10
View File
@@ -21,6 +21,11 @@ const userSubscriptionSchema = new mongoose.Schema({
type: String, type: String,
default: '' default: ''
}, },
type: {
type: String,
enum: ['version-update', 'expiry-reminder'],
default: 'version-update'
},
status: { status: {
type: String, type: String,
enum: ['active', 'expired', 'used'], enum: ['active', 'expired', 'used'],
@@ -37,11 +42,16 @@ const userSubscriptionSchema = new mongoose.Schema({
usedAt: { usedAt: {
type: Date, type: Date,
default: null default: null
},
lastSentDay: {
type: Number,
default: null
} }
}, { }, {
timestamps: true timestamps: true
}); });
userSubscriptionSchema.index({ userId: 1, templateId: 1 }); userSubscriptionSchema.index({ userId: 1, templateId: 1 });
userSubscriptionSchema.index({ type: 1, status: 1 });
module.exports = mongoose.model('UserSubscription', userSubscriptionSchema); module.exports = mongoose.model('UserSubscription', userSubscriptionSchema);
+117
View File
@@ -1,5 +1,6 @@
const express = require('express'); const express = require('express');
const UserSubscription = require('../models/UserSubscription'); const UserSubscription = require('../models/UserSubscription');
const UserEquity = require('../models/UserEquity');
const WechatSubscribeService = require('../services/wechatSubscribeService'); const WechatSubscribeService = require('../services/wechatSubscribeService');
const { auth } = require('../middleware/auth'); const { auth } = require('../middleware/auth');
@@ -113,4 +114,120 @@ router.post('/version-update', auth, async (req, res, next) => {
} }
}); });
router.post('/expiry-save', auth, async (req, res, next) => {
try {
const { templateId } = req.body;
if (!templateId) {
return res.status(400).json({
success: false,
error: 'templateId 不能为空'
});
}
const user = req.user;
const existingSubscription = await UserSubscription.findOne({
userId: user._id,
templateId,
type: 'expiry-reminder',
status: 'active'
});
if (existingSubscription) {
return res.json({ success: true, message: '已存在有效的到期提醒订阅' });
}
const subscription = new UserSubscription({
userId: user._id,
openid: user.openid,
templateId,
type: 'expiry-reminder',
scene: ''
});
await subscription.save();
res.json({ success: true, message: '到期提醒订阅保存成功' });
} catch (error) {
next(error);
}
});
router.post('/send-expiry-reminders', async (req, res, next) => {
try {
const now = new Date();
const results = { total: 0, sent: 0, failed: 0, errors: [] };
const daysToCheck = [10, 3, 0];
for (const daysBefore of daysToCheck) {
const targetDate = new Date(now);
targetDate.setDate(targetDate.getDate() + daysBefore);
const targetDateStr = targetDate.toISOString().split('T')[0];
const subscriptions = await UserSubscription.find({
type: 'expiry-reminder',
status: 'active',
lastSentDay: { $ne: daysBefore }
});
if (subscriptions.length === 0) continue;
for (const sub of subscriptions) {
try {
const equities = await UserEquity.find({
owner: sub.userId,
status: 'active',
expireDate: targetDateStr
});
for (const equity of equities) {
const dayLabel = daysBefore === 0 ? '今天' : `${daysBefore}`;
const result = await WechatSubscribeService.sendExpiryReminderMessage({
openid: sub.openid,
templateId: sub.templateId,
thing1: equity.platform,
thing2: `您的${equity.platform}${equity.type}将在${dayLabel}到期`,
phrase3: daysBefore === 0 ? '已到期' : '即将到期'
});
results.total++;
if (result.success) {
results.sent++;
sub.lastSentDay = daysBefore;
await sub.save();
} else {
results.failed++;
if (result.errcode === 43101 || result.errcode === 40037) {
sub.status = 'expired';
sub.expiredAt = new Date();
await sub.save();
}
results.errors.push({
openid: sub.openid,
platform: equity.platform,
error: result.errmsg
});
}
}
} catch (subError) {
results.errors.push({
openid: sub.openid,
error: subError.message
});
}
}
}
res.json({
success: true,
data: results
});
} catch (error) {
next(error);
}
});
module.exports = router; module.exports = router;
+15
View File
@@ -81,6 +81,21 @@ class WechatSubscribeService {
data data
}); });
} }
static async sendExpiryReminderMessage({ openid, templateId, thing1, thing2, phrase3 }) {
const data = {
thing1: { value: thing1 },
thing2: { value: thing2 },
phrase3: { value: phrase3 }
};
return this.sendSubscribeMessage({
touser: openid,
templateId,
page: 'pages/profile/profile',
data
});
}
} }
module.exports = WechatSubscribeService; module.exports = WechatSubscribeService;