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

398 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 订阅与每日运势推送 — 实现计划
> **面向 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`
---
## 整体架构流程
```mermaid
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
```
## 依赖关系
```mermaid
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`(唯一索引)、`pushEnabled``lastPushDate`
- 权限:仅云函数可读写(不开放客户端直接访问)
- [ ] **步骤 3:创建云函数骨架**
```bash
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
```typescript
// cloudfunctions/subscribe/index.js
exports.main = async (event, context) => {
const { birthInfo, templateId, pushTime } = event
const { OPENID } = cloud.getWXContext()
// 校验参数
// 检查是否已存在订阅
// 写入 subscriptions 集合
// 返回 { success: true }
}
```
### 3.3 云函数:unsubscribe
```typescript
// 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 云函数,在每日定时触发时生成运势内容并推送。
### 云函数逻辑
```mermaid
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[记录错误日志]
```
### 关键实现细节
```typescript
// 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
```typescript
// 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. **原有功能不受影响:** 现有运势页面、紫微排盘、黄历查询不受影响 ✅