Files
doodle-store/docs/云服务端迁移方案.md
2026-10-15 10:09:34 +08:00

53 KiB
Raw Permalink Blame History

Doodle Mini 现有功能迁移到云服务端方案

决策:已确认迁移到自有腾讯云服务器;数据库使用 PostgreSQL。

文档归属:本方案在 doodle-store/docs 中维护,作为迁移实施的唯一方案文档;小程序产品规划与历史设计仍保留在 doodle-mini。

工程组织:小程序继续在独立项目 doodle-mini 中维护;新项目 doodle-store 使用 pnpm workspace 管理服务端、管理后台和 Web,不包含小程序源码。

实施状态:已完成基础工程初始化;业务功能、数据迁移与上线尚未实施。本文不是迁移完成报告。

更新日期:2026-10-12。以 doodle-mini 当前仓库代码为迁移依据,线上数据量、文件清单及服务器环境需在实施第一步核实。

1. 目标与范围

本次将现有小程序的业务后端、数据、文件及定时任务迁出微信 CloudBase,形成 一套业务 API、一套主数据库、一套文件存储。后续 Web 直接复用这套后端。

采用轻量模块化单体,不建立微服务、数据库集群或复杂运维平台。AI 辅助接口、脚本、适配和部署配置开发,人工及实际运行验证负责确认行为、数据和切换结果。

1.1 本次交付

  • 微信静默登录、用户资料、清除资料和注销账户。
  • 题型与分类查询、分龄推荐、首页配置、手动及自动推荐刷新。
  • 收藏、下载历史、个人数量统计、题型下载/收藏计数。
  • 现有题型发布、上下架、分类管理、批量属性同步等管理能力。
  • 头像、预览图、云字体和首页预拉取配置的迁移与访问。
  • 小程序 HTTP 接入、短期旧版本兼容、数据迁移、部署、备份、验收与回滚。

1.2 后续单独实施

  • 独立管理后台 doodle-store/apps/admin 的完整页面;本次先迁移管理 API,并保留小程序现有管理操作,不以新后台上线作为切换前置条件。
  • 完整 Web 网站、SEO 内容页和 Web 登录/账户绑定。
  • 将存量 Canvas 题型全部改为服务端生成。
  • 创意涂色、批量 PDF、会员、支付、孩子档案和学习包等新功能。

本次迁移后,现有随机出题、Canvas 绘制、相册保存、微信分享、广告解锁及本地下载配额仍按小程序现有方式工作。迁移后端并不会自动隐藏已经随小程序发布的生成代码。未来 Web 的私有生成器另行接入同一后端。

2. 已知条件与实施前核实项

项目 当前已知条件 方案处理
云服务器 腾讯云,地区可能为华东;2 核、4 GB 内存、60 GB 系统盘 作为初期 API 与数据库部署目标;实施前检查现有业务和可用资源
服务器期限 已付费至 2027 年 10 月,计划长期保留 不将整台服务器续费全部归于本项目,但记录实际资源占用
流量 每月 500 GB 用于 API 和经服务器上传的流量;七牛直出文件单独计费
域名 已持有长期域名 实施前确认具体 API/资源子域名、备案、DNS 和 HTTPS
文件存储 计划使用七牛 新文件统一进入七牛;存储、请求和下载费用按实际用量核算
CloudBase 当前 19.9 元/月 过渡期保留;完全退出兼容函数与旧文件依赖后再停止套餐
后端规模 17 个云函数,入口 JS 合计约 1,875 行 主要是查询和 CRUD,适合 AI 辅助迁移;行数不等于全部迁移成本

实施前应取得:服务器系统/现有服务/可用内存与磁盘/峰值带宽;域名备案和小程序后台配置权限;现有 AppID 对应的 AppSecret;CloudBase 导出与文件访问权限;七牛空间、区域和 HTTPS 资源域名。密钥通过环境变量配置,不写进文档或仓库。

这些信息用于实施,不影响现在确定架构。备案、审核、DNS 和文件传输等待时间与实际开发工时分开记录。

3. 目标架构与技术选择

flowchart TD
    Mini[新版微信小程序 · doodle-mini] -->|HTTPS| Nginx[Nginx]
    Web[后续 Web · doodle-store] -->|HTTPS| Nginx
    Admin[后续管理后台 · doodle-store] -->|HTTPS| Nginx
    Old[旧版小程序] --> Bridge[过渡期 CloudBase 兼容函数]
    Bridge -->|签名请求| Nginx
    Nginx --> API[统一业务 API · NestJS]
    API --> DB[(PostgreSQL · 唯一主数据库)]
    API --> Files[七牛 · 文件存储]
    API --> Wx[微信登录接口]
    Mini -->|图片和字体访问| Files
    Scheduler[应用内定时任务] --> API
    Render[后续私有生成模块] -.-> API

CloudBase 兼容函数只转发旧协议,不保留独立业务规则;切换后不再写旧数据库。未来 Web 的页面渲染与练习纸绘制使用同一后端数据,生成任务增长后可拆为独立进程,不需要再建一套业务系统。

层级 首期选择 选择理由与限制
服务端 TypeScript + NestJS,单应用 延续仓库技术栈和已有后端预案;按模块组织现有业务
工程管理 独立 doodle-store 项目 + pnpm workspace Monorepo 管理 apps/server、后续 apps/admin / apps/web 和必要共享包;doodle-mini 不加入工作区
数据库 PostgreSQL + Prisma 用唯一键和事务保证收藏/下载一致性;版本与 ORM 兼容性在实施时验证
运行时 受维护的 Node.js LTS 实施时锁定具体版本及依赖,不沿用旧文档的版本号
文件 七牛对象存储,HTTPS 自定义资源域名 与数据库分离;数据库保存对象引用,避免保存会过期的签名 URL
部署 Docker Compose 管理 API 与 PostgreSQL,Nginx 反向代理 固定镜像版本、持久化数据库卷;已有 Nginx 可复用,不覆盖其他业务
定时任务 单实例应用内调度 + 数据库执行记录/锁 任务少,不引入 Redis/队列;控制重复执行及手动刷新竞争
日志与监测 结构化日志、健康检查、错误与任务失败通知 覆盖基本排障,不额外部署大型监控平台

