Files
everything-is-suitable/docs/superpowers/plans/2026-08-13-daily-fortune-push/plan.md
T
zhangxiang e6488cdab9 docs: 添加订阅与每日运势推送功能的设计文档
- ADR-002: 订阅与每日运势推送功能设计决策
- PRD: 订阅与每日推送功能需求规格说明
- 实现计划文档
2026-08-13 08:06:01 +08:00

14 KiB
Raw Blame History

订阅与每日运势推送 — 实现计划

面向 AI 代理的工作者: 必需子技能:使用 subagent-driven-development(推荐)或 executing-plans 逐任务实现此计划。步骤使用复选框(- [ ])语法来跟踪进度。

目标: 为微信小程序添加每日运势订阅与推送功能,用户可在每日固定时间自动收到个性化运势推送。

架构: 微信云开发(CloudBase)作为轻量后端,云函数 + 云数据库 + 定时触发器。运势算法抽取为共享 npm 包,小程序和云函数共用。

设计规格: docs/superpowers/specs/2026-08-13-daily-fortune-push-PRD.md

ADR docs/adr/2026-08-13-daily-fortune-push.md


整体架构流程

graph TD
    subgraph 小程序端
        A[运势页面] -->|"点击订阅"| B[wx.requestSubscribeMessage]
        B -->|"用户授权"| C[调用云函数 subscribe]
        C -->|"返回成功"| D[UI 更新为"已订阅"]
        D --> E[订阅管理页面]
        E -->|"修改时间"| F[调用云函数 updatePushTime]
        E -->|"取消订阅"| G[调用云函数 unsubscribe]
    end
    subgraph 云开发
        C --> H[(云数据库 subscriptions)]
        F --> H
        G --> H
        I[定时触发器 07:00] --> J[云函数 dailyPush]
        J -->|"查询当日订阅"| H
        J -->|"调用共享算法"| K[生成运势内容]
        K --> L[wx.subscribeMessage.send]
        L --> M[用户收到服务通知]
    end
    subgraph 共享算法包
        N[packages/algorithm] -->|"fortuneStrategy"| K
        N -->|"ziweiAlgorithm"| K
        N -->|"types/enums"| K
    end

依赖关系

graph TD
    I1["Issue 1: 共享算法包抽取<br/>P0 ⭐ 2d"] --> I2["Issue 2: 云开发环境搭建<br/>P0 ⭐ 1d"]
    I1 --> I3["Issue 3: 订阅功能实现<br/>P0 ⭐ 2d"]
    I2 --> I3
    I2 --> I4["Issue 4: 每日推送云函数<br/>P0 ⭐ 1.5d"]
    I3 --> I4
    I4 --> I5["Issue 5: 订阅管理功能<br/>P1 ⭐ 1d"]
    I4 --> I6["Issue 6: 集成测试与验收<br/>P0 ⭐ 1.5d"]
    I5 --> I6

Issue 1: 共享算法包抽取

优先级: P0 预估工时: 2d 依赖:

描述: 将现有运势算法中的纯函数层抽取为独立 npm 包 packages/algorithm/,使其可在 UniApp 和 Node.js 云函数中复用。

文件结构

操作 文件路径 说明
创建 packages/algorithm/package.json 包配置,声明类型和入口
创建 packages/algorithm/tsconfig.json TypeScript 配置
创建 packages/algorithm/src/index.ts 导出入口
复制 packages/algorithm/src/types.ts 类型定义(从 src/algorithms/types.ts 抽取)
复制 packages/algorithm/src/enums.ts 枚举定义(从 src/algorithms/enums.ts 抽取)
复制 packages/algorithm/src/fortuneStrategy.ts 运势生成策略
复制 packages/algorithm/src/fortune.ts 综合运势生成
复制 packages/algorithm/src/ziweiAlgorithm.ts 紫微算法基础
复制 packages/algorithm/src/__tests__/fortuneStrategy.test.ts 算法测试
修改 everything-is-suitable-uniapp/package.json 添加本地依赖引用
修改 everything-is-suitable-uniapp/tsconfig.json 添加 paths 别名

