头像视频合成
This commit is contained in:
@@ -135,6 +135,9 @@ public class PasswordUtil {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -119,6 +119,9 @@ public class ServiceManager {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -97,6 +97,9 @@ public class AliyunConfig {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import java.util.Map;
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/tts/log")
|
||||
@Tag(name = "TTS请求日志", description = "TTS请求日志管理接口")
|
||||
@Tag(name = "TTS请求日志", description = "音频合成,TTS请求日志管理接口")
|
||||
public class TtsRequestLogController {
|
||||
|
||||
@Autowired
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package com.rj.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.rj.dto.VideoSynthesisRequestDto;
|
||||
import com.rj.entity.VideoSynthesisLog;
|
||||
import com.rj.mapper.VideoSynthesisLogMapper;
|
||||
import com.rj.service.IVideoSynthesisService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
@@ -29,6 +32,9 @@ public class VideoSynthesisFromController {
|
||||
|
||||
@Autowired
|
||||
private IVideoSynthesisService videoSynthesisService;
|
||||
|
||||
@Autowired
|
||||
private VideoSynthesisLogMapper videoSynthesisLogMapper;
|
||||
|
||||
/**
|
||||
* 视频合成接口
|
||||
@@ -47,7 +53,7 @@ public class VideoSynthesisFromController {
|
||||
|
||||
// 构建响应结果
|
||||
result.put("success", synthesisLog.getSuccess());
|
||||
result.put("message", synthesisLog.getSuccess() ? "视频合成成功" : "视频合成失败");
|
||||
result.put("message", synthesisLog.getTaskStatus());
|
||||
result.put("requestId", synthesisLog.getRequestId());
|
||||
result.put("taskId", synthesisLog.getTaskId());
|
||||
result.put("taskStatus", synthesisLog.getTaskStatus());
|
||||
@@ -177,4 +183,77 @@ public class VideoSynthesisFromController {
|
||||
return ResponseEntity.internalServerError().body(result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询视频合成日志列表
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
@Operation(summary = "查询视频合成日志列表", description = "分页查询视频合成日志")
|
||||
public ResponseEntity<Map<String, Object>> getVideoSynthesisLogs(
|
||||
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer pageNum,
|
||||
@Parameter(description = "每页大小") @RequestParam(defaultValue = "10") Integer pageSize,
|
||||
@Parameter(description = "任务状态") @RequestParam(required = false) String taskStatus,
|
||||
@Parameter(description = "模型") @RequestParam(required = false) String model,
|
||||
@Parameter(description = "模型供应商") @RequestParam(required = false) String modelProvider,
|
||||
@Parameter(description = "归属人姓名") @RequestParam(required = false) String ownerName,
|
||||
@Parameter(description = "归属人电话") @RequestParam(required = false) String ownerPhone,
|
||||
@Parameter(description = "是否成功") @RequestParam(required = false) Boolean success,
|
||||
@Parameter(description = "开始时间") @RequestParam(required = false) String startTime,
|
||||
@Parameter(description = "结束时间") @RequestParam(required = false) String endTime) {
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
try {
|
||||
Page<VideoSynthesisLog> page = new Page<>(pageNum, pageSize);
|
||||
QueryWrapper<VideoSynthesisLog> queryWrapper = new QueryWrapper<>();
|
||||
|
||||
// 添加查询条件
|
||||
if (taskStatus != null && !taskStatus.trim().isEmpty()) {
|
||||
queryWrapper.eq("task_status", taskStatus);
|
||||
}
|
||||
if (model != null && !model.trim().isEmpty()) {
|
||||
queryWrapper.like("model", model);
|
||||
}
|
||||
if (modelProvider != null && !modelProvider.trim().isEmpty()) {
|
||||
queryWrapper.like("model_provider", modelProvider);
|
||||
}
|
||||
if (ownerName != null && !ownerName.trim().isEmpty()) {
|
||||
queryWrapper.like("owner_name", ownerName);
|
||||
}
|
||||
if (ownerPhone != null && !ownerPhone.trim().isEmpty()) {
|
||||
queryWrapper.like("owner_phone", ownerPhone);
|
||||
}
|
||||
if (success != null) {
|
||||
queryWrapper.eq("success", success);
|
||||
}
|
||||
if (startTime != null && !startTime.trim().isEmpty()) {
|
||||
queryWrapper.ge("request_time", startTime);
|
||||
}
|
||||
if (endTime != null && !endTime.trim().isEmpty()) {
|
||||
queryWrapper.le("request_time", endTime);
|
||||
}
|
||||
|
||||
// 按请求时间倒序排列
|
||||
queryWrapper.orderByDesc("request_time");
|
||||
|
||||
// 执行查询
|
||||
Page<VideoSynthesisLog> pageResult = videoSynthesisLogMapper.selectPage(page, queryWrapper);
|
||||
|
||||
result.put("success", true);
|
||||
result.put("message", "查询成功");
|
||||
result.put("data", pageResult.getRecords());
|
||||
result.put("total", pageResult.getTotal());
|
||||
result.put("pageNum", pageResult.getCurrent());
|
||||
result.put("pageSize", pageResult.getSize());
|
||||
result.put("pages", pageResult.getPages());
|
||||
|
||||
return ResponseEntity.ok(result);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("查询视频合成日志失败: {}", e.getMessage(), e);
|
||||
result.put("success", false);
|
||||
result.put("message", "查询失败: " + e.getMessage());
|
||||
return ResponseEntity.status(500).body(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,6 +357,9 @@ public class MenuController {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -327,6 +327,9 @@ public class RoleController {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -333,6 +333,9 @@ public class UserRoleController {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -100,6 +100,9 @@ public class DifyWorkflowResponseDto {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ import lombok.Data;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 视频合成请求DTO
|
||||
@@ -17,11 +19,11 @@ import java.math.BigDecimal;
|
||||
public class VideoSynthesisRequestDto {
|
||||
|
||||
@NotBlank(message = "图片URL不能为空")
|
||||
@Schema(description = "图片URL", required = true, example = "https://example.com/image.jpg")
|
||||
@Schema(description = "图片URL", required = true)
|
||||
private String imageUrl;
|
||||
|
||||
@NotBlank(message = "音频URL不能为空")
|
||||
@Schema(description = "音频URL", required = true, example = "https://example.com/audio.wav")
|
||||
@Schema(description = "音频URL", required = true)
|
||||
private String audioUrl;
|
||||
|
||||
@Schema(description = "使用的模型", example = "liveportrait")
|
||||
@@ -30,7 +32,7 @@ public class VideoSynthesisRequestDto {
|
||||
@Schema(description = "图片ID", example = "img-12345")
|
||||
private String imageId;
|
||||
|
||||
@Schema(description = "音频ID", example = "audio-67890")
|
||||
@Schema(description = "音频ID", example ="audio-67890")
|
||||
private String audioId;
|
||||
|
||||
@Schema(description = "归属人姓名", example = "张三")
|
||||
@@ -59,4 +61,47 @@ public class VideoSynthesisRequestDto {
|
||||
|
||||
@Schema(description = "头部动作强度", example = "0.7")
|
||||
private BigDecimal headMoveStrength = new BigDecimal("0.7");
|
||||
|
||||
@Schema(description = "输入数据", example = "{\"image_url\": \"http://xxx/1.jpg\", \"audio_url\": \"http://xxx/1.wav\"}")
|
||||
private Map<String, String> input;
|
||||
|
||||
@Schema(description = "参数配置", example = "{\"template_id\": \"normal\", \"eye_move_freq\": 0.5, \"video_fps\": 30, \"mouth_move_strength\": 1, \"paste_back\": true, \"head_move_strength\": 0.7}")
|
||||
private Map<String, Object> parameters;
|
||||
|
||||
/**
|
||||
* 构建参数方法
|
||||
* 用于构建 input 和 parameters 属性
|
||||
*/
|
||||
public void buildParam() {
|
||||
// 构建 input 属性
|
||||
this.input = new HashMap<>();
|
||||
if (this.imageUrl != null) {
|
||||
this.input.put("image_url", this.imageUrl);
|
||||
}
|
||||
if (this.audioUrl != null) {
|
||||
this.input.put("audio_url", this.audioUrl);
|
||||
}
|
||||
|
||||
// 构建 parameters 属性
|
||||
this.parameters = new HashMap<>();
|
||||
if (this.templateId != null) {
|
||||
this.parameters.put("template_id", this.templateId);
|
||||
}
|
||||
if (this.eyeMoveFreq != null) {
|
||||
this.parameters.put("eye_move_freq", this.eyeMoveFreq);
|
||||
}
|
||||
if (this.videoFps != null) {
|
||||
this.parameters.put("video_fps", this.videoFps);
|
||||
}
|
||||
if (this.mouthMoveStrength != null) {
|
||||
this.parameters.put("mouth_move_strength", this.mouthMoveStrength);
|
||||
}
|
||||
if (this.pasteBack != null) {
|
||||
this.parameters.put("paste_back", this.pasteBack);
|
||||
}
|
||||
if (this.headMoveStrength != null) {
|
||||
this.parameters.put("head_move_strength", this.headMoveStrength);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -43,6 +43,9 @@ public interface CustomerProfileAnalysisMapper extends BaseMapper<CustomerProfil
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -103,6 +103,9 @@ public class AudioStatisticsScheduler {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,358 @@
|
||||
package com.rj.scheduler;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.rj.config.AliyunConfig;
|
||||
import com.rj.entity.VideoSynthesisLog;
|
||||
import com.rj.mapper.VideoSynthesisLogMapper;
|
||||
import com.rj.service.MinIOService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 视频合成任务状态检查定时器
|
||||
* 每5分钟检查一次PENDING状态的任务,调用阿里云API查询任务状态并更新数据库
|
||||
*
|
||||
* @author rj
|
||||
* @date 2025-01-30
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class VideoSynthesisStatusScheduler {
|
||||
|
||||
@Autowired
|
||||
private VideoSynthesisLogMapper videoSynthesisLogMapper;
|
||||
|
||||
@Autowired
|
||||
private AliyunConfig aliyunConfig;
|
||||
|
||||
@Autowired
|
||||
private RestTemplate restTemplate;
|
||||
|
||||
@Autowired
|
||||
private MinIOService minIOService;
|
||||
|
||||
/**
|
||||
* 每5分钟执行一次任务状态检查
|
||||
*/
|
||||
@Scheduled(fixedRate = 5 * 60 * 1000) // 5分钟 = 5 * 60 * 1000毫秒
|
||||
public void checkVideoSynthesisStatus() {
|
||||
try {
|
||||
log.info("开始执行视频合成任务状态检查...");
|
||||
|
||||
// 查询所有PENDING状态的任务
|
||||
List<VideoSynthesisLog> pendingTasks = getPendingTasks();
|
||||
|
||||
if (pendingTasks.isEmpty()) {
|
||||
log.info("没有找到PENDING状态的任务");
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("找到{}个PENDING状态的任务,开始检查状态", pendingTasks.size());
|
||||
|
||||
// 遍历每个任务,检查状态
|
||||
for (VideoSynthesisLog task : pendingTasks) {
|
||||
try {
|
||||
checkAndUpdateTaskStatus(task);
|
||||
} catch (Exception e) {
|
||||
log.error("检查任务状态失败,任务ID: {}, 错误: {}", task.getTaskId(), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
log.info("视频合成任务状态检查完成");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("视频合成任务状态检查执行失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询所有PENDING状态的任务
|
||||
*/
|
||||
private List<VideoSynthesisLog> getPendingTasks() {
|
||||
QueryWrapper<VideoSynthesisLog> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("task_status", "PENDING");
|
||||
|
||||
return videoSynthesisLogMapper.selectList(queryWrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查并更新单个任务状态
|
||||
*/
|
||||
private void checkAndUpdateTaskStatus(VideoSynthesisLog task) {
|
||||
try {
|
||||
|
||||
log.info("检查任务状态,任务ID: {}", task.getTaskId());
|
||||
if (task == null || task.getTaskId() == null || task.getTaskId().isEmpty()) return;
|
||||
// 调用阿里云API检查任务状态
|
||||
String apiUrl = "https://dashscope.aliyuncs.com/api/v1/tasks/" + task.getTaskId();
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("Authorization", "Bearer " + aliyunConfig.getApiKey());
|
||||
headers.set("Content-Type", "application/json");
|
||||
|
||||
HttpEntity<String> entity = new HttpEntity<>(headers);
|
||||
|
||||
ResponseEntity<String> response = restTemplate.exchange(
|
||||
apiUrl,
|
||||
HttpMethod.GET,
|
||||
entity,
|
||||
String.class
|
||||
);
|
||||
|
||||
if (response.getStatusCode().is2xxSuccessful()) {
|
||||
String responseBody = response.getBody();
|
||||
log.info("API响应: {}", responseBody);
|
||||
|
||||
// 解析响应并更新数据库
|
||||
updateTaskFromResponse(task, responseBody);
|
||||
} else {
|
||||
log.error("API调用失败,状态码: {}, 任务ID: {}", response.getStatusCode(), task.getTaskId());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("检查任务状态异常,任务ID: {}, 错误: {}", task.getTaskId(), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据API响应更新任务状态
|
||||
*/
|
||||
private void updateTaskFromResponse(VideoSynthesisLog task, String responseBody) {
|
||||
try {
|
||||
JSONObject responseJson = JSON.parseObject(responseBody);
|
||||
|
||||
if (responseJson.containsKey("output")) {
|
||||
JSONObject output = responseJson.getJSONObject("output");
|
||||
|
||||
// 更新任务状态
|
||||
String taskStatus = output.getString("task_status");
|
||||
task.setTaskStatus(taskStatus);
|
||||
task.setUpdatedAt(LocalDateTime.now());
|
||||
|
||||
// 如果任务成功完成,处理视频文件
|
||||
if ("SUCCEEDED".equals(taskStatus)) {
|
||||
JSONObject results = output.getJSONObject("results");
|
||||
if (results != null && results.containsKey("video_url")) {
|
||||
String originalVideoUrl = results.getString("video_url");
|
||||
log.info("任务成功完成,原始视频URL: {}", originalVideoUrl);
|
||||
|
||||
// 从阿里云OSS下载视频并上传到MinIO
|
||||
String minioVideoUrl = downloadAndUploadToMinIO(originalVideoUrl, task.getTaskId());
|
||||
if (minioVideoUrl != null) {
|
||||
task.setVideoUrl(minioVideoUrl);
|
||||
log.info("视频已成功转存到MinIO: {}", minioVideoUrl);
|
||||
} else {
|
||||
// 如果转存失败,保留原始URL
|
||||
task.setVideoUrl(originalVideoUrl);
|
||||
log.warn("视频转存到MinIO失败,保留原始URL: {}", originalVideoUrl);
|
||||
}
|
||||
|
||||
task.setSuccess(true);
|
||||
task.setResponseTime(LocalDateTime.now());
|
||||
}
|
||||
} else if ("FAILED".equals(taskStatus)) {
|
||||
task.setSuccess(false);
|
||||
task.setErrorMessage("任务执行失败");
|
||||
task.setResponseTime(LocalDateTime.now());
|
||||
}
|
||||
|
||||
// 更新usage信息
|
||||
if (responseJson.containsKey("usage")) {
|
||||
JSONObject usage = responseJson.getJSONObject("usage");
|
||||
if (usage.containsKey("video_duration")) {
|
||||
task.setVideoDuration(usage.getBigDecimal("video_duration"));
|
||||
}
|
||||
if (usage.containsKey("video_ratio")) {
|
||||
task.setVideoRatio(usage.getString("video_ratio"));
|
||||
}
|
||||
}
|
||||
|
||||
// 保存到数据库
|
||||
int updateResult = videoSynthesisLogMapper.updateById(task);
|
||||
if (updateResult > 0) {
|
||||
log.info("任务状态更新成功,任务ID: {}, 新状态: {}", task.getTaskId(), taskStatus);
|
||||
} else {
|
||||
log.error("任务状态更新失败,任务ID: {}", task.getTaskId());
|
||||
}
|
||||
|
||||
} else {
|
||||
log.error("API响应格式异常,任务ID: {}, 响应: {}", task.getTaskId(), responseBody);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("解析API响应失败,任务ID: {}, 响应: {}, 错误: {}", task.getTaskId(), responseBody, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从阿里云OSS下载视频并上传到MinIO
|
||||
*
|
||||
* @param ossVideoUrl 阿里云OSS视频URL
|
||||
* @param taskId 任务ID,用于生成文件名
|
||||
* @return MinIO中的视频URL,失败时返回null
|
||||
*/
|
||||
private String downloadAndUploadToMinIO(String ossVideoUrl, String taskId) {
|
||||
try {
|
||||
log.info("开始从阿里云OSS下载视频: {}", ossVideoUrl);
|
||||
|
||||
// 从阿里云OSS下载视频
|
||||
byte[] videoData = downloadVideoFromOSS(ossVideoUrl);
|
||||
if (videoData == null || videoData.length == 0) {
|
||||
log.error("从阿里云OSS下载视频失败,数据为空");
|
||||
return null;
|
||||
}
|
||||
|
||||
log.info("视频下载成功,大小: {} bytes", videoData.length);
|
||||
|
||||
// 生成MinIO文件名
|
||||
String fileName = "video_synthesis/" + taskId + ".mp4";
|
||||
|
||||
// 上传到MinIO
|
||||
String minioUrl = minIOService.uploadFile(
|
||||
new ByteArrayInputStream(videoData),
|
||||
fileName,
|
||||
"video/mp4"
|
||||
);
|
||||
|
||||
log.info("视频已成功上传到MinIO: {}", minioUrl);
|
||||
return minioUrl;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("下载并上传视频到MinIO失败: {}", e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从阿里云OSS下载视频文件
|
||||
*
|
||||
* @param videoUrl 视频URL
|
||||
* @return 视频字节数组
|
||||
*/
|
||||
private byte[] downloadVideoFromOSS(String videoUrl) {
|
||||
try {
|
||||
log.info("开始下载视频: {}", videoUrl);
|
||||
|
||||
// 方法1: 使用HttpURLConnection下载视频,避免RestTemplate的URL编码问题
|
||||
byte[] videoData = downloadWithHttpURLConnection(videoUrl);
|
||||
if (videoData != null) {
|
||||
return videoData;
|
||||
}
|
||||
|
||||
// 方法2: 如果HttpURLConnection失败,尝试使用RestTemplate但禁用URL编码
|
||||
log.warn("HttpURLConnection下载失败,尝试使用RestTemplate备用方案");
|
||||
return downloadWithRestTemplate(videoUrl);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("下载视频异常: {}", e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用HttpURLConnection下载视频
|
||||
*/
|
||||
private byte[] downloadWithHttpURLConnection(String videoUrl) {
|
||||
try {
|
||||
URL url = new URL(videoUrl);
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
|
||||
// 设置请求头
|
||||
connection.setRequestMethod("GET");
|
||||
connection.setConnectTimeout(30000); // 30秒连接超时
|
||||
connection.setReadTimeout(300000); // 5分钟读取超时
|
||||
connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
|
||||
connection.setRequestProperty("Accept", "*/*");
|
||||
connection.setRequestProperty("Accept-Encoding", "identity"); // 禁用压缩
|
||||
|
||||
int responseCode = connection.getResponseCode();
|
||||
if (responseCode == 200) {
|
||||
try (InputStream inputStream = connection.getInputStream();
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
||||
|
||||
byte[] buffer = new byte[8192];
|
||||
int bytesRead;
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
outputStream.write(buffer, 0, bytesRead);
|
||||
}
|
||||
|
||||
byte[] videoData = outputStream.toByteArray();
|
||||
log.info("HttpURLConnection视频下载成功,大小: {} bytes", videoData.length);
|
||||
return videoData;
|
||||
}
|
||||
} else {
|
||||
log.error("HttpURLConnection下载失败,HTTP状态码: {}", responseCode);
|
||||
// 读取错误信息
|
||||
try (InputStream errorStream = connection.getErrorStream()) {
|
||||
if (errorStream != null) {
|
||||
byte[] errorBytes = new byte[1024];
|
||||
int errorBytesRead = errorStream.read(errorBytes);
|
||||
if (errorBytesRead > 0) {
|
||||
String errorMessage = new String(errorBytes, 0, errorBytesRead);
|
||||
log.error("错误响应内容: {}", errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("HttpURLConnection下载异常: {}", e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用RestTemplate下载视频(备用方案)
|
||||
*/
|
||||
private byte[] downloadWithRestTemplate(String videoUrl) {
|
||||
try {
|
||||
// 创建自定义的RestTemplate,禁用URL编码
|
||||
RestTemplate customRestTemplate = new RestTemplate();
|
||||
|
||||
// 设置请求头
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
|
||||
headers.set("Accept", "*/*");
|
||||
headers.set("Accept-Encoding", "identity");
|
||||
|
||||
HttpEntity<String> entity = new HttpEntity<>(headers);
|
||||
|
||||
ResponseEntity<byte[]> response = customRestTemplate.exchange(
|
||||
videoUrl,
|
||||
HttpMethod.GET,
|
||||
entity,
|
||||
byte[].class
|
||||
);
|
||||
|
||||
if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
|
||||
log.info("RestTemplate视频下载成功,大小: {} bytes", response.getBody().length);
|
||||
return response.getBody();
|
||||
} else {
|
||||
log.error("RestTemplate下载失败,状态码: {}", response.getStatusCode());
|
||||
return null;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("RestTemplate下载异常: {}", e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,6 +64,9 @@ public interface ICustomerProfileAnalysisService extends IService<CustomerProfil
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -99,6 +99,9 @@ public class CustomerProfileAnalysisServiceImpl extends ServiceImpl<CustomerProf
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.rj.service.impl;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.rj.dto.VideoSynthesisRequestDto;
|
||||
import com.rj.entity.VideoSynthesisLog;
|
||||
import com.rj.mapper.VideoSynthesisLogMapper;
|
||||
@@ -59,7 +61,7 @@ public class VideoSynthesisServiceImpl implements IVideoSynthesisService {
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.set("X-DashScope-Async", "enable");
|
||||
headers.set("Authorization", "Bearer " + apiKey);
|
||||
|
||||
request.buildParam();
|
||||
// 构建请求体
|
||||
HttpEntity<VideoSynthesisRequestDto> entity = new HttpEntity<>(request, headers);
|
||||
|
||||
@@ -76,14 +78,33 @@ public class VideoSynthesisServiceImpl implements IVideoSynthesisService {
|
||||
String responseBody = response.getBody();
|
||||
log.info("API响应: {}", responseBody);
|
||||
|
||||
// 简单解析关键字段
|
||||
if (responseBody != null && responseBody.contains("task_id")) {
|
||||
// 这里可以添加更复杂的JSON解析逻辑
|
||||
// 暂时设置基本状态
|
||||
synthesisLog.setTaskStatus("PENDING");
|
||||
synthesisLog.setSuccess(true);
|
||||
// 解析JSON响应
|
||||
if (responseBody != null && !responseBody.trim().isEmpty()) {
|
||||
try {
|
||||
// 使用fastjson解析JSON响应
|
||||
JSONObject responseJson = JSON.parseObject(responseBody);
|
||||
|
||||
// 提取task_id
|
||||
if (responseJson.containsKey("output")) {
|
||||
JSONObject output = responseJson.getJSONObject("output");
|
||||
if (output != null && output.containsKey("task_id")) {
|
||||
String taskId = output.getString("task_id");
|
||||
synthesisLog.setTaskId(taskId);
|
||||
log.info("提取到任务ID: {}", taskId);
|
||||
}
|
||||
}
|
||||
|
||||
// 设置基本状态
|
||||
synthesisLog.setTaskStatus("PENDING");
|
||||
synthesisLog.setSuccess(true);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("解析API响应JSON失败: {}", e.getMessage(), e);
|
||||
synthesisLog.setErrorMessage("解析API响应失败: " + e.getMessage());
|
||||
synthesisLog.setSuccess(false);
|
||||
}
|
||||
} else {
|
||||
synthesisLog.setErrorMessage("API响应格式异常");
|
||||
synthesisLog.setErrorMessage("API响应为空");
|
||||
synthesisLog.setSuccess(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -66,6 +66,9 @@ public interface IMenuService extends IService<Menu> {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -66,6 +66,9 @@ public interface IUserRoleService extends IService<UserRole> {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -70,6 +70,9 @@ public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements IM
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -70,6 +70,9 @@ public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements IR
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -70,6 +70,9 @@ public class UserRoleServiceImpl extends ServiceImpl<UserRoleMapper, UserRole> i
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -80,6 +80,9 @@ spring:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -56,6 +56,9 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -260,3 +260,6 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -20,3 +20,6 @@ AFTER `sales_name`;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -9,3 +9,6 @@ ADD INDEX `idx_audio_name` (`audio_name`);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user