22 KiB
Doodle Mini — 小程序云开发方案
版本:v1.1 最后更新:2026-03-27 适用范围:与 技术架构设计文档 配套;当前阶段采用本文档所述微信云开发环境实现后端能力。 阶段截止:与总架构文档一致,有效期至 2026 年 9 月 15 日(到期前需复盘用量与成本,并决定是否迁移至 后端部署方案 所述自建服务)。
一、方案定位
微信云开发提供免运维的 Serverless 后端能力(云数据库、云存储、云函数),与小程序 wx.cloud 原生集成,接入与鉴权成本低,适合在阶段内快速落地题型配置下发、用户数据与素材存储。
与总架构文档的关系:
- 技术架构设计文档:描述前端分层、模板引擎、系统总览及后端分阶段策略(现阶段云开发 + 到期后可选 NestJS)。
- 本文档:仅展开云开发侧的库表、云函数、存储与调用策略等技术细节。
到期后选项(不在本文档展开):若免费额度不足或需更强多端能力,可迁移至 后端部署方案(NestJS + 自有服务器);迁移时需做数据导出与 cloud-adapter → http-adapter 的适配,详见技术架构文档中的「可迁移」原则。
二、与自建后端对比(决策参考)
| 维度 | 微信云开发 | 自建云服务器(NestJS) | 现阶段采用 |
|---|---|---|---|
| 接入成本 | ⭐ 极低(原生集成) | ⭐⭐ 需搭建部署 | 云开发 |
| 运维成本 | ⭐ 免运维 | ⭐⭐ 需简单维护 | 云开发 |
| 费用 | 免费额度内 ¥0;超出按套餐计费 | 已有服务器可零增量 | 云开发(阶段内) |
| 小程序集成 | ⭐ 鉴权与 OpenID 一体化 | 需自建登录与 JWT | 云开发 |
| PC Web 支持 | 需 HTTP 云函数或桥接 | RESTful 天然支持 | 远期看 NestJS |
| 灵活性 | 受云开发 SDK 与配额约束 | 完全自主 | 远期看 NestJS |
| 数据迁移 | 可导出,迁移需工作量 | 标准 MySQL,自主备份 | 远期看 NestJS |
三、云数据库集合设计
本节与 后端部署方案 中的 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时自定义为字符串(例如与 后端部署方案 一致的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 字符。 |
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<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 前:结合控制台用量与业务目标,决定续用云开发、升级套餐或启动迁移至 后端部署方案。