15 KiB
数据库设计重构方案 —— 问题分析、解决方案与变更影响
问题总览
| 编号 | 问题 | 解决类型 | 风险等级 |
|---|---|---|---|
| 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 职责边界模糊 | 仅文档注释 | 无 |
问题一:数据冗余
问题描述
completed_tasks_archive表:将完成的任务复制到归档表,但原表数据并未删除,形成冗余副本failed_tasks_cleanup_log表:记录清理日志,但user_error_log表已有完整的错误记录,功能重复user_works状态与task_queue/ 业务任务表不同步:多条更新路径导致状态可能不一致
解决方案
1A — 删除冗余归档表
- 删除 JPA 实体:
CompletedTaskArchive.java、FailedTaskCleanupLog.java - 删除 Repository:
CompletedTaskArchiveRepository.java、FailedTaskCleanupLogRepository.java - 新增 Flyway 迁移:
V16__Drop_Redundant_Archive_Tables.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
问题二:模式文件不同步
问题描述
schema.sql与init_database.sql内容不一致,且与 Flyway 迁移脚本重复- 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_IMAGETaskStatus.TaskType— 同上PointsFreezeRecord.TaskType— 同上UserWork.WorkType— 同上(仅名称不同)
新增任务类型时需要改 4 个地方,容易遗漏。
解决方案
新建统一枚举 com.example.demo.model.enums.CommonTaskType:
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 批量更新所有相关表:
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-1MB,6 张分镜图意味着每条任务记录可能有 3-6MB 的文本存在 LONGTEXT 字段中,严重影响数据库性能和备份效率。
解决方案
在保存时就将每张图上传 COS,存 COS 返回的链接,Base64 不进库。
新增私有方法 uploadStoryboardImagesToCos(taskId, storyboardImagesJson):
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 脚本或编写临时迁移任务