feat:更新core 和首页代码

This commit is contained in:
R524809
2026-03-27 17:31:52 +08:00
parent dc5e057cb3
commit 1b45ba7d11
44 changed files with 2891 additions and 1087 deletions
+348
View File
@@ -0,0 +1,348 @@
# 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 SchemaMySQL** 字段语义对齐,便于阶段结束后迁移;云数据库为 **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\<string\> | 是 | 标签列表。 |
| `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\<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 / subcategoryworksheet 内嵌字符串,非 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/月 │
│ CDN5 GB/月 │
└────────────────────────────────────────┘
预估用量(DAU 1000):
┌────────────────────────────────────────┐
│ 云数据库:~100 MB(足够) │
│ 云存储:~2 GB(素材渐增) │
│ 云函数:~3 万次/月 │
│ CDN~3 GB/月 │
│ │
│ 结论:免费额度可覆盖到 DAU 3000 左右 │
│ 超出后需升级套餐(如 19.9 元/月起) │
└────────────────────────────────────────┘
```
**建议在 2026-09-15 前**:结合控制台用量与业务目标,决定续用云开发、升级套餐或启动迁移至 [后端部署方案](./后端部署方案.md)。