437 lines
19 KiB
Java
437 lines
19 KiB
Java
package com.rj.service.impl;
|
||
|
||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||
import com.rj.entity.DetectVideoTemplate;
|
||
import com.rj.mapper.DetectVideoTemplateMapper;
|
||
import com.rj.service.IDetectVideoTemplateService;
|
||
import com.rj.service.MinIOService;
|
||
import com.rj.utils.MinIOUrlGenerator;
|
||
import lombok.extern.slf4j.Slf4j;
|
||
import org.springframework.beans.factory.annotation.Autowired;
|
||
import org.springframework.beans.factory.annotation.Value;
|
||
import org.springframework.http.*;
|
||
import org.springframework.stereotype.Service;
|
||
import org.springframework.web.client.RestTemplate;
|
||
import org.springframework.web.client.HttpClientErrorException;
|
||
import org.springframework.web.client.HttpServerErrorException;
|
||
import org.springframework.web.multipart.MultipartFile;
|
||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||
import com.fasterxml.jackson.databind.JsonNode;
|
||
|
||
import java.time.LocalDateTime;
|
||
import java.util.HashMap;
|
||
import java.util.Map;
|
||
import java.util.UUID;
|
||
|
||
/**
|
||
* 检测视频模板日志服务实现
|
||
*/
|
||
@Slf4j
|
||
@Service
|
||
public class DetectVideoTemplateServiceImpl extends ServiceImpl<DetectVideoTemplateMapper, DetectVideoTemplate> implements IDetectVideoTemplateService {
|
||
|
||
private static final String API_URL = "https://dashscope.aliyuncs.com/api/v1/services/aigc/image2video/aa-template-generation";
|
||
|
||
@Value("${dashscope.api.key:}")
|
||
private String apiKey;
|
||
|
||
@Autowired
|
||
private MinIOService minioService;
|
||
|
||
@Autowired
|
||
private MinIOUrlGenerator urlGenerator;
|
||
|
||
private final RestTemplate restTemplate;
|
||
private final ObjectMapper objectMapper;
|
||
|
||
public DetectVideoTemplateServiceImpl() {
|
||
this.restTemplate = new RestTemplate();
|
||
this.objectMapper = new ObjectMapper();
|
||
}
|
||
|
||
@Override
|
||
public boolean saveDetectVideoTemplateLog(DetectVideoTemplate detectVideoTemplate) {
|
||
try {
|
||
return save(detectVideoTemplate);
|
||
} catch (Exception e) {
|
||
log.error("保存检测视频模板日志失败: {}", e.getMessage(), e);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 执行视频模板检测并保存日志
|
||
*/
|
||
public DetectVideoTemplate detectVideoTemplateAndSave(String videoUrl, String ownerName, String ownerPhone, String templateName, String model) {
|
||
|
||
if (apiKey == null || apiKey.trim().isEmpty()) {
|
||
log.error("DASHSCOPE_API_KEY未配置");
|
||
return createErrorLog(videoUrl, "DASHSCOPE_API_KEY未配置", ownerName, ownerPhone, templateName);
|
||
}
|
||
|
||
// 创建日志记录
|
||
DetectVideoTemplate detectVideoTemplate = new DetectVideoTemplate();
|
||
String requestId = UUID.randomUUID().toString();
|
||
LocalDateTime requestTime = LocalDateTime.now();
|
||
|
||
detectVideoTemplate.setRequestId(requestId);
|
||
// 设置模型名称,如果未提供则使用默认值
|
||
String modelName = (model != null && !model.trim().isEmpty()) ? model : "animate-anyone-template-gen2";
|
||
detectVideoTemplate.setModel(modelName);
|
||
log.info("使用模型: {}", modelName);
|
||
detectVideoTemplate.setVideoUrl(videoUrl);
|
||
detectVideoTemplate.setOwnerName(ownerName);
|
||
detectVideoTemplate.setOwnerPhone(ownerPhone);
|
||
detectVideoTemplate.setTemplateName(templateName);
|
||
detectVideoTemplate.setRequestTime(requestTime);
|
||
|
||
long startTime = System.currentTimeMillis();
|
||
|
||
try {
|
||
// 创建请求体
|
||
Map<String, Object> requestBody = new HashMap<>();
|
||
requestBody.put("model", modelName);
|
||
|
||
Map<String, String> input = new HashMap<>();
|
||
input.put("video_url", videoUrl);
|
||
requestBody.put("input", input);
|
||
|
||
// 设置请求头
|
||
HttpHeaders headers = new HttpHeaders();
|
||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||
headers.setBearerAuth(apiKey);
|
||
// 添加异步调用请求头
|
||
headers.set("X-DashScope-Async", "enable");
|
||
log.info("发送视频模板检测请求,requestBody : {}, 请求参数headers: {}", requestBody , headers);
|
||
|
||
// 创建请求实体
|
||
HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(requestBody, headers);
|
||
|
||
// 发送请求
|
||
ResponseEntity<String> response = restTemplate.postForEntity(API_URL, requestEntity, String.class);
|
||
|
||
|
||
|
||
log.info("发送视频模板检测请求,response: {}, 视频URL: {}", response, videoUrl);
|
||
|
||
// 计算处理时间
|
||
long endTime = System.currentTimeMillis();
|
||
long processingTime = endTime - startTime;
|
||
|
||
// 更新日志记录
|
||
detectVideoTemplate.setResponseTime(LocalDateTime.now());
|
||
detectVideoTemplate.setStatusCode(response.getStatusCode().value());
|
||
detectVideoTemplate.setProcessingTimeMs(processingTime);
|
||
detectVideoTemplate.setResponseData(response.getBody());
|
||
|
||
// 处理响应
|
||
log.info("视频模板检测响应,状态码: {}, 处理时间: {}ms", response.getStatusCode(), processingTime);
|
||
|
||
// 解析JSON响应
|
||
if (response.getStatusCode() == HttpStatus.OK) {
|
||
JsonNode jsonResponse = objectMapper.readTree(response.getBody());
|
||
|
||
// 解析响应数据
|
||
if (jsonResponse.has("output")) {
|
||
JsonNode output = jsonResponse.get("output");
|
||
|
||
// 解析任务ID
|
||
if (output.has("task_id")) {
|
||
String taskId = output.get("task_id").asText();
|
||
detectVideoTemplate.setTaskId(taskId);
|
||
log.info("任务ID: {}", taskId);
|
||
}
|
||
|
||
// 解析任务状态
|
||
if (output.has("task_status")) {
|
||
String taskStatus = output.get("task_status").asText();
|
||
detectVideoTemplate.setTaskStatus(taskStatus);
|
||
log.info("任务状态: {}", taskStatus);
|
||
|
||
// 根据任务状态判断是否成功
|
||
boolean isSuccess = "SUCCESS".equalsIgnoreCase(taskStatus) || "COMPLETED".equalsIgnoreCase(taskStatus);
|
||
detectVideoTemplate.setSuccess(isSuccess);
|
||
}
|
||
|
||
}
|
||
// 记录message信息
|
||
if (jsonResponse.has("request_id")) {
|
||
String request_id = jsonResponse.get("request_id").asText();
|
||
detectVideoTemplate.setRequestId(request_id);
|
||
}
|
||
|
||
} else {
|
||
// HTTP状态码不是200,直接标记为失败
|
||
detectVideoTemplate.setSuccess(false);
|
||
}
|
||
|
||
// 保存到数据库
|
||
boolean saved = saveDetectVideoTemplateLog(detectVideoTemplate);
|
||
if (saved) {
|
||
log.info("视频模板检测日志已保存,请求ID: {}", requestId);
|
||
} else {
|
||
log.error("保存视频模板检测日志失败,请求ID: {}", requestId);
|
||
}
|
||
|
||
return detectVideoTemplate;
|
||
|
||
} catch (HttpClientErrorException e) {
|
||
return handleError(detectVideoTemplate, e, "客户端错误 (4xx): " + e.getStatusCode(), startTime);
|
||
} catch (HttpServerErrorException e) {
|
||
return handleError(detectVideoTemplate, e, "服务器错误 (5xx): " + e.getStatusCode(), startTime);
|
||
} catch (Exception e) {
|
||
return handleError(detectVideoTemplate, e, "视频模板检测请求失败: " + e.getMessage(), startTime);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 处理错误并保存到数据库
|
||
*/
|
||
private DetectVideoTemplate handleError(DetectVideoTemplate detectVideoTemplate, Exception e, String errorMessage, long startTime) {
|
||
long endTime = System.currentTimeMillis();
|
||
long processingTime = endTime - startTime;
|
||
|
||
detectVideoTemplate.setResponseTime(LocalDateTime.now());
|
||
detectVideoTemplate.setSuccess(false);
|
||
detectVideoTemplate.setProcessingTimeMs(processingTime);
|
||
detectVideoTemplate.setErrorMessage(errorMessage);
|
||
|
||
if (e instanceof HttpClientErrorException) {
|
||
detectVideoTemplate.setStatusCode(((HttpClientErrorException) e).getStatusCode().value());
|
||
detectVideoTemplate.setResponseData(((HttpClientErrorException) e).getResponseBodyAsString());
|
||
} else if (e instanceof HttpServerErrorException) {
|
||
detectVideoTemplate.setStatusCode(((HttpServerErrorException) e).getStatusCode().value());
|
||
detectVideoTemplate.setResponseData(((HttpServerErrorException) e).getResponseBodyAsString());
|
||
}
|
||
|
||
log.error("视频模板检测失败: {}", errorMessage);
|
||
log.error("错误详情: {}", e.getMessage());
|
||
|
||
// 保存错误日志到数据库
|
||
try {
|
||
saveDetectVideoTemplateLog(detectVideoTemplate);
|
||
log.info("错误日志已保存,请求ID: {}", detectVideoTemplate.getRequestId());
|
||
} catch (Exception saveException) {
|
||
log.error("保存错误日志失败: {}", saveException.getMessage());
|
||
}
|
||
|
||
return detectVideoTemplate;
|
||
}
|
||
|
||
/**
|
||
* 创建错误日志
|
||
*/
|
||
private DetectVideoTemplate createErrorLog(String videoUrl, String errorMessage, String ownerName, String ownerPhone, String templateName) {
|
||
DetectVideoTemplate detectVideoTemplate = new DetectVideoTemplate();
|
||
detectVideoTemplate.setRequestId(UUID.randomUUID().toString());
|
||
detectVideoTemplate.setModel("video-template-detect");
|
||
detectVideoTemplate.setVideoUrl(videoUrl);
|
||
detectVideoTemplate.setOwnerName(ownerName);
|
||
detectVideoTemplate.setOwnerPhone(ownerPhone);
|
||
detectVideoTemplate.setTemplateName(templateName);
|
||
detectVideoTemplate.setRequestTime(LocalDateTime.now());
|
||
detectVideoTemplate.setResponseTime(LocalDateTime.now());
|
||
detectVideoTemplate.setSuccess(false);
|
||
detectVideoTemplate.setErrorMessage(errorMessage);
|
||
|
||
try {
|
||
saveDetectVideoTemplateLog(detectVideoTemplate);
|
||
} catch (Exception e) {
|
||
log.error("保存错误日志失败: {}", e.getMessage());
|
||
}
|
||
|
||
return detectVideoTemplate;
|
||
}
|
||
|
||
/**
|
||
* 分页查询检测视频模板日志
|
||
*/
|
||
@Override
|
||
public com.baomidou.mybatisplus.extension.plugins.pagination.Page<DetectVideoTemplate> getPageList(
|
||
Integer current, Integer size, Boolean success, String startTime, String endTime,
|
||
String ownerName, String ownerPhone, String templateName) {
|
||
|
||
com.baomidou.mybatisplus.extension.plugins.pagination.Page<DetectVideoTemplate> page =
|
||
new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(current, size);
|
||
|
||
com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<DetectVideoTemplate> queryWrapper =
|
||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<>();
|
||
|
||
// 添加查询条件
|
||
if (success != null) {
|
||
queryWrapper.eq(DetectVideoTemplate::getSuccess, success);
|
||
}
|
||
if (startTime != null && !startTime.trim().isEmpty()) {
|
||
queryWrapper.ge(DetectVideoTemplate::getRequestTime, startTime);
|
||
}
|
||
if (endTime != null && !endTime.trim().isEmpty()) {
|
||
queryWrapper.le(DetectVideoTemplate::getRequestTime, endTime);
|
||
}
|
||
if (ownerName != null && !ownerName.trim().isEmpty()) {
|
||
queryWrapper.like(DetectVideoTemplate::getOwnerName, ownerName);
|
||
}
|
||
if (ownerPhone != null && !ownerPhone.trim().isEmpty()) {
|
||
queryWrapper.like(DetectVideoTemplate::getOwnerPhone, ownerPhone);
|
||
}
|
||
if (templateName != null && !templateName.trim().isEmpty()) {
|
||
queryWrapper.like(DetectVideoTemplate::getTemplateName, templateName);
|
||
}
|
||
|
||
// 按请求时间倒序排列
|
||
queryWrapper.orderByDesc(DetectVideoTemplate::getRequestTime);
|
||
|
||
return page(page, queryWrapper);
|
||
}
|
||
|
||
/**
|
||
* 获取统计信息
|
||
*/
|
||
@Override
|
||
public Map<String, Object> getStatistics(String startTime, String endTime) {
|
||
Map<String, Object> statistics = new HashMap<>();
|
||
|
||
// 构建查询条件
|
||
com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<DetectVideoTemplate> queryWrapper =
|
||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<>();
|
||
|
||
// 时间范围筛选
|
||
if (startTime != null && !startTime.trim().isEmpty()) {
|
||
queryWrapper.ge(DetectVideoTemplate::getRequestTime, startTime);
|
||
}
|
||
if (endTime != null && !endTime.trim().isEmpty()) {
|
||
queryWrapper.le(DetectVideoTemplate::getRequestTime, endTime);
|
||
}
|
||
|
||
// 查询总数
|
||
long totalCount = count(queryWrapper);
|
||
|
||
// 查询成功数
|
||
com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<DetectVideoTemplate> successWrapper =
|
||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<>();
|
||
|
||
// 时间范围筛选
|
||
if (startTime != null && !startTime.trim().isEmpty()) {
|
||
successWrapper.ge(DetectVideoTemplate::getRequestTime, startTime);
|
||
}
|
||
if (endTime != null && !endTime.trim().isEmpty()) {
|
||
successWrapper.le(DetectVideoTemplate::getRequestTime, endTime);
|
||
}
|
||
successWrapper.eq(DetectVideoTemplate::getSuccess, true);
|
||
long successCount = count(successWrapper);
|
||
|
||
// 查询失败数
|
||
long failureCount = totalCount - successCount;
|
||
|
||
// 计算成功率
|
||
double successRate = totalCount > 0 ? (double) successCount / totalCount * 100 : 0;
|
||
|
||
// 查询平均处理时间
|
||
java.util.List<DetectVideoTemplate> logs = list(queryWrapper);
|
||
double avgProcessingTime = logs.stream()
|
||
.filter(log -> log.getProcessingTimeMs() != null)
|
||
.mapToLong(DetectVideoTemplate::getProcessingTimeMs)
|
||
.average()
|
||
.orElse(0.0);
|
||
|
||
// 构建统计结果
|
||
statistics.put("totalCount", totalCount);
|
||
statistics.put("successCount", successCount);
|
||
statistics.put("failureCount", failureCount);
|
||
statistics.put("successRate", Math.round(successRate * 100.0) / 100.0);
|
||
statistics.put("avgProcessingTimeMs", Math.round(avgProcessingTime));
|
||
|
||
log.info("统计信息查询完成,总数: {}, 成功数: {}, 失败数: {}, 成功率: {}%",
|
||
totalCount, successCount, failureCount, successRate);
|
||
|
||
return statistics;
|
||
}
|
||
|
||
/**
|
||
* 上传视频模板文件并进行检测
|
||
*/
|
||
@Override
|
||
public DetectVideoTemplate uploadAndDetectVideoTemplate(MultipartFile file, String userId, String ownerName,
|
||
String ownerPhone, String templateName, String model, int expiresInSeconds) {
|
||
try {
|
||
log.info("开始处理视频模板上传,文件名: {}, 大小: {} bytes, 用户ID: {}",
|
||
file.getOriginalFilename(), file.getSize(), userId);
|
||
|
||
// 1. 验证文件
|
||
if (file.isEmpty()) {
|
||
return createErrorLog("", "文件不能为空", ownerName, ownerPhone, templateName);
|
||
}
|
||
|
||
// 验证文件类型
|
||
String contentType = file.getContentType();
|
||
if (contentType == null || (!contentType.startsWith("video/"))) {
|
||
return createErrorLog("", "只支持视频文件格式", ownerName, ownerPhone, templateName);
|
||
}
|
||
|
||
// 验证文件大小(限制为100MB)
|
||
if (file.getSize() > 100 * 1024 * 1024) {
|
||
return createErrorLog("", "文件大小不能超过100MB", ownerName, ownerPhone, templateName);
|
||
}
|
||
|
||
// 2. 上传文件到MinIO
|
||
log.info("开始上传视频文件到MinIO...");
|
||
String fileUrl = minioService.uploadFile(file);
|
||
log.info("视频文件上传成功,URL: {}", fileUrl);
|
||
|
||
// 3. 生成短链接(临时访问URL)
|
||
log.info("开始生成短链接,有效期: {}秒", expiresInSeconds);
|
||
String fileName = extractFileNameFromUrl(fileUrl);
|
||
MinIOUrlGenerator.UrlInfo urlInfo = urlGenerator.generateTempUrl(fileName, expiresInSeconds);
|
||
|
||
if (!urlInfo.isSuccess()) {
|
||
return createErrorLog(fileUrl, "生成短链接失败: " + urlInfo.getErrorMessage(), ownerName, ownerPhone, templateName);
|
||
}
|
||
|
||
String shortUrl = urlInfo.getUrl();
|
||
log.info("短链接生成成功: {}", shortUrl);
|
||
|
||
// 4. 进行视频模板检测
|
||
log.info("开始进行视频模板检测,使用模型: {}", model != null ? model : "默认模型");
|
||
DetectVideoTemplate detectLog = detectVideoTemplateAndSave(shortUrl, ownerName, ownerPhone, templateName, model);
|
||
|
||
// 5. 更新日志记录,添加文件信息和临时链接信息
|
||
detectLog.setVideoUrl(fileUrl);
|
||
detectLog.setRequestId(detectLog.getRequestId()); // 保持原有的requestId
|
||
detectLog.setTempUrl(shortUrl);
|
||
detectLog.setTempUrlExpiresAt(urlInfo.getExpiresAt());
|
||
// 如果检测日志中有task_id,也保存到数据库
|
||
if (detectLog.getTaskId() != null) {
|
||
detectLog.setTaskId(detectLog.getTaskId());
|
||
}
|
||
|
||
// 保存更新后的日志
|
||
saveDetectVideoTemplateLog(detectLog);
|
||
|
||
log.info("视频模板上传和检测处理完成,文件URL: {}, 短链接: {}", fileUrl, shortUrl);
|
||
return detectLog;
|
||
|
||
} catch (Exception e) {
|
||
log.error("视频模板上传和检测失败: {}", e.getMessage(), e);
|
||
return createErrorLog("", "视频模板上传和检测失败: " + e.getMessage(), ownerName, ownerPhone, templateName);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 从URL中提取文件名
|
||
*/
|
||
private String extractFileNameFromUrl(String fileUrl) {
|
||
if (fileUrl == null || fileUrl.trim().isEmpty()) {
|
||
return null;
|
||
}
|
||
|
||
// 从URL中提取文件名
|
||
// 例如: http://101.35.52.237:19005/car/20251009_105616_48320fc2d8e24f7bb4be66cbb54a6512.jpg
|
||
// 提取: 20251009_105616_48320fc2d8e24f7bb4be66cbb54a6512.jpg
|
||
String[] parts = fileUrl.split("/");
|
||
if (parts.length > 0) {
|
||
return parts[parts.length - 1];
|
||
}
|
||
|
||
return null;
|
||
}
|
||
}
|