# 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` 统计。