验收标准

  • 算法包在 Node.js 环境下可独立运行(node -e "require('./packages/algorithm')" 无报错)
  • 算法包所有测试通过(cd packages/algorithm && npx vitest run
  • 小程序端引用算法包后,原有运势功能正常运行
  • 算法包导出接口与 PRD 定义的接口一致
  • 原有测试不受影响(cd everything-is-suitable-uniapp && npx vitest run 全部通过)

Issue 2: 微信云开发环境搭建

优先级: P0 预估工时: 1d 依赖:

描述: 开通微信云开发环境,创建云数据库集合、云函数脚手架、定时触发器配置。

任务清单

  • 步骤 1:开通微信云开发

    • 在微信小程序管理后台开通云开发
    • 创建环境(如 prod),记下环境 ID
    • 创建 cloudfunctions/ 目录并初始化
  • 步骤 2:创建云数据库集合

    • 集合名:subscriptions
    • 索引:openid(唯一索引)、pushEnabledlastPushDate
    • 权限:仅云函数可读写(不开放客户端直接访问)
  • 步骤 3:创建云函数骨架

    cloudfunctions/
    ├── subscribe/          # 订阅处理
    ├── unsubscribe/        # 取消订阅
    ├── updatePushTime/     # 更新推送时间
    ├── dailyPush/          # 每日推送(含定时触发器)
    └── package.json
    
    • 每个云函数创建 index.js 骨架,导出 main 函数
    • 公共依赖:@everything-suitable/algorithm(通过本地路径引用)
  • 步骤 4:配置 dailyPush 定时触发器

    • cloudfunctions/dailyPush/ 下创建 config.json
    • cron 表达式:0 0 7 * * * *(每日 07:00
    • 后续支持用户自定义时间时,调整为每分钟扫描(0 * * * * * *)并过滤用户配置时间

验收标准

  • 云开发环境开通成功,可正常访问
  • 云数据库 subscriptions 集合可正常读写
  • 云函数 subscribe 可正常部署和调用
  • 定时触发器配置已上传,云函数可被定时触发

Issue 3: 订阅功能实现

优先级: P0 预估工时: 2d 依赖: Issue 1, Issue 2

描述: 实现小程序端订阅入口 + 订阅管理页面 + 云函数 subscribe/unsubscribe。

3.1 小程序端

订阅入口(运势页面)

  • src/pages/fortune/index.vue 中添加订阅入口区域
  • 未订阅时显示"订阅每日推送"按钮
  • 已订阅时显示"已订阅"状态 + 推送时间 + "管理"入口
  • 未输入生辰信息时提示用户先设置生辰信息

订阅管理页面

  • 新建页面:src/pages/push-subscription/index.vue
  • 功能:
    • 显示当前推送时间
    • 修改推送时间(步长 30 分钟,07:00-22:00
    • 取消订阅按钮
    • 订阅状态说明

3.2 云函数:subscribe

// cloudfunctions/subscribe/index.js
exports.main = async (event, context) => {
  const { birthInfo, templateId, pushTime } = event
  const { OPENID } = cloud.getWXContext()

  // 校验参数
  // 检查是否已存在订阅
  // 写入 subscriptions 集合
  // 返回 { success: true }
}

3.3 云函数:unsubscribe

// cloudfunctions/unsubscribe/index.js
exports.main = async (event, context) => {
  const { OPENID } = cloud.getWXContext()

  // 删除 subscriptions 中对应记录
  // 返回 { success: true }
}

验收标准

  • 未订阅时,运势页面显示"订阅每日推送"入口
  • 点击订阅 → 弹出微信订阅消息授权弹窗
  • 授权成功 → 云函数存储订阅记录 → UI 更新为"已订阅"
  • 未输入生辰信息时,点击订阅提示先设置生辰信息
  • 订阅管理页面可正常打开,显示当前推送时间
  • 取消订阅 → 云端记录删除 → 运势页面恢复显示订阅入口

Issue 4: 每日推送云函数实现

优先级: P0 预估工时: 1.5d 依赖: Issue 2, Issue 3

描述: 实现 dailyPush 云函数,在每日定时触发时生成运势内容并推送。

云函数逻辑

graph TD
    A[定时触发 dailyPush] --> B[查询 subscriptions]
    B --> C{pushEnabled=true<br/>&& lastPushDate != today?}
    C -->|"否"| D[跳过]
    C -->|"是"| E[读取 birthInfo]
    E --> F[生成 ZiweiChart]
    F --> G[计算今日运势]
    G --> H[格式化推送内容]
    H --> I[wx.subscribeMessage.send]
    I --> J{发送成功?}
    J -->|"是"| K[更新 lastPushDate = today]
    J -->|"否"| L[记录错误日志]

关键实现细节

// cloudfunctions/dailyPush/index.js
const cloud = require('wx-server-sdk')
const { generateDailyFortune } = require('@everything-suitable/algorithm')
const { calculateLuckyColor, calculateLuckyNumber } = require('@everything-suitable/algorithm')

cloud.init()
const db = cloud.database()

exports.main = async (event, context) => {
  const today = new Date()
  const todayStr = today.toISOString().slice(0, 10)

  // 查询今日需要推送的订阅
  const { data: subscriptions } = await db.collection('subscriptions')
    .where({
      pushEnabled: true,
      lastPushDate: db.command.neq(todayStr).or(db.command.not exists),
    })
    .get()

  for (const sub of subscriptions) {
    try {
      // 检查是否到推送时间
      const [hour, minute] = sub.pushTime.split(':')
      if (today.getHours() !== parseInt(hour) || today.getMinutes() !== parseInt(minute)) {
        continue
      }

      // 生成 ZiweiChart(基于生辰信息)
      const chart = generateZiweiChart(sub.birthInfo)

      // 生成今日运势
      const fortune = generateDailyFortune(chart, today)
      fortune.luckyColor = calculateLuckyColor(chart, today)
      fortune.luckyNumber = calculateLuckyNumber(chart, today)

      // 发送订阅消息
      await cloud.openapi.subscribeMessage.send({
        touser: sub.openid,
        templateId: sub.templateId,
        data: {
          date1: { value: formatDate(today) },
          thing2: { value: `综合运势评分${fortune.overallScore}分,等级:${fortune.overallLuck}` },
          thing3: { value: formatFortuneSummary(fortune) },
          thing4: { value: `幸运色:${fortune.luckyColor}  幸运数字:${fortune.luckyNumber}` },
        },
      })

      // 更新最后推送日期
      await db.collection('subscriptions').doc(sub._id).update({
        data: { lastPushDate: todayStr },
      })
    } catch (err) {
      console.error(`Push failed for ${sub.openid}:`, err)
    }
  }
}

验收标准

  • 定时触发器在每日 07:00 正确触发云函数
  • 云函数正确过滤已订阅且未推送的用户
  • 推送内容包含:日期、综合运势、各维度运势、幸运信息
  • 推送内容与小程序内当日运势计算一致
  • 同一用户每日不会收到重复推送
  • 推送失败时记录错误日志,不影响其他用户推送
  • 用户自定义推送时间在 minutely 扫描模式下正常工作

Issue 5: 订阅管理功能

优先级: P1 预估工时: 1d 依赖: Issue 4

描述: 完善订阅管理页面的推送时间配置功能,以及相关的云函数 updatePushTime。

5.1 小程序端

  • 推送时间选择器(Picker 组件,步长 30 分钟)
  • "保存"按钮,调用云函数更新推送时间
  • 保存成功提示

5.2 云函数:updatePushTime

// cloudfunctions/updatePushTime/index.js
exports.main = async (event, context) => {
  const { pushTime } = event  // "HH:mm" 格式
  const { OPENID } = cloud.getWXContext()

  // 校验 pushTime 格式
  // 更新 subscriptions 集合中对应记录的 pushTime
  // 返回 { success: true }
}

验收标准

  • 推送时间选择器按 30 分钟步长显示可选时间
  • 保存后云端记录更新
  • 修改后下一次推送按新时间执行
  • 保存成功/失败均有明确提示

Issue 6: 集成测试与验收

优先级: P0 预估工时: 1.5d 依赖: Issue 4, Issue 5

描述: 对订阅和每日推送功能进行完整的集成测试、异常场景验证和最终验收。

测试场景

6.1 功能测试

  • TC-01:完整订阅流程 — 输入生辰信息 → 订阅 → 授权 → 订阅成功
  • TC-02:未输入生辰信息时订阅 — 提示用户先设置生辰信息
  • TC-03:取消订阅 — 取消订阅 → 云端记录删除 → 订阅入口恢复
  • TC-04:修改推送时间 — 修改时间 → 保存 → 云端更新
  • TC-05:每日推送触发 — 定时触发 → 运势生成 → 推送成功
  • TC-06:重复推送防护 — 同一用户同一日不重复推送
  • TC-07:推送内容正确性 — 推送内容与小程序内当日运势一致
  • TC-08:多次订阅同一用户 — 幂等处理,不重复创建记录

6.2 异常测试

  • TC-09:用户拒绝授权 — 订阅消息授权被拒绝 → 提示用户
  • TC-10:云函数超时 — 单个用户推送失败 → 不影响其他用户
  • TC-11:生辰信息不完整 — 缺少必要字段 → 提示用户补充
  • TC-12:订阅消息过期 — 用户重新订阅后恢复正常

6.3 算法兼容性测试

  • TC-13:共享算法包 Node.js 运行 — 算法包在云函数环境输出与小程序一致
  • TC-14:边界日期 — 跨年、闰年日期运势计算正确

验收标准

  • 所有功能测试场景通过
  • 所有异常测试场景通过,系统表现符合预期
  • 共享算法包在小程序和云函数中计算结果一致
  • 原有测试不受影响(cd everything-is-suitable-uniapp && npx vitest run 全部通过)
  • 云函数执行时间 < 3s/用户

执行顺序建议

Week 1:
  ├── Day 1-2: Issue 1 — 共享算法包抽取(可并行)
  ├── Day 1:   Issue 2 — 云开发环境搭建(可并行)
  ├── Day 3-4: Issue 3 — 订阅功能实现
  └── Day 5:   Issue 4 — 每日推送云函数

Week 2:
  ├── Day 1:   Issue 5 — 订阅管理功能
  └── Day 2-3: Issue 6 — 集成测试与验收

自检清单

  1. 规格覆盖度: PRD 中 F1-F8 全部覆盖
  2. 占位符扫描: 无 TODO/待定/后续实现
  3. 类型一致性: 云数据库字段与 PRD 定义一致
  4. 测试覆盖: 功能测试 14 项 + 异常测试 4 项 + 算法兼容性 2 项
  5. 原有功能不受影响: 现有运势页面、紫微排盘、黄历查询不受影响