Files
doodle-mini/docs/Debug发布方案.md
T
2026-04-23 10:54:22 +08:00

23 KiB
Raw Blame History

Doodle Mini — Debug 模式内容发布方案

版本:v1.0 最后更新:2026-04-23 配套文档:技术架构设计文档 | 小程序云开发方案 | 产品设计文档


一、方案定位与动机

1.1 当前痛点

每新增一种可打印资料,从开发完成到上线需要经历以下手动步骤

开发页面 + Draw 逻辑
  → ① 在预览中截图或导出入口图原图
  → ② 放入 node-tools/entranceInput/<分类>/
  → ③ 运行 processEntrancePicture.js 裁剪缩放
  → ④ 从 entranceOuput/ 拷贝到 miniprogram/assets/entrancePicture/
  → ⑤ 手动编辑 category.data.ts 添加元数据(title、subtitle、path、img 等)
  → ⑥ 提交代码 → 审核 → 发版

其中 ①~⑤ 是重复性机械劳动,且容易出错(路径拼错、忘记更新数据文件、图片尺寸不一致等)。

1.2 核心洞察

当一个页面开发调试完成时,所有需要的信息已经就绪

  • 页面路径 — 就是当前正在调试的页面 URL
  • 标题 / 副标题 — 已写在绘制逻辑或配置中
  • 入口图片 — 预览 Canvas 上正在渲染的就是
  • 分类 / 难度 / 年龄段 / 标签 — 已在页面配置中定义

在这个时刻一键上传,是成本最低、数据最准确的做法。

1.3 方案目标

develop / devtools 环境的预览页中增加「发布到云端」按钮,点击后自动:

  1. 从 Canvas 导出入口图 → 裁剪 → 压缩
  2. 上传图片到云存储
  3. 组装页面元数据 → 写入云数据库 worksheets 集合
  4. 线上小程序通过缓存策略拉取云端数据,新内容无需发版即可上线

不需要开发独立后台系统,不需要手动录入数据。


二、整体流程

