9.3 KiB
9.3 KiB
RunningHub集成实现清单
✅ 已完成
- ✅ 创建Provider接口和DTO
- ✅ 数据库表扩展(V5迁移脚本)
- ✅ 配置文件扩展
- ✅ 实体类更新
🔨 待实现(按优先级)
1. 实现OpenAIProvider适配器
文件: src/main/java/com/dora/service/provider/impl/OpenAIProviderImpl.java
@Service
@Slf4j
@RequiredArgsConstructor
public class OpenAIProviderImpl implements AIProvider {
private final ThirdPartyApiService thirdPartyApiService;
@Override
public ProviderTaskResponse submitTask(ProviderTaskRequest request) {
// 调用现有的 thirdPartyApiService
// 同步返回结果
}
@Override
public String getProviderName() {
return "openai";
}
@Override
public boolean isAsyncProvider() {
return false; // OpenAI是同步API
}
}
2. 实现RunningHubProvider适配器
文件: src/main/java/com/dora/service/provider/impl/RunningHubProviderImpl.java
关键逻辑:
@Override
public ProviderTaskResponse submitTask(ProviderTaskRequest request) {
// 1. 从providerConfig中获取webappId
// 2. 构建nodeInfoList
// - prompt节点
// - model节点(portrait/landscape等)
// - duration_seconds节点
// 3. POST到 /task/openapi/ai-app/run
// 4. 解析响应获取taskId
// 5. 返回ProviderTaskResponse,status=PROCESSING
}
@Override
public ProviderTaskStatus queryTaskStatus(String providerTaskId) {
// POST到 /task/openapi/status
// 解析响应:QUEUED/RUNNING/FAILED/SUCCESS
}
@Override
public ProviderTaskResult getTaskResult(String providerTaskId) {
// POST到 /task/openapi/outputs
// 解析data数组,获取fileUrl
}
RunningHub请求DTO:
@Data
class RunningHubSubmitRequest {
private String webappId;
private String apiKey;
private List<RunningHubNodeInfo> nodeInfoList;
}
@Data
class RunningHubNodeInfo {
private String nodeId;
private String fieldName;
private String fieldValue;
private String fieldData; // 可选
private String description;
}
3. 创建AIProviderService路由服务
文件: src/main/java/com/dora/service/AIProviderService.java
@Service
@Slf4j
@RequiredArgsConstructor
public class AIProviderService {
private final Map<String, AIProvider> providerMap;
private final PointsConfigMapper pointsConfigMapper;
@PostConstruct
public void init() {
// 初始化providerMap,key为providerType
}
public AIProvider getProvider(String modelName) {
// 1. 从points_config表查询模型配置
// 2. 获取providerType
// 3. 从providerMap中获取对应的Provider
PointsConfig config = pointsConfigMapper.findByModelName(modelName);
String providerType = config.getProviderType();
return providerMap.get(providerType);
}
}
4. 添加RunningHub轮询定时器
文件: src/main/java/com/dora/scheduler/RunningHubPollingScheduler.java
@Component
@Slf4j
@RequiredArgsConstructor
public class RunningHubPollingScheduler {
private final AiTaskMapper aiTaskMapper;
private final AIProviderService providerService;
private final AiTaskService aiTaskService;
@Scheduled(fixedDelay = 5000) // 每5秒执行一次
public void pollRunningHubTasks() {
// 1. 查询 status='processing' 且 provider_type='runninghub' 的任务
List<AiTask> tasks = aiTaskMapper.findProcessingTasksByProvider("runninghub");
for (AiTask task : tasks) {
try {
AIProvider provider = providerService.getProvider(task.getModelName());
// 2. 查询任务状态
ProviderTaskStatus status = provider.queryTaskStatus(task.getProviderTaskId());
// 3. 根据状态更新
if (status.getStatus() == Status.SUCCESS) {
// 获取结果
ProviderTaskResult result = provider.getTaskResult(task.getProviderTaskId());
// 更新任务为completed
aiTaskService.markTaskCompleted(task.getTaskNo(), result.getFiles().get(0).getFileUrl());
} else if (status.getStatus() == Status.FAILED) {
// 标记为失败
aiTaskService.markTaskFailed(task.getTaskNo(), status.getErrorMessage());
}
} catch (Exception e) {
log.error("轮询RunningHub任务失败: {}", task.getTaskNo(), e);
}
}
}
}
5. 更新AiTaskMapper
文件: src/main/resources/mapper/AiTaskMapper.xml
<!-- 新增:查询指定provider的processing任务 -->
<select id="findProcessingTasksByProvider" parameterType="string" resultType="com.dora.entity.AiTask">
SELECT * FROM ai_task
WHERE status = 'processing'
AND provider_type = #{providerType}
AND is_deleted = 0
ORDER BY update_time ASC
LIMIT 100
</select>
<!-- 更新insert语句,添加provider字段 -->
<insert id="insert" parameterType="com.dora.entity.AiTask" useGeneratedKeys="true" keyProperty="id">
INSERT INTO ai_task (
task_no, user_id, model_name, task_type, provider_type, provider_task_id, provider_response,
prompt, image_url, image_base64, aspect_ratio,
status, progress, progress_message, points_frozen, points_consumed, result_url,
error_message, queue_time, start_time, complete_time, expire_time
)
VALUES (
#{taskNo}, #{userId}, #{modelName}, #{taskType}, #{providerType}, #{providerTaskId}, #{providerResponse},
#{prompt}, #{imageUrl}, #{imageBase64}, #{aspectRatio},
#{status}, #{progress}, #{progressMessage}, #{pointsFrozen}, #{pointsConsumed}, #{resultUrl},
#{errorMessage}, #{queueTime}, #{startTime}, #{completeTime}, #{expireTime}
)
</insert>
6. 更新AiTaskServiceImpl
文件: src/main/java/com/dora/service/impl/AiTaskServiceImpl.java
@Override
@Transactional(rollbackFor = Exception.class)
public AiTask createTask(CreateTaskDto createTaskDto) {
// 1. 验证模型并获取价格
PointsConfig pointsConfig = pointsConfigMapper.findByModelName(createTaskDto.getModelName());
// 2. 扣除积分
// ...
// 3. 创建任务
AiTask task = new AiTask();
// ... 设置基本字段
task.setProviderType(pointsConfig.getProviderType()); // 设置provider类型
aiTaskMapper.insert(task);
// 4. 判断provider类型
if ("runninghub".equals(pointsConfig.getProviderType())) {
// RunningHub:直接提交到服务商
submitToRunningHub(task, pointsConfig);
} else {
// OpenAI:加入队列,由原有流程处理
queueService.enqueue(task.getModelName(), task.getTaskNo());
updateTaskStatus(task.getTaskNo(), "queued", "任务已进入等待队列");
}
return task;
}
private void submitToRunningHub(AiTask task, PointsConfig config) {
try {
AIProvider provider = aiProviderService.getProvider(task.getModelName());
// 构建请求
ProviderTaskRequest request = ProviderTaskRequest.builder()
.modelName(task.getModelName())
.prompt(task.getPrompt())
.imageUrl(task.getImageUrl())
.imageBase64(task.getImageBase64())
.providerConfig(parseProviderConfig(config.getProviderConfig()))
.build();
// 提交任务
ProviderTaskResponse response = provider.submitTask(request);
// 更新任务
task.setProviderTaskId(response.getProviderTaskId());
task.setProviderResponse(JSON.toJSONString(response.getRawResponse()));
task.setStatus("processing");
task.setStartTime(LocalDateTime.now());
aiTaskMapper.update(task);
} catch (Exception e) {
log.error("提交RunningHub任务失败: {}", task.getTaskNo(), e);
markTaskFailed(task.getTaskNo(), "提交失败: " + e.getMessage());
}
}
🧪 测试步骤
1. 数据库迁移
mysql -u root -p your_database < V5__add_provider_support.sql
2. 配置RunningHub API Key
修改application.yml中的ai.providers.runninghub.api-key
3. 测试RunningHub模型
curl -X POST "http://localhost:8081/user/ai/tasks/submit" \
-H "Authorization: Bearer YOUR_JWT_OR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"modelName": "rh_sora2_portrait",
"prompt": "测试视频生成"
}'
4. 查看轮询日志
tail -f logs/application.log | grep "RunningHub"
📝 注意事项
- 配置管理:RunningHub的webappId需要从数据库的provider_config中读取
- 错误处理:RunningHub API可能返回各种错误,需要完善异常处理
- 超时控制:设置最大轮询次数,防止任务无限轮询
- 并发限制:轮询时要控制并发数,避免给RunningHub造成压力
- 日志记录:详细记录每次API调用的请求和响应,便于排查问题
🎯 预期效果
完成后,系统将支持:
- ✅ OpenAI格式的同步API(原有功能,无影响)
- ✅ RunningHub的异步API(新功能)
- ✅ 用户无感切换,根据模型自动选择服务商
- ✅ 统一的任务管理和状态追踪