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 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 requestBody = new HashMap<>(); requestBody.put("model", modelName); Map 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> requestEntity = new HttpEntity<>(requestBody, headers); // 发送请求 ResponseEntity 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 getPageList( Integer current, Integer size, Boolean success, String startTime, String endTime, String ownerName, String ownerPhone, String templateName) { com.baomidou.mybatisplus.extension.plugins.pagination.Page page = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(current, size); com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper 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 getStatistics(String startTime, String endTime) { Map statistics = new HashMap<>(); // 构建查询条件 com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper 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 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 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; } }