┌──────────────────────────────────────────────────────────────────┐
│                     Debug 模式发布流程                             │
│                                                                  │
│  ① 开发者在 develop / devtools 环境下完成页面开发                   │
│     (绘制逻辑调试完毕,预览效果满意)                               │
│                                                                  │
│  ② 预览区底部出现 [📤 发布到云端] 按钮                              │
│     (仅 develop / devtools 环境可见,线上版本不显示)               │
│                                                                  │
│  ③ 点击后弹出确认面板,预览即将上传的数据:                          │
│     ┌─────────────────────────────────────────┐                  │
│     │  ID: letter-tracing-daily-checkin        │                  │
│     │  标题: 每日打卡                           │                  │
│     │  副标题: 四宫格每日字母打卡练习            │                  │
│     │  分类: english                           │                  │
│     │  路径: /englishPages/letterTracing/...    │                  │
│     │  难度: beginner                          │                  │
│     │  年龄: 4-7岁                              │                  │
│     │  标签: [字母, 描红, 打卡]                  │                  │
│     │  [预览图缩略图]                            │                  │
│     │                                           │                  │
│     │  ─ 可编辑字段(允许微调) ─                │                  │
│     │                                           │                  │
│     │  状态: ○ draft(默认)  ○ active             │                  │
│     │                                           │                  │
│     │        [取消]         [确认发布]            │                  │
│     └─────────────────────────────────────────┘                  │
│                                                                  │
│  ④ 确认发布后,自动执行:                                          │
│     a. Canvas 导出 → 裁剪页眉页脚 → 压缩至 ≤200KB                 │
│     b. 上传图片到云存储                                             │
│        cloud://xxx/assets/previews/<category>/<id>.jpg            │
│     c. 组装 WorksheetConfig → upsert 到云数据库 worksheets 集合    │
│     d. 显示「发布成功 ✅」                                          │
│                                                                  │
│  ⑤ 线上小程序按缓存策略拉取新数据,内容自动出现                      │
│     (或在 debug 管理页手动将 status 从 draft 切为 active         │
└──────────────────────────────────────────────────────────────────┘

三、技术设计

3.1 环境门控

发布功能仅在开发环境可见,绝不暴露给普通用户

/**
 * 判断当前是否为开发环境,可执行 debug 发布操作。
 * 复用 downloadPrint.ts 中 isDevBypassLimits 的判断模式。
 */
function isDebugPublishEnabled(): boolean {
    const accountInfo = wx.getAccountInfoSync();
    const { envVersion } = accountInfo.miniProgram;
    // develop: 开发版;trial: 体验版;release: 正式版
    return envVersion === 'develop';
}

WXML 中条件渲染:

<!-- 仅开发环境显示发布按钮 -->
<view wx:if="{{isDevEnv}}" class="debug-publish-bar">
    <button bind:tap="onDebugPublish">📤 发布到云端</button>
</view>

3.2 页面元数据约定

每个 draw 页面需提供 getPublishMeta() 方法,返回标准化的发布元数据。

/**
 * 发布元数据接口
 * 各 draw 页面实现此接口,提供自身的配置信息
 */
interface PublishMeta {
    // ─── 必填字段 ───
    id: string;                                  // 唯一标识,如 'letter-tracing-daily-checkin'
    title: string;                               // 显示标题
    subtitle: string;                            // 显示副标题
    category: 'math' | 'chinese' | 'english' | 'puzzle' | 'craft';
    subcategory: string;                         // 子分类
    path: string;                                // 页面完整路径(含参数)

    // ─── 选填字段(有默认值)───
    icon?: string;                               // Emoji 图标
    ageRange?: [number, number];                 // 适用年龄,默认 [3, 8]
    difficulty?: 'beginner' | 'basic' | 'intermediate' | 'advanced';
    tags?: string[];                             // 搜索标签
    sortOrder?: number;                          // 排序权重
    status?: 'draft' | 'active' | 'hidden';     // 默认 'draft'

    // ─── 模板引擎相关(可选,未来扩展)───
    template?: string;
    generator?: string;
    generatorConfig?: Record<string, any>;
    layoutConfig?: Record<string, any>;
}

pageMixin 中约定调用方式:

// pageMixin 中新增
debugPublishMixin: {
    getPublishMeta(): PublishMeta {
        // 子类覆写此方法
        throw new Error('页面未实现 getPublishMeta()');
    }
}

各页面实现示例(letterTracing):

getPublishMeta(): PublishMeta {
    const drawService = this.drawService;
    return {
        id: this.data.currentId,
        title: drawService.title,
        subtitle: drawService.subtitle,
        category: 'english',
        subcategory: 'letter-tracing',
        path: `/englishPages/letterTracing/letterTracing?id=${this.data.currentId}`,
        icon: '🔠',
        ageRange: [4, 7],
        difficulty: 'beginner',
        tags: ['字母', '描红', '英语'],
    };
}

3.3 入口图导出与处理

复用已有的 preview-card 组件的 exportToTempFile 能力,再做裁剪处理。

/**
 * 从预览 Canvas 导出入口图
 * 
 * 处理流程:
 * 1. 从 preview-card 导出完整 A4 临时文件
 * 2. 用离屏 Canvas 裁剪掉页眉/页脚区域(与 processEntrancePicture.js 逻辑对齐)
 * 3. 缩放至入口图标准宽度(600px)
 * 4. 导出为 JPEGquality: 0.85
 */
async function exportEntranceImage(
    canvas: WechatMiniprogram.Canvas,
    ctx: CanvasRenderingContext2D,
    paperConfig: { headerHeight: number; footerHeight: number; width: number; height: number }
): Promise<string> {
    // 裁剪参数 — 与 node-tools/processEntrancePicture.js 保持一致
    const cropTop = paperConfig.headerHeight;
    const cropBottom = paperConfig.footerHeight;
    const sourceW = paperConfig.width;
    const sourceH = paperConfig.height - cropTop - cropBottom;

    const ENTRANCE_WIDTH = 600;
    const scale = ENTRANCE_WIDTH / sourceW;
    const targetH = Math.round(sourceH * scale);

    // 调整 Canvas 尺寸用于裁剪输出
    canvas.width = ENTRANCE_WIDTH;
    canvas.height = targetH;
    ctx.drawImage(
        canvas,                         // 自身作为源(需先 toDataURL 再 loadImage,实际实现需用临时文件中转)
        0, cropTop, sourceW, sourceH,   // 源区域:去掉页眉页脚
        0, 0, ENTRANCE_WIDTH, targetH   // 目标区域:缩放到标准宽度
    );

    const tempPath = await canvasToTempFilePath(canvas, {
        fileType: 'jpg',
        quality: 0.85,
        destWidth: ENTRANCE_WIDTH,
        destHeight: targetH,
    });

    return tempPath;
}

实际实现说明:小程序 Canvas 不能直接自引用 drawImage,需要先导出为临时文件(canvasToTempFilePath),再用 canvas.createImage() 加载临时文件,然后在清空的 Canvas 上绘制裁剪区域。具体实现参考 preview-card 已有的 exportToTempFile 方法。

3.4 云端上传

3.4.1 图片上传到云存储

async function uploadEntranceImage(
    tempFilePath: string,
    category: string,
    id: string
): Promise<string> {
    const cloudPath = `assets/previews/${category}/${id}.jpg`;
    const res = await wx.cloud.uploadFile({
        cloudPath,
        filePath: tempFilePath,
    });
    return res.fileID;  // cloud://doodle-xxx/assets/previews/english/letter-tracing-daily-checkin.jpg
}

3.4.2 元数据写入云数据库

async function publishWorksheet(meta: PublishMeta, imageFileID: string): Promise<void> {
    const db = wx.cloud.database();
    const collection = db.collection('worksheets');

    const doc = {
        ...meta,
        previewImage: imageFileID,
        status: meta.status || 'draft',
        publishedAt: db.serverDate(),
        updatedAt: db.serverDate(),
        version: 1,
        publishedBy: 'debug',          // 标记为 debug 发布
    };

    // upsert:如果已存在则更新,不存在则创建
    const existing = await collection.where({ id: meta.id }).get();
    if (existing.data.length > 0) {
        const oldDoc = existing.data[0];
        await collection.doc(oldDoc._id).update({
            data: {
                ...doc,
                version: (oldDoc.version || 0) + 1,
                updatedAt: db.serverDate(),
            },
        });
    } else {
        await collection.add({ data: doc });
    }
}

3.5 发布流程整合

/**
 * Debug 发布入口 — 挂载在 pageMixin 上
 * 由预览页的「发布到云端」按钮触发
 */
async function onDebugPublish(this: any): Promise<void> {
    if (!isDebugPublishEnabled()) return;

    // 1. 获取页面元数据
    const meta: PublishMeta = this.getPublishMeta();

    // 2. 弹出确认面板(展示即将发布的数据,允许微调)
    const confirmed = await showPublishConfirmDialog(meta);
    if (!confirmed) return;

    wx.showLoading({ title: '发布中...' });
    try {
        // 3. 导出入口图
        const tempPath = await exportEntranceImage(
            this.canvas, this.ctx, this.paperConfig
        );

        // 4. 上传图片到云存储
        const fileID = await uploadEntranceImage(
            tempPath, meta.category, meta.id
        );

        // 5. 写入云数据库
        await publishWorksheet(meta, fileID);

        wx.hideLoading();
        wx.showToast({ title: '发布成功 ✅', icon: 'success' });
    } catch (err) {
        wx.hideLoading();
        wx.showModal({
            title: '发布失败',
            content: JSON.stringify(err),
            showCancel: false,
        });
    }
}

四、云端数据与现有架构的衔接

4.1 数据加载优先级(保持不变)

技术架构设计文档 第 6.3 节、小程序云开发方案 第五节一致:

优先级 1: 本地缓存(wx.Storage
优先级 2: 云端拉取(worksheets 集合)    ← debug 发布的数据在此
优先级 3: 前端内置兜底(category.data.ts)← 保持稳定兜底

4.2 category.data.ts 的角色变化

阶段 category.data.ts 的作用
当前 唯一数据源(硬编码所有题型信息)
方案实施后 兜底数据源 + 离线保障(云端不可用时生效)
长期 通过 syncFromCloud 脚本自动同步,保持与云端一致

4.3 worksheets 集合字段映射

debug 发布写入的字段,与 小程序云开发方案 §3.3 worksheets 集合的字段完全对齐

PublishMeta 字段 worksheets 集合字段 说明
id id 业务唯一标识
title title 显示标题
subtitle desc 显示描述
category category 所属大类
subcategory subcategory 子分类
path 新增字段 pagePath 小程序页面路径
icon 新增字段 icon Emoji 图标
ageRange ageRange 适用年龄段
difficulty difficulty 难度级别
tags tags 搜索标签
sortOrder sortOrder 排列顺序
status status 上架状态
previewImage 云存储 fileID
publishedAt 发布时间
version 版本号(递增)

五、云存储目录规划

小程序云开发方案 §6 一致:

cloud://doodle-xxx/
├── assets/
│   └── previews/               ← debug 发布的入口图存放于此
│       ├── math/
│       │   ├── number-find.jpg
│       │   ├── addition-10.jpg
│       │   └── ...
│       ├── english/
│       │   ├── letter-tracing-single.jpg
│       │   ├── letter-tracing-daily-checkin.jpg
│       │   └── ...
│       ├── puzzle/
│       ├── chinese/
│       └── craft/

命名规则:<id>.jpg,与 PublishMeta.id 一致,便于查找和管理。


六、安全与权限控制

6.1 客户端门控

// 三重保障
const canPublish =
    isDebugPublishEnabled()          // ① envVersion === 'develop'
    && isDevBypassLimits()           // ② 与下载绕过逻辑一致
    && wx.getStorageSync('enableDebug'); // ③ debug 页面手动开启

6.2 云端安全规则

在云数据库安全规则中,worksheets 集合限制写入权限:

{
    "worksheets": {
        ".write": false,
        ".read": true
    }
}

debug 发布通过云函数中转,云函数内校验 openId 白名单:

// 云函数 debugPublish
exports.main = async (event, context) => {
    const { OPENID } = cloud.getWXContext();
    const ADMIN_OPENIDS = ['开发者的openid'];

    if (!ADMIN_OPENIDS.includes(OPENID)) {
        throw new Error('无权限执行此操作');
    }

    // 执行 upsert 操作...
};

6.3 数据保护

措施 说明
默认 draft 状态 发布后默认 status: 'draft',需手动激活为 active
版本递增 每次更新 version + 1,可追踪变更历史
publishedBy 标记 publishedBy: 'debug' 区分来源
时间戳 publishedAt / updatedAt 记录操作时间

七、辅助工具

7.1 syncFromCloud 脚本

node-tools/ 中新增脚本,发版前从云数据库拉取最新数据,同步回 category.data.ts 作为兜底数据。

Debug 发布 → 云端数据 → 线上可见
                ↓
         syncFromCloud.js         ← 发版前运行
                ↓
        category.data.ts 更新      ← 兜底数据同步
                ↓
           下次发版包含
// node-tools/src/syncFromCloud.js(伪代码)
// 通过云开发 HTTP API 或管理端 SDK 拉取 worksheets 集合
// 按 category 分组 → 生成 TypeScript 代码 → 写入 category.data.ts

7.2 Debug 管理页扩展

在已有的 supportPages/debug/debug 页面中增加 tab,提供简易内容管理:

┌─────────────────────────────────────────────┐
│  Debug 工具页                                │
│                                             │
│  [调试配置]  [已发布内容]  [系统信息]          │
│                                             │
│  ┌─────────────────────────────────────┐    │
│  │  已发布内容列表                       │    │
│  │                                     │    │
│  │  ● letter-tracing-single            │    │
│  │    状态: active  版本: 3  英语        │    │
│  │    [编辑] [下架]                      │    │
│  │                                     │    │
│  │  ● letter-tracing-daily-checkin     │    │
│  │    状态: draft   版本: 1  英语        │    │
│  │    [激活] [编辑] [删除]               │    │
│  │                                     │    │
│  │  ● addition-10                      │    │
│  │    状态: active  版本: 5  数学        │    │
│  │    [编辑] [下架]                      │    │
│  └─────────────────────────────────────┘    │
└─────────────────────────────────────────────┘

八、实施计划

Phase 1:基础发布能力(约 0.5~1 天)

  • pageMixin 中新增 getPublishMeta() 接口约定
  • 实现 isDebugPublishEnabled() 环境判断
  • 实现入口图导出 + 裁剪逻辑
  • 实现云存储上传 + 云数据库写入
  • preview-cardpreview-footer-actions 中添加发布按钮(条件渲染)

Phase 2:确认面板与安全(约 0.5 天)

  • 发布前确认弹窗(数据预览 + 可编辑字段)
  • 云函数安全校验(openId 白名单)
  • 默认 draft 状态 + 版本管理

Phase 3:数据消费侧适配(约 0.5~1 天)

  • category.ts(或 worksheet-service)增加从云 DB 拉取逻辑
  • 实现缓存策略(本地缓存 → 云端 → 内置兜底)
  • 入口图从云存储 fileID 获取临时 URL 展示

Phase 4:辅助工具(约 0.5 天)

  • syncFromCloud.js 脚本
  • Debug 管理页扩展(列表 + 状态管理)

总计预估:2~3 天


九、与模板引擎的衔接(远期)

技术架构设计文档 第五章所述模板引擎落地后,debug 发布方案可进一步升级:

┌─────────────────────────────────────────────────────────────────┐
│  模板引擎 + Debug 发布(远期形态)                                 │
│                                                                 │
│  ① 在通用 worksheet 页面中:                                      │
│     选择模板 + 配置生成器参数 → 实时预览                            │
│                                                                 │
│  ② 效果满意后点击「发布」:                                        │
│     自动上传:                                                    │
│     • JSON 配置(template + generator + generatorConfig + layout)│
│     • 入口预览图                                                  │
│     • 元数据(title、tags、ageRange 等)                           │
│                                                                 │
│  ③ 新题型无需任何前端代码改动即可上线                                │
│     (前提:使用已有的 TemplateRenderer + Generator 组合)          │
│                                                                 │
│  ④ 配合 80% 新题型可动态上线的目标                                  │
│     debug 发布成为主要的内容上线方式                                 │
└─────────────────────────────────────────────────────────────────┘

十、风险与应对

风险 影响 应对
误操作发布测试数据 污染线上列表 默认 status: 'draft';确认面板二次确认
入口图质量不一致 不同设备/DPR 下渲染差异 统一使用开发者工具发布;导出时固定 destWidth/destHeight
云端数据丢失 已发布内容消失 category.data.ts 兜底;syncFromCloud 定期同步
安全:非授权上传 恶意写入数据 云函数白名单校验;客户端三重门控
双数据源不一致 本地兜底与云端数据冲突 云端优先,发版前 syncFromCloud 对齐
云存储额度 图片累积占用存储 入口图约 50~200KB/张,100 张仅 ~20MB,远低于免费额度

附录 A:与现有代码的关系

现有模块 本方案的关联
preview-card 组件 复用 exportToTempFile,新增裁剪逻辑
pageMixin.ts 新增 getPublishMeta() 约定和 onDebugPublish() 方法
downloadPrint.ts / isDevBypassLimits() 复用环境判断模式
category.data.ts 角色从「唯一数据源」变为「兜底数据源」
supportPages/debug/debug 扩展内容管理 tab
node-tools/processEntrancePicture.js 裁剪参数对齐;流程被 debug 发布替代
云数据库 worksheets 集合 写入发布数据(字段与云开发方案对齐)
云存储 assets/previews/ 存放入口图