2 核 4 GB 可作为起步配置,但承载能力以实际资源检查和联调结果为准。先部署 API 与数据库;服务端绘制的内存/CPU 需求在后续样板中单独测量。数据库不开放公网端口。

3.1 两个独立项目的职责

  • doodle-mini:继续维护现有微信小程序、微信平台适配、原管理页面及过渡期 CloudBase 兼容函数;HTTP 接入改造也在此项目完成。保持现有小程序目录和开发工具配置,不搬入新项目。
  • doodle-store:独立项目位于 /Users/joey-xd/sites/doodle/doodle-store,采用 pnpm workspace Monorepo;apps/server 承载统一业务 API,后续 apps/admin 和 apps/web 分别提供管理后台与用户网站。当前只完成基础工程与健康检查,尚无业务 API。
  • 两个项目通过有版本的 HTTPS API 协作,独立管理依赖、版本与发布;不使用跨仓库源码导入、相对路径引用或本地软链接作为正式依赖。
  • Monorepo 只用于代码、依赖与开发任务管理,不改变“一套业务 API、一套主数据库”的目标,也不要求使用 NestJS 自身的多应用 Monorepo 模式。

3.2 doodle-store 计划目录与工作区

已初始化 pnpm 工作区、apps/server 的 NestJS 骨架和 packages/contracts 的基础响应/健康检查类型;apps/admin、apps/web、Prisma、迁移脚本与部署目录目前仅有用途说明。以下为目标结构,业务模块、数据库接入、完整前端和部署配置尚未实现,不为凑齐目录提前开发完整应用。

doodle-store/
  apps/
    server/                     # 首期统一业务 API
      src/
      prisma/                   # 数据模型与版本化迁移,仅服务端访问
      scripts/                  # 数据/文件导入、审计与恢复
      package.json
      .env.example              # 变量名示例,不包含真实密钥
    admin/                      # 后续独立管理后台
    web/                        # 后续用户网站与 SEO 页面
  packages/
    contracts/                  # 必要的公开请求/响应类型及枚举
  deploy/                       # Compose、Nginx、备份配置
  docs/                         # 新系统接口与部署文档
  pnpm-workspace.yaml
  pnpm-lock.yaml
  package.json

doodle-store/pnpm-workspace.yaml:

packages:
    - 'apps/*'
    - 'packages/*'
  • 每个应用与共享包有独立 package.json,声明自身依赖;根项目设为 private: true,主要负责工作区脚本和公共开发工具,不集中安装所有应用的运行依赖。
  • doodle-store 使用单一 pnpm-lock.yaml,锁定 packageManager 和 Node.js 版本;CI 使用冻结锁文件安装。doodle-mini 保持自身依赖和构建流程,不因新项目使用 pnpm 而强制切换。
  • 工作区内部包使用 workspace:* 引用,不要求发布到 npm;packages/contracts 仅用于新项目内部共享公开契约,不包含数据库模型、密钥或私有生成器实现。
  • 使用 pnpm 的筛选与递归脚本执行构建、检查、测试;应用构建需包含其依赖共享包的构建步骤。先采用 pnpm 原生能力,不默认引入 Turborepo、Nx、私有包仓库或复杂发布平台。
  • TypeScript 基础配置可以复用,但 Node.js、浏览器和 SSR 应用保留各自运行环境配置;UI、SDK 等共享包仅在出现真实复用需求后提取,不建设无边界的通用 shared 包。

doodle-store/apps/server 内部目标结构(当前仅实现启动配置与健康检查,业务模块待开发):

apps/server/                    # 以下路径相对于 doodle-store
  src/
    health/                     # 已实现:仅 HTTP 进程存活检查
    modules/                    # 以下业务模块尚未实现,当前仅用途说明
      auth/                     # 微信登录、用户身份、管理权限
      users/                    # 用户资料与注销
      worksheets/               # 题型、上下架和属性同步
      categories/               # 分类
      favorites/                # 收藏与计数
      downloads/                # 成功保存记录、历史与计数
      content/                  # 首页配置、推荐和发布
      assets/                   # 文件上传、引用和访问地址
      legacy/                   # 旧云函数协议适配,过渡后移除
    common/                     # 校验、错误格式、日志
  prisma/                       # 数据结构、版本化迁移
  scripts/                      # 导入、文件迁移、审计与恢复
  package.json                  # 服务端独立依赖和构建脚本
  .env.example                  # 变量名示例,不包含真实密钥

