init: 整合项目(dashboard + www + docs)
This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
# 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`
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"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`
|
||||
|
||||
```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:
|
||||
|
||||
```json
|
||||
{
|
||||
"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 连接)
|
||||
|
||||
```ts
|
||||
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:
|
||||
|
||||
```ts
|
||||
// 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 查询:
|
||||
|
||||
```ts
|
||||
// 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}%`));
|
||||
```
|
||||
|
||||
4. 添加请求/响应类型(配合 Zod validator)
|
||||
5. 验证接口行为不变
|
||||
|
||||
---
|
||||
|
||||
### 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 天** |
|
||||
Reference in New Issue
Block a user