7.7 KiB
7.7 KiB
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.jsonscripts:
{
"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 | 最复杂,涉及文件上传/外部服务 |
每个路由的迁移步骤:
- 重命名
.js→.ts require()→import- 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}%`));
- 添加请求/响应类型(配合 Zod validator)
- 验证接口行为不变
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 天 |