3.3 跨项目契约、发布与代码边界

  • 公共 API 契约以 doodle-store 为权威来源,提供版本化接口文档/OpenAPI。小程序不能直接使用另一个仓库的 workspace:* 包;首期可将按契约生成或核对的必要类型同步到 doodle-mini,记录来源版本,并由小程序服务层继续使用 wx.request / wx.uploadFile。
  • 公开契约变化时,分别核对 doodle-mini、apps/admin、apps/web 的适配与测试结果。新增兼容字段优先;字段删除、更名或语义变化须保留兼容期,不能假设两个项目或全部客户端同时升级。
  • apps/server、apps/admin、apps/web 独立构建和部署。后台若采用 SPA,可发布静态文件;Web 是否采用 SSR/预渲染另行选择。共享包变化时检查所有受影响应用,但不要求所有应用一起发布。
  • 数据库结构、访问凭证、管理权限判断、未来私有生成/绘制实现仅由服务端持有。管理后台和 Web 通过 API 访问,不直接导入 Prisma Client 或服务端源码;前端包也不得间接依赖这些私有模块。private: true 不是防止代码被前端打包的安全边界。
  • 服务端和各前端分别管理环境变量,真实密钥不提交仓库、不写入前端可公开配置。生产仅部署目标应用及必要依赖,不在 2 核 4 GB 服务器上长期运行整套开发模式或无关应用。

4. 现有功能与云函数迁移清单

以下 API 是目标契约,实施时生成接口文档。统一前缀为 /api/v1;表中使用省略前缀的路径。用户接口从可信登录态取得 userId,不接受客户端指定身份。管理接口一律由服务端校验管理员权限。

当前云函数 当前功能 迁移目标 关键要求
userLogin 静默登录、更新昵称头像 POST /auth/wechat/login、GET /me、PATCH /me/profile 原用户 ID 不变;AppSecret/session_key 不下发
userSettings 清除资料、注销 DELETE /me/profile、DELETE /me 清资料保留收藏/历史;注销落实页面承诺的关联数据删除
userFavorites 添加/取消/列表/数量/单张检查/批量检查 PUT/DELETE /me/favorites/:worksheetId、GET /me/favorites、GET /me/favorites/count、POST /me/favorites/check 收藏关系唯一;真实增删与题型计数同一事务
userDownloadLogs 添加成功保存记录、列表、数量 POST /me/downloads、GET /me/downloads、GET /me/downloads/count 新版必须传 exportId;只在实际保存成功后上报
worksheetsQuery 题型查询、状态筛选、分类状态数量、展示计数 GET /worksheets、GET /worksheets/:id、GET /admin/worksheets 普通用户只看 active;管理查询支持全部状态和原始计数
worksheetsPublish 按固定 ID 新增或更新题型 PUT /admin/worksheets/:id 保留发布能力;普通内容更新不重置累计计数
worksheetsUpdateStatus draft/active/hidden 切换 PATCH /admin/worksheets/:id/status 状态变更后刷新受影响首页展示
worksheetsBatchPatch 批量年龄、年级、标签、种子及计数同步 POST /admin/worksheets/batch-patch 保留 dryRun、逐项结果和错误;计数修订单独审计
worksheetsStatsUpdate 直接递增 likes/downloads 不向普通用户提供通用加数接口;由收藏/下载服务承接 当前仓库工具无调用方;旧协议仅在确认必要时适配,不能绕过幂等
categoriesQuery 分类排序查询 GET /categories 保留 ID、父子关系、图标、颜色和排序
categoriesCreate 创建分类 POST /admin/categories ID 唯一;校验父分类和循环引用
categoriesUpdate 更新分类 PUT /admin/categories/:id 不修改已有主键;分类关系可审计
ageRecommendedQuery 5 个年龄段推荐 GET /recommendations/age?ageKey=3-4 保留筛选、Top3 下载 + Top3 收藏、去重及不足补齐规则
pageConfigFetch 读取 current 页面配置 GET /page-config 保留 home/category/age、version、updatedAt 和 previewImg
pageContentBuild 手动首页配置、栏目与展示重建 POST /admin/page-config/build、GET /admin/page-config 保留 featuredIds/hotIds/sections 与现有 page 分支语义
homeAutoRefresh 自动更新首页下载/收藏榜 服务端任务、POST /admin/home/refresh 保留各 Top5 和其他首页配置;启停、时区、重复执行可核对
homeTimerControl 开关自动推荐 GET/PATCH /admin/home/auto-refresh 数据从新数据库读写,替代管理页数据库直读

4.1 不依赖云函数名称也必须迁移的部分

本节代码位置均属于 doodle-mini;接入改造留在原项目,新业务实现位于 doodle-store/apps/server。

当前依赖 代码位置 迁移处理
云初始化、静默登录、首页配置加载 miniprogram/app.ts 改为 API 配置与登录;预拉取、缓存、兜底保持可用
云调用与资料缓存 miniprogram/utils/auth.ts wx.login + HTTP;资料缓存与有效登录态分开管理
收藏/下载业务包装 miniprogram/utils/favorites.ts、downloadLogs.ts 替换内部实现,保留页面使用方式和统计触发时机
分类与分龄直接调用 pages/category/category.ts、pages/age/age.ts 接入业务服务层,保持排序/推荐/路由与字段兼容
头像上传 pages/profile/profile.ts 替换 wx.cloud.uploadFile,存入七牛
预览图上传与题型发布 base/pageMixin.ts API 上传 + 管理发布;开发环境隐藏入口不等于管理授权
云文件字体地址 core/font/fontLoader.ts、fontProfiles.ts 改成 HTTPS 文件引用;继续使用 wx.loadFontFace 并验证字形
内置云预览地址 pages/home/home.data.ts 迁移并替换 cloud://;兼顾本地兜底数据
管理页云函数调用 supportPages/categoryManage、categoryContentManage、homeContentManage、worksheetSync、settings 接入管理/用户 API,逐页核对
管理页直读 settings supportPages/homeContentManage/homeContentManage.ts 改为管理 API;仅换云函数封装无法覆盖此处
旧适配器与 HTTP 工具 platform/cloud-adapter.ts、utils/http.ts 逐调用点收口;前者目前指向 doodle,不能假设全站均经它调用
小程序后台预拉取 URL 仓库之外的微信后台配置 改为新公共配置地址,关闭调试绕过后验证
云开发定时触发配置 cloudfunctions/homeAutoRefresh/config.json 与控制台 核实实际执行时间/时区,迁入调度,切换时停旧任务

