315 lines
8.6 KiB
Markdown
315 lines
8.6 KiB
Markdown
# 数字人任务策略模式优化
|
||
|
||
## 概述
|
||
|
||
本次重构将数字人任务的口型同步逻辑从传统的 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)**:依赖抽象而非具体实现
|