Files
zmAI/demo/数据库重构方案.md

370 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 数据库设计重构方案 —— 问题分析、解决方案与变更影响
---
## 问题总览
| 编号 | 问题 | 解决类型 | 风险等级 |
|------|------|----------|----------|
| 1 | 数据冗余(冗余归档表 + user_works 状态不同步) | 代码重构 + Flyway 迁移 | 中 |
| 2 | 模式文件不同步schema.sql / init_database.sql / Flyway 冲突) | 文件清理 + 重命名 | 低 |
| 3 | 枚举类型重复定义4个实体各自定义 TaskType/WorkType | 代码重构 | 低 |
| 4 | Payment 字段命名混淆orderId 实为业务订单号,非外键) | **已撤回,不做改动** | 无 |
| 5 | 用户名冗余存储8张表都存 username改名时无法同步 | 新增级联更新方法 | 低 |
| 6 | Base64 图片直接存数据库storyboardImages 字段) | 保存时上传 COS | 低 |
| 7 | task_queue 与 task_status 职责边界模糊 | 仅文档注释 | 无 |
---
## 问题一:数据冗余
### 问题描述
1. `completed_tasks_archive` 表:将完成的任务复制到归档表,但原表数据并未删除,形成冗余副本
2. `failed_tasks_cleanup_log` 表:记录清理日志,但 `user_error_log` 表已有完整的错误记录,功能重复
3. `user_works` 状态与 `task_queue` / 业务任务表不同步:多条更新路径导致状态可能不一致
### 解决方案
**1A — 删除冗余归档表**
- 删除 JPA 实体:`CompletedTaskArchive.java``FailedTaskCleanupLog.java`
- 删除 Repository`CompletedTaskArchiveRepository.java``FailedTaskCleanupLogRepository.java`
- 新增 Flyway 迁移:`V16__Drop_Redundant_Archive_Tables.sql`
```sql
DROP TABLE IF EXISTS completed_tasks_archive;
DROP TABLE IF EXISTS failed_tasks_cleanup_log;
```
**1B — 重构 TaskCleanupService**
- 移除所有归档/清理日志的写入逻辑
- 失败任务清理时改用 `userErrorLogService.logErrorAsync()` 记录,不再写单独的表
**1C — 增强 user_works 状态修复**
-`TaskQueueService.repairUserWorkStatus()` 中增加对长时间停留在 `PROCESSING` 状态的任务的检测和修复
-`TaskQueueScheduler` 中添加 5 分钟执行一次的定时任务,自动发现并修复不一致状态
### 变更影响
| 影响范围 | 说明 |
|----------|------|
| 数据库 | 删除 2 张表。已有数据丢失(归档记录),但原始数据仍在 task_queue / user_error_log 中 |
| TaskCleanupService | 大幅精简,从 ~150 行减到 ~60 行 |
| TaskQueueScheduler | 新增 `repairUserWorkStatus()` 定时任务 |
| 前端 | **无影响**,前端未直接查询这两张表 |
### 涉及文件
```
[已删除] model/CompletedTaskArchive.java
[已删除] model/FailedTaskCleanupLog.java
[已删除] repository/CompletedTaskArchiveRepository.java
[已删除] repository/FailedTaskCleanupLogRepository.java
[已修改] service/TaskCleanupService.java — 移除归档逻辑
[已修改] service/TaskQueueService.java — 增强 repairUserWorkStatus()
[已修改] scheduler/TaskQueueScheduler.java — 新增状态修复定时任务
[已新增] db/migration/V16__Drop_Redundant_Archive_Tables.sql
```
---
## 问题二:模式文件不同步
### 问题描述
1. `schema.sql``init_database.sql` 内容不一致,且与 Flyway 迁移脚本重复
2. Flyway 迁移脚本存在版本号冲突V2、V3、V7 各有两个同版本号的文件
### 解决方案
**2A — 删除过时的 schema.sql**
该文件仅在最初开发时使用Flyway 已接管所有数据库变更。
**2B — 标注 init_database.sql**
在文件头部添加醒目注释,说明仅用于全新手动部署,不由 Flyway 管理。同时移除其中 `completed_tasks_archive``failed_tasks_cleanup_log` 的建表语句(与问题一保持一致)。
**2C — 修复 Flyway 版本号冲突**
| 原文件名 | 新文件名 |
|----------|----------|
| V2__add_indexes.sql | V2.1__add_indexes.sql |
| V2__add_task_status_cascade_triggers.sql | V2.2__add_task_status_cascade_triggers.sql |
| V3__Create_Task_Queue_Table.sql | V3.1__Create_Task_Queue_Table.sql |
| V3__fix_foreign_key_cascade.sql | V3.2__fix_foreign_key_cascade.sql |
| V7__Add_indexes_task_status.sql | V7.1__Add_indexes_task_status.sql |
| V7__Create_Task_Cleanup_Tables.sql | V7.2__Create_Task_Cleanup_Tables.sql |
`V7.2__Create_Task_Cleanup_Tables.sql` 内容改为空操作(注释说明已被 V16 取代)。
### 变更影响
| 影响范围 | 说明 |
|----------|------|
| 数据库 | **无影响**——仅重命名迁移文件,内容不变 |
| 应用启动 | Flyway 不再因版本冲突报错 |
| 开发者 | 只需关注 `db/migration/` 下的 Flyway 脚本 |
### 涉及文件
```
[已删除] src/main/resources/schema.sql
[已修改] init_database.sql — 添加注释头 + 移除冗余建表
[已重命名] V2/V3/V7 重复的迁移脚本(共 6 个文件)
[已修改] V7.2__Create_Task_Cleanup_Tables.sql — 改为 no-op
```
---
## 问题三:枚举类型重复定义
### 问题描述
4 个实体类各自在内部定义了功能相同的枚举:
- `TaskQueue.TaskType` — TEXT_TO_VIDEO / IMAGE_TO_VIDEO / STORYBOARD_VIDEO / STORYBOARD_IMAGE
- `TaskStatus.TaskType` — 同上
- `PointsFreezeRecord.TaskType` — 同上
- `UserWork.WorkType` — 同上(仅名称不同)
新增任务类型时需要改 4 个地方,容易遗漏。
### 解决方案
新建统一枚举 `com.example.demo.model.enums.CommonTaskType`
```java
public enum CommonTaskType {
TEXT_TO_VIDEO("文生视频"),
IMAGE_TO_VIDEO("图生视频"),
STORYBOARD_VIDEO("分镜视频"),
STORYBOARD_IMAGE("分镜图");
}
```
删除 4 个实体类中的内部枚举所有字段类型、构造方法参数、getter/setter 统一改为 `CommonTaskType`
### 全项目扫描结果
| 扫描范围 | 旧枚举引用数 | 状态 |
|----------|-------------|------|
| `src/main/`(主代码,所有 service / model / controller | 0 | 已全部替换 |
| `src/test/PointsRefundIntegrationTest.java` | 16 处 `PointsFreezeRecord.TaskType` | 已修复 → `CommonTaskType` |
| `frontend/src/`(前端 Vue 组件) | 使用字符串值 `'TEXT_TO_VIDEO'` 等 | 无需修改枚举名不变API 返回值不变) |
### 变更影响
| 影响范围 | 说明 |
|----------|------|
| 数据库 | **无影响**——JPA 使用 `@Enumerated(EnumType.STRING)`,数据库中存储的字符串值不变 |
| 编译 | 所有引用 `TaskQueue.TaskType``TaskStatus.TaskType``UserWork.WorkType``PointsFreezeRecord.TaskType` 的地方都需要改为 `CommonTaskType` |
| 测试代码 | `PointsRefundIntegrationTest.java` 中 16 处旧引用已同步更新 |
| 前端 | **无影响**——前端使用字符串值匹配,枚举名称未改变 |
### 涉及文件
```
[已新增] model/enums/CommonTaskType.java
[已修改] model/TaskQueue.java — 删除内部枚举,字段改为 CommonTaskType
[已修改] model/TaskStatus.java — 同上
[已修改] model/PointsFreezeRecord.java — 同上
[已修改] model/UserWork.java — 删除 WorkType字段改为 CommonTaskType
[已修改] service/TaskQueueService.java — 更新所有枚举引用
[已修改] service/TaskStatusPollingService.java — 更新 createTaskStatus / convertToTaskStatusType
[已修改] service/UserWorkService.java — 更新 WorkType 引用
[已修改] test/service/PointsRefundIntegrationTest.java — 16 处旧枚举引用更新
```
---
## 问题四Payment 字段命名混淆 —— 已撤回,不做改动
### 问题描述
`Payment.orderId` 存储的是业务订单号(如 `"ORD123456789"`),是一个 String 标识符,不是外键。命名为 `orderId` 可能被误读为外键。
### 评估结论:不需要改动
经过完整的支付链路分析,改名会导致以下问题:
**JSON 序列化会破坏前端**
- `Payment` 实体没有 `@JsonProperty` 注解Jackson 序列化直接使用 Java 字段名
- 改名后 JSON key 从 `"orderId"` 变为 `"orderNumber"`
- 前端 `Payments.vue` 等多个组件通过 `payment.orderId` 访问该字段,改名后全部变成 `undefined`
- 修复需要同步改动前端 6+ 个 Vue 文件,涉及面过大
**原始问题不影响功能**
- `orderId` 命名虽然容易被误读为外键,但代码逻辑完全正确
- 支付回调Alipay / LuluPay通过 `out_trade_no` 查找,链路正常
- 这只是一个代码可读性问题,不值得冒支付功能出错的风险
### 涉及文件
```
无改动
```
---
## 问题五:用户名冗余存储
### 问题描述
8 张业务表都存储了 `username` 字段(冗余存储,非外键关联),用户改名时这些表无法同步更新,导致数据不一致。
涉及的表:`task_queue``task_status``text_to_video_tasks``image_to_video_tasks``storyboard_video_tasks``points_freeze_records``user_error_log``user_works`
### 解决方案
`UserService` 中新增 `cascadeUpdateUsername(oldUsername, newUsername)` 方法,在用户改名时通过 `JdbcTemplate` 批量更新所有相关表:
```java
private void cascadeUpdateUsername(String oldUsername, String newUsername) {
String[] tables = {
"task_queue", "task_status", "text_to_video_tasks",
"image_to_video_tasks", "storyboard_video_tasks",
"points_freeze_records", "user_error_log", "user_works"
};
for (String table : tables) {
jdbcTemplate.update(
"UPDATE " + table + " SET username = ? WHERE username = ?",
newUsername, oldUsername);
}
}
```
该方法在 `updateUsername()` 中被调用,确保改名操作是原子性的。
### 变更影响
| 影响范围 | 说明 |
|----------|------|
| 数据库 | **无结构变更**——仅在改名时批量 UPDATE 数据 |
| 性能 | 改名操作会执行 8 条 UPDATE 语句,但用户改名是低频操作,影响可忽略 |
| 索引 | 各表的 `username` 字段已有索引UPDATE 不会全表扫描 |
| 前端 | **无影响** |
### 涉及文件
```
[已修改] service/UserService.java — 注入 JdbcTemplate新增 cascadeUpdateUsername()
```
---
## 问题六Base64 图片直接存数据库
### 问题描述
分镜图功能中,外部 AI API 返回 Base64 图片后:
- `resultUrl`(网格合并图)**已经在保存时上传 COS** ✓
- `storyboardImages`(单独分镜图片 JSON 数组)**直接将 Base64 存入数据库** ✗
一张 Base64 图片约 200KB-1MB6 张分镜图意味着每条任务记录可能有 3-6MB 的文本存在 `LONGTEXT` 字段中,严重影响数据库性能和备份效率。
### 解决方案
**在保存时就将每张图上传 COS存 COS 返回的链接Base64 不进库。**
新增私有方法 `uploadStoryboardImagesToCos(taskId, storyboardImagesJson)`
```java
private String uploadStoryboardImagesToCos(String taskId, String storyboardImagesJson) {
// 1. 解析 JSON 数组
// 2. 遍历每张图,如果是 data:image 开头的 Base64 就上传 COS
// 3. 替换为 COS URL
// 4. 上传失败时保留原值(不丢数据)
// 5. 返回替换后的 JSON
}
```
两个保存路径都在写库之前调用此方法:
- `saveStoryboardImageResultWithTransactionTemplate()` — 异步事务保存
- `saveStoryboardImageResult()` — 同步事务保存
**不需要批量迁移**——修复源头后,新数据直接存 COS URL不会再有 Base64 进库。
### 变更影响
| 影响范围 | 说明 |
|----------|------|
| 数据库 | 新数据 `storyboardImages` 字段从 ~3-6MB Base64 变为 ~500 字节的 URL JSON**大幅减小存储** |
| COS | 每次生成分镜图会多上传 6 张图片到 COS本就应该上传 |
| 前端 | **无影响**——前端通过 URL 加载图片COS URL 和 Base64 Data URI 都能被 `<img>` 标签识别 |
| 视频生成 | 后续用分镜图生成视频时,需要从 COS URL 下载图片(代码中已有 `downloadImageAsBase64` 方法处理) |
### 涉及文件
```
[已修改] service/StoryboardVideoService.java — 新增 uploadStoryboardImagesToCos()
两个保存方法在存库前调用
[已删除] migrateBase64ToCos() 批量迁移方法(不再需要)
[已修改] scheduler/TaskQueueScheduler.java — 删除 migrateBase64ToCos 定时任务
```
---
## 问题七task_queue 与 task_status 职责边界模糊
### 问题描述
`task_queue``task_status` 两张表字段高度相似(都有 taskId、username、taskType、status开发者容易误认为是重复表可以合并。但实际上它们职责完全不同
- `task_queue`**任务调度引擎**:管理内存 BlockingQueue 的持久化,控制并发执行
- `task_status`**外部 API 轮询**:存储 externalTaskId跟踪外部 AI API 的返回状态
合并会破坏现有的调度和轮询逻辑。
### 解决方案
**不做代码结构变更**,仅在关键位置添加 Javadoc 注释明确职责边界:
- `TaskQueue.java` — 类级注释说明"任务调度与执行引擎的持久化层"
- `TaskStatus.java` — 类级注释说明"跟踪已提交到外部 AI API 的任务轮询状态"
- `TaskQueueService.java` — 类级注释说明调度逻辑和调用关系
- `TaskStatusPollingService.java` — 类级注释说明轮询逻辑和级联更新路径
### 变更影响
| 影响范围 | 说明 |
|----------|------|
| 数据库 | **无影响** |
| 运行时 | **无影响**——仅添加注释 |
| 开发者 | 阅读代码时能快速理解两张表的区别,避免误合并 |
### 涉及文件
```
[已修改] model/TaskQueue.java — 添加 Javadoc
[已修改] model/TaskStatus.java — 添加 Javadoc
[已修改] service/TaskQueueService.java — 添加 Javadoc
[已修改] service/TaskStatusPollingService.java — 添加 Javadoc
```
---
## 部署注意事项
### Flyway 迁移
- 如果数据库是**全新部署**Flyway 会自动按版本号顺序执行所有迁移脚本,包括 V16删除冗余表
- 如果数据库**已有数据**
- `V16__Drop_Redundant_Archive_Tables.sql` 会删除 `completed_tasks_archive``failed_tasks_cleanup_log`,其中数据会丢失
- 原始任务数据仍保留在 `task_queue``user_error_log` 中,不受影响
- 如果 Flyway 的 `flyway_schema_history` 中已有旧版本号的记录V2、V3、V7需要手动清理或设置 `flyway.baseline-on-migrate=true`
### COS 配置
- 问题六的修复依赖 COS 服务开启(`cosService.isEnabled() == true`
- 如果 COS 未配置或不可用,代码会**回退保留 Base64**,不会导致数据丢失或报错
### 历史 Base64 数据
- 修复后新生成的分镜图会直接存 COS URL
- 已存在的历史 Base64 数据不会自动清理
- 如需清理历史数据,可手动执行一次性 SQL 脚本或编写临时迁移任务