wx.reportEvent 是微信统计能力,不依赖业务 CloudBase 数据库,无需为了迁移而更换。分享、广告和相册权限也继续使用微信能力。

5. API、身份和统计约定

5.1 新接口与兼容格式

新 API 使用统一响应,例如:

{
    "code": 0,
    "message": "ok",
    "data": {},
    "requestId": "request-id"
}
  • HTTP 状态码区分参数错误、未登录、无权限、冲突、服务异常;失败不包装成成功的空列表。
  • 列表提供分页和总量。当前一次最多取 100 条的实现不能成为数据库迁移后的隐形上限;小程序服务层负责拉完所需内容或采用分页展示。
  • 新服务内部使用 id;小程序 DTO/兼容桥继续提供旧 _id、path、previewImg。数据库路径字段可命名 miniProgramPath,Web 路径后续另做映射。
  • 云函数目前存在 {code:0,data} 与 {success:true,data,stats} 两种协议,由业务服务层/兼容桥转换,不要求所有页面同时改写数据形状。
  • previewImg 是唯一预览字段;公共 DTO 不包含生成器私有配置、用户身份密钥或未发布内容。

5.2 微信登录与用户延续

  1. 小程序调用 wx.login() 获取一次性 code。
  2. 服务器用配置的 AppID/AppSecret 调用微信 code2Session,取得可信 OpenID,UnionID 可为空。
  3. 使用配置的单一小程序 AppID 验证登录,按可信 OpenID 查找迁入用户,沿用原 users._id;确实不存在时才新建。
  4. 签发有期限的应用登录凭证。首期可用 JWT,建议初始有效期 24 小时;过期后通过微信静默登录续用,暂不增加独立刷新令牌体系。
  5. 每次用户请求核对账户有效状态;角色从服务端配置/数据取得。注销立即使旧凭证失效。

客户端缓存的 user 对象、OpenID、UUID 都不能作为可信登录凭证。请求遇到 401 时合并并发登录,成功后最多重试一次;写请求需具备幂等性。当前 7 天资料缓存不能替代 API 身份验证。

Web 用户不会凭现有小程序 OpenID 自动变成同一账户。跨端身份绑定作为后续业务设计,当前保留稳定 userId 和可选 UnionID,不自动合并用户。

管理权限首期采用明确管理员名单或角色字段,默认不授权。发布、上下架、批量修订和定时任务配置必须服务端验证;不以客户端 envVersion、按钮隐藏或自报角色为依据。

5.3 成功保存与收藏计数

  • 保存成功的判定继续是 wx.saveImageToPhotosAlbum 成功回调;点击保存、图片生成完成、拿到文件、接口返回 200 都不是成功保存。
  • 保存成功后递增本地配额、记录 export_success,用同一个 exportId 向 API 写下载记录。
  • 数据库对 (userId, exportId) 建唯一键,下载记录和 worksheets.downloads + 1 在同一事务内完成。重试只返回已有结果,不再次加数。
  • API 上报失败不能把已保存的图片判定为保存失败。新版可用小型本地补报队列重试同一个 exportId;补报不再次递增本地配额或发送 export_success。队列绑定原 userId,换账户不代报,注销时清除。
  • 服务端接收的是客户端成功回报,无法独立证明图片已进入手机相册;本次不把该回报设计成支付凭据。
  • 收藏统一保存功能和内容目标,按资源 ID 是否为空分别去重。只有功能收藏的真实增删才更新 likes;内容收藏不增减功能计数。旧接口限定资源 ID 为 NULL,重复请求不增减;注销清理两种关系但只扣功能贡献。
  • 展示计数与真实计数分开:展示可沿用真实值 + likes_seed/downloads_seed,分析和管理原始查询不加 seed。迁移期间不改变已有推荐的种子参与规则。
  • 常规重新发布题型不能将真实计数重置为 0。显式统计修订保留管理员入口,但记录操作者及前后值。

旧版缺少 exportId 的保存请求不能凭题型 ID 或几秒时间窗口去重,这会误删用户真实多次保存。兼容桥按每次收到的旧请求生成事件 ID,内部转发重试复用该 ID;接受无法跨独立旧请求精确去重的限制,并监测其调用量。

6. 数据迁移

环境安排见 v3.5 开发与部署环境方案:本机 Docker PostgreSQL 用于开发,图片等直接使用七牛;云服务器首期仅常驻生产库。试导先在本机隔离库进行,真实微信联调时按需创建同实例独立 staging 库及测试 API,不长期部署两套数据库服务,测试不写生产库。

数据库详细设计见 v3.5 数据库设计文档,使用 PostgreSQL + Prisma,覆盖以下集合及辅助表。

6.1 集合映射与主键

代码实际使用 7 个核心集合。历史设计文档中的 favorites_log 与当前代码不一致,应迁移实际 favorites;feedback、learning_plans 等只出现在设计中的集合,先盘点线上是否存在,不默认当成已上线功能。

CloudBase 集合 新表 迁移要求
users users _id → id 原值保留;OpenID、可选 UnionID、资料、创建/活跃时间及旧字段保留
categories categories ID、parentId、图标、颜色、排序保留
worksheets worksheets ID、路由、年龄、grade、难度、tags、状态、预览、真实/seed 计数及各时间字段保留
favorites favorites 用户/题型关联不变;重复关系按审计规则归并
download_logs download_logs ID、用户/题型关联、时间、可选 exportId 保留;历史缺 exportId 可为空
page_configs page_configs current ID、JSON 结构、版本、时间保留;所有嵌套文件引用转换
settings settings 包括 homeAutoRefresh.enabled,先盘点全部线上配置

