This commit is contained in:
2026-06-25 18:24:29 +08:00
commit a1190d7d9a
90 changed files with 5459 additions and 0 deletions

195
docs/03-云函数设计.md Normal file
View File

@@ -0,0 +1,195 @@
# 03 · 云函数设计
约定:所有云函数用 `wx-server-sdk`,统一返回 `{ ok: true, data }``{ ok: false, code, msg }``openid` 一律从 `cloud.getWXContext().OPENID` 取,**绝不信任客户端传入的 openid**。
---
## 1. `login` — 登录 / 首次建用户
**职责**:拿 openid若用户不存在则建档返回用户信息 + 是否已有狗狗(决定跳建档还是首页)。
```
入参: {}
出参: {
ok, data: {
userInfo: { openid, nickname, avatar, walkCount, joinedAt },
hasDog: Boolean // false → 前端跳 0b 建档true → 跳首页
}
}
```
逻辑:`users``_openid`,无则插入默认用户;再 `dogs` count 判断 `hasDog`
---
## 2. `dogManage` — 狗狗增 / 改 / 查
**职责**:建档、编辑、列表(建档与编辑复用,由 `action` 区分)。
```
入参: {
action: 'create' | 'update' | 'list',
dog?: { _id?, name, breed, sizeLevel?, gender, ageStage, ageText, weight, avatar }
}
出参: { ok, data: dog | dogs[] }
```
要点:
- 服务端校验 `name``breed` 非空。
-`breed` 查表得到体型档位 `sizeLevel`;中华田园犬、串串、找不到的犬种必须由前端让用户手动选择 `sizeLevel` 后再提交。
-`sizeLevel` 得到 `baseStride`,填了 `weight` 时计算最终 `stride``baseStride * (1 + (weight - 档位标准体重) * 0.005)`,修正幅度封顶 ±15%。
- `create` 时初始化 `stats` 全 0。
- `update` 校验该 dog 的 `_openid` == 调用者,防越权改他人狗。
---
## 3. `walkSave` — 保存遛狗(核心)
**职责**:写入一条 `walks`**事务式**更新每只狗 `stats`,触发成就判定;并支持成果卡页回写选填字段。这是 V1 最重的云函数。
### 3.1 结束遛狗:`action='finish'`
```
入参: {
action: 'finish',
dogIds: [String],
startAt, endAt, duration,
distance?,
pooped
}
出参: {
ok, data: {
walkId,
dogStepsByDog,
newAchievements: [{ key, name, desc }] // 本次新解锁,成果卡展示
}
}
```
执行步骤:
1. 校验 `dogIds` 均归属当前 openid未建档任何狗时拒绝保存。
2. 计算 `walkDate`(按用户本地时区,前端传时区偏移或统一 +8
3. 插入 `walks``source='realtime'``isManual=false``countsForRanking=true`
4. 获得定位授权时写入 `distance`,并按每只狗的 `stride` 在服务端计算 `dogStepsByDog`;未授权时 `distance``dogStepsByDog``null`,只保存时长。
5. 对每个 dogId
- `totalWalks += 1``totalDistance += distance||0``totalSteps += dogStepsByDog[dogId]||0`
- **连续天数**:比较 `lastWalkDate``walkDate`——同日不变;昨日则 `currentStreak += 1`;更早则重置为 1。更新 `lastWalkDate`
-`db.command.inc()` 做原子自增,避免并发覆盖。
6. `users.walkCount += 1`
7. 调成就判定(见 `achievementCheck`),收集新解锁返回。
### 3.2 成果卡补充字段:`action='attach'`
```
入参: {
action: 'attach',
walkId,
weather?, mood?, note?, photos?
}
出参: { ok, data: { walkId, newAchievements?: [{ key, name, desc }] } }
```
要点:
- `weather/mood/note/photos` 全部选填,用户跳过也不影响 `finish` 已保存的核心记录。
- 只允许回写当前 openid 自己的 walk。
-`weather='rainy'`,可在 attach 后补触发「雨天战士」等场景成就,并返回新增成就给成果卡刷新。
- 旧记录回看不复用 attach后续单次记录详情页若支持编辑应单独定义记录编辑能力。
---
## 4. `walkManual` — 手动补记
**职责**:补记一次遛狗。规则与 `walkSave` 不同。
```
入参: { dogId, startAt, duration, distance? }
出参: { ok, data: { walkId, newAchievements?: [{ key, name, desc }] } }
```
关键差异(**业务规则,必落地**
- `source='manual'``isManual=true``countsForRanking=false`(不计排行榜,防刷量)。
- **可维持连续打卡**:同样参与 `currentStreak` 计算(不断签)。
- 不触发「分享/成果卡」类成就,但可触发「连续」类成就。
- 列表展示带「补记」标识(前端按 `isManual` 渲染)。
---
## 5. `getDogDetail` — 狗狗主页聚合
**职责**:一次返回狗狗主页所需全部数据,减少往返。
```
入参: { dogId }
出参: {
ok, data: {
dog: {...}, // 含 stats
achievements: { // 成就墙
unlocked: [{key,name,unlockedAt}],
total: 12
},
recentWalks: [walk, ...] // 近期记录(取最近 5~10 条)
}
}
```
---
## 6. `getHistory` — 历史 / 记录 Tab 聚合
**职责**:返回当前用户全部狗狗的遛狗记录列表,供历史页和记录 Tab 复用,减少前端跨集合拼装。
```
入参: { dogId?, page?, pageSize? }
出参: {
ok, data: {
list: [{ walk, dogNames, manualLabel }],
hasMore: Boolean
}
}
```
要点:
- 默认查当前 openid 下全部记录;传 `dogId` 时只查某只狗。
-`walkDate/startAt` 倒序返回,可由前端按周或日期分组。
- 返回项带狗名、是否补记标识,避免列表层再多次请求 `dogs`
- 只读,不写库。
---
## 7. 内部模块:`achievementCheck`(被 walkSave/walkManual 复用)
不是独立云函数,是云函数内共享的判定模块(也可做成被 `cloud.callFunction` 内部调用的函数)。
```
输入: dogId, 该狗最新 stats, 本次 walk含 weather/时间)
流程:
for 每个成就定义:
if 已解锁(查 dog_achievements) → skip
if 满足条件(基于 stats / walk):
插入 dog_achievements
收集进 newAchievements
返回: newAchievements[]
```
判定基于 **连续 / 累计 / 场景**,不基于「最多 / 最快」。
---
## 8. 云函数清单速查
| 云函数 | 触发时机 | 写库 |
|--------|---------|------|
| `login` | App 启动 | users |
| `dogManage` | 建档/编辑/进首页 | dogs |
| `walkSave` | 结束遛狗、成果卡补充字段回写 | walks, dogs, users, dog_achievements |
| `walkManual` | 历史页补记保存 | walks, dogs, dog_achievements |
| `getDogDetail` | 进狗狗主页 | 只读 |
| `getHistory` | 进历史页 / 记录 Tab | 只读(跨全部狗狗,附狗名) |
> 后续若上排行榜pending新增定时触发云函数做聚合按 `countsForRanking=true` 统计。