# RunningHub集成实现清单 ## ✅ 已完成 1. ✅ 创建Provider接口和DTO 2. ✅ 数据库表扩展(V5迁移脚本) 3. ✅ 配置文件扩展 4. ✅ 实体类更新 ## 🔨 待实现(按优先级) ### 1. 实现OpenAIProvider适配器 **文件:** `src/main/java/com/dora/service/provider/impl/OpenAIProviderImpl.java` ```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` **关键逻辑:** ```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:** ```java @Data class RunningHubSubmitRequest { private String webappId; private String apiKey; private List 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` ```java @Service @Slf4j @RequiredArgsConstructor public class AIProviderService { private final Map 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` ```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 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` ```xml 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} ) ``` ### 6. 更新AiTaskServiceImpl **文件:** `src/main/java/com/dora/service/impl/AiTaskServiceImpl.java` ```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. 数据库迁移 ```bash mysql -u root -p your_database < V5__add_provider_support.sql ``` ### 2. 配置RunningHub API Key 修改`application.yml`中的`ai.providers.runninghub.api-key` ### 3. 测试RunningHub模型 ```bash 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. 查看轮询日志 ```bash tail -f logs/application.log | grep "RunningHub" ``` ## 📝 注意事项 1. **配置管理**:RunningHub的webappId需要从数据库的provider_config中读取 2. **错误处理**:RunningHub API可能返回各种错误,需要完善异常处理 3. **超时控制**:设置最大轮询次数,防止任务无限轮询 4. **并发限制**:轮询时要控制并发数,避免给RunningHub造成压力 5. **日志记录**:详细记录每次API调用的请求和响应,便于排查问题 ## 🎯 预期效果 完成后,系统将支持: - ✅ OpenAI格式的同步API(原有功能,无影响) - ✅ RunningHub的异步API(新功能) - ✅ 用户无感切换,根据模型自动选择服务商 - ✅ 统一的任务管理和状态追踪