新增必要辅助表:文件迁移/引用表 assets、任务执行记录 job_runs、管理审计记录 admin_audit_logs。它们用于迁移与运行,不代表已有业务集合。另新增 worksheet_resources 和 worksheet_resource_assets,用于一份内容关联多份线框/参考素材,再新增 worksheet_grades 支持多适用年级,共 13 张表。categories 继续管理内容分类,年级菜单组合查询 worksheet_grades;旧 grade 仅保留兼容,需审核后写入年级映射,不按年龄自动回填。功能与内容分别记录首次发布时间及下载量;新 isNew 按时间派生,功能 isHot 保留为运营推荐,旧累计下载不分摊资源。worksheet 增加生成方式、renderer_key 和共用配置;现有 ID/path/previewImg 保持兼容,下载和收藏增加可空的内容条目 ID;旧收藏保持功能目标,新内容收藏不影响功能 likes。详见 Worksheet 数据库设计说明。

主键采用足够长度的字符串,保留大小写语义,不重新生成所有 ID。业务字段用结构化列,tags/页面配置/扩展配置用 JSON;导入前保留完整原始导出,未识别字段进入迁移报告,不能默默丢弃。

主要索引:用户 openid 唯一(首期仅接入一个小程序,AppID 由服务端配置);收藏以内容 ID 是否为空区分功能/内容,分别采用 (userId, worksheetId) WHERE worksheetResourceId IS NULL 与 (userId, worksheetResourceId) WHERE worksheetResourceId IS NOT NULL 部分唯一索引,保留 (userId, createdAt) 列表索引;下载 (userId, exportId) 唯一及用户/题型时间索引;题型分类/状态/排序索引。空 exportId 规范为 NULL,历史多条无 ID 下载不得互相冲突。

资源收藏使用统一 favorites;新增 PUT/DELETE /me/favorites/resources/:worksheetResourceId,旧按 worksheetId 的路由及 CloudBase 桥只操作资源 ID 为 NULL 的功能关系。列表/数量显式 target=worksheet/resource/all,默认 worksheet;资源收藏的独立预览及跳转由新版客户端接入。注销删除两种收藏,仅功能收藏扣减 likes。

6.2 导入与审计规则

  1. 完整分页导出实际集合,记录每个集合总数、主键清单、时间范围和校验摘要;不可只导出首批 100 条。
  2. 规范 BSON/JSON 日期为同一 UTC 时间,数据库和 API 时间一致;业务日与任务时区明确使用 Asia/Shanghai。空值与 0 区分,禁止因默认值覆盖已有数据。
  3. 先导分类和用户,再导题型、收藏、下载、配置及设置。题型导入后,根据审核过的 worksheetId → grades 映射写入 worksheet_grades;不从旧 grade 或年龄自动生成适用范围。脚本可重复执行、按主键 upsert,并支持 dryRun;生产切换后不允许再次用旧导出覆盖新写入。
  4. 检测重复 OpenID、重复收藏、重复 exportId、失联关联、非法状态/年龄、缺失图片和未知字段。异常输出逐项报告,不以“跳过错误”当成功。
  5. 原用户已经注销而留下的收藏/下载记录应隔离归档,不能重建已注销用户。题型缺失时保留历史 worksheetId 和记录,列表 LEFT JOIN 返回空详情,不能整个历史接口报错。
  6. 收藏去重、异常隔离等转换明确写进审计报告:原始数 = 正常导入数 + 合并重复数 + 隔离数。不得无说明要求转换后条数与原始条数相同。
  7. 保留历史真实计数及 seed;旧计数与历史条数可能已有差异,先报告,不直接按日志总数重算下载量。切换后的新事件用事务建立一致性。

验收既对总量,也对用户集合、主键和关联。重点核对若干有收藏/历史的老用户、无资料用户、缺失题型记录和多个年龄段,不只抽查首页。

7. 文件、首页预拉取与定时任务

7.1 文件迁移

资源清单同时覆盖云数据库、页面配置嵌套对象、字体配置、首页内置兜底、用户头像和云存储目录。现有 HTTPS 资源(如部分 cdn.joeyone.cn 字体)先核实归属与可用性,不重复搬运已符合目标的文件。

生成可追溯映射:原 cloud:// fileID → 新 objectKey/assetId → checksum/大小/类型 → 迁移状态。确认源文件后下载、上传七牛并验证内容;仅改 URL 不算文件已迁移。

  • 对可公开使用的预览图、共享图片和小程序所需字体使用公共资源空间/域名,采用版本化对象 key 和缓存。
  • 头像等用户文件使用私有访问策略;数据库存对象引用,API 生成访问地址。小程序缓存需记录有效期并刷新,不能把短期签名 URL 当永久 avatarUrl。
  • 未来私有线稿/模板另行存放;当前小程序使用的字体与最终预览不因迁移就自动变成保密素材。
  • 首期头像和预览图较小,统一通过 POST /api/v1/assets 上传,由 API 校验身份、用途、类型、大小和归属,再写七牛。普通用户仅上传本人头像;预览上传需要管理权限。
  • 后续有大文件需求时再加入七牛直传凭证。AK/SK 只在服务端;上传地址、对象 key 与归属由服务端控制,不接受任意远程 URL 抓取。
  • 旧版仍会先上传 CloudBase 再调用更新资料/发布接口。兼容期保留所需上传能力,兼容入口校验旧文件环境、允许路径和所有者后复制到七牛再提交新数据库;不把永久 cloud:// 引用写入新数据库。

