# 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 | 是 | 主键;建议与后端一致用 cuid 或可读业务 ID。 | | `title` | string | 是 | 标题,建议 ≤100 字符。 | | `desc` | string | 是 | 描述,建议 ≤500 字符。 | | `category` | string | 是 | 大类:`math` \| `chinese` \| `english` \| `puzzle` \| `craft`。 | | `subcategory` | string | 是 | 子类,建议 ≤50 字符。 | | `ageMin` | int | 是 | 适龄最小值(与 Prisma 一致;替代原 `ageRange` 数组)。 | | `ageMax` | int | 是 | 适龄最大值。 | | `difficulty` | int | 是 | 难度 1–4。 | | `previewImage` | string | 是 | 预览图 URL/云存储 fileID,建议 ≤500 字符。 | | `tags` | array\ | 是 | 标签列表。 | | `isNew` | bool | 是 | 是否新品,默认 `false`。 | | `isHot` | bool | 是 | 是否热门,默认 `false`。 | | `sortOrder` | int | 是 | 排序权重,默认 `0`。 | | `downloadCount` | int | 是 | 下载次数,默认 `0`。 | | `status` | string | 是 | `active` \| `draft` \| `hidden`,默认 `active`。 | | `template` | string | 是 | 渲染模板类型 `TemplateType`,建议 ≤30 字符。 | | `generator` | string | 是 | 生成器类型 `GeneratorType`,建议 ≤30 字符。 | | `generatorConfig` | object | 是 | 生成器参数(JSON 对象)。 | | `layoutConfig` | object | 是 | 排版参数(JSON 对象)。 | | `userConfigurable` | object \| null | 否 | 用户可调整参数定义(JSON)。 | | `legacyPage` | string \| null | 否 | 旧页面路径,迁移过渡用,建议 ≤200 字符。 | | `createdAt` | date | 是 | 创建时间。 | | `updatedAt` | date | 是 | 更新时间。 | **索引建议** | 索引键 | 类型 | 说明 | | ------------------------------------------ | ---- | ---------------- | | `{ 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 字符。 | | `totalDownloads` | int | 是 | 累计下载次数,默认 `0`。 | | `createdAt` | date | 是 | 首次创建时间。 | | `lastActiveAt` | date | 是 | 最近一次活跃时间(登录、打开小程序、写库等策略由实现约定)。 | **索引建议** | 索引键 | 类型 | 说明 | | --------- | ------------ | ---------------------------------------------------- | | `openid` | 唯一 | 登录与 upsert 主查。 | | `unionid` | 唯一(稀疏) | 仅当存在开放平台绑定且需跨端关联时使用;空值不互斥。 | --- ### 3.6 集合:`favorites`(收藏) 对应 Prisma `Favorite`。 | 字段 | 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\ | 是 | 能力目标描述(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)。