Initial commit: 添加项目代码

This commit is contained in:
2026-02-13 18:24:52 +08:00
commit 05d3cc539d
303 changed files with 97922 additions and 0 deletions

View File

@@ -0,0 +1,369 @@
# 数据库设计重构方案 —— 问题分析、解决方案与变更影响
---
## 问题总览
| 编号 | 问题 | 解决类型 | 风险等级 |
|------|------|----------|----------|
| 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 脚本或编写临时迁移任务