新版本完成后,正式代码和兜底资源均不再依赖 cloud://。旧版本硬编码字体/预览无法通过接口转发改掉,所以旧文件需要与兼容期一起保留,退场时单独检查访问量。

7.2 首页配置与推荐

现有 app.ts 顺序为预拉取 → 本地缓存 → 云函数兜底。迁移后由新公共 GET /api/v1/page-config 提供配置,微信后台预拉取 URL 同步更新,失败继续使用可用缓存/内置数据,网络恢复后按版本刷新。

  • 新 API 保留预拉取解析所需的数据形状与 previewImg;升级缓存命名/版本,避免长期沿用 cloud:// 和旧首页字段。
  • PostgreSQL 中的 current 配置是权威源。静态配置文件若为缓存或旧版兼容发布,只是派生产物;发布失败可重试,保留上一有效版本,不形成第二套内容数据库。
  • 手动栏目设置、各 page 分支及 category/age 为 null 的现有行为通过固定输入样本比对,避免迁移时无意改变运营配置。
  • 自动刷新仍只更新下载/收藏 Top5,保留手动维护的其他栏目。分龄推荐保持现有“ageMin 等于目标下限且 ageMax 覆盖上限”的匹配方式,业务算法调整留到后续。
  • 排名并列时定义稳定排序并与旧样本核对。统计更新不等于内容上新,保留 createdAt/contentUpdatedAt/updatedAt 各自用途。

7.3 定时任务

仓库存在 7 段定时表达式 0 0 22 * * * *,不得直接当作新调度器的表达式或凭它认定北京时间执行时刻。先核对 CloudBase 控制台实际时区与最近执行记录,再换为服务端明确时区的计划。

保留 enabled 开关;首次配置缺失默认启用的规则显式记录。自动执行与手动执行共用内容服务,使用锁/事务避免互相覆盖,记录执行时间、结果与失败原因。服务重启后发现当日应执行任务遗漏,可补跑一次;旧任务在正式切换前停用。

8. 小程序接入与旧版兼容

8.1 新版改造

  • 本节改造在独立的 doodle-mini 项目中实施,不移动小程序源码到 doodle-store;按新项目公开 API 契约接入,记录联调使用的服务端版本。
  • 按开发、体验、正式配置 API 地址,不在生产构建中使用 localhost/IP。接口域名、下载资源域名及实际上传域名按微信后台要求配置 HTTPS 与备案。
  • 建立业务服务层(auth/worksheet/category/content/favorite/download/asset/admin),页面调用业务方法。当前散落的调用点逐一替换,不依赖修改单个 cloud-adapter 就完成迁移。
  • HTTP 包装补齐登录、错误格式、超时和日志;wx.uploadFile、wx.downloadFile 分别处理。当前 utils/http.ts 下载包装读取 res.data 不适用于 wx.downloadFile 的临时文件路径,使用前修正契约。
  • 所有正式入口及分享 path 保留。收藏/下载历史中的原 worksheetId 能打开原题型,不因存储变化重定向到默认题型。
  • 网络故障时保留已有生成/保存能力;远程收藏与历史展示明确失败/缓存状态,不将失败误显示为真实空数据。
  • 微信事件名和 worksheet_id/export_id 口径延续;收藏成功事件仍在远端成功后记录。

8.2 过渡期兼容桥

发布新版并不意味着所有旧版立即消失。兼容桥覆盖旧正式用户的登录、资料、收藏、历史和内容查询;旧管理页存在数据库直读,要求管理员使用新版,不承诺旧调试管理页完全兼容。

  1. 原云函数名称保留,按旧 action 白名单转发统一服务端业务方法,并转换回原响应格式。
  2. 身份从 cloud.getWXContext() 取得,不能从 event 信任 OpenID/角色。
  3. 兼容请求以服务间密钥签名,包含函数/action、可信 AppID/OpenID、请求体、时间和唯一请求 ID;服务端验签、校验时效并拒绝重放。
  4. 内部转发重试使用新的传输请求 ID,但复用同一业务幂等键。签名密钥不下发小程序;管理 action 仍需验证管理员身份。
  5. 切换后兼容桥只读写新数据库,旧数据库停止写入;旧存储仅用于兼容文件读取/上传与迁移。

这一阶段有两个接入协议,但只有一份业务实现和一个写入源。兼容模块带退场期限和调用统计,不长期维护两套后端。

9. 实施里程碑与可执行待办

阶段 工作与交付 完成条件
S0 盘点 线上集合/文件清单、资源和域名核实、固定功能样本、迁移报告模板 迁移对象、数量和异常可追溯;有原始备份
S1 纵向样板 doodle-store pnpm 工作区、apps/server 骨架、数据库迁移、微信登录、题型读取、收藏/下载事务 工作区安装/构建通过;老用户身份关联正确;重复收藏/保存请求不重复计数
S2 功能迁移 补齐其余 API、管理权限、配置/推荐/任务、文件模块 17 个云函数各有落点,管理页直读替代完成
S3 接入与试迁 doodle-mini 服务层改造、跨项目契约核对、文件与数据库试导、兼容桥、体验版部署 新旧用户协议共同访问测试数据库;页面/字体/预拉取实测通过
S4 上线切换 生产导入、单写切换、正式版本发布、在线核对 用户关联与累计数据一致;新写入只在新数据库
S5 退场 持续核对兼容/旧文件调用、移除云开发依赖、归档旧数据 达到退场门槛后完全退出 CloudBase

