Files
doodle-mini/docs/技术架构设计文档.md
T

929 lines
50 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Doodle Mini - 技术架构设计文档
> 版本:v2.0
> 最后更新:2026-03-24
> 配套文档:[现有功能清单](./现有功能清单.md) | [产品设计文档](./产品设计文档.md)
---
## 一、技术决策总览
### 1.1 核心原则
| 原则 | 说明 |
|------|------|
| **成本优先** | 优先使用微信云开发,避免自建服务器的运维和费用 |
| **渐进增强** | 先小程序跑通,后续再扩展 PC Web,不为未来过度设计 |
| **前端为主** | Canvas 渲染、随机生成等核心逻辑保持前端执行,后端仅做必要的数据存储和服务 |
| **可迁移** | 核心绘制逻辑与平台 API 解耦,为将来迁移 Web 做准备 |
### 1.2 后端方案决策
#### 需要后端的场景 vs 不需要的场景
```
┌─────────────────────────────────────────────────────────┐
│ 不需要后端(前端完成) │
├─────────────────────────────────────────────────────────┤
│ ✅ Canvas 绘制所有题型 │
│ ✅ 随机题目生成算法 │
│ ✅ A4 排版与打印预览 │
│ ✅ PNG 导出 → 保存相册 │
│ ✅ 题型配置数据(可前端内置 JSON) │
│ ✅ 分享功能 │
│ ✅ 本地收藏/历史(wx.Storage
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 需要后端(轻量级) │
├─────────────────────────────────────────────────────────┤
│ 📦 内容配置远程下发(热更新题型/分类,无需发版) │
│ 📦 静态素材存储与 CDN 分发(涂色卡线稿、折纸模板等) │
│ 📦 用户数据同步(收藏、历史记录跨设备) │
│ 📦 下载统计 / 热度排名 │
│ 📦 PDF 多页合并导出 │
│ 📦 未来 PC Web 端的 API 服务 │
└─────────────────────────────────────────────────────────┘
```
#### 方案对比与选择
| 维度 | 微信云开发 | 自建云服务器 | 决策 |
|------|-----------|-------------|------|
| 接入成本 | ⭐ 极低(已有基础) | ⭐⭐⭐ 需搭建部署 | 云开发 ✅ |
| 运维成本 | ⭐ 免运维 | ⭐⭐⭐ 需监控/维护 | 云开发 ✅ |
| 费用 | 免费额度足够早期(数据库 2GB/存储 5GB/云函数 10万次/月) | 最低约 ¥50-100/月 | 云开发 ✅ |
| 小程序集成 | ⭐ 原生集成,鉴权零成本 | ⭐⭐ 需对接登录/鉴权 | 云开发 ✅ |
| PC Web 支持 | ⭐⭐ 需通过 HTTP API 桥接 | ⭐ 天然支持 | 自建 ✅ |
| 灵活性 | ⭐⭐ 受限于云开发 SDK | ⭐ 完全自由 | 自建 ✅ |
| 数据迁移 | ⭐⭐ 可导出但不方便 | ⭐ 标准数据库 | 自建 ✅ |
**最终决策:近期使用微信云开发,远期按需引入轻量后端**
```
Phase 1-3(当前~8周):100% 微信云开发
└── 云数据库 + 云存储 + 云函数,零服务器成本
Phase 4+(需要 PC Web 时):引入轻量后端
└── 方案 A(推荐):云开发 HTTP API + PC Web 直接调用
└── 方案 BCloudflare Workers / Vercel Serverless 做 API 代理层
└── 方案 C:轻量 Node.js 服务(仅在流量大时考虑)
```
---
## 二、系统架构总图
```
┌─────────────────────────────────────────────────────────────────────┐
│ 用户端 │
│ │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ 微信小程序 (当前) │ │ PC Web (未来) │ │
│ │ │ │ │ │
│ │ ┌────────────────┐ │ │ ┌────────────────┐ │ │
│ │ │ UI 层 (WXML) │ │ │ │ UI 层 (React) │ │ │
│ │ │ Skyline 渲染 │ │ │ │ Tailwind CSS │ │ │
│ │ └───────┬────────┘ │ │ └───────┬────────┘ │ │
│ │ │ │ │ │ │ │
│ │ ┌───────▼────────┐ │ │ ┌───────▼────────┐ │ │
│ │ │ 业务逻辑层 (TS) │ │ │ │ 业务逻辑层 (TS) │ │ │
│ │ │ Page + Mixin │ │ │ │ Hooks + Store │ │ │
│ │ └───────┬────────┘ │ │ └───────┬────────┘ │ │
│ │ │ │ │ │ │ │
│ │ ┌───────▼────────┐ │ │ ┌───────▼────────┐ │ │
│ │ │ 共享核心层 │◄─┼─────────┼──► 共享核心层 │ │ │
│ │ │ @doodle/core │ │ │ │ @doodle/core │ │ │
│ │ │ ┌─────────────┐ │ │ │ │ (同一套代码) │ │ │
│ │ │ │ DrawService │ │ │ │ └───────┬────────┘ │ │
│ │ │ │ DataModels │ │ │ │ │ │ │
│ │ │ │ Generators │ │ │ │ │ │ │
│ │ │ │ Utils │ │ │ │ │ │ │
│ │ │ └─────────────┘ │ │ │ │ │ │
│ │ └───────┬────────┘ │ │ │ │ │
│ │ │ │ │ │ │ │
│ │ ┌───────▼────────┐ │ │ ┌───────▼────────┐ │ │
│ │ │ 平台适配层 │ │ │ │ 平台适配层 │ │ │
│ │ │ wx.* API │ │ │ │ Web API │ │ │
│ │ │ wx.cloud.* │ │ │ │ HTTP Client │ │ │
│ │ │ Canvas 2D │ │ │ │ Canvas 2D │ │ │
│ │ └───────┬────────┘ │ │ └───────┬────────┘ │ │
│ └──────────┼──────────┘ └──────────┼──────────┘ │
└──────────────┼───────────────────────────────┼──────────────────┘
│ │
▼ ▼
┌─────────────────────────────────────────────────────────────────────┐
│ 后端服务层 │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 微信云开发 (Phase 1-3) │ │
│ │ │ │
│ │ ┌─────────────┐ ┌──────────┐ ┌────────────┐ │ │
│ │ │ 云数据库 │ │ 云存储 │ │ 云函数 │ │ │
│ │ │ (MongoDB) │ │ (COS) │ │ (Node.js) │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ • 用户数据 │ │ • 涂色卡 │ │ • getOpenId│ │ │
│ │ │ • 收藏记录 │ │ • 折纸图 │ │ • genPDF │ │ │
│ │ │ • 下载统计 │ │ • 手工图 │ │ • syncData │ │ │
│ │ │ • 内容配置 │ │ • 预览图 │ │ • stats │ │ │
│ │ │ • 反馈数据 │ │ • 字体 │ │ │ │ │
│ │ └─────────────┘ └──────────┘ └────────────┘ │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 轻量 API 层 (Phase 4+, 仅 PC Web) │ │
│ │ │ │
│ │ 方案 A: 云开发 HTTP API (推荐,零额外成本) │ │
│ │ 方案 B: Cloudflare Workers (极低成本) │ │
│ │ 方案 C: Vercel Serverless Functions │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ CDN (腾讯云) │ │
│ │ 素材图片 / 字体文件 / 预览图 / 分享图 │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
```
---
## 三、前端架构详细设计
### 3.1 现有架构问题
```
当前代码组织:
miniprogram/
├── pages/ ← 主包页面,各自直接引用 mixin
├── mathPages/ ← 数学分包,每个页面有自己的 Draw 服务
│ └── shared/ ← 数学共用(service、templates、assets
├── focusPages/ ← 专注力分包,结构同上
│ └── shared/
├── service/ ← 识字/练字的 Draw 服务
├── base/ ← pageMixin、callCloud 等基础层
├── components/ ← UI 组件
├── constants/ ← 静态配置数据
└── utils/ ← 工具函数
```
**问题分析:**
| 问题 | 详情 |
|------|------|
| Mixin 模式笨重 | `pageMixin` 通过对象展开合并,无类型安全,难以追踪数据流 |
| Draw 服务分散 | 每个分包下各有 `shared/service/`,大量重复的绘制工具方法 |
| 平台耦合严重 | Draw 服务直接使用 `wx.createImage``canvas.createImage` 等 API |
| 模板重复 | `mathPages``focusPages``canvas-page-template.wxml` 内容相同 |
| 无共享核心 | 数据模型、生成算法与 UI 逻辑混在一起,无法跨平台复用 |
| 状态管理原始 | 纯 `setData`,无统一的状态管理方案 |
### 3.2 目标架构
```
miniprogram/
├── core/ ← 🆕 共享核心层(平台无关)
│ ├── models/ ← 数据模型定义
│ │ ├── worksheet.ts ← WorksheetType 统一模型
│ │ ├── category.ts ← 分类模型
│ │ ├── user.ts ← 用户数据模型
│ │ └── print-config.ts ← 打印配置模型
│ ├── generators/ ← 题目生成算法(纯逻辑)
│ │ ├── math/ ← 数学题目生成器
│ │ │ ├── addition.ts
│ │ │ ├── subtraction.ts
│ │ │ ├── number-sequence.ts
│ │ │ └── ...
│ │ ├── focus/ ← 专注力题目生成器
│ │ ├── chinese/ ← 语文题目生成器
│ │ └── english/ ← 英语题目生成器
│ ├── draw/ ← 绘制服务(Canvas 2D API,平台无关)
│ │ ├── base-draw.ts ← 基础绘制(纸面、页眉、网格等)
│ │ ├── math-draw/ ← 数学绘制服务
│ │ ├── focus-draw/ ← 专注力绘制服务
│ │ ├── chinese-draw/ ← 语文绘制服务
│ │ ├── english-draw/ ← 英语绘制服务
│ │ └── craft-draw/ ← 手工/涂色绘制服务
│ ├── data/ ← 内容配置数据
│ │ ├── worksheets.ts ← 所有题型定义(本地兜底)
│ │ ├── categories.ts ← 分类定义
│ │ ├── words.ts ← 词库
│ │ └── difficulty.ts ← 难度与年龄映射
│ └── utils/ ← 纯工具函数(无平台依赖)
│ ├── random.ts
│ ├── math-utils.ts
│ └── format.ts
├── platform/ ← 🆕 平台适配层
│ ├── canvas-adapter.ts ← Canvas API 适配(wx ↔ Web
│ ├── storage-adapter.ts ← 存储适配(wxStorage ↔ localStorage
│ ├── cloud-adapter.ts ← 云服务适配(wx.cloud ↔ HTTP
│ ├── image-adapter.ts ← 图片加载适配
│ └── share-adapter.ts ← 分享能力适配
├── services/ ← 🆕 业务服务层
│ ├── worksheet-service.ts ← 题型数据加载(本地 + 远程兜底)
│ ├── user-service.ts ← 用户数据管理
│ ├── favorite-service.ts ← 收藏管理
│ ├── history-service.ts ← 下载历史管理
│ ├── print-service.ts ← 打印/导出统一入口
│ ├── stats-service.ts ← 统计上报
│ └── ad-service.ts ← 广告管理
├── store/ ← 🆕 状态管理
│ ├── app-store.ts ← 全局状态(用户/配置/主题)
│ └── page-store.ts ← 页面级状态工具
├── pages/ ← 重构后的主包页面
│ ├── home/ ← 发现首页(合并原 4 个 Tab 入口)
│ ├── category/ ← 分类详情页
│ ├── worksheet/ ← 统一的题型生成/预览页
│ ├── profile/ ← 我的
│ ├── guide/ ← 打印指南
│ ├── favorites/ ← 收藏列表
│ ├── history/ ← 下载历史
│ └── settings/ ← 设置
├── subpackages/ ← 🆕 按品类分包
│ ├── math/ ← 数学题型页面
│ ├── chinese/ ← 语文题型页面
│ ├── english/ ← 英语题型页面
│ ├── puzzle/ ← 益智游戏页面
│ └── craft/ ← 创意手工页面
├── components/ ← UI 组件
│ ├── shared/ ← 通用组件
│ │ ├── worksheet-card/ ← 题型卡片(带预览图)
│ │ ├── category-tabs/ ← 分类标签栏
│ │ ├── age-filter/ ← 年龄筛选器
│ │ ├── difficulty-badge/ ← 难度标签
│ │ ├── canvas-preview/ ← Canvas 预览区
│ │ ├── action-bar/ ← 底部操作栏(保存/分享/换一批)
│ │ ├── print-header/ ← 打印页眉配置
│ │ └── empty-state/ ← 空状态
│ └── business/ ← 业务组件
│ ├── worksheet-list/ ← 题型列表
│ ├── param-panel/ ← 参数设置面板
│ └── ...
├── config/ ← 配置
├── assets/ ← 静态资源
└── style/ ← 全局样式
```
### 3.3 分层职责
```
┌──────────────────────────────────────────────────────┐
│ UI 层 │
│ pages/ + components/ (WXML + WXSS + 页面 TS) │
│ 职责:界面渲染、用户交互、调用 services │
├──────────────────────────────────────────────────────┤
│ 业务服务层 │
│ services/ + store/ │
│ 职责:组合 core 能力,管理状态,对接平台 API │
├──────────────────────────────────────────────────────┤
│ 平台适配层 │
│ platform/ │
│ 职责:抹平 wx.* 和 Web API 差异 │
├──────────────────────────────────────────────────────┤
│ 共享核心层 │
│ core/ (纯 TypeScript,零平台依赖) │
│ 职责:绘制服务、生成算法、数据模型 │
│ 🔑 可直接在 Web 项目中 import 使用 │
└──────────────────────────────────────────────────────┘
```
---
## 四、共享核心层设计(@doodle/core
这是未来跨平台复用的关键。所有与 `wx.*` 无关的逻辑都应该放在这一层。
### 4.1 Canvas 适配器接口
```typescript
/**
* 平台无关的 Canvas 上下文接口
* 微信小程序和 Web 的 Canvas 2D API 基本一致,
* 但图片加载、字体加载等需要适配
*/
interface ICanvasAdapter {
getContext(): CanvasRenderingContext2D;
setSize(width: number, height: number): void;
loadImage(src: string): Promise<CanvasImageSource>;
toDataURL(type?: string, quality?: number): Promise<string>;
toBlob(): Promise<Blob>; // Web 专用
toTempFilePath(): Promise<string>; // 小程序专用
}
```
### 4.2 绘制服务基类(平台无关)
```typescript
abstract class BaseDrawService {
protected ctx: CanvasRenderingContext2D;
protected paperWidth: number;
protected paperHeight: number;
// 这些方法仅使用标准 Canvas 2D API,不依赖任何平台
protected drawGrid(rows: number, cols: number): void { /* ... */ }
protected drawText(text: string, x: number, y: number, options: TextOptions): void { /* ... */ }
protected drawLine(x1: number, y1: number, x2: number, y2: number): void { /* ... */ }
protected drawHeader(config: PrintConfig): void { /* ... */ }
protected drawRoundedRect(x: number, y: number, w: number, h: number, r: number): void { /* ... */ }
abstract draw(params: DrawParams): Promise<void>;
}
```
### 4.3 题目生成器模式
```typescript
/**
* 所有题目生成器实现此接口
* 输入配置参数,输出与平台无关的题目数据
* 绘制服务消费这些数据来渲染 Canvas
*/
interface IWorksheetGenerator<TConfig, TData> {
generate(config: TConfig): TData;
getDefaultConfig(): TConfig;
}
// 示例:加法题生成器
class AdditionGenerator implements IWorksheetGenerator<AdditionConfig, AdditionData> {
generate(config: AdditionConfig): AdditionData {
const questions = [];
for (let i = 0; i < config.count; i++) {
const a = randomInt(0, config.maxNumber);
const b = randomInt(0, config.maxNumber - a);
questions.push({ a, b, answer: a + b });
}
return { questions, config };
}
}
```
---
## 五、后端(云开发)详细设计
### 5.1 云数据库集合设计
```
云数据库 Collections:
┌─────────────────────────────────────────────────┐
│ │
│ worksheets (题型配置表) │
│ ┌─────────────────────────────────────┐ │
│ │ _id: string │ │
│ │ category: string │ │
│ │ subcategory: string │ │
│ │ title: string │ │
│ │ desc: string │ │
│ │ ageRange: [number, number] │ │
│ │ difficulty: 1|2|3|4 │ │
│ │ previewImage: string │ │
│ │ page: string │ │
│ │ tags: string[] │ │
│ │ isNew: boolean │ │
│ │ isHot: boolean │ │
│ │ sortOrder: number │ │
│ │ downloadCount: number │ │
│ │ status: 'active'|'draft'|'hidden' │ │
│ │ createdAt: Date │ │
│ │ updatedAt: Date │ │
│ └─────────────────────────────────────┘ │
│ │
│ categories (分类表) │
│ ┌─────────────────────────────────────┐ │
│ │ _id: string │ │
│ │ name: string │ │
│ │ icon: string │ │
│ │ color: string │ │
│ │ sortOrder: number │ │
│ │ parentId: string | null │ │
│ └─────────────────────────────────────┘ │
│ │
│ users (用户表) │
│ ┌─────────────────────────────────────┐ │
│ │ _id: string (openid) │ │
│ │ uuid: string │ │
│ │ nickName: string │ │
│ │ avatarUrl: string │ │
│ │ totalDownloads: number │ │
│ │ createdAt: Date │ │
│ │ lastActiveAt: Date │ │
│ └─────────────────────────────────────┘ │
│ │
│ favorites (收藏表) │
│ ┌─────────────────────────────────────┐ │
│ │ _id: string │ │
│ │ _openid: string │ │
│ │ worksheetId: string │ │
│ │ createdAt: Date │ │
│ └─────────────────────────────────────┘ │
│ │
│ download_logs (下载日志表) │
│ ┌─────────────────────────────────────┐ │
│ │ _id: string │ │
│ │ _openid: string │ │
│ │ worksheetId: string │ │
│ │ params: object (生成参数快照) │ │
│ │ createdAt: Date │ │
│ └─────────────────────────────────────┘ │
│ │
│ feedback (用户反馈表) │
│ ┌─────────────────────────────────────┐ │
│ │ _id: string │ │
│ │ _openid: string │ │
│ │ content: string │ │
│ │ contact: string │ │
│ │ createdAt: Date │ │
│ └─────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────┘
```
### 5.2 云函数设计
```
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 生成(Phase 4
└── common/
├── responseMiddleware.js ← ✅ 已有,保留
└── auth.js ← 🆕 鉴权中间件
```
### 5.3 云函数调用策略
```
┌────────────────────────────────────────────────────────────┐
│ 数据加载策略 │
│ │
│ 题型配置数据 (worksheets/categories) │
│ ┌────────────────────────────────────────┐ │
│ │ 优先级 1: 本地缓存(wx.Storage │ │
│ │ 优先级 2: 云数据库查询 │ │
│ │ 优先级 3: 前端内置兜底数据 │ ← 保证离线可用 │
│ └────────────────────────────────────────┘ │
│ │
│ 缓存策略: │
│ • 首次启动:云端拉取 → 写入本地缓存 │
│ • 后续启动:先用缓存渲染 → 后台静默更新 │
│ • 缓存有效期:24 小时 │
│ • 无网络:使用本地缓存或内置兜底 │
│ │
│ 用户数据 (favorites/history) │
│ ┌────────────────────────────────────────┐ │
│ │ 本地优先写入 → 后台同步云端 │ │
│ │ 冲突策略:以云端为准(云端时间戳更新) │ │
│ └────────────────────────────────────────┘ │
│ │
│ 统计数据 (download count) │
│ ┌────────────────────────────────────────┐ │
│ │ 批量上报:本地累计 → 退出时/定时上报 │ │
│ │ 非关键路径,允许丢失 │ │
│ └────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
```
### 5.4 云存储规划
```
云存储目录结构:
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
```
---
## 六、关键技术方案
### 6.1 Canvas 渲染架构(改进)
**现有问题**:预览区尺寸初始化和 A4 导出尺寸是两阶段,`setPaper()` 会重设 Canvas 尺寸。
**改进方案**
```
┌─────────────────────────────────────────────────────┐
│ Canvas 渲染管线 │
│ │
│ ① 逻辑坐标系统(所有 Draw 服务统一使用) │
│ 固定使用 A4 逻辑坐标: 595 × 842 │
│ 所有绘制操作都在这个坐标系下 │
│ │
│ ② 预览渲染 │
│ Canvas 物理尺寸 = 容器宽度 × dpr │
│ ctx.scale(容器宽度/595 × dpr, ...) │
│ → 绘制服务使用 A4 坐标 → 自动缩放到预览区 │
│ │
│ ③ 导出渲染 │
│ Canvas 物理尺寸 = 1240 × 1754 (150DPI A4) │
│ ctx.scale(1240/595, 1754/842) │
│ → 同一套绘制代码 → 导出高清图 │
│ │
│ 好处: │
│ • Draw 服务完全不感知预览/导出差异 │
│ • 同一套代码在小程序和 Web 上直接可用 │
│ • 预览和导出结果 100% 一致 │
└─────────────────────────────────────────────────────┘
```
### 6.2 图片加载适配
```typescript
// platform/image-adapter.ts
// 小程序实现
class WxImageAdapter implements IImageAdapter {
async loadImage(canvas: WechatMiniprogram.Canvas, src: string) {
const img = canvas.createImage();
return new Promise((resolve, reject) => {
img.onload = () => resolve(img);
img.onerror = reject;
img.src = src;
});
}
}
// Web 实现(未来)
class WebImageAdapter implements IImageAdapter {
async loadImage(_canvas: HTMLCanvasElement, src: string) {
const img = new Image();
img.crossOrigin = 'anonymous';
return new Promise((resolve, reject) => {
img.onload = () => resolve(img);
img.onerror = reject;
img.src = src;
});
}
}
```
### 6.3 PDF 导出方案(Phase 4
```
方案对比:
A. 云函数生成 PDF(推荐)
云函数接收绘制参数 JSON → 服务端用 pdfkit 生成 → 上传云存储 → 返回下载链接
优点:不受客户端限制,可生成多页
缺点:云函数有执行时间限制(60s),复杂图形渲染较慢
B. 客户端多页 Canvas 拼接
前端生成多张 PNG → 云函数用 pdfkit 合并为 PDF
优点:渲染在客户端,云函数只做拼接
缺点:需上传多张图片到云存储,流量开销大
C. Web 端直接使用 jsPDF
仅适用于 PC Web 版本
优点:零服务器成本
缺点:小程序不可用
推荐路径:
小程序:方案 B(客户端渲染 + 云端拼接)
PC Web:方案 CjsPDF 纯前端)
```
### 6.4 涂色卡/迷宫等素材型内容方案
```
┌──────────────────────────────────────────────────────┐
│ 两类内容的技术方案差异 │
│ │
│ 类型 A:算法生成型(现有主力) │
│ ┌──────────────────────────────────────┐ │
│ │ 前端算法随机生成题目数据 │ │
│ │ → DrawService 渲染到 Canvas │ │
│ │ → 导出图片 │ │
│ │ │ │
│ │ 适用:数学题、专注力题、字母练习等 │ │
│ │ 优点:无限变化、零存储成本 │ │
│ │ 开发方式:写 Generator + DrawService │ │
│ └──────────────────────────────────────┘ │
│ │
│ 类型 B:素材模板型(需扩展) │
│ ┌──────────────────────────────────────┐ │
│ │ 设计师制作 SVG/PNG 线稿 │ │
│ │ → 上传到云存储 │ │
│ │ → 前端加载 + Canvas 排版到 A4 │ │
│ │ → 导出图片 │ │
│ │ │ │
│ │ 适用:涂色卡、折纸、手工、贴纸等 │ │
│ │ 优点:品质高、风格可控 │ │
│ │ 开发方式:素材管理 + 通用排版渲染器 │ │
│ └──────────────────────────────────────┘ │
│ │
│ 类型 C:混合型 │
│ ┌──────────────────────────────────────┐ │
│ │ 算法生成结构 + 素材填充 │ │
│ │ │ │
│ │ 适用:迷宫(算法生路径+主题皮肤) │ │
│ │ 找不同(模板+随机差异点) │ │
│ │ 闪卡(模板+词库数据) │ │
│ └──────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘
```
---
## 七、PC Web 端技术预案
### 7.1 时机判断
**当前不急于开发 PC Web 的原因:**
- 小程序用户群与目标用户(家长)高度重合,微信即触达
- PC Web 需要额外解决支付、登录、SEO 等问题
- 先把小程序做好,积累内容和用户
**何时启动 PC Web**
- 小程序 DAU 稳定 > 1000
- 用户反馈中"想在电脑上用"的需求频繁出现
- 内容积累到 100+ 种题型
### 7.2 技术选型预案
```
PC Web 技术栈(未来):
┌────────────────────────────────────┐
│ 框架:Next.js (App Router) │
│ UITailwind CSS + Radix UI │
│ CanvasHTML5 Canvas 2D │
│ PDFjsPDF + html2canvas │
│ 状态:Zustand │
│ 部署:Vercel │
│ │
│ 核心复用: │
│ @doodle/core (共享核心层) │
│ └── 绘制服务、生成器、数据模型 │
│ → 直接 import 使用 │
│ │
│ 需新建: │
│ • Web UI 组件 │
│ • 平台适配层 (Web 实现) │
│ • 用户登录(微信扫码/手机号) │
│ • SEO 优化(SSR/SSG 题型页) │
└────────────────────────────────────┘
```
### 7.3 代码共享策略
```
仓库结构(Monorepo,未来演进):
doodle/
├── packages/
│ └── core/ ← 共享核心(npm 包)
│ ├── src/
│ │ ├── draw/
│ │ ├── generators/
│ │ ├── models/
│ │ └── utils/
│ ├── package.json
│ └── tsconfig.json
├── apps/
│ ├── mini/ ← 微信小程序
│ │ ├── miniprogram/
│ │ ├── cloudfunctions/
│ │ └── project.config.json
│ └── web/ ← PC Web(未来)
│ ├── src/
│ ├── package.json
│ └── next.config.js
├── pnpm-workspace.yaml
└── package.json
当前阶段不需要 Monorepo,只需在小程序项目中:
1. 将 core/ 作为目录组织代码
2. 确保 core/ 内不 import 任何 wx.* API
3. 未来迁移时,将 core/ 提取为独立 npm 包即可
```
---
## 八、前端重构路线图
### Phase 1:基础重构(第 1-2 周)
```
优先级:🔴 关键
目标:不改变现有功能,优化代码结构
1. 修复技术债务
├── 统一云函数名称(callCloud 中 robotaxi → doodle
├── 清理 cloudfunctions/doodle/index.js 中无效的模块引用
├── 移除 debug 页中指向不存在页面的死链
└── 清理未使用的代码和资源
2. 建立 core/ 目录
├── 将 constants/ 迁移到 core/data/
├── 将纯工具函数迁移到 core/utils/
├── 将 DrawService 基类提取到 core/draw/
└── 定义统一的 WorksheetType 数据模型
3. 建立 platform/ 目录
├── 抽取 canvas-adapter(封装 wx Canvas API
├── 抽取 storage-adapter(封装 wx.Storage
└── 抽取 image-adapter(封装图片加载)
4. 统一 WXML 模板
└── 合并 mathPages 和 focusPages 的重复 canvas-page-template
```
### Phase 2:首页重构 + 内容配置化(第 3-4 周)
```
优先级:🟡 重要
1. 数据模型升级
├── WorksheetType 增加 category/ageRange/difficulty/tags 等字段
├── 定义 Category 模型
└── 重构 MATH_FUNCTION_TYPES / FOCUS_FUNCTION_TYPES 为统一格式
2. 首页重构
├── 新建 pages/home/ 替代原四个 Tab 入口页
├── 实现分类标签栏 + 搜索 + 推荐区
├── 实现年龄筛选、难度筛选
└── 使用新的 worksheet-card 组件(带预览图)
3. TabBar 重构
└── 发现 | 分龄 | 收藏 | 我的
4. 云数据库初始化
├── 建表:worksheets、categories
├── 编写数据初始化脚本
└── 实现前端数据加载(缓存优先 + 云端更新)
```
### Phase 3:新内容接入(第 5-6 周)
```
优先级:🟡 重要
1. 开发通用素材渲染器
└── 通用的「加载图片 + A4 排版 + 导出」流程
2. 新增题型开发
├── 数学:时钟练习、形状分类
├── 语文:拼音练习、笔画练习
├── 英语:字母描红(复用 numberFind 逻辑)
├── 益智:控笔练习
└── 创意:涂色卡(素材型)
3. 完善新分包
├── english/ 分包
├── puzzle/ 分包
└── craft/ 分包
```
### Phase 4:体验与功能升级(第 7-8 周)
```
优先级:🟢 增强
1. 用户体系
├── 微信登录
├── 收藏功能(本地 + 云端同步)
└── 下载历史
2. 打印体验
├── 打印指南页面
├── 批量生成(多题一页/多页)
└── PDF 导出(云函数)
3. 运营能力
├── 数据埋点完善
├── 下载统计展示(热门排行)
└── 用户反馈入口
```
---
## 九、成本预估
### 9.1 微信云开发费用
```
免费额度(基础版 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 元/月即可 │
└────────────────────────────────────────┘
```
### 9.2 开发资源
```
开发投入估算:
┌──────────────────────────────┐
│ Phase 1: ~20 人时 │
│ Phase 2: ~40 人时 │
│ Phase 3: ~40 人时 │
│ Phase 4: ~30 人时 │
│ ────────────────────── │
│ 总计: ~130 人时 │
│ (1 人兼职 ~8 周可完成) │
└──────────────────────────────┘
```
---
## 十、技术风险与应对
| 风险 | 影响 | 应对方案 |
|------|------|---------|
| 云开发免费额度不够 | 功能受限 | ① 优化查询减少调用 ② 本地缓存减少读取 ③ 升级付费版(19.9元/月起) |
| Canvas 兼容性 | 低端机渲染异常 | ① 使用 Canvas 2D(已采用)② 控制单次绘制复杂度 ③ 降级方案 |
| 小程序包体积 | 超 2MB 限制 | ① 素材用 CDN / 云存储 ② 合理分包 ③ 图片压缩 |
| 素材制作瓶颈 | 涂色卡等需设计资源 | ① 使用开源 SVG 素材 ② AI 生成线稿 ③ 社区投稿 |
| 跨平台迁移成本 | Web 端重复开发 | ① core/ 层保持平台无关 ② 适配器模式隔离差异 |
| 云开发 HTTP API 限制 | PC Web 调用不便 | ① 使用云开发 HTTP API 触发器 ② 必要时引入轻量后端 |
---
## 附录 A:现有代码与目标架构映射
| 现有文件 | 目标位置 | 迁移动作 |
|---------|---------|---------|
| `constants/mathFunctions.ts` | `core/data/worksheets.ts` | 合并,增加字段 |
| `constants/focusFunctions.ts` | `core/data/worksheets.ts` | 合并,增加字段 |
| `constants/words.ts` | `core/data/words.ts` | 移动 |
| `constants/colors.ts` | `core/data/colors.ts` | 移动 |
| `constants/shapes.ts` | `core/data/shapes.ts` | 移动 |
| `service/baseDraw.ts` | `core/draw/base-draw.ts` | 重构,去除 wx.* 依赖 |
| `service/wordDrawService.ts` | `core/draw/chinese-draw/word-draw.ts` | 移动,去除 wx.* |
| `service/drawServiceFactory.ts` | `core/draw/draw-factory.ts` | 扩展 |
| `mathPages/shared/service/*.ts` | `core/draw/math-draw/*.ts` | 移动,去除 wx.* |
| `focusPages/shared/service/*.ts` | `core/draw/focus-draw/*.ts` | 移动,去除 wx.* |
| `base/pageMixin.ts` | `services/print-service.ts` + 页面代码 | 拆分职责 |
| `base/callCloud.ts` | `platform/cloud-adapter.ts` | 重构 |
| `utils/saveImage.ts` | `platform/canvas-adapter.ts` | 合并到适配器 |
| `utils/downloadPrint.ts` | `services/print-service.ts` | 整合 |
| `utils/tracker.ts` | `services/stats-service.ts` | 整合 |
| `config/config.ts` | `core/models/print-config.ts` | 类型化 |
## 附录 B:技术选型决策记录
| 决策点 | 选项 | 决策 | 理由 |
|--------|------|------|------|
| 跨端框架 | Taro / uni-app / 原生 | **原生** | 项目已用原生开发,迁移成本高;Taro 等框架对 Canvas 2D 支持有限;core/ 层抽取足以实现代码复用 |
| 状态管理 | MobX / 自定义 / 原生 setData | **轻量自定义** | 小程序场景简单,不需要 Redux 级方案;页面级 setData + 全局 Store 够用 |
| UI 组件库 | Vant / 自研 | **Vant + 自定义组件** | 已用 Vant,保持;业务组件自研 |
| 云开发 vs 自建 | 云开发 / 云服务器 | **云开发** | 零运维、免费额度充足、原生集成鉴权 |
| PDF 方案 | pdfkit(云) / jsPDF(端) | **两者结合** | 小程序用云端 pdfkitWeb 用 jsPDF |
| Monorepo | pnpm workspace / Turborepo | **暂不采用** | 当前只有小程序,过早引入增加复杂度;core/ 作为目录组织,未来再拆包 |