feat(cloud): 实现微信云函数 - 订阅管理与每日运势推送

- subscribe: 用户订阅/取消订阅/查询订阅状态
- daily-push: 定时查询订阅用户、生成运势、发送订阅消息
- push-test: 推送调试云函数
- 定时触发器配置,每日 7:00/12:00/18:00 推送
This commit is contained in:
2026-08-13 08:06:10 +08:00
parent e6488cdab9
commit bd4a8aa358
10 changed files with 520 additions and 0 deletions
@@ -0,0 +1,142 @@
// 云函数:dailyPush
// 每日定时推送运势通知
// 定时触发器:cron: 0 0 7 * * * *(每日 07:00
// 或每分钟扫描:cron: 0 * * * * * *(支持用户自定义时间)
const cloud = require('wx-server-sdk')
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
const db = cloud.database()
// 引入共享算法包(需在云函数部署时安装)
// 由于云函数环境限制,算法代码会内联到本文件
// 参见 _algorithm.js 文件
const _ = require('./_algorithm')
exports.main = async (event, context) => {
const now = new Date()
const todayStr = now.toISOString().slice(0, 10)
const currentHour = now.getHours()
const currentMinute = now.getMinutes()
console.log(`[dailyPush] Triggered at ${now.toISOString()}`)
try {
// 查询所有已开启推送的订阅
const { data: subscriptions } = await db.collection('subscriptions')
.where({
pushEnabled: true,
})
.get()
console.log(`[dailyPush] Found ${subscriptions.length} active subscriptions`)
let pushCount = 0
let skipCount = 0
let errorCount = 0
for (const sub of subscriptions) {
try {
// 检查是否已达到推送时间
const [pushHour, pushMinute] = (sub.pushTime || '07:00').split(':').map(Number)
if (currentHour !== pushHour || currentMinute !== pushMinute) {
skipCount++
continue
}
// 检查是否已推送过(防重复)
if (sub.lastPushDate === todayStr) {
skipCount++
continue
}
// 生成运势内容
const fortune = _.generatePushContent(sub.birthInfo, now)
// 格式化推送内容
const pushData = {
touser: sub.openid,
templateId: sub.templateId,
page: '/pages/fortune/index', // 点击跳转到运势页面
data: {
date1: { value: formatDate(now) },
thing2: { value: `综合运势评分${fortune.overallScore}分,等级:${fortune.overallLuck}` },
thing3: { value: formatFortuneSummary(fortune) },
thing4: { value: `幸运色:${fortune.luckyColor} 幸运数字:${fortune.luckyNumber}` },
},
}
// 发送微信订阅消息
await cloud.openapi.subscribeMessage.send(pushData)
// 更新最后推送日期
await db.collection('subscriptions').doc(sub._id).update({
data: { lastPushDate: todayStr },
})
pushCount++
console.log(`[dailyPush] Pushed to ${sub.openid}`)
} catch (err) {
errorCount++
console.error(`[dailyPush] Failed for ${sub.openid}:`, err.message)
}
}
return {
success: true,
summary: {
total: subscriptions.length,
pushed: pushCount,
skipped: skipCount,
errors: errorCount,
},
}
} catch (err) {
console.error('[dailyPush] Fatal error:', err)
return { success: false, error: err.message }
}
}
/**
* 格式化日期为中文显示
*/
function formatDate(date) {
const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
const weekdays = ['日', '一', '二', '三', '四', '五', '六']
const weekday = weekdays[date.getDay()]
return `${year}${month}${day}日 星期${weekday}`
}
/**
* 格式化运势摘要
*/
function formatFortuneSummary(fortune) {
const parts = []
if (fortune.careerAdvice) {
const brief = fortune.careerAdvice.length > 10
? fortune.careerAdvice.slice(0, 10) + '…'
: fortune.careerAdvice
parts.push(`事业:${brief}`)
}
if (fortune.wealthAdvice) {
const brief = fortune.wealthAdvice.length > 10
? fortune.wealthAdvice.slice(0, 10) + '…'
: fortune.wealthAdvice
parts.push(`财运:${brief}`)
}
if (fortune.relationshipAdvice) {
const brief = fortune.relationshipAdvice.length > 10
? fortune.relationshipAdvice.slice(0, 10) + '…'
: fortune.relationshipAdvice
parts.push(`感情:${brief}`)
}
if (fortune.healthAdvice) {
const brief = fortune.healthAdvice.length > 10
? fortune.healthAdvice.slice(0, 10) + '…'
: fortune.healthAdvice
parts.push(`健康:${brief}`)
}
return parts.join(' ')
}