执行清单

  • S0:导出实际集合及文件清单;确认 API 域名、备案、服务器和七牛环境。
  • S0:记录老用户/题型/推荐/配置样本及当前原始计数,准备数据审计规则。
  • S1:新建独立 doodle-store 项目,建立 pnpm workspace、锁文件、版本约束及 apps/server 的独立构建/检查脚本;不迁入 doodle-mini 源码。2026-10-12 已完成基础初始化,格式/代码/类型检查、5 项基础测试与构建入口 HTTP 检查通过;不代表 S1 业务样板已完成。
  • S1:在 doodle-store/apps/server 建立 PostgreSQL/Prisma 数据结构、统一响应、日志与版本化公开接口契约。
  • S1:微信登录沿用原 userId;服务端权限校验;收藏与下载幂等事务。
  • S2:完成全部公开/用户/管理接口及现有推荐行为对照。
  • S2:迁移文件、首页预拉取配置和定时任务,完成手动/自动刷新互斥。
  • S3:在 doodle-mini 替换所有正式云调用、管理直读、上传和字体解析,核对旧缓存升级及新 API 契约;记录两个项目的联调版本。
  • S3:验证兼容签名、身份、响应格式、旧文件新增迁移及无 exportId 请求行为。
  • S3:试导后核对集合总量、主键、关联、转换/隔离项及文件校验。
  • S3:完成备份恢复演练、API 检查和微信体验版真机验收。
  • S4:准备写入冻结、切换顺序、回滚记录,再执行生产切换与正式发布。
  • S5:确认兼容/旧资源访问退场门槛,归档后停止旧任务、函数、存储与套餐。

10. 正式切换与回滚

10.1 推荐切换:短维护窗口,全量最终导入

现有业务较小,优先使用经过演练的短写入冻结,不建设长期双写或增量同步平台。窗口时长由试导耗时决定,不能提前承诺“零停机”或固定分钟数。

  1. 先完成试迁和体验版验收,保持现有正式版仍使用 CloudBase。新正式版完成审核准备,但暂不放量。
  2. 预部署全部兼容函数,支持统一的 cloud / readonly / server 切换模式;在演练中验证。旧云函数对用户创建/活跃时间、收藏、下载、资料和管理写入均受模式约束。
  3. 选择低峰窗口,冻结旧端全部业务写入,停止旧推荐任务和管理发布;明确提示维护,读取与本地生成/保存仍可用。窗口内旧版成功保存但日志未上报的限制应在切换记录中注明;新版补报队列在试迁时验证。
  4. 在冻结状态导出最终快照,完成新增文件迁移和全量导入审计;此前试迁目标不得保留混入的测试记录。
  5. 将兼容桥整体切到 server,新数据库成为唯一写入源;更新公共配置/预拉取来源,开启新定时任务。核对桥和任务状态,旧数据库保持只读。
  6. 发布新版小程序;过渡期新版 HTTP 和旧版桥均调用同一业务服务。新版本未推广完成也不得把一部分用户写回旧数据库。
  7. 核对登录、收藏、成功保存记录、头像/字体、首页刷新与真实计数,再进入观察期。

若首次盘点发现数据规模或维护窗口不满足全量导入,应重新设计最终同步方式,不能直接在生产并行写两个库。试迁阶段的测试用户和数据不得混入生产。

10.2 回滚的两种情况

时点 回滚方式
新库尚未接收真实业务写入 停新任务/新入口,将桥恢复 cloud;解除旧库写入冻结,恢复旧任务
新库已接收真实业务写入 优先回滚 API 版本并继续使用新数据库;不可直接把客户端切回旧库

如必须回到旧数据库:同时冻结 HTTP 和兼容桥写入,导出新库最新数据并反向转换,将切换后的新增、修改和删除全部同步到旧库,完成关联/计数审计后再恢复旧入口。反向导入按新库权威快照替换相关数据,不能只补新增,否则会复活已取消收藏或已注销用户。

S3 应验证反向转换和恢复步骤。每次切换保存版本、模式、时间点和审计报告;数据库变更采用可兼容旧 API 版本的方式,避免应用回滚时表结构无法使用。

10.3 CloudBase 退场门槛

建议先观察至少 14 天,再结合实际旧版调用决定退场;14 天本身不是可以删除云资源的证明。要求:

  • 新版正式功能及管理操作已稳定使用新 API。
  • 兼容调用和旧 cloud:// 文件访问在约定观察窗口内已归零,或明确接受剩余旧版必须升级才能继续在线使用;不能凭新版发布推断旧版本消失。
  • 新数据库、七牛文件及恢复副本均可使用,旧数据已完成可恢复归档。
  • 旧任务停止,新版无 CloudBase 运行依赖;客户端 cloud 初始化及项目云配置在最终清理阶段移除。

删除旧资源和停止套餐应在上述条件满足后执行。兼容函数、上传或云字体仍使用旧环境时,不能宣称已完全迁出或已省下全部 19.9 元费用。

11. 验收与最低运行要求

11.1 自动验证与真实环境验收

验证层 检查项目 通过标准
服务端自动检查 登录错误、用户越权、管理权限、分页、重复收藏、并发重复 exportId、事务失败、注销 不越权、不重复计数;失败无半条业务写入
工程与契约检查 doodle-store 工作区安装/构建、应用依赖边界、公开契约与 doodle-mini 适配 服务端可独立构建;客户端不导入私有模块;联调版本及兼容行为可追溯
行为对照 固定输入的分类、分龄、手动配置、自动榜单、展示/真实计数 字段、去重、排序、兜底与预期一致;差异显式记录
迁移审计 ID、数量、日期、关系、重复/隔离项、旧计数、文件 checksum 数据量可对账;可用关联完整;无未解释丢失
小程序体验版/真机 老用户登录、首页/分类/年龄、预览生成、实际保存、收藏/历史、资料、注销、管理发布 使用有效域名和证书;功能链路与原版一致
故障与回滚 token 过期、离线、保存后上报失败、API 重启、备份恢复、切换后新增/删除 本地保存结果准确;同事件重试不重复;恢复不遗漏切换后的变化

