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

15 KiB
Raw Permalink Blame History

数据库设计重构方案 —— 问题分析、解决方案与变更影响


问题总览

编号 问题 解决类型 风险等级
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.javaFailedTaskCleanupLog.java
  • 删除 RepositoryCompletedTaskArchiveRepository.javaFailedTaskCleanupLogRepository.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

问题二:模式文件不同步

问题描述

  1. schema.sqlinit_database.sql 内容不一致,且与 Flyway 迁移脚本重复
  2. Flyway 迁移脚本存在版本号冲突V2、V3、V7 各有两个同版本号的文件

解决方案

2A — 删除过时的 schema.sql

该文件仅在最初开发时使用Flyway 已接管所有数据库变更。

2B — 标注 init_database.sql

在文件头部添加醒目注释,说明仅用于全新手动部署,不由 Flyway 管理。同时移除其中 completed_tasks_archivefailed_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

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.TaskTypeTaskStatus.TaskTypeUserWork.WorkTypePointsFreezeRecord.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_queuetask_statustext_to_video_tasksimage_to_video_tasksstoryboard_video_taskspoints_freeze_recordsuser_error_loguser_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-1MB6 张分镜图意味着每条任务记录可能有 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_queuetask_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_archivefailed_tasks_cleanup_log,其中数据会丢失
    • 原始任务数据仍保留在 task_queueuser_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 脚本或编写临时迁移任务