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)**:依赖抽象而非具体实现
|