微信开发者工具“跳过域名校验”下通过不算正式网络验收。至少检查 iOS、Android 的上传、图片/字体加载和保存回调。服务端和脚本可以自动测试;相册权限、实际保存、微信分享、广告、预拉取与真机字体效果需实际微信环境验证,不能以静态检查替代。

管理员验收包括:首页手动推荐/栏目、定时开关、分类创建/更新、题型上传/发布/上下架、年龄标签同步及 dryRun。正式用户验收还应覆盖所有分类入口、首页推荐、分龄和收藏历史中的跳转路径。

11.2 轻量运行基线

  • 每日自动备份数据库到服务器之外,备份访问受控并保留滚动版本;上线前成功恢复一次。七牛文件迁移清单和必要配置随数据备份保留。
  • 初始目标可设数据库 RPO 不超过 24 小时、恢复操作 RTO 不超过 60 分钟,以演练结果确认;若需要更小数据损失窗口,再增加 binlog/更频繁备份。每日备份不等于零数据损失。
  • HTTPS 自动续期;检查 API/数据库健康、磁盘、错误、备份失败和定时任务失败。日志轮转,不记录 AppSecret、登录 code、令牌、签名 URL 或完整用户敏感信息。
  • 版本和数据库变更可追踪;更新前备份,保留上一可运行应用版本。对本项目采用简单可执行步骤,不要求集群或复杂运维平台。
  • 发布记录关联 doodle-store 服务端版本、数据库迁移版本和 doodle-mini 小程序版本;后续后台/Web 各自记录版本。回滚服务端时保持已发布客户端的 API 兼容,不要求两个项目同步回滚。
  • 成本按 API 流量、数据库体量、七牛存储/下载/请求和实际维护工时记录,不以“已有服务器”推导零总成本。

12. AI 辅助工作量与排期

以下是初步预算,不是已验证的工期承诺。假设线上数据和文件较少、服务器可部署、权限齐备、沿用本方案接口/数据库选择;包含 doodle-store 基础 pnpm 工作区与 apps/server,以及 doodle-mini 接入改造,不包含独立后台/Web 页面、私有包发布平台或存量绘图重写。

工作包 AI 辅助后的初步投入
实际盘点、固定样本、接口与导入字段确认 2–3 小时
doodle-store 工作区、服务端骨架、登录/权限、现有业务与管理 API 8–12 小时
数据/文件导入、对账与反向转换脚本 4–8 小时
小程序服务层、登录、上传、缓存/字体与页面适配 6–10 小时
部署、兼容桥、必要测试/真机联调与切换演练 6–10 小时
合计 26–43 小时;约加 25% 修正余量后按 32–54 小时准备

按每天 8 小时集中投入,可先预算约 4–7 个工作日;兼职开发的日历周期更长,外部审核/备案等待另算。S5 还有至少 14 天的退场观察,代码与正式切换完成时间不等于关闭 CloudBase 的时间,观察期也不代表连续全日投入。先完成 S0 + S1 的纵向样板,以实际用时、数据量和发现的问题更新后续预算。

AI 主要承担接口/模型初稿、SQL 转换、数据脚本、小程序调用替换、部署配置和测试用例;必须运行验证的内容是老用户身份、导入结果、文件可用性、实际保存、管理权限及生产切换。无需将常规备份和升级夸大为单独的大型工程。

13. 与现有规划的关系及参考

本方案覆盖旧文档“暂缓核心 API/数据库迁移”“只替换 cloud-adapter 即可”的表述。目标改为自有服务器统一后端;过渡期保留 CloudBase 仅用于兼容和恢复。现有产品功能待办、Web 试点和服务端绘图的范围分别管理。

工程组织以本方案为准:不在 doodle-mini 中新增服务端工程;独立 doodle-store pnpm Monorepo 管理服务端、后续后台和 Web,目前只完成基础初始化,业务迁移仍待实施。本方案迁入 doodle-store/docs/云服务端迁移方案.md,不在两个项目中维护重复副本。

实施依据是 doodle-mini 当前代码,历史文档中的 Prisma 示例、免费额度、Node 版本和“零成本 CDN”表述不能直接作为部署事实。

  • 微信小程序网络与服务器域名要求:HTTPS、备案、request/upload/download 域名与真实环境校验。
  • 微信小程序登录流程:code2Session 与身份/会话密钥边界。
  • 七牛上传凭证:凭证由服务端生成,限制范围和有效期;直传作为后续可选优化。
  • PostgreSQL 数据库设计(v3.5):表结构、约束、事务、导入映射与数据库验收;具体 PostgreSQL / Prisma 版本在实施时验证并锁定。
  • pnpm 工作区:Monorepo、内部包引用及工作区锁文件。
  • pnpm 筛选命令:按应用、依赖关系执行开发与检查任务。
  • doodle-mini 仓库依据:cloudfunctions/*/index.js、miniprogram/app.ts、utils/auth.ts、favorites.ts、downloadLogs.ts、downloadPrint.ts、base/pageMixin.ts、pages/profile/profile.ts、core/font/* 及上述管理页面。

资源收藏是新增能力,原 CloudBase 收藏函数不能直接承接。若确需回到旧数据库,应先明确新增内容及收藏关系的存储和旧查询隔离方案并演练,不能把资源收藏压成功能收藏;优先回滚 API 版本并继续使用新主库。