feat: 功能优化

This commit is contained in:
2025-12-02 01:55:57 +08:00
parent 900b47f585
commit 0fffd787bb
32 changed files with 974 additions and 2156 deletions

View File

@@ -1,365 +0,0 @@
# 阿里云智能媒体服务 - SubmitMediaProducingJob API文档
## 📋 API概述
**接口名称:** SubmitMediaProducingJob提交剪辑合成作业
**服务名称:** Intelligent Media Services (IMS) - 智能媒体服务
**API版本** 2020-11-09
### 业务说明
`SubmitMediaProducingJob` 是阿里云智能媒体服务的核心API接口主要用于**提交媒体剪辑合成任务**。当用户需要对视频或音频素材进行剪辑、合成、添加特效、转码等后期制作时,可以通过调用此接口自动完成这些复杂的媒体处理工作。
#### 核心业务场景
1. **视频剪辑制作**
- 多个视频片段的拼接合成
- 添加转场效果和过渡动画
- 视频片段的裁剪和缩放
2. **音视频处理**
- 音频与视频的同步合成
- 添加背景音乐和音效
- 音频混合和音量调节
3. **多轨道编辑**
- 支持视频轨道、音频轨道、字幕轨道
- 实现复杂的多层编辑效果
- 视频叠加和水印添加
4. **模板化制作**
- 使用预定义模板快速生成视频
- 批量内容生产
- 统一风格的视频输出
5. **云端转码**
- 视频格式转换MP4、AVI、MOV等
- 分辨率和码率调整
- 自适应码率输出
---
## 🔐 授权信息
| 操作名 | 访问级别 | 资源类型 | 条件键 | 关联操作 |
|--------|----------|----------|--------|----------|
| ice:SubmitMediaProducingJob | 写权限 | 所有资源 (`*`) | 无 | 无 |
---
## 📡 接口调用
**请求方法:** POST
**调用地址:** `https://ims.ap-southeast-1.aliyuncs.com/`
**请求路径:** `/2020-11-09/submitMediaProducingJob`
### ⚠️ 重要说明
- 此接口仅返回作业**提交结果**,作业提交后将在后台异步处理
- 时间线中引用的素材可以是媒体库中的资产或OSS对象
- **不支持**外部URL或CDN URL
- 生产完成后,输出文件会自动注册为媒体资产
- 需要先分析媒体资产,才能查询时长和分辨率信息
---
## 🔒 调用限制
| 限制项 | 限制值 | 说明 |
|--------|--------|------|
| **QPS限制** | 30次/秒 | 超出限制会返回"Throttling.User"错误 |
| **视频轨道** | 最多100个 | 每个项目最多可创建100条视频轨道 |
| **图片轨道** | 最多100个 | 每个项目最多可创建100条图片轨道 |
| **字幕轨道** | 最多100个 | 每个项目最多可创建100条字幕轨道 |
| **素材总大小** | 不超过1TB | 项目中所有素材文件的总大小限制 |
| **输出分辨率** | 128px - 4096px | 宽度和高度都必须在128-4096像素之间 |
| **视频短边** | 不超过2160px | 视频的短边不能超过2160像素 |
| **区域限制** | 同一区域 | 素材和输出的OSS桶必须与IMS服务区域一致 |
---
## 📝 请求参数
### 主要参数说明
| 参数名 | 类型 | 必填 | 描述 | 示例值 |
|--------|------|------|------|--------|
| **ProjectId** | string | 否 | 编辑项目的ID | `xxxxxfb2101cb318xxxxx` |
| **Timeline** | string | 否 | 在线编辑作业的时间线配置 | 详见时间线配置 |
| **TemplateId** | string | 否 | 模板ID使用模板快速构建时间线 | `****96e8864746a0b6f3****` |
| **ClipsParam** | string | 否 | 模板素材参数JSON格式 | - |
| **ProjectMetadata** | string | 否 | 编辑项目的元数据JSON格式 | - |
| **OutputMediaTarget** | string | 否 | 输出文件类型:`oss-object`/`vod-media`/`S3` | `oss-object` |
| **OutputMediaConfig** | **Yes** | **是** | 输出文件配置JSON格式 | 详见配置说明 |
| **UserData** | string | 否 | 用户自定义数据最多512字节 | `{"NotifyAddress":"https://..."}` |
| **ClientToken** | string | 否 | 客户端令牌(确保请求幂等性) | `****12e8864746a0a398****` |
| **Source** | string | 否 | 请求来源:`OpenAPI`/`AliyunConsole`/`WebSDK` | `OPENAPI` |
| **EditingProduceConfig** | string | 否 | 编辑制作参数 | 详见配置说明 |
| **MediaMetadata** | string | 否 | 产出视频的元数据 | `{"Title":"test-title"}` |
### 参数组合规则
**三选一参数:** `ProjectId``Timeline``TemplateId` 中必须指定一个,其余两个必须为空。
- 如果指定 `ProjectId`:使用现有编辑项目
- 如果指定 `Timeline`:直接定义时间线
- 如果指定 `TemplateId`:必须同时指定 `ClipsParam`
---
## 💾 输出配置示例
### 示例1输出到OSS
```json
{
"MediaURL": "https://my-test-bucket.oss-cn-shanghai.aliyuncs.com/test/xxxxxtest001xxxxx.mp4",
"Bitrate": 2000,
"Width": 800,
"Height": 680
}
```
**配置说明:**
- `MediaURL`OSS对象URL格式为 `https://bucketname.oss-region-name.aliyuncs.com/xxx/yyy.ext`
- `Bitrate`输出码率Kbit/s越高视频越清晰最大值为5000
- `Width`/`Height`:输出分辨率,留空则使用输入素材的最大分辨率
### 示例2输出到ApsaraVideo VOD
```json
{
"StorageLocation": "outin-*xxxxxx7d2a3811eb83da00163exxxxxx.oss-cn-shanghai.aliyuncs.com",
"FileName": "output.mp4",
"Bitrate": 2000,
"Width": 800,
"Height": 680,
"VodTemplateGroupId": "VOD_NO_TRANSCODE"
}
```
**配置说明:**
- `StorageLocation`VOD中的存储位置不含http://前缀)
- `FileName`:输出文件名(包含扩展名)
- `VodTemplateGroupId`VOD转码模板组ID设为`VOD_NO_TRANSCODE`表示不转码
### OutputMediaConfig 参数详解
| 参数名 | 类型 | 说明 |
|--------|------|------|
| MediaURL | String | 输出文件URLoss-object类型 |
| StorageLocation | String | VOD存储位置vod-media类型 |
| FileName | String | 输出文件名vod-media类型 |
| Width | Integer | 输出宽度(默认:输入素材最大宽度) |
| Height | Integer | 输出高度(默认:输入素材最大高度) |
| Bitrate | Integer | 输出码率(默认:输入素材最大码率) |
| VodTemplateGroupId | String | VOD转码模板组ID |
---
## 📤 响应参数
| 参数名 | 类型 | 描述 | 示例值 |
|--------|------|------|--------|
| RequestId | string | 请求ID唯一标识 | `****36-3C1E-4417-BDB2-1E034F****` |
| ProjectId | string | 编辑项目ID | `****b4549d46c88681030f6e****` |
| **JobId** | string | **作业ID用于查询作业状态** | `****d80e4e4044975745c14b****` |
| MediaId | string | 输出文件的媒体资产ID | `****c469e944b5a856828dc2****` |
| VodMediaId | string | 输出文件在VOD中的媒体资产ID如适用 | `****d8s4h75ci975745c14b****` |
### 响应示例
```json
{
"RequestId": "****36-3C1E-4417-BDB2-1E034F****",
"ProjectId": "****b4549d46c88681030f6e****",
"JobId": "****d80e4e4044975745c14b****",
"MediaId": "****c469e944b5a856828dc2****",
"VodMediaId": "****d8s4h75ci975745c14b****"
}
```
---
## 🔧 EditingProduceConfig 配置
用于控制编辑制作过程的参数。
```json
{
"AutoRegisterInputVodMedia": "true",
"OutputWebmTransparentChannel": "true",
"CoverConfig": {
"CustomThumbnail": "https://example.com/thumb.jpg"
}
}
```
**参数说明:**
- `AutoRegisterInputVodMedia`是否自动注册时间线中的VOD媒体资产到IMS默认 `true`
- `OutputWebmTransparentChannel`输出视频是否包含Alpha通道透明度默认 `false`
- `CoverConfig`:自定义缩略图配置
---
## 🚨 错误码
| HTTP状态码 | 错误码 | 错误消息 |
|------------|--------|----------|
| 400 | InvalidParameter | 参数不合法 |
| 404 | ProjectNotFound | 指定的项目不存在 |
| 429 | Throttling.User | 请求频率超过限制30 QPS |
---
## 💡 典型使用场景
### 场景1视频片段拼接
```json
{
"Timeline": {
"VideoTracks": [
{
"VideoTrackClips": [
{"MediaId": "****4d7cf14dc7b83b0e801c****"},
{"MediaId": "****4d7cf14dc7b83b0e801c****"}
]
}
]
},
"OutputMediaConfig": {
"MediaURL": "https://my-bucket.oss-cn-shanghai.aliyuncs.com/output.mp4",
"Bitrate": 2000
}
}
```
**业务场景:** 将两个视频片段无缝拼接成一个完整视频
### 场景2模板化视频生产
```json
{
"TemplateId": "****template-id****",
"ClipsParam": {
"clips": [
{"MediaId": "****video1****"},
{"MediaId": "****video2****"}
]
},
"OutputMediaConfig": {
"MediaURL": "https://my-bucket.oss-cn-shanghai.aliyuncs.com/template-output.mp4"
}
}
```
**业务场景:** 使用预定义模板快速生成风格统一的视频内容
### 场景3视频转码并上传VOD
```json
{
"Timeline": {
"VideoTracks": [
{
"VideoTrackClips": [
{"MediaId": "****source-video****"}
]
}
]
},
"OutputMediaTarget": "vod-media",
"OutputMediaConfig": {
"StorageLocation": "outin-xxxxx.oss-cn-shanghai.aliyuncs.com",
"FileName": "transcoded-video.mp4",
"Bitrate": 1500,
"Width": 1920,
"Height": 1080
},
"UserData": {
"NotifyAddress": "https://your-callback-url.com"
}
}
```
**业务场景:** 将视频转码为不同分辨率和码率并直接上传到VOD系统
---
## 📊 相关API
- **GetMediaProducingJob**:查询媒体剪辑合成作业状态
- **CancelMediaProducingJob**:取消媒体剪辑合成作业
- **CreateEditingProject**:创建编辑项目
---
## 🔗 相关文档
- [时间线配置说明](https://www.alibabacloud.com/help/en/ims/developer-reference/timeline-configuration-description)
- [编辑制作参数说明](https://www.alibabacloud.com/help/en/ims/developer-reference/clip-composition-parameter-description)
- [模板创建和使用](https://www.alibabacloud.com/help/en/ims/user-guide/create-and-use-a-normal-template)
- [回调配置](https://www.alibabacloud.com/help/en/ims/use-cases/to-configure-a-callback-when-a-clip-completes)
- [常见问题FAQ](https://www.alibabacloud.com/help/en/ims/support/intelligent-production-making-faq)
---
## 📌 注意事项
1. **异步处理:** 作业提交后立即返回,任务在后台异步执行
2. **费用说明:** 按实际处理时长和输出文件大小计费
3. **配额管理:** 建议使用 `ClientToken` 确保请求幂等性
4. **回调通知:** 通过 `UserData.NotifyAddress` 设置完成回调通知
5. **文件大小:** 单次处理的文件总大小建议不超过1GB超过建议分段处理
6. **格式支持:** 支持主流视频/音频格式MP4、AVI、MOV、MP3、AAC等
7. **转码速度:** 处理速度取决于输出质量设置,高质量处理时间较长
---
## 🎯 最佳实践
### 1. 幂等性保证
```javascript
// 使用ClientToken确保同一请求不会被重复处理
const clientToken = generateUUID();
await submitMediaProducingJob({
ClientToken: clientToken,
// ... 其他参数
});
```
### 2. 状态轮询
```javascript
// 提交作业后使用JobId轮询查询状态
const jobId = response.JobId;
const status = await getMediaProducingJob({ JobId: jobId });
```
### 3. 错误重试
```javascript
// 针对网络错误或限流错误进行指数退避重试
try {
await submitMediaProducingJob(params);
} catch (error) {
if (error.code === 'Throttling.User') {
// 等待后重试
await sleep(1000);
await submitMediaProducingJob(params);
}
}
```
### 4. 资源清理
```javascript
// 处理完成后,及时清理不必要的中间文件
await deleteMediaAssets([tempMediaId1, tempMediaId2]);
```
---
*文档版本v1.0* | *最后更新2025-11-29*

View File

@@ -1,155 +0,0 @@
# 可灵数字人功能集成报告
## 功能概述
基于302.ai的可灵API我们成功集成了独立的人脸识别功能为后续对口型服务提供基础支持。
## 后端开发
### 1. API客户端
- **文件**: `yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/kling/KlingClient.java`
- **功能**: 调用302.ai的Identify-Face接口
- **复用**: 使用现有的LatentsyncProperties配置API密钥已配置
### 2. 业务服务
- **Service接口**: `KlingService.java`
- **Service实现**: `KlingServiceImpl.java`
- **Controller**: `KlingController.java` (路径: `/webApi/api/tik/kling/identify-face`)
### 3. 数据传输对象
- **请求VO**: `KlingIdentifyFaceReqVO.java`
- **响应VO**: `KlingIdentifyFaceRespVO.java`
- **DTO**: `KlingIdentifyFaceRequest.java`, `KlingIdentifyFaceResponse.java`
### API接口
```
POST /webApi/api/tik/kling/identify-face
Content-Type: application/json
Request:
{
"videoUrl": "https://example.com/video.mp4"
}
Response:
{
"code": 0,
"data": {
"sessionId": "session_xxxxx",
"faceData": [
{
"faceId": "face_001",
"faceImage": "https://...",
"startTime": 1000,
"endTime": 5000
}
]
}
}
```
## 前端开发
### 1. API服务
- **文件**: `frontend/app/web-gold/src/api/kling.js`
- **功能**: `identifyFace()` 方法
### 2. 页面组件
- **文件**: `frontend/app/web-gold/src/views/kling/IdentifyFace.vue`
- **功能**:
- 视频上传(支持 .mp4/.mov
- 拖拽上传支持
- 进度显示
- 识别结果展示(人脸列表、时间段)
- 科技极简风格UI黑蓝紫色调
### 3. 路由配置
- **文件**: `frontend/app/web-gold/src/router/index.js`
- **路径**: `/digital-human/kling`
- **菜单项**: "可灵数字人"(位于"数字人"分组下)
## 技术特点
### 1. 配置复用
- 复用了现有的 `LatentsyncProperties` 配置
- API Key: `sk-0IZJ2oo7VCkegFuF3JRsSRtyFUsIvLoHNK8OpulnlsStFN78`
- Base URL: `https://api.302.ai`
### 2. 代码架构
- 遵循Yudao框架的分层架构
- 复用现有的客户端模式参考LatentsyncClient
- VO/DTO分层设计
### 3. 前端设计
- 采用Vue 3 Composition API
- 响应式设计(支持移动端)
- 科技极简风格(渐变背景、毛玻璃效果)
## 文件结构
```
yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/kling/
├── KlingClient.java # API客户端
├── KlingService.java # 服务接口
├── KlingServiceImpl.java # 服务实现
├── KlingController.java # REST控制器
├── dto/
│ ├── KlingIdentifyFaceRequest.java
│ └── KlingIdentifyFaceResponse.java
└── vo/
├── KlingIdentifyFaceReqVO.java
└── KlingIdentifyFaceRespVO.java
frontend/app/web-gold/src/
├── api/
│ └── kling.js # 前端API
├── views/
│ └── kling/
│ └── IdentifyFace.vue # 识别页面
└── router/
└── index.js # 路由配置(含可灵路由)
```
## 使用说明
### 1. 访问页面
- 登录系统后,点击左侧菜单 **数字人** > **可灵数字人**
### 2. 上传视频
- 支持格式:.mp4, .mov
- 文件大小:≤ 100MB
- 视频时长2-60秒
- 支持拖拽上传
### 3. 人脸识别
- 点击"开始识别"按钮
- 系统调用302.ai API进行分析
- 显示识别结果:
- 会话ID用于后续操作
- 检测到的人脸列表
- 每张人脸的可对口型时间段
## 注意事项
1. **视频URL**: 目前页面使用本地URL进行测试生产环境需要先上传到OSS获取公网URL
2. **API密钥**: 已使用现有的302.ai配置无需额外配置
3. **跨域**: 确保前端已配置API代理到后端
## 后续扩展
1. **口型同步功能**: 基于sessionId进行口型同步
2. **批量处理**: 支持多个视频的批量识别
3. **历史记录**: 保存识别历史到数据库
4. **结果导出**: 支持导出识别结果
## 测试建议
1. 测试不同格式的视频文件
2. 测试大文件上传接近100MB
3. 测试网络异常情况
4. 测试识别失败场景
---
**开发完成时间**: 2024-11-30
**状态**: 后端API完成前端页面完成待测试联调

View File

@@ -1,314 +0,0 @@
# 数字人任务策略模式优化
## 概述
本次重构将数字人任务的口型同步逻辑从传统的 if-else 条件判断优化为**策略模式**,提升了代码的可维护性、可扩展性和可测试性。
## 架构设计
### 1. 策略模式结构
```
┌─────────────────────────────────────────┐
│ DigitalHumanTaskServiceImpl │
│ - syncLip() │
│ ├─ lipSyncStrategyFactory │
│ └─ getStrategyForTask() │
└───────────────┬─────────────────────────┘
┌───────────────────┐
│ LipSyncStrategy │ (接口)
│ + syncLip() │
│ + getStrategyName()│
│ + supports() │
│ + getPriority() │
│ + getDescription()│
└────────┬──────────┘
├────────────────────┬──────────────────────
│ │
▼ ▼
┌──────────────┐ ┌──────────────────────┐
│KlingStrategy │ │LatentsyncStrategy │
│ - Priority: │ │ - Priority: 50 │
│ 100 │ │ - Fallback策略 │
│ - Advanced │ │ - 通用口型同步 │
│ Lip-Sync │ └──────────────────────┘
└──────────────┘
```
### 2. 核心组件
#### 2.1 LipSyncStrategy 接口
```java
public interface LipSyncStrategy {
// 执行口型同步
String syncLip(TikDigitalHumanTaskDO task, String audioUrl) throws Exception;
// 策略名称
String getStrategyName();
// 是否支持该任务
boolean supports(TikDigitalHumanTaskDO task);
// 优先级(数字越大优先级越高)
int getPriority();
// 策略描述
String getDescription();
}
```
#### 2.2 LipSyncStrategyFactory 工厂类
```java
@Component
public class LipSyncStrategyFactory {
// 注册策略
public void registerStrategy(LipSyncStrategy strategy);
// 根据任务选择策略
public LipSyncStrategy getStrategyForTask(TikDigitalHumanTaskDO task);
// 获取所有支持的策略
public List<LipSyncStrategy> getAllStrategies();
}
```
#### 2.3 具体策略实现
**KlingLipSyncStrategy优先级 100**
- 使用可灵 advanced-lip-sync API
- 要求:`klingSessionId``klingFaceId`
- 如果参数缺失,自动回退到 Latentsync
**LatentsyncLipSyncStrategy优先级 50**
- 使用 302.ai Latentsync 通用接口
- 作为默认回退策略
- 支持所有标准的口型同步任务
### 3. 工作流程
```
1. 创建任务 → 验证参数 → 存储记录
2. 异步处理 → prepareFiles → synthesizeVoice
3. 选择策略 → getStrategyForTask()
├─ Kling策略如果支持
└─ Latentsync策略回退
4. 执行口型同步 → 提交异步任务 → 加入轮询队列
5. 轮询服务检测状态 → 更新任务 → 返回结果
```
## 重构详情
### 修改前的问题
**问题 1违反开闭原则**
```java
// 传统 if-else 实现
if ("302ai".equals(aiProvider)) {
syncWithLatentsync();
} else if ("kling".equals(aiProvider)) {
syncWithKling();
} else if ("aliyun".equals(aiProvider)) {
// TODO: 新增供应商需要修改此方法
}
```
**问题 2职责不单一**
- 每个分支包含大量业务逻辑
- 难以单元测试
- 重复代码多
**问题 3可扩展性差**
- 新增 AI 供应商需要修改核心服务类
- 违反单一职责原则
### 修改后的优势
**优势 1符合开闭原则**
```java
// 新增供应商只需:
1. 创建新的策略实现类
2. 使用 @Component 注解自动注册
3. 无需修改 DigitalHumanTaskServiceImpl
```
**优势 2职责分离**
```java
// 每个策略类专注自己的业务逻辑
KlingLipSyncStrategy 专注可灵接口
LatentsyncLipSyncStrategy 专注 Latentsync 接口
DigitalHumanTaskServiceImpl 专注任务流程编排
```
**优势 3可测试性强**
```java
// 可以独立测试每个策略
@Test
public void testKlingStrategy() {
// 测试可灵策略逻辑
}
@Test
public void testLatentsyncStrategy() {
// 测试 Latentsync 策略逻辑
}
```
## 新增文件
### 1. 策略接口
- `cn.iocoder.yudao.module.tik.voice.strategy.LipSyncStrategy.java`
### 2. 策略工厂
- `cn.iocoder.yudao.module.tik.voice.strategy.LipSyncStrategyFactory.java`
### 3. 具体策略
- `cn.iocoder.yudao.module.tik.voice.strategy.impl.KlingLipSyncStrategy.java`
- `cn.iocoder.yudao.module.tik.voice.strategy.impl.LatentsyncLipSyncStrategy.java`
## 修改文件
### 1. DigitalHumanTaskServiceImpl.java
- ✅ 移除 `syncWithLatentsync()` 方法
- ✅ 移除 `syncWithKling()` 方法
- ✅ 重构 `syncLip()` 方法使用策略模式
- ✅ 注入 `LipSyncStrategyFactory``KlingService`
## 策略选择逻辑
```java
public LipSyncStrategy getStrategyForTask(TikDigitalHumanTaskDO task) {
// 1. 获取所有支持的策略
List<LipSyncStrategy> supportedStrategies = strategies.stream()
.filter(strategy -> strategy.supports(task))
.collect(Collectors.toList());
// 2. 按优先级排序
supportedStrategies.sort((a, b) -> b.getPriority() - a.getPriority());
// 3. 返回最高优先级的策略
return supportedStrategies.isEmpty() ? null : supportedStrategies.get(0);
}
```
## 扩展指南
### 新增 AI 供应商(例如:阿里云)
**步骤 1创建策略类**
```java
@Component
public class AliyunLipSyncStrategy implements LipSyncStrategy {
@Override
public String syncLip(TikDigitalHumanTaskDO task, String audioUrl) {
// 1. 构建阿里云请求参数
// 2. 调用阿里云 API
// 3. 加入轮询队列
}
@Override
public String getStrategyName() {
return "aliyun";
}
@Override
public boolean supports(TikDigitalHumanTaskDO task) {
return "aliyun".equalsIgnoreCase(task.getAiProvider());
}
@Override
public int getPriority() {
return 75; // 可灵之下Latentsync 之上
}
@Override
public String getDescription() {
return "阿里云语音驱动视频服务";
}
}
```
**步骤 2无需修改其他代码**
- 策略工厂会自动注册
- 任务处理时会自动选择
## 回退机制
当高优先级策略因参数缺失无法执行时,会自动回退到低优先级策略:
```
Kling任务 → KlingStrategy检查参数
├─ 参数完整 → 使用 Kling advanced-lip-sync
└─ 参数缺失 → 返回false尝试Latentsync策略
└─ LatentsyncStrategy → 支持 → 使用通用接口
```
## 性能优化
1. **策略缓存**:工厂类使用 `@Cacheable` 缓存策略选择结果
2. **延迟加载**:策略按需创建和初始化
3. **优先级排序**:一次性排序,避免重复计算
## 测试建议
### 单元测试
```java
@Test
public void testKlingStrategySupports() {
// 测试支持条件
}
@Test
public void testKlingStrategySync() {
// 测试口型同步逻辑
}
@Test
public void testLatentsyncStrategySupports() {
// 测试回退逻辑
}
@Test
public void testStrategyFactory() {
// 测试策略选择逻辑
}
```
### 集成测试
```java
@Test
public void testEndToEndWithKling() {
// 测试完整的可灵流程
}
@Test
public void testEndToEndWithLatentsync() {
// 测试完整的 Latentsync 流程
}
```
## 总结
通过策略模式的引入,我们实现了:
**高内聚低耦合** - 每个策略专注自己的业务
**易于扩展** - 新增供应商无需修改核心代码
**易于维护** - 策略之间相互独立,修改互不影响
**易于测试** - 每个策略可以独立单元测试
**代码复用** - 移除重复代码,统一处理流程
这套架构设计遵循了 SOLID 原则,特别是:
- **单一职责原则SRP**:每个策略只负责一种 AI 供应商
- **开放封闭原则OCP**:对扩展开放,对修改封闭
- **依赖倒置原则DIP**:依赖抽象而非具体实现

View File

@@ -1,213 +0,0 @@
# 命名冲突问题修复
## 🚨 问题描述
在之前的重构中,我们遇到了一个严重的**命名歧义**问题:
### 原始问题代码
```java
public class KlingLipSyncCreateResponse {
private Data data; // ❌ 成员变量类型是 Data
private String message;
@Data
public static class Data { // ❌ 静态内部类也叫 Data
private String taskId;
}
}
```
### 问题分析
1. **歧义性**
- `private Data data;` - 这里 `Data` 既是成员变量的**类型**,又是**静态内部类**的名称
- 编译器需要推断 `Data` 是指内部类 `KlingLipSyncCreateResponse.Data` 还是其他包中的类
2. **可读性差**
- `private Data data;` - 这样的命名不够清晰
- 不明确 `data` 变量的具体含义
3. **潜在错误**
- 如果有其他包的 `Data` 类被导入,可能会导致类型混淆
- 代码维护困难
---
## ✅ 修复方案
### 修复后的代码
```java
public class KlingLipSyncCreateResponse {
private Data data; // ✅ 现在明确指向内部静态类 Data
private String message;
@Data
public static class Data { // ✅ 静态内部类,名称清晰
private String taskId;
}
}
```
### 为什么这样修复可行?
1. **明确性**
- 在类内部,`Data` 默认指向当前类的内部静态类
- 编译器可以正确解析类型
2. **保持简洁**
- 保持原有的简洁命名
- 内部类的名称与变量名称不同,不会有歧义
3. **符合规范**
- 静态内部类名称使用首字母大写的驼峰命名法
- 成员变量名称使用小写开头的驼峰命名法
---
## 📋 受影响文件
以下文件都存在相同的问题,已全部修复:
### DTO 包
1.`KlingLipSyncCreateResponse.java`
2.`KlingLipSyncQueryResponse.java`
### VO 包
1.`KlingLipSyncCreateRespVO.java`
2.`KlingLipSyncQueryRespVO.java`
---
## 🔍 代码检查清单
### 检查点 1静态内部类命名
```java
// ✅ 正确:静态内部类使用首字母大写的驼峰命名
public static class Data { }
// ✅ 正确:静态内部类使用首字母大写的驼峰命名
public static class TaskInfo { }
// ❌ 错误:不应该使用小写命名
public static class data { }
```
### 检查点 2成员变量命名
```java
// ✅ 正确:成员变量使用小写开头的驼峰命名
private Data data;
private String message;
// ✅ 正确:变量名应该尽量描述性
private ResponseData responseData;
```
### 检查点 3类型引用
```java
// ✅ 在类内部,默认指向内部静态类
private Data data; // 指向 KlingXxx.Data
// ✅ 如果有歧义,可以显式指定
private KlingLipSyncCreateResponse.Data data;
// ✅ 跨包引用需要完整路径
private com.example.OtherData otherData;
```
---
## 💡 最佳实践
### 1. 静态内部类命名规范
```java
// ✅ 推荐:使用有意义的名称
public static class ResponseData { }
public static class RequestData { }
// ✅ 推荐:即使简单也要遵循规范
public static class Data { }
public static class Info { }
```
### 2. 避免歧义的策略
```java
// ✅ 方法1使用描述性变量名
private Data responseData; // 明确这是响应数据
// ✅ 方法2使用显式类型
private KlingLipSyncCreateResponse.Data data;
// ✅ 方法3重构类名
public static class CreateResponseData { }
private CreateResponseData data;
```
### 3. 导入语句的注意事项
```java
// ✅ 如果导入了其他 Data 类,优先使用内部类
import com.example.Data; // 可能冲突
// ✅ 解决方案:使用全限定名
private com.example.Data externalData;
private Data internalData; // 指向内部静态类
```
---
## 📝 测试建议
### 单元测试
```java
@Test
public void testResponseData() {
KlingLipSyncCreateResponse response = new KlingLipSyncCreateResponse();
KlingLipSyncCreateResponse.Data data = response.getData();
assertNotNull(data);
}
```
### 集成测试
```java
@Test
public void testJsonSerialization() {
KlingLipSyncCreateResponse response = new KlingLipSyncCreateResponse();
String json = objectMapper.writeValueAsString(response);
assertNotNull(json);
}
```
---
## ✅ 验证清单
修复完成后,请检查以下项目:
- [ ] 所有文件的命名冲突已修复
- [ ] 编译无错误和警告
- [ ] 单元测试通过
- [ ] 集成测试通过
- [ ] JSON 序列化正常
- [ ] 类型转换正常
---
## 🎯 总结
本次修复解决了静态内部类与成员变量命名冲突的问题。通过遵循Java命名规范我们确保了
1. **代码清晰** - 命名无歧义,易于理解
2. **类型安全** - 编译器能正确解析类型
3. **易于维护** - 遵循最佳实践,便于后续维护
4. **向下兼容** - API 不变,不影响现有调用
这次修复提升了代码质量,为项目的长期维护奠定了坚实基础。

View File

@@ -1,194 +0,0 @@
# VO/DTO 静态内部类重构 - 最终总结
## 🎯 重构目标
将大量重复的独立VO/DTO类合并使用静态内部类减少类数量。
---
## ✅ 已完成的修复
### 📁 DTO 包 (dto/)
**重构前:**
- 13+ 个文件,包括大量重复的独立类
**重构后5个核心文件**
```
1. KlingIdentifyFaceRequest.java
2. KlingIdentifyFaceResponse.java
3. KlingLipSyncCreateRequest.java
└─ 静态内部类: FaceChoose
4. KlingLipSyncCreateResponse.java
└─ 静态内部类: Data, TaskInfo
5. KlingLipSyncQueryResponse.java
└─ 静态内部类: Data, TaskInfo, ParentVideo, TaskResult, Video
```
### 📁 VO 包 (vo/)
**重构前:**
- 13+ 个文件,包括大量重复的独立类
**重构后5个核心文件**
```
1. KlingIdentifyFaceReqVO.java
2. KlingIdentifyFaceRespVO.java
3. KlingLipSyncCreateReqVO.java
└─ 静态内部类: FaceChooseVO
4. KlingLipSyncCreateRespVO.java
└─ 静态内部类: Data, TaskInfo
5. KlingLipSyncQueryRespVO.java
└─ 静态内部类: Data, TaskInfo, ParentVideo, TaskResult, Video
```
---
## 📊 重构成果
### 类数量对比
| 类别 | 重构前 | 重构后 | 减少 |
|------|--------|--------|------|
| DTO文件 | 13+ | 5 | **-62%** |
| VO文件 | 13+ | 5 | **-62%** |
| 总文件数 | 26+ | 10 | **-62%** |
### 命名对比
| 场景 | 重构前 | 重构后 |
|------|--------|--------|
| 请求对象 | `KlingFaceChooseVO` (独立类) | `KlingLipSyncCreateReqVO.FaceChooseVO` (静态内部类) |
| 响应数据 | `KlingLipSyncCreateDataVO` (独立类) | `KlingLipSyncCreateRespVO.Data` (静态内部类) |
| 任务信息 | `KlingLipSyncTaskInfoVO` (独立类) | `KlingLipSyncCreateRespVO.TaskInfo` (静态内部类) |
---
## 🔧 技术方案
### 方案选择:静态内部类
**为什么选择静态内部类?**
1.**减少类数量** - 避免创建大量独立的重复类
2.**保持类型安全** - 通过静态内部类保持强类型
3.**逻辑分组** - 相关类组织在一起,便于理解
4.**API清晰** - 层次结构明确:`OuterClass.InnerClass`
5.**BeanUtils兼容** - 支持 DTO ↔ VO 转换
### 注意事项
1. **@Data 注解** - 静态内部类可以使用 @Data
2. **Lombok 配置** - 确保项目正确配置 Lombok
3. **Bean 转换** - 使用 `BeanUtils.toBean()` 进行对象转换
4. **JSON 序列化** - @JsonProperty 注解确保正确的序列化
---
## 📝 使用示例
### 前端调用 (不变)
```javascript
// 仍然使用相同的API
const response = await createLipSyncTask({
sessionId: 'xxx',
faceChoose: [{
faceId: 'xxx',
soundFile: 'audio.mp3'
}]
})
```
### 后端转换
```java
// DTO -> VO 转换
KlingLipSyncCreateRespVO vo = BeanUtils.toBean(dto, KlingLipSyncCreateRespVO.class);
// 访问静态内部类
String taskId = vo.getData().getTaskId();
String externalTaskId = vo.getData().getTaskInfo().getExternalTaskId();
```
### 策略模式 (不变)
```java
// 仍然使用相同的策略
LipSyncStrategy strategy = lipSyncStrategyFactory.getStrategyForTask(task);
return strategy.syncLip(task, audioUrl);
```
---
## 🎨 文件结构
### DTO 包结构
```
cn.iocoder.yudao.module.tik.kling.dto
├── KlingIdentifyFaceRequest.java
├── KlingIdentifyFaceResponse.java
├── KlingLipSyncCreateRequest.java
│ └── FaceChoose (静态内部类)
├── KlingLipSyncCreateResponse.java
│ ├── Data (静态内部类)
│ └── TaskInfo (静态内部类)
└── KlingLipSyncQueryResponse.java
├── Data (静态内部类)
├── TaskInfo (静态内部类)
├── ParentVideo (静态内部类)
├── TaskResult (静态内部类)
└── Video (静态内部类)
```
### VO 包结构
```
cn.iocoder.yudao.module.tik.kling.vo
├── KlingIdentifyFaceReqVO.java
├── KlingIdentifyFaceRespVO.java
├── KlingLipSyncCreateReqVO.java
│ └── FaceChooseVO (静态内部类)
├── KlingLipSyncCreateRespVO.java
│ ├── Data (静态内部类)
│ └── TaskInfo (静态内部类)
└── KlingLipSyncQueryRespVO.java
├── Data (静态内部类)
├── TaskInfo (静态内部类)
├── ParentVideo (静态内部类)
├── TaskResult (静态内部类)
└── Video (静态内部类)
```
---
## ✨ 优势总结
### 1. **代码简洁**
- 从 26+ 个文件减少到 10 个文件
- 减少 62% 的类数量
### 2. **结构清晰**
- 相关类组织在同一个文件中
- 层次结构明确
### 3. **易于维护**
- 减少重复代码
- 便于理解代码逻辑
### 4. **类型安全**
- 保持强类型检查
- 避免类型混淆
### 5. **向下兼容**
- API 接口不变
- 前端调用不变
- 策略模式逻辑不变
---
## 🎉 总结
本次重构成功将大量重复的VO/DTO类合并为静态内部类大大简化了项目结构。从**26+个文件**减少到**10个文件**,减少**62%**的类数量同时保持了所有功能的完整性和API的兼容性。
重构后的代码更加简洁、清晰、易于维护,为后续的功能扩展奠定了良好的基础。

View File

@@ -1,177 +0,0 @@
# VO/DTO 静态内部类重构总结
## 已完成的修复
### 1. KlingLipSyncCreateRespVO 和相关类
#### 原始问题
`KlingLipSyncCreateRespVO` 中定义了静态内部类,导致使用不便且不符合最佳实践。
#### 修复方案
**已完成**
- 移除 `KlingLipSyncCreateRespVO.Data` 静态内部类
- 移除 `KlingLipSyncCreateRespVO.TaskInfo` 静态内部类
- 创建独立的 `KlingLipSyncCreateDataVO.java`
- 创建独立的 `KlingLipSyncTaskInfoVO.java`
- 更新 `KlingLipSyncCreateRespVO` 使用新的独立类
#### 文件变更
```
新增文件:
- KlingLipSyncCreateDataVO.java
- KlingLipSyncTaskInfoVO.java
修改文件:
- KlingLipSyncCreateRespVO.java (移除了静态内部类)
```
### 2. KlingLipSyncCreateReqVO 和相关类
#### 原始问题
`KlingLipSyncCreateReqVO` 中定义了 `FaceChooseVO` 静态内部类。
#### 修复方案
**已完成**
- 移除 `KlingLipSyncCreateReqVO.FaceChooseVO` 静态内部类
- 创建独立的 `KlingFaceChooseVO.java`
- 更新 `KlingLipSyncCreateReqVO` 使用新的独立类
- 更新 `KlingLipSyncStrategy.java` 引用新类
#### 文件变更
```
新增文件:
- KlingFaceChooseVO.java
修改文件:
- KlingLipSyncCreateReqVO.java
- KlingLipSyncStrategy.java
```
### 3. DTO 类修复
#### 修复方案
**已完成**
- 移除 `KlingLipSyncCreateRequest.FaceChoose` 静态内部类
- 创建独立的 `KlingFaceChoose.java` (DTO版本)
- 移除 `KlingLipSyncCreateResponse.Data` 静态内部类
- 移除 `KlingLipSyncCreateResponse.TaskInfo` 静态内部类
- 创建独立的 `KlingLipSyncCreateData.java` (DTO版本)
- 创建独立的 `KlingLipSyncTaskInfo.java` (DTO版本)
- 更新相关引用
#### 文件变更
```
新增文件:
- KlingFaceChoose.java (dto package)
- KlingLipSyncCreateData.java (dto package)
- KlingLipSyncTaskInfo.java (dto package)
修改文件:
- KlingLipSyncCreateRequest.java
- KlingLipSyncCreateResponse.java
```
## 需要继续修复的文件
### 待修复 1: KlingLipSyncQueryResponse.java
**问题**: 存在多层嵌套的静态内部类
```java
public class KlingLipSyncQueryResponse {
private Data data;
@Data
public static class Data {
private TaskInfo taskInfo;
private TaskResult taskResult;
@Data
public static class TaskInfo {
private ParentVideo parentVideo;
@Data
public static class ParentVideo {
private String id;
private String url;
private String duration;
}
}
@Data
public static class TaskResult {
private List<Video> videos;
@Data
public static class Video {
private String id;
private String url;
private String duration;
}
}
}
}
```
**建议修复方案**:
1. 创建 `KlingLipSyncQueryData.java`
2. 创建 `KlingLipSyncQueryTaskInfo.java`
3. 创建 `KlingParentVideo.java`
4. 创建 `KlingLipSyncQueryTaskResult.java`
5. 创建 `KlingLipSyncVideo.java`
6. 更新 `KlingLipSyncQueryResponse.java` 使用新类
### 待修复 2: KlingLipSyncQueryRespVO.java
**问题**: 与 `KlingLipSyncQueryResponse.java` 类似,存在多层嵌套的静态内部类。
**建议修复方案**: 与上面类似创建对应的VO类。
### 待修复 3: 其他文件中的静态内部类
以下文件可能也需要检查:
- `AppAiChatMessageRespVO.java`
- `AppAiChatMessageSendRespVO.java`
- `LatentsyncSubmitResponse.java`
- `AppTikLatentsyncResultRespVO.java`
## 重构的好处
### ✅ 已实现的好处
1. **更好的代码可读性** - 独立类更清晰
2. **便于单元测试** - 可以单独测试每个类
3. **更好的序列化兼容性** - 避免静态内部类的序列化问题
4. **符合最佳实践** - VO/DTO应该使用顶级类
5. **使用更方便** - 无需通过外部类访问
### 📊 对比
| 修复前 | 修复后 |
|--------|--------|
| `KlingLipSyncCreateRespVO.Data data` | `KlingLipSyncCreateDataVO data` |
| `KlingLipSyncCreateReqVO.FaceChooseVO face` | `KlingFaceChooseVO face` |
| `new KlingLipSyncCreateReqVO.FaceChooseVO()` | `new KlingFaceChooseVO()` |
## 建议的修复顺序
### 优先级 1 (高)
1. `KlingLipSyncQueryResponse.java` - 使用广泛,影响大
2. `KlingLipSyncQueryRespVO.java` - 与上面配对
### 优先级 2 (中)
3. 检查并修复其他模块的VO/DTO静态内部类
### 优先级 3 (低)
4. 编写测试用例验证修复后的类
## 注意事项
1. **保持命名一致性** - VO类使用 `*VO` 后缀DTO类使用普通名称
2. **同步更新引用** - 修改后需要更新所有引用这些类的文件
3. **测试重要性** - 确保JSON序列化/反序列化正常工作
4. **文档更新** - 更新API文档以反映新的类结构
## 总结
到目前为止,已成功修复了 **6个文件** 的静态内部类问题,创建了 **6个新的独立类**。这些修复提高了代码质量和可维护性,为后续的维护和扩展奠定了良好基础。
建议继续完成剩余文件的修复,特别是 `KlingLipSyncQueryResponse.java``KlingLipSyncQueryRespVO.java`,因为它们被广泛使用。