Files
admin/docs/dashboard/backend-migration-plan.md
T
2026-07-30 11:23:50 +08:00

7.7 KiB
Raw Blame History

Dashboard 后端迁移计划:JS + Sequelize → TypeScript + Drizzle

概述

将 dashboard 后端从 JavaScript + Sequelize(CommonJS)迁移至 TypeScript + Drizzle ORM(ESM),提升类型安全性和开发体验,同时为官网 CMS 模块(/api/www/*)奠定技术基础。

迁移策略:渐进式,逐模块替换,确保每个阶段可独立验证。


现有代码清单

Models(9 个表)

文件 表名 说明
Brand.js brand 耳机品牌
Model.js model 耳机型号
Ota.js ota OTA 固件
OtaTargetDevice.js ota_target_device OTA 目标设备
BlackList.js black_list 黑名单
DashboardUser.js dashboard_user 后台用户
ShareCodeLog.js share_code_log 分享码日志
UserActive.js user_active 用户活跃记录
UserDevice.js user_device 用户设备

Routes(9 个路由模块)

文件 体量 复杂度 迁移优先级
auth.js 3.6KB ⭐ P1(先迁)
shareCodeLogs.js 2.7KB ⭐ P1
brands.js 4.8KB ⭐ P1
blacklist.js 6.6KB ⭐⭐ P2
users.js 6.2KB ⭐⭐ P2
otaTargetDevice.js 7.8KB ⭐⭐ P2
dashboard.js 9.5KB ⭐⭐ P2
ota.js 9.9KB ⭐⭐⭐ P3
models.js 26.1KB ⭐⭐⭐ P3(最后)

Services(6 个)

文件 说明
curveClient.js 频响曲线 API 客户端
eqCacheStorage.js Redis EQ 缓存
measurementStorage.js S3 测量文件存储
otaStorage.js OTA 固件存储
squiglink.js Squiglink 数据服务
userBootstrap.js 超级管理员初始化

其他

模块 文件 说明
validators/ 5 个 已使用 Zod,迁移成本极低
middleware/ auth.js, bodyLimit.js JWT 鉴权 + 请求体限制
config/ database.js, env.js, loadEnv.js, logger.js, redis.js 基础配置
utils/ 3 个 工具函数

迁移阶段

Phase 1:搭建 TypeScript 基础设施

目标:项目能运行 TS,新旧代码可共存。

  • 安装依赖:typescript、tsx、drizzle-orm、drizzle-kit、@types/express、@types/cors
  • 移除依赖:sequelize(Phase 4 最后移除)
  • 创建 tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "esModuleInterop": true,
    "strict": true,
    "outDir": "dist",
    "rootDir": "src",
    "skipLibCheck": true,
    "resolveJsonModule": true,
    "declaration": true,
    "paths": { "@/*": ["./src/*"] }
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules", "dist"]
}
  • 创建 drizzle.config.ts
import { defineConfig } from 'drizzle-kit';

export default defineConfig({
  schema: './src/schemas/index.ts',
  out: './drizzle',
  dialect: 'mysql',
  dbCredentials: {
    host: process.env.DATABASE_HOST || 'localhost',
    port: Number(process.env.DATABASE_PORT || 3306),
    user: process.env.DATABASE_USER || 'root',
    password: process.env.DATABASE_PASSWORD || '',
    database: process.env.DATABASE_NAME || 'audio',
  },
});
  • 更新 package.json scripts:
{
  "type": "module",
  "scripts": {
    "dev": "tsx watch src/app.ts",
    "build": "tsc",
    "start": "node dist/app.js",
    "db:generate": "drizzle-kit generate",
    "db:migrate": "drizzle-kit migrate",
    "db:push": "drizzle-kit push",
    "db:studio": "drizzle-kit studio"
  }
}
  • 创建 src/config/database.ts(Drizzle 连接)
import { drizzle } from 'drizzle-orm/mysql2';
import mysql from 'mysql2/promise';
import * as schema from '../schemas';

const pool = mysql.createPool({
  host: process.env.DATABASE_HOST || 'localhost',
  port: Number(process.env.DATABASE_PORT || 3306),
  user: process.env.DATABASE_USER || 'root',
  password: process.env.DATABASE_PASSWORD || '',
  database: process.env.DATABASE_NAME || 'audio',
  charset: 'utf8mb4',
  waitForConnections: true,
  connectionLimit: 10,
});

export const db = drizzle(pool, { schema, mode: 'default' });

验证:tsx src/app.ts 能正常启动(此时仍用旧 JS 代码)。


Phase 2:迁移 Models → Drizzle Schema

目标:所有表定义用 Drizzle schema 表达,生成初始迁移。

  • 创建 src/schemas/ 目录
  • 逐表编写 Drizzle schema:
// src/schemas/brand.ts
import { mysqlTable, int, varchar, datetime } from 'drizzle-orm/mysql-core';
import { sql } from 'drizzle-orm';

export const brands = mysqlTable('brand', {
  id: int('id').primaryKey().autoincrement(),
  name: varchar('name', { length: 100 }).notNull(),
  // ...
});
  • 创建 src/schemas/index.ts 统一导出
  • 运行 drizzle-kit generate 生成初始迁移(与现有表结构对齐)
  • 验证:drizzle-kit studio 能正确读取现有数据

注意:现有表已存在,首次迁移使用 drizzle-kit push(同步 schema 到数据库而不执行 SQL),或生成迁移后标记为已执行。


Phase 3:逐个迁移 Routes

目标:将路由处理函数中的 Sequelize 查询替换为 Drizzle。

迁移顺序(从简到繁):

批次 路由 原因
第 1 批 auth.ts, shareCodeLogs.ts, brands.ts 逻辑简单,CRUD 为主
第 2 批 blacklist.ts, users.ts, otaTargetDevice.ts, dashboard.ts 中等复杂度
第 3 批 ota.ts, models.ts 最复杂,涉及文件上传/外部服务

每个路由的迁移步骤:

  1. 重命名 .js → .ts
  2. require() → import
  3. Sequelize 查询 → Drizzle 查询:
// Before (Sequelize)
const brands = await Brand.findAll({ where: { name: { [Op.like]: `%${keyword}%` } } });

// After (Drizzle)
const result = await db.select().from(brands).where(like(brands.name, `%${keyword}%`));
  1. 添加请求/响应类型(配合 Zod validator)
  2. 验证接口行为不变

Phase 4:迁移 Services + 清理

目标:全部代码 TS 化,移除 Sequelize。

  • 迁移 6 个 service 文件(主要是加类型标注,逻辑不变)
  • 迁移 middleware(auth.ts, bodyLimit.ts)
  • 迁移 config(env.ts, logger.ts, redis.ts)
  • 迁移 utils
  • 迁移入口 app.js → app.ts
  • 删除所有旧 .js 文件
  • 卸载 sequelize 依赖
  • 全量 tsc --noEmit 通过
  • 更新 Dockerfile(构建步骤改为 tsc && node dist/app.js)

Phase 5:新增官网 CMS 模块

目标:在迁移完成的基础上,开发 /api/www/* 接口。

  • 设计 CMS 数据表(pages、sections、banners、products、news、faq、site_settings 等)
  • 编写 Drizzle schema + 迁移
  • 实现前台接口 /api/www/*(公开)
  • 实现管理接口 /api/admin/www/*(JWT 鉴权)
  • dashboard 前端新增「官网管理」菜单

风险与注意事项

风险 应对
迁移期间线上服务中断 渐进式迁移,新旧代码共存,逐模块切换
Sequelize 隐式行为(如自动时间戳) 现有配置已关闭 timestamps,影响小
sequelize.sync() 自动建表 → 手动迁移 Phase 2 完成后用 drizzle-kit 管理,不再自动 sync
CommonJS → ESM 兼容问题 部分依赖(如 multer)可能需要 esModuleInterop 或动态 import
models.js 路由 26KB 过大 迁移时拆分为 models + measurements 两个路由文件

预计工时

阶段 预计时间
Phase 1:TS 基础设施 0.5 天
Phase 2:Schema 迁移 0.5 天
Phase 3:Routes 迁移 1~1.5 天
Phase 4:Services + 清理 0.5 天
Phase 5:CMS 模块 另计(属于新功能开发)
合计(不含 Phase 5) 2.5~3 天