# 02 · 数据模型(云数据库) 云数据库为文档型(类 MongoDB)。共 4 个集合。约定:`_openid` 由云开发自动写入,作为归属与权限依据;时间统一存毫秒时间戳(Number)。 --- ## 1. `users` — 用户 | 字段 | 类型 | 说明 | |------|------|------| | `_id` | String | 自动 | | `_openid` | String | 微信 openid,唯一 | | `nickname` | String | 昵称,默认「铲屎官」 | | `avatar` | String | 云存储 fileID,可空 | | `walkCount` | Number | 累计遛狗次数(冗余,展示用) | | `joinedAt` | Number | 加入时间戳 | | `createdAt` | Number | 创建时间 | > 「加入 92 天」由 `joinedAt` 客户端算;「遛狗 87 次」读 `walkCount`。 --- ## 2. `dogs` — 狗狗 | 字段 | 类型 | 说明 | |------|------|------| | `_id` | String | 狗狗 ID | | `_openid` | String | 归属用户 | | `name` | String | **必填**,名字 | | `breed` | String | **必填**,犬种(如「柯基」) | | `sizeLevel` | String | 体型档位:`xs`/`s`/`m`/`l`/`xl`,由犬种映射;未知犬种由用户手动选择 | | `baseStride` | Number | 档位基础步幅,单位米/步 | | `stride` | Number | 最终步幅,填体重时在 `baseStride` 上校准,封顶 ±15% | | `gender` | String | `male` / `female` / null | | `ageStage` | String | `puppy` / `adult` / `senior` / null | | `ageText` | String | 展示文案,如「成犬 3 岁」,可空 | | `weight` | Number | kg,可空(二次校准狗步) | | `avatar` | String | 云存储 fileID,可空 | | `stats` | Object | 累计统计(见下,冗余加速主页) | | `createdAt` | Number | 建档时间 | **`stats` 内嵌对象**(每次遛狗结算时在云函数原子更新): ```js stats: { totalWalks: 87, // 总次数 totalDistance: 142.0, // 总公里 totalSteps: 0, // 总狗步(可选) currentStreak: 7, // 连续天数 lastWalkDate: '2026-06-24', // 最近一次有效遛狗的日期(yyyy-mm-dd),算连续用 } ``` > 仅「名字 + 犬种」必填——降低首次门槛。`sizeLevel/baseStride/stride` 由 `utils/dogStep.js` 按「犬种 → 体型档位 → 步幅」计算;中华田园犬、串串、找不到的犬种必须让用户手动选体型档位。 --- ## 3. `walks` — 遛狗记录 | 字段 | 类型 | 说明 | |------|------|------| | `_id` | String | 记录 ID | | `_openid` | String | 归属用户 | | `dogIds` | Array<String> | 本次遛的狗(支持多狗一起遛) | | `startAt` | Number | 开始时间戳 | | `endAt` | Number | 结束时间戳 | | `duration` | Number | 净遛狗秒数(已扣暂停) | | `distance` | Number | 公里,未授权定位时为 null | | `dogStepsByDog` | Object | `{ [dogId]: steps }`,按每只狗的 `stride` 分别换算;未授权定位时为 null | | `pooped` | Boolean | 便便打卡 | | `weather` | String | `sunny`/`cloudy`/`rainy`,可空 | | `mood` | String | 状态标签,可空 | | `note` | String | 备注,可空 | | `photos` | Array<String> | 云存储 fileID,可空 | | `source` | String | `realtime` / `manual`,对应 PRD 的数据来源 | | `isManual` | Boolean | 是否手动补记;兼容展示判断,等价于 `source === 'manual'` | | `countsForRanking` | Boolean | 是否计入排行榜;补记恒为 `false` | | `walkDate` | String | `yyyy-mm-dd`,按本地时区,连续/分组用 | | `createdAt` | Number | 入库时间 | > 成果卡所有补充字段(photos/weather/mood/note)全部选填,跳过也能保存。`dogStepsByDog` 仅用于拟人化展示;成就与未来排行榜只使用距离、时长、连续天数等客观量。 --- ## 4. `dog_achievements` — 成就解锁记录 成就**按狗维度**(原型「成就墙 · 豆豆」)。成就定义不入库,固化在代码 `utils/achievements.js`(含 key、名称、描述、判定规则、图标)。本集合只存「某狗解锁了某成就」。 | 字段 | 类型 | 说明 | |------|------|------| | `_id` | String | 自动 | | `_openid` | String | 归属用户 | | `dogId` | String | 狗狗 ID | | `achievementKey` | String | 成就标识,如 `week_streak` | | `unlockedAt` | Number | 解锁时间戳 | 唯一性:`(dogId, achievementKey)` 不重复解锁(云函数判定前先查重)。 **成就 key 参考**(来自原型): | key | 名称 | 解锁条件 | |-----|------|---------| | `first_walk` | 首遛 | 第一次记录遛狗 | | `week_streak` | 一周不断 | 连续遛狗满 7 天 | | `month_streak` | 满月坚持 | 连续遛狗满 30 天 | | `rainy_warrior` | 雨天战士 | 雨天也坚持遛狗 | | `hundred_km` | 百里同行 | 累计满 100 公里 | | `early_bird` | 早起鸟 | 7 点前遛狗 10 次 | --- ## 5. 索引 | 集合 | 索引字段 | 说明 | |------|---------|------| | `users` | `_openid`(唯一) | 登录查重 | | `dogs` | `_openid` | 列出我的狗 | | `walks` | `_openid` + `walkDate`(降序) | 历史列表、连续判定 | | `walks` | `dogIds`(多键) | 按狗查记录 | | `dog_achievements` | `dogId` + `achievementKey`(联合唯一) | 查重与成就墙 | ## 6. 数据库权限(安全规则) 全部集合设为 **「仅创建者可读写」**,写操作再经云函数二次校验。客户端只允许读自己的数据;统计累加、成就解锁等只能由云函数(管理员权限)写,防止刷量。 ```json // 示例:walks 集合权限 { "read": "doc._openid == auth.openid", "write": false // 客户端禁写,统一走云函数 } ```