头像视频合成后,上传,查看

This commit is contained in:
spllzh
2025-10-08 11:50:49 +08:00
parent 45d061bad7
commit e5cba62007
26 changed files with 266 additions and 5 deletions

View File

@@ -139,6 +139,8 @@ public class PasswordUtil {

View File

@@ -123,6 +123,8 @@ public class ServiceManager {

View File

@@ -101,6 +101,8 @@ public class AliyunConfig {

View File

@@ -2,6 +2,9 @@ package com.rj.controller;
import com.rj.dto.SiliconFlowTtsRequest;
import com.rj.dto.SiliconFlowTtsResponse;
import com.rj.entity.TtsRequestLog;
import com.rj.service.ITtsRequestLogService;
import com.rj.service.MinIOService;
import com.rj.service.SiliconFlowTtsService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
@@ -13,10 +16,13 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.time.LocalDateTime;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* SiliconFlow TTS 控制器
@@ -33,6 +39,13 @@ public class SiliconFlowTtsController {
@Autowired
private SiliconFlowTtsService ttsService;
@Autowired
private MinIOService minIOService;
@Autowired
private ITtsRequestLogService ttsRequestLogService;
@PostMapping("/speech")
@Operation(summary = "文本转语音", description = "将文本转换为语音音频")
public ResponseEntity<Map<String, Object>> textToSpeech(
@@ -370,4 +383,118 @@ public class SiliconFlowTtsController {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
}
}
@PostMapping(value = "/upload-audio", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Operation(summary = "音频文件上传", description = "上传音频文件到MinIO并生成临时访问链接")
public ResponseEntity<Map<String, Object>> uploadAudio(
@Parameter(description = "音频文件") @RequestParam("file") MultipartFile file,
@Parameter(description = "音频名称") @RequestParam("audioName") String audioName,
@Parameter(description = "创建人姓名") @RequestParam("creatorName") String creatorName,
@Parameter(description = "创建人电话") @RequestParam("creatorPhone") String creatorPhone) {
Map<String, Object> result = new HashMap<>();
try {
log.info("开始上传音频文件: {}, 创建人: {}, 电话: {}", audioName, creatorName, creatorPhone);
// 验证文件
if (file.isEmpty()) {
log.error("上传文件为空");
result.put("success", false);
result.put("message", "上传文件为空");
return ResponseEntity.badRequest().body(result);
}
// 验证文件类型
String contentType = file.getContentType();
if (contentType == null || !contentType.startsWith("audio/")) {
log.error("文件类型不是音频文件: {}", contentType);
result.put("success", false);
result.put("message", "文件类型不是音频文件,请上传音频文件");
return ResponseEntity.badRequest().body(result);
}
// 生成唯一文件名
String originalFilename = file.getOriginalFilename();
String fileExtension = originalFilename != null && originalFilename.contains(".")
? originalFilename.substring(originalFilename.lastIndexOf("."))
: ".mp3";
String uniqueFileName = "audio/" + UUID.randomUUID().toString() + fileExtension;
// 上传文件到MinIO
String minioUrl = minIOService.uploadFileWithName(file, uniqueFileName);
if (minioUrl == null) {
log.error("文件上传到MinIO失败");
result.put("success", false);
result.put("message", "文件上传到MinIO失败");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
}
log.info("文件上传成功MinIO URL: {}", minioUrl);
// 生成7天临时访问链接
int expiresInSeconds = 7 * 24 * 60 * 60; // 7天
String shortUrl = minIOService.getPresignedUrl(uniqueFileName, expiresInSeconds);
if (shortUrl == null) {
log.error("生成临时访问链接失败");
result.put("success", false);
result.put("message", "生成临时访问链接失败");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
}
LocalDateTime expireTime = LocalDateTime.now().plusSeconds(expiresInSeconds);
log.info("生成临时访问链接成功,过期时间: {}", expireTime);
// 创建TTS请求日志记录
TtsRequestLog ttsLog = new TtsRequestLog();
ttsLog.setId(UUID.randomUUID().toString());
ttsLog.setRequestTime(LocalDateTime.now());
ttsLog.setModel("AUDIO_UPLOAD");
ttsLog.setInputText("音频文件上传");
ttsLog.setInputLength(0);
ttsLog.setStatus("SUCCESS");
ttsLog.setAudioSizeBytes(file.getSize());
ttsLog.setMinioUrl(minioUrl);
ttsLog.setAudioName(audioName);
ttsLog.setShortUrl(shortUrl);
ttsLog.setShortUrlExpireTime(expireTime);
ttsLog.setCreatorName(creatorName);
ttsLog.setCreatorPhone(creatorPhone);
ttsLog.setCreateTime(LocalDateTime.now());
ttsLog.setUpdateTime(LocalDateTime.now());
// 保存到数据库
boolean saveResult = ttsRequestLogService.saveTtsRequestLog(ttsLog);
if (!saveResult) {
log.error("保存TTS请求日志失败");
result.put("success", false);
result.put("message", "保存TTS请求日志失败");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
}
// 构建成功响应
result.put("success", true);
result.put("message", "音频文件上传成功");
result.put("id", ttsLog.getId());
result.put("audioName", audioName);
result.put("minioUrl", minioUrl);
result.put("shortUrl", shortUrl);
result.put("shortUrlExpireTime", expireTime);
result.put("creatorName", creatorName);
result.put("creatorPhone", creatorPhone);
result.put("audioSizeBytes", file.getSize());
result.put("status", "SUCCESS");
result.put("createTime", LocalDateTime.now());
log.info("音频文件上传完成记录ID: {}, MinIO URL: {}, 临时URL: {}",
ttsLog.getId(), minioUrl, shortUrl);
return ResponseEntity.ok(result);
} catch (Exception e) {
log.error("音频文件上传异常: {}", e.getMessage(), e);
result.put("success", false);
result.put("message", "音频文件上传异常: " + e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
}
}
}

View File

@@ -361,6 +361,8 @@ public class MenuController {

View File

@@ -331,6 +331,8 @@ public class RoleController {

View File

@@ -337,6 +337,8 @@ public class UserRoleController {

View File

@@ -104,6 +104,8 @@ public class DifyWorkflowResponseDto {

View File

@@ -18,6 +18,9 @@ import java.util.Map;
@Schema(description = "视频合成请求参数")
public class VideoSynthesisRequestDto {
@Schema(description = "视频名称")
private String videoName;
@NotBlank(message = "图片URL不能为空")
@Schema(description = "图片URL", required = true)
private String imageUrl;

View File

@@ -145,6 +145,18 @@ public class VideoSynthesisLog {
@TableField("video_url")
private String videoUrl;
/**
* 视频名称
*/
@TableField("video_name")
private String videoName;
/**
* 临时对外URL链接
*/
@TableField("video_temp_url")
private String videoTempUrl;
/**
* 视频时长(秒)
*/

View File

@@ -47,6 +47,8 @@ public interface CustomerProfileAnalysisMapper extends BaseMapper<CustomerProfil

View File

@@ -107,6 +107,8 @@ public class AudioStatisticsScheduler {

View File

@@ -56,15 +56,15 @@ public class VideoSynthesisStatusScheduler {
try {
log.info("开始执行视频合成任务状态检查...");
// 查询所有PENDING状态的任务
// 查询所有PENDING或RUNNING状态的任务
List<VideoSynthesisLog> pendingTasks = getPendingTasks();
if (pendingTasks.isEmpty()) {
log.info("没有找到PENDING状态的任务");
log.info("没有找到PENDING或RUNNING状态的任务");
return;
}
log.info("找到{}个PENDING状态的任务开始检查状态", pendingTasks.size());
log.info("找到{}个PENDING或RUNNING状态的任务,开始检查状态", pendingTasks.size());
// 遍历每个任务,检查状态
for (VideoSynthesisLog task : pendingTasks) {
@@ -83,11 +83,11 @@ public class VideoSynthesisStatusScheduler {
}
/**
* 查询所有PENDING状态的任务
* 查询所有PENDING或RUNNING状态的任务
*/
private List<VideoSynthesisLog> getPendingTasks() {
QueryWrapper<VideoSynthesisLog> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("task_status", "PENDING");
queryWrapper.in("task_status", "PENDING", "RUNNING");
return videoSynthesisLogMapper.selectList(queryWrapper);
}
@@ -166,6 +166,15 @@ public class VideoSynthesisStatusScheduler {
task.setSuccess(true);
task.setResponseTime(LocalDateTime.now());
// 更新临时访问URL使用MinIO的预签名URL
try {
String tempUrl = generateTempVideoUrl(task.getVideoName(), task.getRequestId());
task.setVideoTempUrl(tempUrl);
log.info("更新临时访问URL: {}", tempUrl);
} catch (Exception e) {
log.warn("更新临时访问URL失败: {}", e.getMessage());
}
}
} else if ("FAILED".equals(taskStatus)) {
task.setSuccess(false);
@@ -355,4 +364,28 @@ public class VideoSynthesisStatusScheduler {
return null;
}
}
/**
* 生成临时视频访问URL
*
* @param videoName 视频名称
* @param requestId 请求ID
* @return 临时访问URL
*/
private String generateTempVideoUrl(String videoName, String requestId) {
try {
// 构建临时访问URL
// 格式: https://your-domain.com/video/temp/{requestId}?name={videoName}
String baseUrl = "https://your-domain.com"; // 这里应该从配置文件读取
String encodedVideoName = java.net.URLEncoder.encode(videoName, "UTF-8");
String tempUrl = String.format("%s/video/temp/%s?name=%s", baseUrl, requestId, encodedVideoName);
log.info("生成临时视频URL: 视频名称={}, 请求ID={}, URL={}", videoName, requestId, tempUrl);
return tempUrl;
} catch (Exception e) {
log.error("生成临时视频URL失败: {}", e.getMessage(), e);
return null;
}
}
}

View File

@@ -68,6 +68,8 @@ public interface ICustomerProfileAnalysisService extends IService<CustomerProfil

View File

@@ -103,6 +103,8 @@ public class CustomerProfileAnalysisServiceImpl extends ServiceImpl<CustomerProf

View File

@@ -52,6 +52,16 @@ public class VideoSynthesisServiceImpl implements IVideoSynthesisService {
// 使用BeanUtils复制相同名称的属性
BeanUtils.copyProperties(request, synthesisLog);
// 设置视频名称(如果请求中有提供)
if (request.getVideoName() != null && !request.getVideoName().trim().isEmpty()) {
synthesisLog.setVideoName(request.getVideoName());
log.info("设置视频名称: {}", request.getVideoName());
} else {
// 如果没有提供视频名称,使用默认名称
synthesisLog.setVideoName("视频合成_" + requestId.substring(0, 8));
log.info("使用默认视频名称: {}", synthesisLog.getVideoName());
}
try {
log.info("开始视频合成请求ID: {}, 图片URL: {}, 音频URL: {}",
requestId, request.getImageUrl(), request.getAudioUrl());
@@ -98,6 +108,16 @@ public class VideoSynthesisServiceImpl implements IVideoSynthesisService {
synthesisLog.setTaskStatus("PENDING");
synthesisLog.setSuccess(true);
// 生成临时访问URL7天有效期
try {
String tempUrl = generateTempVideoUrl(synthesisLog.getVideoName(), requestId);
synthesisLog.setVideoTempUrl(tempUrl);
log.info("生成临时访问URL: {}", tempUrl);
} catch (Exception e) {
log.warn("生成临时访问URL失败: {}", e.getMessage());
// 临时URL生成失败不影响主流程
}
} catch (Exception e) {
log.error("解析API响应JSON失败: {}", e.getMessage(), e);
synthesisLog.setErrorMessage("解析API响应失败: " + e.getMessage());
@@ -169,4 +189,28 @@ public class VideoSynthesisServiceImpl implements IVideoSynthesisService {
return null;
}
}
/**
* 生成临时视频访问URL
*
* @param videoName 视频名称
* @param requestId 请求ID
* @return 临时访问URL
*/
private String generateTempVideoUrl(String videoName, String requestId) {
try {
// 构建临时访问URL
// 格式: https://your-domain.com/video/temp/{requestId}?name={videoName}
String baseUrl = "https://your-domain.com"; // 这里应该从配置文件读取
String encodedVideoName = java.net.URLEncoder.encode(videoName, "UTF-8");
String tempUrl = String.format("%s/video/temp/%s?name=%s", baseUrl, requestId, encodedVideoName);
log.info("生成临时视频URL: 视频名称={}, 请求ID={}, URL={}", videoName, requestId, tempUrl);
return tempUrl;
} catch (Exception e) {
log.error("生成临时视频URL失败: {}", e.getMessage(), e);
return null;
}
}
}

View File

@@ -70,6 +70,8 @@ public interface IMenuService extends IService<Menu> {

View File

@@ -74,6 +74,8 @@ public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements IM

View File

@@ -74,6 +74,8 @@ public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements IR

View File

@@ -74,6 +74,8 @@ public class UserRoleServiceImpl extends ServiceImpl<UserRoleMapper, UserRole> i

View File

@@ -84,6 +84,8 @@ spring:

View File

@@ -263,3 +263,5 @@

View File

@@ -23,3 +23,5 @@ AFTER `sales_name`;

View File

@@ -12,3 +12,5 @@ ADD INDEX `idx_audio_name` (`audio_name`);

View File

@@ -142,3 +142,5 @@ public class TtsRequestLogShortUrlTest {