351 lines
22 KiB
Markdown
351 lines
22 KiB
Markdown
# Doodle Mini — 小程序云开发方案
|
||
|
||
> 版本:v1.1
|
||
> 最后更新:2026-03-27
|
||
> **适用范围**:与 [技术架构设计文档](./技术架构设计文档.md) 配套;**当前阶段**采用本文档所述微信云开发环境实现后端能力。
|
||
> **阶段截止**:与总架构文档一致,**有效期至 2026 年 9 月 15 日**(到期前需复盘用量与成本,并决定是否迁移至 [后端部署方案](./后端部署方案.md) 所述自建服务)。
|
||
|
||
---
|
||
|
||
## 一、方案定位
|
||
|
||
微信云开发提供免运维的 Serverless 后端能力(云数据库、云存储、云函数),与小程序 `wx.cloud` 原生集成,**接入与鉴权成本低**,适合在阶段内快速落地题型配置下发、用户数据与素材存储。
|
||
|
||
**与总架构文档的关系**:
|
||
|
||
- [技术架构设计文档](./技术架构设计文档.md):描述前端分层、模板引擎、系统总览及**后端分阶段策略**(现阶段云开发 + 到期后可选 NestJS)。
|
||
- **本文档**:仅展开**云开发侧**的库表、云函数、存储与调用策略等技术细节。
|
||
|
||
**到期后选项(不在本文档展开)**:若免费额度不足或需更强多端能力,可迁移至 [后端部署方案](./后端部署方案.md)(NestJS + 自有服务器);迁移时需做数据导出与 `cloud-adapter` → `http-adapter` 的适配,详见技术架构文档中的「可迁移」原则。
|
||
|
||
---
|
||
|
||
## 二、与自建后端对比(决策参考)
|
||
|
||
| 维度 | 微信云开发 | 自建云服务器(NestJS) | 现阶段采用 |
|
||
| ----------- | ----------------------------- | ---------------------- | -------------------- |
|
||
| 接入成本 | ⭐ 极低(原生集成) | ⭐⭐ 需搭建部署 | **云开发** |
|
||
| 运维成本 | ⭐ 免运维 | ⭐⭐ 需简单维护 | **云开发** |
|
||
| 费用 | 免费额度内 ¥0;超出按套餐计费 | 已有服务器可零增量 | **云开发(阶段内)** |
|
||
| 小程序集成 | ⭐ 鉴权与 OpenID 一体化 | 需自建登录与 JWT | **云开发** |
|
||
| PC Web 支持 | 需 HTTP 云函数或桥接 | RESTful 天然支持 | 远期看 NestJS |
|
||
| 灵活性 | 受云开发 SDK 与配额约束 | 完全自主 | 远期看 NestJS |
|
||
| 数据迁移 | 可导出,迁移需工作量 | 标准 MySQL,自主备份 | 远期看 NestJS |
|
||
|
||
---
|
||
|
||
## 三、云数据库集合设计
|
||
|
||
本节与 [后端部署方案](./后端部署方案.md) 中的 **Prisma Schema(MySQL)** 字段语义对齐,便于阶段结束后迁移;云数据库为 **MongoDB 兼容模型**(文档 = BSON),下列类型按 MongoDB 惯例书写。
|
||
|
||
### 3.1 设计约定
|
||
|
||
| 约定 | 说明 |
|
||
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||
| `_id` | 每条文档主键。可由云开发自动生成,也可在写入时指定为字符串(如业务 ID、cuid)。**不要求**与微信 `openid` 相同。 |
|
||
| `Date` | 对应 BSON `Date`,云函数/控制台写入时使用 `serverDate()` 或 `new Date()`。 |
|
||
| `openid` / `unionid` | 显式业务字段;与下节「用户标识」一致。 |
|
||
| `_openid` | 仅当文档由**小程序端直连数据库**写入且开启用户校验时,云开发可自动注入当前用户 `openid`;若数据经**云函数**写入,通常自行维护 `openid`/`userId` 字段,不依赖 `_openid`。 |
|
||
| 命名 | 集合名与 Prisma `@@map` 一致:`worksheets`、`categories`、`users`、`favorites`、`download_logs`、`feedback`、`learning_plans`。 |
|
||
|
||
### 3.2 用户标识:`openid`、`unionid` 与 `_id`
|
||
|
||
**用户的 `_id` 只能是 `openid` 吗?——不是。**
|
||
|
||
- 云数据库文档的 `_id` 默认为云开发生成的唯一 ID;也可以在 `add` 时**自定义**为字符串(例如与 [后端部署方案](./后端部署方案.md) 一致的 `cuid()`,或直接等于 `openid`)。
|
||
- **推荐**:`users` 集合使用 **独立 `openid` 字段(唯一)** + **`unionid` 字段(可选、唯一)**,`_id` 可用自动生成或自定义;与 Prisma 中 `User.id`(cuid)+ `openid` / `unionid` 的建模方式一致,迁移时映射清晰。
|
||
- **`unionid`**:同一微信开放平台下多应用用户唯一标识;需在小程序后台绑定开放平台,且用户已授权;未绑定时可能为空,字段应为可选并建**稀疏唯一索引**(仅非空值唯一)。
|
||
|
||
**`users` 集合完整字段与索引**:见 **§3.5**(与 Prisma `User` 一一对应)。
|
||
|
||
子表(收藏、下载日志、反馈)中的 `userId` 建议与 `users._id` 保持一致(若 `_id` 采用 cuid,则存 cuid;若 `_id` 即 `openid`,则存 `openid`),与 Prisma 外键语义一致。
|
||
|
||
**`unionid` 的写入**:在云函数中可通过 `cloud.getWXContext()` 读取 `UNIONID`(用户已绑定开放平台且当前会话可返回时才有值);首次登录 upsert `users` 时写入或更新 `unionid` 字段。若仅能在服务端用 `code` 换 `session`,则使用微信 `code2Session` 返回的 `unionid` 字段(同样可能为空)。**不要**在小程序端把 `unionid` 当可信主键直接展示给第三方,存储与鉴权仍以服务端为准。
|
||
|
||
---
|
||
|
||
### 3.3 集合:`worksheets`(题型配置)
|
||
|
||
| 字段 | BSON 类型 | 必填 | 说明 |
|
||
| ------------- | --------------- | ---- | --------------------------------------------------- |
|
||
| `_id` | string | 是 | 主键;唯一的可读业务 ID。 |
|
||
| `title` | string | 是 | 标题,建议 ≤8 字符。 |
|
||
| `subtitle` | string | 是 | 副标题,建议 ≤12 字符。 |
|
||
| `category` | string | 是 | 大类 对应categories 表中parentId=null 的分类 |
|
||
| `subcategory` | string | 是 | 子类,建议 ≤50 字符。 |
|
||
| `ageMin` | int | 是 | 适龄最小值 |
|
||
| `ageMax` | int | 是 | 适龄最大值 |
|
||
| `grade` | int | 是 | 年级(-4=托班,-3=小班 ... 0=幼小衔接,1=一年级..) |
|
||
| `difficulty` | int | 是 | 难度 1-4(1=入门,2=基础,3=进阶,4=挑战) |
|
||
| `previewImg` | string | 是 | 预览图 URL/云存储 fileID |
|
||
| `tags` | array\<string\> | 是 | 标签列表。 |
|
||
| `isNew` | bool | 是 | 是否新品,默认 `false`。 |
|
||
| `isHot` | bool | 是 | 是否热门,默认 `false`。 |
|
||
| `sortOrder` | int | 是 | 排序权重,默认 `0`。 |
|
||
| `downloads` | int | 是 | 下载次数,默认 `0`。 |
|
||
| `likes` | int | 是 | 收藏此时,默认 `0`。 |
|
||
| `status` | string | 是 | `active` \| `draft`,默认 `draft`。 |
|
||
| `createdAt` | date | 是 | 创建时间。 |
|
||
| `updatedAt` | date | 是 | 更新时间。 |
|
||
|
||
后续可能需要使用的字段,现在先不用
|
||
| `template` | string | 是 | 渲染模板类型 `TemplateType`,建议 ≤30 字符。 |
|
||
| `generator` | string | 是 | 生成器类型 `GeneratorType`,建议 ≤30 字符。 |
|
||
| `generatorConfig` | object | 是 | 生成器参数(JSON 对象)。 |
|
||
| `layoutConfig` | object | 是 | 排版参数(JSON 对象)。 |
|
||
| `userConfigurable` | object \| null | 否 | 用户可调整参数定义(JSON)。 |
|
||
| `legacyPage` | string \| null | 否 | 旧页面路径,迁移过渡用,建议 ≤200 字符。 |
|
||
**索引建议**
|
||
|
||
| 索引键 | 类型 | 说明 |
|
||
| ------------------------------------------ | ---- | ---------------- |
|
||
| `{ category: 1, status: 1, sortOrder: 1 }` | 复合 | 列表筛选与排序。 |
|
||
| `{ status: 1, sortOrder: 1 }` | 复合 | 全量上架列表。 |
|
||
|
||
---
|
||
|
||
### 3.4 集合:`categories`(分类)
|
||
|
||
对应 Prisma `Category`。
|
||
|
||
| 字段 | BSON 类型 | 必填 | 说明 |
|
||
| ----------- | -------------- | ---- | --------------------------------- |
|
||
| `_id` | string | 是 | 主键。 |
|
||
| `name` | string | 是 | 名称,建议 ≤50 字符。 |
|
||
| `icon` | string | 是 | 图标 URL/fileID,建议 ≤200 字符。 |
|
||
| `color` | string | 是 | 色值,建议 ≤10 字符。 |
|
||
| `sortOrder` | int | 是 | 排序,默认 `0`。 |
|
||
| `parentId` | string \| null | 否 | 父分类 ID。 |
|
||
|
||
**索引建议**:`{ parentId: 1, sortOrder: 1 }`。
|
||
|
||
---
|
||
|
||
### 3.5 集合:`users`(用户)
|
||
|
||
对应 Prisma `User`(`@@map("users")`)。**`openid` / `unionid` 与 `_id` 的取舍、`unionid` 写入方式**见 §3.2。
|
||
|
||
| 字段 | BSON 类型 | 必填 | 说明 |
|
||
| -------------- | -------------- | ---- | ----------------------------------------------------------------------------------------------------- | ----- |
|
||
| `_id` | string | 是 | 主键;可与 Prisma `User.id` 一样使用 cuid,也可自定义为其它字符串,团队内与用户档案查询方式统一即可。 |
|
||
| `openid` | string | 是 | 当前小程序下微信用户标识;与 Prisma 一致建议 ≤100 字符;**业务唯一**,索引见下表。 |
|
||
| `unionid` | string \| null | 否 | 开放平台下跨应用用户标识;建议 ≤100 字符;未绑开放平台或未返回时为空。 |
|
||
| `nickName` | string \| null | 否 | 用户昵称;建议 ≤50 字符。 |
|
||
| `avatarUrl` | string \| null | 否 | 头像 URL 或云存储 fileID;建议 ≤500 字符。 | `0`。 |
|
||
| `createdAt` | date | 是 | 首次创建时间。 |
|
||
| `lastActiveAt` | date | 是 | 最近一次活跃时间(登录、打开小程序、写库等策略由实现约定)。 |
|
||
|
||
**索引建议**
|
||
|
||
| 索引键 | 类型 | 说明 |
|
||
| --------- | ------------ | ---------------------------------------------------- |
|
||
| `openid` | 唯一 | 登录与 upsert 主查。 |
|
||
| `unionid` | 唯一(稀疏) | 仅当存在开放平台绑定且需跨端关联时使用;空值不互斥。 |
|
||
|
||
---
|
||
|
||
### 3.6 集合:`favorites_log`(收藏日志)
|
||
|
||
对应 Prisma `FavoriteLog`。
|
||
|
||
| 字段 | BSON 类型 | 必填 | 说明 |
|
||
| ------------- | --------- | ---- | ----------------------- |
|
||
| `_id` | string | 是 | 主键。 |
|
||
| `userId` | string | 是 | 关联 `users._id`。 |
|
||
| `worksheetId` | string | 是 | 关联 `worksheets._id`。 |
|
||
| `createdAt` | date | 是 | 收藏时间。 |
|
||
|
||
**索引建议**
|
||
|
||
| 索引键 | 类型 | 说明 |
|
||
| ------------------------------- | ---- | ---------------------------- |
|
||
| `{ userId: 1, worksheetId: 1 }` | 唯一 | 同一用户同一题型仅一条收藏。 |
|
||
| `{ userId: 1, createdAt: -1 }` | 复合 | 「我的收藏」按时间倒序。 |
|
||
|
||
> 若小程序端直连写库且使用安全规则,可额外保留 `_openid` 与 `userId` 二选一做一致性校验;以云函数为主时以 `userId` + 云函数鉴权为准。
|
||
|
||
---
|
||
|
||
### 3.7 集合:`download_logs`(下载日志)
|
||
|
||
对应 Prisma `DownloadLog`。
|
||
|
||
| 字段 | BSON 类型 | 必填 | 说明 |
|
||
| ------------- | -------------- | ---- | --------------------------------- |
|
||
| `_id` | string | 是 | 主键。 |
|
||
| `userId` | string | 是 | 关联 `users._id`。 |
|
||
| `worksheetId` | string | 是 | 关联 `worksheets._id`。 |
|
||
| `params` | object \| null | 否 | 生成参数快照(JSON)。 暂时不使用 |
|
||
| `createdAt` | date | 是 | 下载时间。 |
|
||
|
||
**索引建议**:`{ userId: 1, createdAt: -1 }`;`{ worksheetId: 1, createdAt: -1 }`(统计/运营)。
|
||
|
||
---
|
||
|
||
### 3.8 集合:`feedback`(用户反馈)
|
||
|
||
对应 Prisma `Feedback`。
|
||
|
||
| 字段 | BSON 类型 | 必填 | 说明 |
|
||
| ----------- | -------------- | ---- | -------------------------- |
|
||
| `_id` | string | 是 | 主键。 |
|
||
| `userId` | string | 是 | 关联 `users._id`。 |
|
||
| `content` | string | 是 | 反馈正文。 |
|
||
| `contact` | string \| null | 否 | 联系方式,建议 ≤100 字符。 |
|
||
| `createdAt` | date | 是 | 提交时间。 |
|
||
|
||
**索引建议**:`{ userId: 1 }`。
|
||
|
||
---
|
||
|
||
### 3.9 集合:`learning_plans`(学习路线)
|
||
|
||
对应 Prisma `LearningPlan`(总架构中的适龄学习路线配置)。
|
||
|
||
| 字段 | BSON 类型 | 必填 | 说明 |
|
||
| ------------ | --------------- | ---- | ------------------------------------------------------ |
|
||
| `_id` | string | 是 | 主键。 |
|
||
| `ageMin` | int | 是 | 适龄下限。 |
|
||
| `ageMax` | int | 是 | 适龄上限。 |
|
||
| `ageLabel` | string | 是 | 展示用年龄段文案,建议 ≤20 字符。 |
|
||
| `milestones` | array\<string\> | 是 | 能力目标描述(JSON 数组,与 Prisma `Json` 一致)。 |
|
||
| `weeks` | array | 是 | 周计划结构 `WeekPlan[]`(JSON 数组,与 Prisma 一致)。 |
|
||
|
||
**索引建议**:`{ ageMin: 1, ageMax: 1 }` **唯一**,与 Prisma `@@unique([ageMin, ageMax])` 一致。
|
||
|
||
---
|
||
|
||
### 3.10 ER 关系(逻辑)
|
||
|
||
```
|
||
users (1) ──< favorites >── (N) worksheets
|
||
users (1) ──< download_logs >── (N) worksheets
|
||
users (1) ──< feedback
|
||
categories ──(可选业务关联)── worksheets.category / subcategory(worksheet 内嵌字符串,非 DB 外键)
|
||
learning_plans:独立配置表,按 ageMin/ageMax 查询
|
||
```
|
||
|
||
---
|
||
|
||
## 四、云函数设计
|
||
|
||
```
|
||
cloudfunctions/
|
||
└── doodle/
|
||
├── index.js ← 统一入口(action 路由)
|
||
├── package.json
|
||
├── src/
|
||
│ ├── user/
|
||
│ │ ├── getOpenId.js ← ✅ 已有,保留
|
||
│ │ └── updateProfile.js ← 🆕 更新用户信息
|
||
│ ├── worksheet/
|
||
│ │ ├── getList.js ← 🆕 获取题型列表(支持分类/筛选)
|
||
│ │ ├── getDetail.js ← 🆕 获取题型详情
|
||
│ │ └── incrementDownload.js← 🆕 下载计数+1
|
||
│ ├── favorite/
|
||
│ │ ├── add.js ← 🆕 添加收藏
|
||
│ │ ├── remove.js ← 🆕 取消收藏
|
||
│ │ └── list.js ← 🆕 我的收藏列表
|
||
│ ├── history/
|
||
│ │ └── list.js ← 🆕 下载历史
|
||
│ ├── feedback/
|
||
│ │ └── submit.js ← 🆕 提交反馈
|
||
│ └── pdf/
|
||
│ └── generate.js ← 🆕 PDF 生成(远期增值功能,会员专属)
|
||
└── common/
|
||
├── responseMiddleware.js ← ✅ 已有,保留
|
||
└── auth.js ← 🆕 鉴权中间件
|
||
```
|
||
|
||
---
|
||
|
||
## 五、云函数调用与数据加载策略
|
||
|
||
```
|
||
┌────────────────────────────────────────────────────────────┐
|
||
│ 数据加载策略 │
|
||
│ │
|
||
│ 题型配置数据 (worksheets/categories) │
|
||
│ ┌────────────────────────────────────────┐ │
|
||
│ │ 优先级 1: 本地缓存(wx.Storage) │ │
|
||
│ │ 优先级 2: 云数据库查询 │ │
|
||
│ │ 优先级 3: 前端内置兜底数据 │ ← 保证离线可用 │
|
||
│ └────────────────────────────────────────┘ │
|
||
│ │
|
||
│ 缓存策略: │
|
||
│ • 首次启动:云端拉取 → 写入本地缓存 │
|
||
│ • 后续启动:先用缓存渲染 → 后台静默更新 │
|
||
│ • 缓存有效期:24 小时 │
|
||
│ • 无网络:使用本地缓存或内置兜底 │
|
||
│ │
|
||
│ 用户数据 (favorites/history) │
|
||
│ ┌────────────────────────────────────────┐ │
|
||
│ │ 本地优先写入 → 后台同步云端 │ │
|
||
│ │ 冲突策略:以云端为准(云端时间戳更新) │ │
|
||
│ └────────────────────────────────────────┘ │
|
||
│ │
|
||
│ 统计数据 (download count) │
|
||
│ ┌────────────────────────────────────────┐ │
|
||
│ │ 批量上报:本地累计 → 退出时/定时上报 │ │
|
||
│ │ 非关键路径,允许丢失 │ │
|
||
│ └────────────────────────────────────────┘ │
|
||
└────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 六、云存储规划
|
||
|
||
```
|
||
云存储目录结构:
|
||
cloud://doodle-xxx.xxxx/
|
||
├── assets/
|
||
│ ├── previews/ ← 题型效果预览图
|
||
│ │ ├── math/
|
||
│ │ ├── chinese/
|
||
│ │ ├── english/
|
||
│ │ ├── puzzle/
|
||
│ │ └── craft/
|
||
│ ├── coloring/ ← 涂色卡线稿(SVG/PNG)
|
||
│ │ ├── animals/
|
||
│ │ ├── vehicles/
|
||
│ │ ├── holidays/
|
||
│ │ └── ...
|
||
│ ├── origami/ ← 折纸展开图
|
||
│ ├── stickers/ ← 贴纸素材
|
||
│ ├── maze-templates/ ← 迷宫模板数据(JSON)
|
||
│ └── craft-templates/ ← 手工模板
|
||
├── fonts/ ← 字体文件
|
||
│ ├── SimHei.ttf
|
||
│ └── handwriting.ttf
|
||
└── share/ ← 分享图
|
||
└── default-share.png
|
||
```
|
||
|
||
---
|
||
|
||
## 七、费用与额度(阶段内监控)
|
||
|
||
```
|
||
免费额度(基础版 1):
|
||
┌────────────────────────────────────────┐
|
||
│ 云数据库:2 GB 存储 / 50 万次读写/天 │
|
||
│ 云存储:5 GB / 2 GB 下载/天 │
|
||
│ 云函数:10 万次调用/月 / 1000 GBs/月 │
|
||
│ CDN:5 GB/月 │
|
||
└────────────────────────────────────────┘
|
||
|
||
预估用量(DAU 1000):
|
||
┌────────────────────────────────────────┐
|
||
│ 云数据库:~100 MB(足够) │
|
||
│ 云存储:~2 GB(素材渐增) │
|
||
│ 云函数:~3 万次/月 │
|
||
│ CDN:~3 GB/月 │
|
||
│ │
|
||
│ 结论:免费额度可覆盖到 DAU 3000 左右 │
|
||
│ 超出后需升级套餐(如 19.9 元/月起) │
|
||
└────────────────────────────────────────┘
|
||
```
|
||
|
||
**建议在 2026-09-15 前**:结合控制台用量与业务目标,决定续用云开发、升级套餐或启动迁移至 [后端部署方案](./后端部署方案.md)。
|