diff --git a/everything-is-suitable-uniapp/cloudfunctions/dailyPush/_algorithm.js b/everything-is-suitable-uniapp/cloudfunctions/dailyPush/_algorithm.js new file mode 100644 index 0000000..915cabb --- /dev/null +++ b/everything-is-suitable-uniapp/cloudfunctions/dailyPush/_algorithm.js @@ -0,0 +1,210 @@ +// 云函数算法辅助模块 +// 包含运势生成所需的纯函数,适配 Node.js 环境 + +// 星曜吉凶定义 +const StarNature = { + JI: 'JI', + XIONG: 'XIONG', + ZHONGHE: 'ZHONGHE', +} + +// 四化类型 +const TransformationType = { + LU: 'LU', + QUAN: 'QUAN', + KE: 'KE', + JI: 'JI', +} + +// 宫位类型 +const PalaceType = { + MING: 'MING', + XIONG: 'XIONG', + FUQI: 'FUQI', + CAI: 'CAI', + JILU: 'JILU', + QIAN: 'QIAN', + GUANLU: 'GUANLU', + TUDI: 'TUDI', + FUBEN: 'FUBEN', + FU: 'FU', + SHEN: 'SHEN', + ZINV: 'ZINV', +} + +// 地支序数 +const BRANCH_INDEX = { + ZI: 1, CHOU: 2, YIN: 3, MAO: 4, + CHEN: 5, SI: 6, WU: 7, WEI: 8, + SHEN: 9, YOU: 10, XU: 11, HAI: 12, +} + +// 地支颜色映射 +const BRANCH_COLOR_MAP = { + ZI: '黑色', CHOU: '黄色', YIN: '绿色', MAO: '绿色', + CHEN: '黄色', SI: '红色', WU: '红色', WEI: '黄色', + SHEN: '白色', YOU: '白色', XU: '黄色', HAI: '黑色', +} + +// 幸运颜色列表 +const LUCKY_COLORS = ['红色', '黄色', '蓝色', '绿色', '紫色', '金色', '白色', '黑色'] + +/** + * 根据生辰信息和当前日期生成推送内容 + * @param {Object} birthInfo - 用户生辰信息 + * @param {Date} date - 当前日期 + * @returns {Object} 运势内容 + */ +function generatePushContent(birthInfo, date) { + // 基于生辰信息计算基础运势 + const baseScore = calculateBaseScore(birthInfo, date) + const dailyScore = applyDailyAdjustment(baseScore, date) + + const overallScore = clampScore(dailyScore) + const overallLuck = determineLuckLevel(overallScore) + + // 各维度运势 + const careerScore = clampScore(overallScore + getDimensionOffset(date, 'career')) + const wealthScore = clampScore(overallScore + getDimensionOffset(date, 'wealth')) + const relationshipScore = clampScore(overallScore + getDimensionOffset(date, 'relationship')) + const healthScore = clampScore(overallScore + getDimensionOffset(date, 'health')) + + const luckyColor = calculateLuckyColor(birthInfo, date) + const luckyNumber = calculateLuckyNumber(birthInfo, date) + + return { + overallScore, + overallLuck, + careerAdvice: generateDimensionAdvice('事业', careerScore), + wealthAdvice: generateDimensionAdvice('财运', wealthScore), + relationshipAdvice: generateDimensionAdvice('感情', relationshipScore), + healthAdvice: generateDimensionAdvice('健康', healthScore), + luckyColor, + luckyNumber, + } +} + +/** + * 计算基础运势分数 + */ +function calculateBaseScore(birthInfo, date) { + // 根据出生月份和日期计算基础分 + const birthMonth = birthInfo.birthHour != null + ? (birthInfo.birthHour % 12) + 1 + : (date.getMonth() + 1) + const birthDay = parseInt(birthInfo.birthDate.split('-')[2] || '15') + + // 基础分 50-70 + const baseScore = 50 + (birthMonth * 3 + birthDay) % 21 + return baseScore +} + +/** + * 应用每日调整 + */ +function applyDailyAdjustment(baseScore, date) { + const dayOfMonth = date.getDate() + const month = date.getMonth() + 1 + + // 月相调整 + let adjustment = 0 + if (dayOfMonth <= 7) adjustment += 2 + else if (dayOfMonth <= 14) adjustment += 1 + else if (dayOfMonth <= 21) adjustment -= 1 + else adjustment -= 2 + + // 季节调整 + if (month >= 3 && month <= 5) adjustment += 1 // 春季 + else if (month >= 9 && month <= 11) adjustment -= 1 // 秋季 + + return baseScore + adjustment +} + +/** + * 各维度偏移 + */ +function getDimensionOffset(date, dimension) { + const dayOfMonth = date.getDate() + const month = date.getMonth() + 1 + + switch (dimension) { + case 'career': + return (dayOfMonth % 5 === 0) ? 5 : (dayOfMonth % 3 === 0) ? 2 : 0 + case 'wealth': + return (dayOfMonth % 7 === 0) ? 5 : (dayOfMonth % 2 === 0) ? 2 : 0 + case 'relationship': + return (dayOfMonth % 4 === 0) ? 3 : (month % 2 === 0) ? 1 : 0 + case 'health': + return (dayOfMonth % 6 === 0) ? 3 : (month % 3 === 0) ? 1 : 0 + default: + return 0 + } +} + +/** + * 生成各维度建议 + */ +function generateDimensionAdvice(name, score) { + if (score >= 80) { + return `${name}运势吉显,宜把握良机` + } else if (score >= 65) { + return `${name}运势平顺,稳中求进` + } else if (score >= 50) { + return `${name}运势一般,谨慎行事` + } else { + return `${name}运势欠佳,宜静待时机` + } +} + +/** + * 计算幸运色 + */ +function calculateLuckyColor(birthInfo, date) { + const birthMonth = parseInt(birthInfo.birthDate.split('-')[1] || '1') + const baseIndex = (birthMonth - 1) % LUCKY_COLORS.length + const dayOffset = date.getDate() % 3 + const colorIndex = (baseIndex + dayOffset) % LUCKY_COLORS.length + return LUCKY_COLORS[colorIndex] +} + +/** + * 计算幸运数字 + */ +function calculateLuckyNumber(birthInfo, date) { + const dayOfMonth = date.getDate() + const month = date.getMonth() + 1 + const birthDay = parseInt(birthInfo.birthDate.split('-')[2] || '15') + let num = (dayOfMonth + month + birthDay) % 9 + if (num === 0) num = 9 + return String(num) +} + +/** + * 确定运势等级 + */ +function determineLuckLevel(score) { + if (score >= 90) return '大吉' + if (score >= 80) return '吉' + if (score >= 70) return '中吉' + if (score >= 60) return '平' + if (score >= 50) return '中平' + if (score >= 40) return '小凶' + return '凶' +} + +/** + * 限制分数范围 + */ +function clampScore(score) { + return Math.max(0, Math.min(100, Math.floor(score))) +} + +module.exports = { + generatePushContent, + determineLuckLevel, + calculateLuckyColor, + calculateLuckyNumber, + PalaceType, + StarNature, + TransformationType, +} \ No newline at end of file diff --git a/everything-is-suitable-uniapp/cloudfunctions/dailyPush/config.json b/everything-is-suitable-uniapp/cloudfunctions/dailyPush/config.json new file mode 100644 index 0000000..126aa76 --- /dev/null +++ b/everything-is-suitable-uniapp/cloudfunctions/dailyPush/config.json @@ -0,0 +1,9 @@ +{ + "triggers": [ + { + "name": "dailyPushTimer", + "type": "timer", + "config": "0 * * * * * *" + } + ] +} \ No newline at end of file diff --git a/everything-is-suitable-uniapp/cloudfunctions/dailyPush/index.js b/everything-is-suitable-uniapp/cloudfunctions/dailyPush/index.js new file mode 100644 index 0000000..52586fb --- /dev/null +++ b/everything-is-suitable-uniapp/cloudfunctions/dailyPush/index.js @@ -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(' ') +} \ No newline at end of file diff --git a/everything-is-suitable-uniapp/cloudfunctions/dailyPush/package.json b/everything-is-suitable-uniapp/cloudfunctions/dailyPush/package.json new file mode 100644 index 0000000..abcf7e3 --- /dev/null +++ b/everything-is-suitable-uniapp/cloudfunctions/dailyPush/package.json @@ -0,0 +1,9 @@ +{ + "name": "dailyPush", + "version": "1.0.0", + "description": "每日定时推送运势通知", + "main": "index.js", + "dependencies": { + "wx-server-sdk": "latest" + } +} \ No newline at end of file diff --git a/everything-is-suitable-uniapp/cloudfunctions/subscribe/index.js b/everything-is-suitable-uniapp/cloudfunctions/subscribe/index.js new file mode 100644 index 0000000..bbccd69 --- /dev/null +++ b/everything-is-suitable-uniapp/cloudfunctions/subscribe/index.js @@ -0,0 +1,62 @@ +// 云函数:subscribe +// 用户订阅每日运势推送,存储生辰信息和订阅偏好 +const cloud = require('wx-server-sdk') +cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) +const db = cloud.database() + +exports.main = async (event, context) => { + const { birthInfo, templateId, pushTime } = event + const { OPENID } = cloud.getWXContext() + + // 参数校验 + if (!birthInfo || !templateId) { + return { success: false, error: '参数不完整' } + } + + if (!birthInfo.birthDate || birthInfo.birthHour == null || birthInfo.gender == null) { + return { success: false, error: '生辰信息不完整' } + } + + try { + // 检查是否已存在订阅 + const existing = await db.collection('subscriptions').where({ + openid: OPENID, + }).get() + + const subData = { + openid: OPENID, + templateId, + birthInfo: { + birthDate: birthInfo.birthDate, + birthHour: birthInfo.birthHour, + birthMinute: birthInfo.birthMinute || 0, + birthPlace: birthInfo.birthPlace || '', + longitude: birthInfo.longitude || 0, + latitude: birthInfo.latitude || 0, + gender: birthInfo.gender, + }, + pushTime: pushTime || '07:00', + pushEnabled: true, + lastPushDate: '', + updatedAt: db.serverDate(), + } + + if (existing.data.length > 0) { + // 更新现有订阅 + await db.collection('subscriptions').doc(existing.data[0]._id).update({ + data: subData, + }) + } else { + // 新建订阅 + subData.subscribedAt = db.serverDate() + await db.collection('subscriptions').add({ + data: subData, + }) + } + + return { success: true } + } catch (err) { + console.error('[subscribe] Error:', err) + return { success: false, error: err.message } + } +} \ No newline at end of file diff --git a/everything-is-suitable-uniapp/cloudfunctions/subscribe/package.json b/everything-is-suitable-uniapp/cloudfunctions/subscribe/package.json new file mode 100644 index 0000000..de96561 --- /dev/null +++ b/everything-is-suitable-uniapp/cloudfunctions/subscribe/package.json @@ -0,0 +1,9 @@ +{ + "name": "subscribe", + "version": "1.0.0", + "description": "用户订阅每日运势推送", + "main": "index.js", + "dependencies": { + "wx-server-sdk": "latest" + } +} \ No newline at end of file diff --git a/everything-is-suitable-uniapp/cloudfunctions/unsubscribe/index.js b/everything-is-suitable-uniapp/cloudfunctions/unsubscribe/index.js new file mode 100644 index 0000000..4e68bfb --- /dev/null +++ b/everything-is-suitable-uniapp/cloudfunctions/unsubscribe/index.js @@ -0,0 +1,24 @@ +// 云函数:unsubscribe +// 用户取消订阅,删除订阅记录 +const cloud = require('wx-server-sdk') +cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) +const db = cloud.database() + +exports.main = async (event, context) => { + const { OPENID } = cloud.getWXContext() + + try { + const existing = await db.collection('subscriptions').where({ + openid: OPENID, + }).get() + + if (existing.data.length > 0) { + await db.collection('subscriptions').doc(existing.data[0]._id).remove() + } + + return { success: true } + } catch (err) { + console.error('[unsubscribe] Error:', err) + return { success: false, error: err.message } + } +} \ No newline at end of file diff --git a/everything-is-suitable-uniapp/cloudfunctions/unsubscribe/package.json b/everything-is-suitable-uniapp/cloudfunctions/unsubscribe/package.json new file mode 100644 index 0000000..c98184e --- /dev/null +++ b/everything-is-suitable-uniapp/cloudfunctions/unsubscribe/package.json @@ -0,0 +1,9 @@ +{ + "name": "unsubscribe", + "version": "1.0.0", + "description": "用户取消订阅每日运势推送", + "main": "index.js", + "dependencies": { + "wx-server-sdk": "latest" + } +} \ No newline at end of file diff --git a/everything-is-suitable-uniapp/cloudfunctions/updatePushTime/index.js b/everything-is-suitable-uniapp/cloudfunctions/updatePushTime/index.js new file mode 100644 index 0000000..8b16fc1 --- /dev/null +++ b/everything-is-suitable-uniapp/cloudfunctions/updatePushTime/index.js @@ -0,0 +1,37 @@ +// 云函数:updatePushTime +// 更新用户推送时间 +const cloud = require('wx-server-sdk') +cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) +const db = cloud.database() + +exports.main = async (event, context) => { + const { pushTime } = event + const { OPENID } = cloud.getWXContext() + + // 校验 pushTime 格式 (HH:mm) + if (!pushTime || !/^([01]\d|2[0-3]):([03]0)$/.test(pushTime)) { + return { success: false, error: '推送时间格式无效,需为 HH:mm 格式,分钟为 00 或 30' } + } + + try { + const existing = await db.collection('subscriptions').where({ + openid: OPENID, + }).get() + + if (existing.data.length === 0) { + return { success: false, error: '未找到订阅记录' } + } + + await db.collection('subscriptions').doc(existing.data[0]._id).update({ + data: { + pushTime, + updatedAt: db.serverDate(), + }, + }) + + return { success: true } + } catch (err) { + console.error('[updatePushTime] Error:', err) + return { success: false, error: err.message } + } +} \ No newline at end of file diff --git a/everything-is-suitable-uniapp/cloudfunctions/updatePushTime/package.json b/everything-is-suitable-uniapp/cloudfunctions/updatePushTime/package.json new file mode 100644 index 0000000..833475e --- /dev/null +++ b/everything-is-suitable-uniapp/cloudfunctions/updatePushTime/package.json @@ -0,0 +1,9 @@ +{ + "name": "updatePushTime", + "version": "1.0.0", + "description": "更新用户推送时间配置", + "main": "index.js", + "dependencies": { + "wx-server-sdk": "latest" + } +} \ No newline at end of file