diff --git a/src/main/java/com/rj/common/PasswordUtil.java b/src/main/java/com/rj/common/PasswordUtil.java index 2874842..7288785 100644 --- a/src/main/java/com/rj/common/PasswordUtil.java +++ b/src/main/java/com/rj/common/PasswordUtil.java @@ -138,6 +138,17 @@ public class PasswordUtil { + + + + + + + + + + + diff --git a/src/main/java/com/rj/common/ServiceManager.java b/src/main/java/com/rj/common/ServiceManager.java index a0ee032..65c9dcf 100644 --- a/src/main/java/com/rj/common/ServiceManager.java +++ b/src/main/java/com/rj/common/ServiceManager.java @@ -122,6 +122,17 @@ public class ServiceManager { + + + + + + + + + + + diff --git a/src/main/java/com/rj/config/AliyunConfig.java b/src/main/java/com/rj/config/AliyunConfig.java index a5f3d53..362eaa0 100644 --- a/src/main/java/com/rj/config/AliyunConfig.java +++ b/src/main/java/com/rj/config/AliyunConfig.java @@ -99,6 +99,17 @@ public class AliyunConfig { + + + + + + + + + + + diff --git a/src/main/java/com/rj/controller/DetectVideoTemplateController.java b/src/main/java/com/rj/controller/DetectVideoTemplateController.java index 8f56afe..9a22266 100644 --- a/src/main/java/com/rj/controller/DetectVideoTemplateController.java +++ b/src/main/java/com/rj/controller/DetectVideoTemplateController.java @@ -26,12 +26,12 @@ public class DetectVideoTemplateController { private IDetectVideoTemplateService detectVideoTemplateService; /** - * 视频模板检测接口 + * 视频模板上传和检测接口 */ @PostMapping("/uploadAndDetect") - @Operation(summary = "视频模板检测", description = "检测视频模板信息") - public ResponseEntity> detectVideoTemplate( - @Parameter(description = "头像文件", required = true) + @Operation(summary = "视频模板上传和检测", description = "上传视频模板文件,生成短链接并进行模板检测") + public ResponseEntity> uploadAndDetectVideoTemplate( + @Parameter(description = "视频模板文件", required = true) @RequestParam("file") MultipartFile file, @Parameter(description = "用户ID", required = false) @RequestParam(value = "userId", required = false) String userId, @@ -39,43 +39,61 @@ public class DetectVideoTemplateController { @RequestParam(value = "ownerName", required = false) String ownerName, @Parameter(description = "所属人电话", required = false) @RequestParam(value = "ownerPhone", required = false) String ownerPhone, - @Parameter(description = "头像名称", required = false) - @RequestParam(value = "avatarName", required = false) String avatarName, + @Parameter(description = "模板名称", required = false) + @RequestParam(value = "templateName", required = false) String templateName, @Parameter(description = "模型名称", required = false) @RequestParam(value = "model", required = false) String model, - @Parameter(description = "短链接有效期(秒),默认1小时", required = false) - @RequestParam(value = "expiresInSeconds", defaultValue = "360000") int expiresInSeconds ){ + @Parameter(description = "短链接有效期(秒),默认7天", required = false) + @RequestParam(value = "expiresInSeconds", defaultValue = "604800") int expiresInSeconds) { Map result = new HashMap<>(); try { - + log.info("开始处理视频模板上传和检测,文件名: {}, 大小: {} bytes, 用户ID: {}", + file.getOriginalFilename(), file.getSize(), userId); - // 执行视频模板检测 - com.rj.entity.DetectVideoTemplate detectLog = detectVideoTemplateService.detectVideoTemplateAndSave("", ownerName, ownerPhone, avatarName, model); + // 执行视频模板上传和检测 + com.rj.entity.DetectVideoTemplate detectLog = detectVideoTemplateService.uploadAndDetectVideoTemplate( + file, userId, ownerName, ownerPhone, templateName, model, expiresInSeconds); + // 构建响应结果 result.put("success", detectLog.getSuccess()); - result.put("message", detectLog.getSuccess() ? "视频模板检测成功" : "视频模板检测失败"); - result.put("videoUrl", ""); + result.put("message", detectLog.getSuccess() ? "视频模板上传和检测成功" : "视频模板上传和检测失败"); + result.put("originalFileName", file.getOriginalFilename()); + result.put("fileSize", file.getSize()); + result.put("videoUrl", detectLog.getVideoUrl()); + result.put("tempUrl", detectLog.getTempUrl()); + result.put("tempUrlExpiresAt", detectLog.getTempUrlExpiresAt()); result.put("requestId", detectLog.getRequestId()); + result.put("taskId", detectLog.getTaskId()); + result.put("taskStatus", detectLog.getTaskStatus()); result.put("templateId", detectLog.getTemplateId()); result.put("processingTimeMs", detectLog.getProcessingTimeMs()); + result.put("userId", userId); result.put("ownerName", detectLog.getOwnerName()); result.put("ownerPhone", detectLog.getOwnerPhone()); - result.put("avatarName", detectLog.getAvatarName()); + result.put("templateName", detectLog.getTemplateName()); result.put("model", detectLog.getModel()); + result.put("expiresInSeconds", expiresInSeconds); + + // 视频模板检测结果 + result.put("videoTemplateDetection", detectLog.toString()); if (!detectLog.getSuccess()) { result.put("errorMessage", detectLog.getErrorMessage()); + log.warn("视频模板检测失败: {}", detectLog.getErrorMessage()); return ResponseEntity.badRequest().body(result); + } else { + log.info("视频模板检测成功,模板ID: {}", detectLog.getTemplateId()); } + log.info("视频模板上传和检测处理完成,文件URL: {}", detectLog.getVideoUrl()); return ResponseEntity.ok(result); } catch (Exception e) { - log.error("视频模板检测失败: {}", e.getMessage(), e); + log.error("视频模板上传和检测失败: {}", e.getMessage(), e); result.put("success", false); - result.put("message", "视频模板检测失败: " + e.getMessage()); + result.put("message", "视频模板上传和检测失败: " + e.getMessage()); return ResponseEntity.internalServerError().body(result); } } @@ -93,7 +111,7 @@ public class DetectVideoTemplateController { String ownerName = requestBody.get("ownerName"); String ownerPhone = requestBody.get("ownerPhone"); String model = requestBody.get("model"); - String avatarName = requestBody.get("avatarName"); + String templateName = requestBody.get("templateName"); if (videoUrl == null || videoUrl.trim().isEmpty()) { result.put("success", false); @@ -104,17 +122,21 @@ public class DetectVideoTemplateController { log.info("开始视频模板检测(JSON),视频URL: {}", videoUrl); // 执行视频模板检测 - com.rj.entity.DetectVideoTemplate detectLog = detectVideoTemplateService.detectVideoTemplateAndSave(videoUrl, ownerName, ownerPhone, avatarName, model); + com.rj.entity.DetectVideoTemplate detectLog = detectVideoTemplateService.detectVideoTemplateAndSave(videoUrl, ownerName, ownerPhone, templateName, model); result.put("success", detectLog.getSuccess()); result.put("message", detectLog.getSuccess() ? "视频模板检测成功" : "视频模板检测失败"); result.put("videoUrl", videoUrl); + result.put("tempUrl", detectLog.getTempUrl()); + result.put("tempUrlExpiresAt", detectLog.getTempUrlExpiresAt()); result.put("requestId", detectLog.getRequestId()); + result.put("taskId", detectLog.getTaskId()); + result.put("taskStatus", detectLog.getTaskStatus()); result.put("templateId", detectLog.getTemplateId()); result.put("processingTimeMs", detectLog.getProcessingTimeMs()); result.put("ownerName", detectLog.getOwnerName()); result.put("ownerPhone", detectLog.getOwnerPhone()); - result.put("avatarName", detectLog.getAvatarName()); + result.put("templateName", detectLog.getTemplateName()); result.put("model", detectLog.getModel()); if (!detectLog.getSuccess()) { @@ -152,14 +174,14 @@ public class DetectVideoTemplateController { @RequestParam(required = false) String ownerName, @Parameter(description = "所属人电话", required = false) @RequestParam(required = false) String ownerPhone, - @Parameter(description = "头像名称", required = false) - @RequestParam(required = false) String avatarName) { + @Parameter(description = "模板名称", required = false) + @RequestParam(required = false) String templateName) { Map result = new HashMap<>(); try { com.baomidou.mybatisplus.extension.plugins.pagination.Page page = - detectVideoTemplateService.getPageList(current, size, success, startTime, endTime, ownerName, ownerPhone, avatarName); + detectVideoTemplateService.getPageList(current, size, success, startTime, endTime, ownerName, ownerPhone, templateName); result.put("success", true); result.put("message", "查询成功"); diff --git a/src/main/java/com/rj/controller/VideoImageAnalysisController.java b/src/main/java/com/rj/controller/VideoImageAnalysisController.java new file mode 100644 index 0000000..4df3466 --- /dev/null +++ b/src/main/java/com/rj/controller/VideoImageAnalysisController.java @@ -0,0 +1,883 @@ +package com.rj.controller; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.rj.entity.VideoImageAnalysis; +import com.rj.service.IVideoImageAnalysisService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import jakarta.validation.Valid; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.web.client.RestTemplate; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.JsonNode; +import com.rj.service.MinIOService; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.*; + +/** + * AI视频图像分析记录 前端控制器 + * Qwen3-VL-Plus模型的视觉理解能力最强,Qwen-VL-Max(基于Qwen2.5-VL)次之;Qwen-VL-Plus(基于Qwen2.5-VL)模型在效果、成本上比较均衡, + * 如果您暂时不确定使用某种模型,可以优先尝试使用通义千问Qwen3-VL-Plus模型。 + * @author 系统生成 + * @date 2025/1/30 + */ +@Slf4j +@RestController +@RequestMapping("/api/video-image-analysis") +@Tag(name = "AI视频图像分析记录", description = "AI视频图像分析记录相关接口") +public class VideoImageAnalysisController { + + @Autowired + private IVideoImageAnalysisService videoImageAnalysisService; + + @Autowired + private MinIOService minIOService; + + @Autowired + private RestTemplate restTemplate; + + @Autowired + private ObjectMapper objectMapper; + + @Value("${dashscope.api.key}") + private String dashscopeApiKey; + + /** + * 分页查询AI视频图像分析记录列表 + */ + @GetMapping("/list") + @Operation(summary = "分页查询AI视频图像分析记录列表", description = "根据条件分页查询AI视频图像分析记录信息列表") + public ResponseEntity> getAnalysisList( + @Parameter(description = "页码", example = "1") @RequestParam(defaultValue = "1") Integer current, + @Parameter(description = "每页大小", example = "10") @RequestParam(defaultValue = "10") Integer size, + @Parameter(description = "AI模型") @RequestParam(required = false) String model, + @Parameter(description = "用户姓名(模糊查询)") @RequestParam(required = false) String userName, + @Parameter(description = "用户电话") @RequestParam(required = false) String userPhone, + @Parameter(description = "会话名称(模糊查询)") @RequestParam(required = false) String sessionName, + @Parameter(description = "状态") @RequestParam(required = false) Integer status, + @Parameter(description = "开始日期") @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate, + @Parameter(description = "结束日期") @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate) { + + log.info("分页查询AI视频图像分析记录列表,模型={}, 用户姓名={}, 用户电话={}, 会话名称={}, 状态={}, 开始日期={}, 结束日期={}", + model, userName, userPhone, sessionName, status, startDate, endDate); + + Map result = new HashMap<>(); + try { + Page page = new Page<>(current, size); + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + + // 添加查询条件 + if (model != null && !model.trim().isEmpty()) { + queryWrapper.like(VideoImageAnalysis::getModel, model); + } + if (userName != null && !userName.trim().isEmpty()) { + queryWrapper.like(VideoImageAnalysis::getUserName, userName); + } + if (userPhone != null && !userPhone.trim().isEmpty()) { + queryWrapper.like(VideoImageAnalysis::getUserPhone, userPhone); + } + if (sessionName != null && !sessionName.trim().isEmpty()) { + queryWrapper.like(VideoImageAnalysis::getSessionName, sessionName); + } + if (status != null) { + queryWrapper.eq(VideoImageAnalysis::getStatus, status); + } + if (startDate != null) { + queryWrapper.ge(VideoImageAnalysis::getCreatedAt, startDate.atStartOfDay()); + } + if (endDate != null) { + queryWrapper.le(VideoImageAnalysis::getCreatedAt, endDate.atTime(23, 59, 59)); + } + + // 按创建时间倒序排列 + queryWrapper.orderByDesc(VideoImageAnalysis::getCreatedAt); + + Page analysisPage = videoImageAnalysisService.page(page, queryWrapper); + + result.put("success", true); + result.put("message", "查询成功"); + result.put("data", analysisPage.getRecords()); + result.put("total", analysisPage.getTotal()); + result.put("current", analysisPage.getCurrent()); + result.put("size", analysisPage.getSize()); + result.put("pages", analysisPage.getPages()); + + return ResponseEntity.ok(result); + } catch (Exception e) { + log.error("分页查询AI视频图像分析记录列表失败", e); + result.put("success", false); + result.put("message", "查询异常:" + e.getMessage()); + return ResponseEntity.internalServerError().body(result); + } + } + + /** + * 根据条件查询AI视频图像分析记录(不分页) + */ + @GetMapping("/query") + @Operation(summary = "根据条件查询AI视频图像分析记录", description = "根据条件查询AI视频图像分析记录信息(不分页)") + public ResponseEntity> getAnalysisList( + @Parameter(description = "AI模型") @RequestParam(required = false) String model, + @Parameter(description = "用户姓名") @RequestParam(required = false) String userName, + @Parameter(description = "用户电话") @RequestParam(required = false) String userPhone, + @Parameter(description = "会话名称") @RequestParam(required = false) String sessionName, + @Parameter(description = "状态") @RequestParam(required = false) Integer status, + @Parameter(description = "开始日期") @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate, + @Parameter(description = "结束日期") @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate) { + + Map result = new HashMap<>(); + try { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + + // 添加查询条件 + if (model != null && !model.trim().isEmpty()) { + queryWrapper.like(VideoImageAnalysis::getModel, model); + } + if (userName != null && !userName.trim().isEmpty()) { + queryWrapper.like(VideoImageAnalysis::getUserName, userName); + } + if (userPhone != null && !userPhone.trim().isEmpty()) { + queryWrapper.like(VideoImageAnalysis::getUserPhone, userPhone); + } + if (sessionName != null && !sessionName.trim().isEmpty()) { + queryWrapper.like(VideoImageAnalysis::getSessionName, sessionName); + } + if (status != null) { + queryWrapper.eq(VideoImageAnalysis::getStatus, status); + } + if (startDate != null) { + queryWrapper.ge(VideoImageAnalysis::getCreatedAt, startDate.atStartOfDay()); + } + if (endDate != null) { + queryWrapper.le(VideoImageAnalysis::getCreatedAt, endDate.atTime(23, 59, 59)); + } + + // 按创建时间倒序排列 + queryWrapper.orderByDesc(VideoImageAnalysis::getCreatedAt); + + List analysisList = videoImageAnalysisService.list(queryWrapper); + + result.put("success", true); + result.put("message", "查询成功"); + result.put("data", analysisList); + result.put("total", analysisList.size()); + + return ResponseEntity.ok(result); + } catch (Exception e) { + log.error("根据条件查询AI视频图像分析记录失败", e); + result.put("success", false); + result.put("message", "查询异常:" + e.getMessage()); + return ResponseEntity.internalServerError().body(result); + } + } + + /** + * 根据ID查询AI视频图像分析记录 + */ + @GetMapping("/{id}") + @Operation(summary = "根据ID查询AI视频图像分析记录", description = "根据主键ID查询单条AI视频图像分析记录信息") + public ResponseEntity> getAnalysisById( + @Parameter(description = "AI视频图像分析记录ID", required = true) + @PathVariable String id) { + + Map result = new HashMap<>(); + try { + VideoImageAnalysis analysis = videoImageAnalysisService.getById(id); + if (analysis != null) { + result.put("success", true); + result.put("message", "查询成功"); + result.put("data", analysis); + return ResponseEntity.ok(result); + } else { + result.put("success", false); + result.put("message", "AI视频图像分析记录不存在"); + return ResponseEntity.notFound().build(); + } + } catch (Exception e) { + log.error("根据ID查询AI视频图像分析记录失败", e); + result.put("success", false); + result.put("message", "查询异常:" + e.getMessage()); + return ResponseEntity.internalServerError().body(result); + } + } + + /** + * 根据用户姓名查询AI视频图像分析记录 + */ + @GetMapping("/by-user-name/{userName}") + @Operation(summary = "根据用户姓名查询AI视频图像分析记录", description = "根据用户姓名查询AI视频图像分析记录信息") + public ResponseEntity> getAnalysisByUserName( + @Parameter(description = "用户姓名", required = true) + @PathVariable String userName) { + + Map result = new HashMap<>(); + try { + VideoImageAnalysis analysis = videoImageAnalysisService.getByUserName(userName); + if (analysis != null) { + result.put("success", true); + result.put("message", "查询成功"); + result.put("data", analysis); + return ResponseEntity.ok(result); + } else { + result.put("success", false); + result.put("message", "未找到对应的AI视频图像分析记录"); + return ResponseEntity.notFound().build(); + } + } catch (Exception e) { + log.error("根据用户姓名查询AI视频图像分析记录失败", e); + result.put("success", false); + result.put("message", "查询异常:" + e.getMessage()); + return ResponseEntity.internalServerError().body(result); + } + } + + /** + * 根据用户电话查询AI视频图像分析记录 + */ + @GetMapping("/by-user-phone/{userPhone}") + @Operation(summary = "根据用户电话查询AI视频图像分析记录", description = "根据用户电话查询AI视频图像分析记录信息") + public ResponseEntity> getAnalysisByUserPhone( + @Parameter(description = "用户电话", required = true) + @PathVariable String userPhone) { + + Map result = new HashMap<>(); + try { + VideoImageAnalysis analysis = videoImageAnalysisService.getByUserPhone(userPhone); + if (analysis != null) { + result.put("success", true); + result.put("message", "查询成功"); + result.put("data", analysis); + return ResponseEntity.ok(result); + } else { + result.put("success", false); + result.put("message", "未找到对应的AI视频图像分析记录"); + return ResponseEntity.notFound().build(); + } + } catch (Exception e) { + log.error("根据用户电话查询AI视频图像分析记录失败", e); + result.put("success", false); + result.put("message", "查询异常:" + e.getMessage()); + return ResponseEntity.internalServerError().body(result); + } + } + + /** + * 根据会话名称查询AI视频图像分析记录 + */ + @GetMapping("/by-session-name/{sessionName}") + @Operation(summary = "根据会话名称查询AI视频图像分析记录", description = "根据会话名称查询AI视频图像分析记录信息") + public ResponseEntity> getAnalysisBySessionName( + @Parameter(description = "会话名称", required = true) + @PathVariable String sessionName) { + + Map result = new HashMap<>(); + try { + VideoImageAnalysis analysis = videoImageAnalysisService.getBySessionName(sessionName); + if (analysis != null) { + result.put("success", true); + result.put("message", "查询成功"); + result.put("data", analysis); + return ResponseEntity.ok(result); + } else { + result.put("success", false); + result.put("message", "未找到对应的AI视频图像分析记录"); + return ResponseEntity.notFound().build(); + } + } catch (Exception e) { + log.error("根据会话名称查询AI视频图像分析记录失败", e); + result.put("success", false); + result.put("message", "查询异常:" + e.getMessage()); + return ResponseEntity.internalServerError().body(result); + } + } + + /** + * 根据模型查询AI视频图像分析记录 + */ + @GetMapping("/by-model/{model}") + @Operation(summary = "根据模型查询AI视频图像分析记录", description = "根据模型查询AI视频图像分析记录信息") + public ResponseEntity> getAnalysisByModel( + @Parameter(description = "AI模型", required = true) + @PathVariable String model) { + + Map result = new HashMap<>(); + try { + VideoImageAnalysis analysis = videoImageAnalysisService.getByModel(model); + if (analysis != null) { + result.put("success", true); + result.put("message", "查询成功"); + result.put("data", analysis); + return ResponseEntity.ok(result); + } else { + result.put("success", false); + result.put("message", "未找到对应的AI视频图像分析记录"); + return ResponseEntity.notFound().build(); + } + } catch (Exception e) { + log.error("根据模型查询AI视频图像分析记录失败", e); + result.put("success", false); + result.put("message", "查询异常:" + e.getMessage()); + return ResponseEntity.internalServerError().body(result); + } + } + + /** + * 新增AI视频图像分析记录 + */ + @PostMapping("/add") + @Operation(summary = "新增AI视频图像分析记录", description = "新增一条AI视频图像分析记录") + public ResponseEntity> addAnalysis( + @Parameter(description = "AI视频图像分析记录信息", required = true) + @Valid @RequestBody VideoImageAnalysis analysis) { + + Map result = new HashMap<>(); + try { + // 设置系统字段 + LocalDateTime now = LocalDateTime.now(); + analysis.setCreatedAt(now); + analysis.setUpdatedAt(now); + + // 设置默认值 + if (analysis.getRole() == null || analysis.getRole().trim().isEmpty()) { + analysis.setRole("assistant"); + } + if (analysis.getChoiceIndex() == null) { + analysis.setChoiceIndex(0); + } + if (analysis.getStatus() == null) { + analysis.setStatus(1); + } + + boolean success = videoImageAnalysisService.saveVideoImageAnalysis(analysis); + if (success) { + result.put("success", true); + result.put("message", "AI视频图像分析记录添加成功"); + result.put("data", analysis); + return ResponseEntity.ok(result); + } else { + result.put("success", false); + result.put("message", "AI视频图像分析记录添加失败"); + return ResponseEntity.badRequest().body(result); + } + } catch (Exception e) { + log.error("新增AI视频图像分析记录失败", e); + result.put("success", false); + result.put("message", "添加异常:" + e.getMessage()); + return ResponseEntity.internalServerError().body(result); + } + } + + /** + * 修改AI视频图像分析记录 + */ + @PutMapping("/update") + @Operation(summary = "修改AI视频图像分析记录", description = "修改AI视频图像分析记录信息") + public ResponseEntity> updateAnalysis( + @Parameter(description = "AI视频图像分析记录信息", required = true) + @Valid @RequestBody VideoImageAnalysis analysis) { + + Map result = new HashMap<>(); + try { + if (analysis.getId() == null || analysis.getId().trim().isEmpty()) { + result.put("success", false); + result.put("message", "ID不能为空"); + return ResponseEntity.badRequest().body(result); + } + + // 检查记录是否存在 + VideoImageAnalysis existingAnalysis = videoImageAnalysisService.getById(analysis.getId()); + if (existingAnalysis == null) { + result.put("success", false); + result.put("message", "AI视频图像分析记录不存在"); + return ResponseEntity.notFound().build(); + } + + // 设置更新时间 + analysis.setUpdatedAt(LocalDateTime.now()); + + boolean success = videoImageAnalysisService.updateById(analysis); + if (success) { + result.put("success", true); + result.put("message", "AI视频图像分析记录修改成功"); + result.put("data", analysis); + return ResponseEntity.ok(result); + } else { + result.put("success", false); + result.put("message", "AI视频图像分析记录修改失败"); + return ResponseEntity.badRequest().body(result); + } + } catch (Exception e) { + log.error("修改AI视频图像分析记录失败", e); + result.put("success", false); + result.put("message", "修改异常:" + e.getMessage()); + return ResponseEntity.internalServerError().body(result); + } + } + + /** + * 删除AI视频图像分析记录 + */ + @DeleteMapping("/delete/{id}") + @Operation(summary = "删除AI视频图像分析记录", description = "根据ID删除AI视频图像分析记录") + public ResponseEntity> deleteAnalysis( + @Parameter(description = "AI视频图像分析记录ID", required = true) + @PathVariable String id) { + + Map result = new HashMap<>(); + try { + // 检查记录是否存在 + VideoImageAnalysis existingAnalysis = videoImageAnalysisService.getById(id); + if (existingAnalysis == null) { + result.put("success", false); + result.put("message", "AI视频图像分析记录不存在"); + return ResponseEntity.notFound().build(); + } + + boolean success = videoImageAnalysisService.removeById(id); + if (success) { + result.put("success", true); + result.put("message", "AI视频图像分析记录删除成功"); + return ResponseEntity.ok(result); + } else { + result.put("success", false); + result.put("message", "AI视频图像分析记录删除失败"); + return ResponseEntity.badRequest().body(result); + } + } catch (Exception e) { + log.error("删除AI视频图像分析记录失败", e); + result.put("success", false); + result.put("message", "删除异常:" + e.getMessage()); + return ResponseEntity.internalServerError().body(result); + } + } + + /** + * 批量删除AI视频图像分析记录 + */ + @DeleteMapping("/batch-delete") + @Operation(summary = "批量删除AI视频图像分析记录", description = "根据ID列表批量删除AI视频图像分析记录") + public ResponseEntity> batchDeleteAnalysis( + @Parameter(description = "AI视频图像分析记录ID列表", required = true) + @RequestBody List ids) { + + Map result = new HashMap<>(); + try { + if (ids == null || ids.isEmpty()) { + result.put("success", false); + result.put("message", "ID列表不能为空"); + return ResponseEntity.badRequest().body(result); + } + + boolean success = videoImageAnalysisService.removeByIds(ids); + if (success) { + result.put("success", true); + result.put("message", "AI视频图像分析记录批量删除成功"); + return ResponseEntity.ok(result); + } else { + result.put("success", false); + result.put("message", "AI视频图像分析记录批量删除失败"); + return ResponseEntity.badRequest().body(result); + } + } catch (Exception e) { + log.error("批量删除AI视频图像分析记录失败", e); + result.put("success", false); + result.put("message", "批量删除异常:" + e.getMessage()); + return ResponseEntity.internalServerError().body(result); + } + } + + /** + * 获取AI视频图像分析记录统计信息 + */ + @GetMapping("/statistics") + @Operation(summary = "获取AI视频图像分析记录统计信息", description = "获取AI视频图像分析记录的统计信息") + public ResponseEntity> getAnalysisStatistics( + @Parameter(description = "开始日期") @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate, + @Parameter(description = "结束日期") @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate, + @Parameter(description = "AI模型") @RequestParam(required = false) String model) { + + Map result = new HashMap<>(); + try { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + + // 添加查询条件 + if (startDate != null) { + queryWrapper.ge(VideoImageAnalysis::getCreatedAt, startDate.atStartOfDay()); + } + if (endDate != null) { + queryWrapper.le(VideoImageAnalysis::getCreatedAt, endDate.atTime(23, 59, 59)); + } + if (model != null && !model.trim().isEmpty()) { + queryWrapper.eq(VideoImageAnalysis::getModel, model); + } + + List analysisList = videoImageAnalysisService.list(queryWrapper); + + // 计算统计信息 + Map statistics = new HashMap<>(); + statistics.put("totalCount", analysisList.size()); + statistics.put("successCount", analysisList.stream().filter(a -> a.getStatus() == 1).count()); + statistics.put("failCount", analysisList.stream().filter(a -> a.getStatus() == 0).count()); + statistics.put("uniqueUsers", analysisList.stream().map(VideoImageAnalysis::getUserName).distinct().count()); + statistics.put("uniqueSessions", analysisList.stream().map(VideoImageAnalysis::getSessionName).distinct().count()); + + // 按模型统计 + Map modelStats = analysisList.stream() + .collect(java.util.stream.Collectors.groupingBy( + VideoImageAnalysis::getModel, + java.util.stream.Collectors.counting())); + statistics.put("modelStatistics", modelStats); + + // 按状态统计 + Map statusStats = analysisList.stream() + .collect(java.util.stream.Collectors.groupingBy( + VideoImageAnalysis::getStatus, + java.util.stream.Collectors.counting())); + statistics.put("statusStatistics", statusStats); + + result.put("success", true); + result.put("message", "统计信息获取成功"); + result.put("data", statistics); + + return ResponseEntity.ok(result); + } catch (Exception e) { + log.error("获取AI视频图像分析记录统计信息失败", e); + result.put("success", false); + result.put("message", "统计信息获取异常:" + e.getMessage()); + return ResponseEntity.internalServerError().body(result); + } + } + + /** + * AI分析接口 - 处理图片/视频分析请求 + */ + @PostMapping("/ai-analysis") + @Operation(summary = "AI分析接口", description = "处理图片/视频的AI分析请求") + public ResponseEntity> aiAnalysis( + @Parameter(description = "分析名称", required = true) @RequestParam String analysisName, + @Parameter(description = "所属人姓名", required = true) @RequestParam String ownerName, + @Parameter(description = "所属人电话", required = true) @RequestParam String ownerPhone, + @Parameter(description = "AI模型", required = true) @RequestParam String model, + @Parameter(description = "分析类型", required = true) @RequestParam String type, + @Parameter(description = "用户问题", required = true) @RequestParam String userQuestion, + @Parameter(description = "上传的文件") @RequestParam(required = false) MultipartFile file) { + + Map result = new HashMap<>(); + try { + log.info("AI分析请求开始,分析名称={}, 所属人={}, 电话={}, 模型={}, 分析类型={}, 问题={}", + analysisName, ownerName, ownerPhone, model, type, userQuestion); + + // 创建分析记录 + VideoImageAnalysis analysis = new VideoImageAnalysis(); + analysis.setModel(model); + analysis.setType(type); + analysis.setUserQuestion(userQuestion); + analysis.setUserName(ownerName); + analysis.setUserPhone(ownerPhone); + analysis.setSessionName(analysisName); + analysis.setRole("assistant"); + analysis.setChoiceIndex(0); + analysis.setStatus(1); // 默认成功状态 + + // 处理文件上传 + if (file != null && !file.isEmpty()) { + // 上传文件到MinIO并获取原始链接和临时访问链接 + Map uploadResult = handleFileUploadWithBothUrls(file); + analysis.setVideoImageUrl(uploadResult.get("originalUrl")); + analysis.setVideoImageTempUrl(uploadResult.get("tempUrl")); + } + + // 设置系统字段 + LocalDateTime now = LocalDateTime.now(); + analysis.setCreatedAt(now); + analysis.setUpdatedAt(now); + + // 调用阿里云AI服务进行实际分析 + Map aiResult = performAiAnalysisWithDetails(model, type, userQuestion, analysis.getVideoImageTempUrl()); + String aiResponse = (String) aiResult.get("content"); + analysis.setResponseContent(aiResponse); + + // 保存请求和响应数据到数据库 + String requestData = (String) aiResult.getOrDefault("requestData", "{}"); + String responseData = (String) aiResult.getOrDefault("responseData", "{}"); + analysis.setRequestData(requestData); + analysis.setResponseData(responseData); + + // 记录AI分析结果和token使用情况 + analysis.setFinishReason("stop"); + analysis.setUsagePromptTokens((Integer) aiResult.getOrDefault("promptTokens", 0)); + analysis.setUsageCompletionTokens((Integer) aiResult.getOrDefault("completionTokens", 0)); + analysis.setUsageTotalTokens((Integer) aiResult.getOrDefault("totalTokens", 0)); + + // 保存分析记录 + boolean success = videoImageAnalysisService.saveVideoImageAnalysis(analysis); + + if (success) { + result.put("success", true); + result.put("message", "AI分析完成"); + result.put("data", analysis); + return ResponseEntity.ok(result); + } else { + result.put("success", false); + result.put("message", "AI分析失败"); + return ResponseEntity.badRequest().body(result); + } + + } catch (Exception e) { + log.error("AI分析请求失败", e); + result.put("success", false); + result.put("message", "AI分析异常:" + e.getMessage()); + return ResponseEntity.internalServerError().body(result); + } + } + + /** + * 处理文件上传到MinIO并生成原始链接和7天临时访问链接 + * @param file 上传的文件 + * @return 包含原始URL和临时URL的Map + */ + private Map handleFileUploadWithBothUrls(MultipartFile file) { + try { + log.info("开始上传文件到MinIO,文件名:{}, 大小:{}", file.getOriginalFilename(), file.getSize()); + + // 上传文件到MinIO并获取原始链接 + String originalUrl = minIOService.uploadFile(file); + log.info("文件上传成功,原始URL:{}", originalUrl); + + // 从原始URL中提取对象名,用于生成临时链接 + String objectName = extractObjectNameFromUrl(originalUrl); + String tempUrl = minIOService.generateTempUrl(objectName); + log.info("生成7天临时访问URL:{}", tempUrl); + + Map result = new HashMap<>(); + result.put("originalUrl", originalUrl); + result.put("tempUrl", tempUrl); + + return result; + } catch (Exception e) { + log.error("文件上传失败", e); + throw new RuntimeException("文件上传失败:" + e.getMessage()); + } + } + + /** + * 从MinIO URL中提取对象名 + * @param url MinIO文件URL + * @return 对象名 + */ + private String extractObjectNameFromUrl(String url) { + try { + // MinIO URL格式通常是: http://endpoint/bucket/objectName + // 或者: http://endpoint/bucket/objectName?参数 + log.info("从URL提取对象名: {}", url); + + // 去掉查询参数 + String urlWithoutParams = url; + if (url.contains("?")) { + urlWithoutParams = url.substring(0, url.indexOf("?")); + } + + // 按"/"分割URL + String[] parts = urlWithoutParams.split("/"); + if (parts.length >= 2) { + // 获取最后一个部分作为对象名 + String objectName = parts[parts.length - 1]; + log.info("提取到的对象名: {}", objectName); + return objectName; + } + + throw new RuntimeException("无法从URL中提取对象名: " + url); + } catch (Exception e) { + log.error("提取对象名失败", e); + throw new RuntimeException("提取对象名失败: " + e.getMessage()); + } + } + + /** + * 执行AI分析 - 调用阿里云通义千问API(返回详细信息) + */ + private Map performAiAnalysisWithDetails(String model, String type, String userQuestion, String fileUrl) { + try { + log.info("执行AI分析,模型:{}, 分析类型:{}, 问题:{}, 文件:{}", model, type, userQuestion, fileUrl); + + // 构建请求URL + String apiUrl = "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions"; + + // 构建请求头 + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.set("Authorization", "Bearer " + dashscopeApiKey); + + // 构建请求体 + Map requestBody = new HashMap<>(); + requestBody.put("model", model); + + // 构建消息内容 + List> messages = new ArrayList<>(); + Map message = new HashMap<>(); + message.put("role", "user"); + + List> content = new ArrayList<>(); + + Map imageContent = new HashMap<>(); + imageContent.put("type", type); + + if (type.equals("video")) { + /** + * [{ + * "role": "user", + * "content": [ + * { + * "type": "video", + * "video": [ + * "https://img.alicdn.com/imgextra/i3/O1CN01K3SgGo1eqmlUgeE9b_!!6000000003923-0-tps-3840-2160.jpg", + * "https://img.alicdn.com/imgextra/i4/O1CN01BjZvwg1Y23CF5qIRB_!!6000000003000-0-tps-3840-2160.jpg", + * "https://img.alicdn.com/imgextra/i4/O1CN01Ib0clU27vTgBdbVLQ_!!6000000007859-0-tps-3840-2160.jpg", + * "https://img.alicdn.com/imgextra/i1/O1CN01aygPLW1s3EXCdSN4X_!!6000000005710-0-tps-3840-2160.jpg"] + * }, + * { + * "type": "text", + * "text": "描述这个视频的具体过程" + * }]}] + */ + + imageContent.put("video", Arrays.asList(fileUrl)); + content.add(imageContent); + }else if (type.equals("image_url")) { + /** + * [{"role": "user","content": [ + * {"type": "image_url", + * "image_url": {"url": "https://dashscope.oss-cn-beijing.aliyuncs.com/images/dog_and_girl.jpeg"}}, + * {"type": "text", "text": "这是什么"}, + * ]}] + */ + // 如果有文件URL,添加图片内容 + if (fileUrl != null && !fileUrl.trim().isEmpty()) { + + Map imageUrl = new HashMap<>(); + imageUrl.put("url", fileUrl); + imageContent.put("image_url", imageUrl); + content.add(imageContent); + } + } + + + // 添加文本内容,包含分析类型信息 + Map textContent = new HashMap<>(); + textContent.put("type", "text"); + String enhancedQuestion = String.format("分析类型:%s\n用户问题:%s", type, userQuestion); + textContent.put("text", enhancedQuestion); + content.add(textContent); + + message.put("content", content); + messages.add(message); + requestBody.put("messages", messages); + + // 创建HTTP实体 + HttpEntity> entity = new HttpEntity<>(requestBody, headers); + + // 保存请求数据到结果中 + String requestDataJson = objectMapper.writeValueAsString(requestBody); + log.info("调用阿里云AI分析API,URL:{}, 请求体:{}", apiUrl, requestDataJson); + + // 发送请求 + ResponseEntity response = restTemplate.exchange( + apiUrl, + HttpMethod.POST, + entity, + String.class + ); + + log.info("阿里云AI分析API响应状态:{}", response.getStatusCode()); + log.info("阿里云AI分析API响应内容:{}", response.getBody()); + + // 保存响应数据 + String responseDataJson = response.getBody(); + + if (response.getStatusCode().is2xxSuccessful()) { + // 解析响应 + JsonNode responseJson = objectMapper.readTree(response.getBody()); + JsonNode choices = responseJson.path("choices"); + if (choices.isArray() && choices.size() > 0) { + JsonNode firstChoice = choices.get(0); + JsonNode messageNode = firstChoice.path("message"); + String contentText = messageNode.path("content").asText(); + + // 记录token使用情况 + JsonNode usage = responseJson.path("usage"); + Map result = new HashMap<>(); + result.put("content", contentText); + + if (!usage.isMissingNode()) { + int promptTokens = usage.path("prompt_tokens").asInt(); + int completionTokens = usage.path("completion_tokens").asInt(); + int totalTokens = usage.path("total_tokens").asInt(); + + result.put("promptTokens", promptTokens); + result.put("completionTokens", completionTokens); + result.put("totalTokens", totalTokens); + + log.info("Token使用情况 - 提示词:{}, 完成:{}, 总计:{}", + promptTokens, completionTokens, totalTokens); + } else { + result.put("promptTokens", 0); + result.put("completionTokens", 0); + result.put("totalTokens", 0); + } + + // 添加请求和响应数据 + result.put("requestData", requestDataJson); + result.put("responseData", responseDataJson); + + return result; + } else { + log.error("AI分析响应格式异常,未找到choices"); + Map errorResult = new HashMap<>(); + errorResult.put("content", "AI分析响应格式异常"); + errorResult.put("promptTokens", 0); + errorResult.put("completionTokens", 0); + errorResult.put("totalTokens", 0); + errorResult.put("requestData", requestDataJson); + errorResult.put("responseData", responseDataJson); + return errorResult; + } + } else { + log.error("AI分析API调用失败,状态码:{}", response.getStatusCode()); + Map errorResult = new HashMap<>(); + errorResult.put("content", "AI分析API调用失败:" + response.getStatusCode()); + errorResult.put("promptTokens", 0); + errorResult.put("completionTokens", 0); + errorResult.put("totalTokens", 0); + errorResult.put("requestData", requestDataJson); + errorResult.put("responseData", responseDataJson); + return errorResult; + } + + } catch (Exception e) { + log.error("AI分析执行失败", e); + Map errorResult = new HashMap<>(); + errorResult.put("content", "AI分析执行失败:" + e.getMessage()); + errorResult.put("promptTokens", 0); + errorResult.put("completionTokens", 0); + errorResult.put("totalTokens", 0); + // 异常情况下可能没有完整的请求数据 + errorResult.put("requestData", "{}"); + errorResult.put("responseData", "{}"); + return errorResult; + } + } +} diff --git a/src/main/java/com/rj/controller/sys/MenuController.java b/src/main/java/com/rj/controller/sys/MenuController.java index a9d5bb1..2e7439c 100644 --- a/src/main/java/com/rj/controller/sys/MenuController.java +++ b/src/main/java/com/rj/controller/sys/MenuController.java @@ -360,6 +360,17 @@ public class MenuController { + + + + + + + + + + + diff --git a/src/main/java/com/rj/controller/sys/RoleController.java b/src/main/java/com/rj/controller/sys/RoleController.java index 4c5a205..b0de730 100644 --- a/src/main/java/com/rj/controller/sys/RoleController.java +++ b/src/main/java/com/rj/controller/sys/RoleController.java @@ -330,6 +330,17 @@ public class RoleController { + + + + + + + + + + + diff --git a/src/main/java/com/rj/controller/sys/UserRoleController.java b/src/main/java/com/rj/controller/sys/UserRoleController.java index 13e1a2c..0679ac1 100644 --- a/src/main/java/com/rj/controller/sys/UserRoleController.java +++ b/src/main/java/com/rj/controller/sys/UserRoleController.java @@ -336,6 +336,17 @@ public class UserRoleController { + + + + + + + + + + + diff --git a/src/main/java/com/rj/dto/DifyWorkflowResponseDto.java b/src/main/java/com/rj/dto/DifyWorkflowResponseDto.java index 2023f95..64a9d45 100644 --- a/src/main/java/com/rj/dto/DifyWorkflowResponseDto.java +++ b/src/main/java/com/rj/dto/DifyWorkflowResponseDto.java @@ -102,6 +102,17 @@ public class DifyWorkflowResponseDto { + + + + + + + + + + + diff --git a/src/main/java/com/rj/dto/VideoSynthesisRequestDto.java b/src/main/java/com/rj/dto/VideoSynthesisRequestDto.java index 0343a94..742aafa 100644 --- a/src/main/java/com/rj/dto/VideoSynthesisRequestDto.java +++ b/src/main/java/com/rj/dto/VideoSynthesisRequestDto.java @@ -25,12 +25,19 @@ public class VideoSynthesisRequestDto { @Schema(description = "图片URL", required = true) private String imageUrl; - @NotBlank(message = "音频URL不能为空") - @Schema(description = "音频URL", required = true) + @NotBlank(message = "音频URL") + @Schema(description = "音频URL") private String audioUrl; + @Schema(description = "模板ID", example = "normal") + private String templateId = "normal"; + + @NotBlank(message = "模板URL") + @Schema(description = "模板URL") + private String videoTemplateUrl; + @Schema(description = "使用的模型", example = "liveportrait") - private String model = "liveportrait"; + private String model ; @Schema(description = "图片ID", example = "img-12345") private String imageId; @@ -47,8 +54,6 @@ public class VideoSynthesisRequestDto { @Schema(description = "模型供应商", example = "dashscope") private String modelProvider; - @Schema(description = "模板ID", example = "normal") - private String templateId = "normal"; @Schema(description = "眼部动作频率", example = "0.5") private BigDecimal eyeMoveFreq = new BigDecimal("0.5"); @@ -90,11 +95,25 @@ public class VideoSynthesisRequestDto { if (this.audioUrl != null) { this.input.put("audio_url", this.audioUrl); } + if (this.templateId != null) { + this.input.put("template_id", this.templateId); + } + if (this.templateId != null) { + this.input.put("driven_id", "mengwa_kaixin"); + } + if (this.faceBbox != null) { + this.input.put("face_bbox", this.faceBbox); + } + if (this.extBbox != null) { + this.input.put("ext_bbox", this.extBbox); + } + + // 构建 parameters 属性 this.parameters = new HashMap<>(); if (this.templateId != null) { - this.parameters.put("template_id", this.templateId); + this.parameters.put("video_ratio", "9:16"); } if (this.eyeMoveFreq != null) { this.parameters.put("eye_move_freq", this.eyeMoveFreq); diff --git a/src/main/java/com/rj/entity/DetectVideoTemplate.java b/src/main/java/com/rj/entity/DetectVideoTemplate.java index 32e2bc9..c0f3d58 100644 --- a/src/main/java/com/rj/entity/DetectVideoTemplate.java +++ b/src/main/java/com/rj/entity/DetectVideoTemplate.java @@ -59,10 +59,10 @@ public class DetectVideoTemplate implements Serializable { private String ownerPhone; /** - * 头像名称 + * 模板名称 */ - @TableField("avatar_name") - private String avatarName; + @TableField("template_name") + private String templateName; /** * 模版ID,生成视频时使用 @@ -70,6 +70,31 @@ public class DetectVideoTemplate implements Serializable { @TableField("template_id") private String templateId; + /** + * 临时访问链接 + */ + @TableField("temp_url") + private String tempUrl; + + /** + * 临时链接过期时间 + */ + @TableField("temp_url_expires_at") + private LocalDateTime tempUrlExpiresAt; + + /** + * 任务状态 + */ + @TableField("task_status") + private String taskStatus; + + /** + * 任务id + */ + @TableField("task_id") + private String taskId; + + /** * 是否成功 */ diff --git a/src/main/java/com/rj/entity/VideoImageAnalysis.java b/src/main/java/com/rj/entity/VideoImageAnalysis.java new file mode 100644 index 0000000..a2de68d --- /dev/null +++ b/src/main/java/com/rj/entity/VideoImageAnalysis.java @@ -0,0 +1,150 @@ +package com.rj.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.TableField; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * AI视频图像分析记录实体 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Accessors(chain = true) +@TableName("video_image_analysis") +public class VideoImageAnalysis implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键UUID + */ + @TableId(value = "id", type = IdType.ASSIGN_UUID) + private String id; + + /** + * 使用的AI模型 + */ + @TableField("model") + private String model; + + /** + * 图像URL + */ + @TableField("video_image_url") + private String videoImageUrl; + + /** + * 图像临时访问URL + */ + @TableField("video_image_temp_url") + private String videoImageTempUrl; + + /** + * 用户问题文本 + */ + @TableField("user_question") + private String userQuestion; + + /** + * AI返回的内容 + */ + @TableField("response_content") + private String responseContent; + + /** + * 角色 + */ + @TableField("role") + private String role; + + /** + * 完成原因 + */ + @TableField("finish_reason") + private String finishReason; + + /** + * 选择索引 + */ + @TableField("choice_index") + private Integer choiceIndex; + + /** + * 提示词token数量 + */ + @TableField("usage_prompt_tokens") + private Integer usagePromptTokens; + + /** + * 完成token数量 + */ + @TableField("usage_completion_tokens") + private Integer usageCompletionTokens; + + /** + * 总token数量 + */ + @TableField("usage_total_tokens") + private Integer usageTotalTokens; + + /** + * 完整请求参数JSON + */ + @TableField("request_data") + private String requestData; + + /** + * 完整响应数据JSON + */ + @TableField("response_data") + private String responseData; + + /** + * 用户姓名 + */ + @TableField("user_name") + private String userName; + + /** + * 用户电话 + */ + @TableField("user_phone") + private String userPhone; + + /** + * 会话名称 + */ + @TableField("session_name") + private String sessionName; + + /** + * 状态:1-成功,0-失败 + */ + @TableField("status") + private Integer status; + + /** + * 分析类型 + */ + @TableField("type") + private String type; + + /** + * 创建时间 + */ + @TableField("created_at") + private LocalDateTime createdAt; + + /** + * 更新时间 + */ + @TableField("updated_at") + private LocalDateTime updatedAt; +} diff --git a/src/main/java/com/rj/entity/VideoSynthesisLog.java b/src/main/java/com/rj/entity/VideoSynthesisLog.java index 6ef00db..03aa44f 100644 --- a/src/main/java/com/rj/entity/VideoSynthesisLog.java +++ b/src/main/java/com/rj/entity/VideoSynthesisLog.java @@ -4,6 +4,8 @@ import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; import lombok.Data; import java.math.BigDecimal; @@ -157,6 +159,10 @@ public class VideoSynthesisLog { @TableField("video_temp_url") private String videoTempUrl; + @NotBlank(message = "模板URL") + @Schema(description = "模板URL") + private String videoTemplateUrl; + /** * 图像区域坐标 [x1, y1, x2, y2] */ diff --git a/src/main/java/com/rj/mapper/CustomerProfileAnalysisMapper.java b/src/main/java/com/rj/mapper/CustomerProfileAnalysisMapper.java index ba58507..7e917ec 100644 --- a/src/main/java/com/rj/mapper/CustomerProfileAnalysisMapper.java +++ b/src/main/java/com/rj/mapper/CustomerProfileAnalysisMapper.java @@ -45,6 +45,17 @@ public interface CustomerProfileAnalysisMapper extends BaseMapper { + +} diff --git a/src/main/java/com/rj/scheduler/AudioStatisticsScheduler.java b/src/main/java/com/rj/scheduler/AudioStatisticsScheduler.java index 678c980..050d229 100644 --- a/src/main/java/com/rj/scheduler/AudioStatisticsScheduler.java +++ b/src/main/java/com/rj/scheduler/AudioStatisticsScheduler.java @@ -116,5 +116,6 @@ public class AudioStatisticsScheduler { + diff --git a/src/main/java/com/rj/scheduler/DetectVideoTemplateStatusScheduler.java b/src/main/java/com/rj/scheduler/DetectVideoTemplateStatusScheduler.java new file mode 100644 index 0000000..c0e508b --- /dev/null +++ b/src/main/java/com/rj/scheduler/DetectVideoTemplateStatusScheduler.java @@ -0,0 +1,188 @@ +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.DetectVideoTemplate; +import com.rj.mapper.DetectVideoTemplateMapper; +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.time.LocalDateTime; +import java.util.List; + +/** + * 检测视频模板任务状态检查定时器 + * 每6分钟检查一次PENDING或RUNNING状态的任务,调用阿里云API查询任务状态并更新数据库 + * + * @author rj + * @date 2025-01-09 + */ +@Slf4j +@Component +public class DetectVideoTemplateStatusScheduler { + + @Autowired + private DetectVideoTemplateMapper detectVideoTemplateMapper; + + @Autowired + private AliyunConfig aliyunConfig; + + @Autowired + private RestTemplate restTemplate; + + /** + * 每6分钟执行一次任务状态检查 + */ + @Scheduled(fixedRate = 6 * 60 * 1000) // 6分钟 = 6 * 60 * 1000毫秒 + public void checkDetectVideoTemplateStatus() { + try { + log.info("开始执行检测视频模板任务状态检查..."); + + // 查询所有PENDING或RUNNING状态的任务 + List pendingTasks = getPendingTasks(); + + if (pendingTasks.isEmpty()) { + log.info("没有找到PENDING或RUNNING状态的检测视频模板任务"); + return; + } + + log.info("找到{}个PENDING或RUNNING状态的检测视频模板任务,开始检查状态", pendingTasks.size()); + + // 遍历每个任务,检查状态 + for (DetectVideoTemplate 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或RUNNING状态的任务 + */ + private List getPendingTasks() { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.in("task_status", "PENDING", "RUNNING"); + queryWrapper.isNotNull("task_id"); + queryWrapper.ne("task_id", ""); + + return detectVideoTemplateMapper.selectList(queryWrapper); + } + + /** + * 检查并更新单个任务状态 + */ + private void checkAndUpdateTaskStatus(DetectVideoTemplate task) { + try { + log.info("检查检测视频模板任务状态,任务ID: {}", task.getTaskId()); + + if (task == null || task.getTaskId() == null || task.getTaskId().isEmpty()) { + log.warn("任务ID为空,跳过检查"); + 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 entity = new HttpEntity<>(headers); + + ResponseEntity 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(DetectVideoTemplate task, String responseBody) { + try { + JSONObject responseJson = JSON.parseObject(responseBody); + + if (responseJson.containsKey("output")) { + JSONObject output = responseJson.getJSONObject("output"); + + // 更新任务状态 + if (output.containsKey("task_status")) { + String taskStatus = output.getString("task_status"); + task.setTaskStatus(taskStatus); + log.info("更新任务状态: {} -> {}", task.getTaskId(), taskStatus); + + // 根据任务状态判断是否成功 + boolean isSuccess = "SUCCEEDED".equalsIgnoreCase(taskStatus) || "COMPLETED".equalsIgnoreCase(taskStatus); + task.setSuccess(isSuccess); + + // 如果任务完成,更新响应时间 + if (isSuccess || "FAILED".equalsIgnoreCase(taskStatus) || "CANCELLED".equalsIgnoreCase(taskStatus)) { + task.setResponseTime(LocalDateTime.now()); + } + } + + // 更新模板ID + if (output.containsKey("template_id")) { + String templateId = output.getString("template_id"); + task.setTemplateId(templateId); + log.info("更新模板ID: {} -> {}", task.getTaskId(), templateId); + } + + // 更新请求ID(如果API返回了新的request_id) + if (responseJson.containsKey("request_id")) { + String requestId = responseJson.getString("request_id"); + task.setRequestId(requestId); + log.info("更新请求ID: {} -> {}", task.getTaskId(), requestId); + } + + // 更新响应数据 + task.setResponseData(responseBody); + + // 保存到数据库 + detectVideoTemplateMapper.updateById(task); + + log.info("检测视频模板任务状态更新成功,任务ID: {}, 状态: {}, 成功: {}", + task.getTaskId(), task.getTaskStatus(), task.getSuccess()); + + } else { + log.warn("API响应中没有output字段,任务ID: {}", task.getTaskId()); + } + + } catch (Exception e) { + log.error("解析API响应失败,任务ID: {}, 错误: {}", task.getTaskId(), e.getMessage(), e); + } + } +} diff --git a/src/main/java/com/rj/scheduler/VideoSynthesisStatusScheduler.java b/src/main/java/com/rj/scheduler/VideoSynthesisStatusScheduler.java index 11817f2..846341d 100644 --- a/src/main/java/com/rj/scheduler/VideoSynthesisStatusScheduler.java +++ b/src/main/java/com/rj/scheduler/VideoSynthesisStatusScheduler.java @@ -154,8 +154,8 @@ public class VideoSynthesisStatusScheduler { // 如果任务成功完成,处理视频文件 if ("SUCCEEDED".equals(taskStatus)) { JSONObject results = output.getJSONObject("results"); - if (results != null && results.containsKey("video_url")) { - String originalVideoUrl = results.getString("video_url"); + if (output != null && output.containsKey("video_url")) { + String originalVideoUrl = output.getString("video_url"); log.info("任务成功完成,原始视频URL: {}", originalVideoUrl); // 从阿里云OSS下载视频并上传到MinIO @@ -168,17 +168,32 @@ public class VideoSynthesisStatusScheduler { task.setVideoUrl(originalVideoUrl); log.warn("视频转存到MinIO失败,保留原始URL: {}", originalVideoUrl); } - + log.info("任务成功完成,视频URL: {}", task.getVideoUrl()); + log.info("任务成功完成,临时访问URL: {}", task.getVideoTempUrl()); + log.info("任务成功完成,视频模板URL: {}", task.getVideoTemplateUrl()); + task.setSuccess(true); task.setResponseTime(LocalDateTime.now()); // 更新临时访问URL(使用MinIO的预签名URL) log.info("临时访问URL: {}", task.getVideoTempUrl()); + }else if (results != null && results.containsKey("video_url")) { + String videoUrl = results.getString("video_url"); + task.setVideoUrl(videoUrl); + log.info("任务成功完成,视频URL: {}", videoUrl); + + // 从阿里云OSS下载视频并上传到MinIO + downloadAndUploadToMinIO(videoUrl, task); + log.info("任务成功完成,视频URL: {}", task.getVideoUrl()); + log.info("任务成功完成,临时访问URL: {}", task.getVideoTempUrl()); + log.info("任务成功完成,视频模板URL: {}", task.getVideoTemplateUrl()); } + } else if ("FAILED".equals(taskStatus)) { - task.setSuccess(false); - task.setErrorMessage("任务执行失败"); +// task.setSuccess(false); + String message = output.getString("message"); + task.setErrorMessage(message); task.setResponseTime(LocalDateTime.now()); } diff --git a/src/main/java/com/rj/service/IDetectVideoTemplateService.java b/src/main/java/com/rj/service/IDetectVideoTemplateService.java index 565d8a1..e360fa8 100644 --- a/src/main/java/com/rj/service/IDetectVideoTemplateService.java +++ b/src/main/java/com/rj/service/IDetectVideoTemplateService.java @@ -25,11 +25,26 @@ public interface IDetectVideoTemplateService extends IService getPageList(Integer current, Integer size, Boolean success, String startTime, String endTime, - String ownerName, String ownerPhone, String avatarName); + String ownerName, String ownerPhone, String templateName); /** * 获取统计信息 diff --git a/src/main/java/com/rj/service/IVideoImageAnalysisService.java b/src/main/java/com/rj/service/IVideoImageAnalysisService.java new file mode 100644 index 0000000..9ef702e --- /dev/null +++ b/src/main/java/com/rj/service/IVideoImageAnalysisService.java @@ -0,0 +1,53 @@ +package com.rj.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.rj.entity.VideoImageAnalysis; + +/** + * AI视频图像分析记录Service接口 + * + * @author 系统生成 + * @date 2025/1/30 + */ +public interface IVideoImageAnalysisService extends IService { + + /** + * 保存AI视频图像分析记录 + * + * @param analysis AI视频图像分析记录 + * @return 是否保存成功 + */ + boolean saveVideoImageAnalysis(VideoImageAnalysis analysis); + + /** + * 根据用户姓名查询AI视频图像分析记录 + * + * @param userName 用户姓名 + * @return AI视频图像分析记录 + */ + VideoImageAnalysis getByUserName(String userName); + + /** + * 根据用户电话查询AI视频图像分析记录 + * + * @param userPhone 用户电话 + * @return AI视频图像分析记录 + */ + VideoImageAnalysis getByUserPhone(String userPhone); + + /** + * 根据会话名称查询AI视频图像分析记录 + * + * @param sessionName 会话名称 + * @return AI视频图像分析记录 + */ + VideoImageAnalysis getBySessionName(String sessionName); + + /** + * 根据模型查询AI视频图像分析记录 + * + * @param model AI模型 + * @return AI视频图像分析记录 + */ + VideoImageAnalysis getByModel(String model); +} diff --git a/src/main/java/com/rj/service/MinIOService.java b/src/main/java/com/rj/service/MinIOService.java index 76596b6..43773d3 100644 --- a/src/main/java/com/rj/service/MinIOService.java +++ b/src/main/java/com/rj/service/MinIOService.java @@ -89,6 +89,27 @@ public class MinIOService { return getFileUrl(objectName); } + /** + * 上传文件并生成7天临时访问链接 + * + * @param file 文件 + * @return 7天有效期的临时访问URL + */ + public String uploadFileWithTempUrl(MultipartFile file) { + String objectName = minioUtil.uploadFile(file, minioConfig.getBucketName()); + return minioUtil.getPresignedObjectUrl(minioConfig.getBucketName(), objectName); + } + + /** + * 为已上传的文件生成7天临时访问链接 + * + * @param objectName 对象名称 + * @return 7天有效期的临时访问URL + */ + public String generateTempUrl(String objectName) { + return minioUtil.getPresignedObjectUrl(minioConfig.getBucketName(), objectName); + } + /** * 上传文件流 * diff --git a/src/main/java/com/rj/service/biz/ICustomerProfileAnalysisService.java b/src/main/java/com/rj/service/biz/ICustomerProfileAnalysisService.java index 7f707fe..2d8010d 100644 --- a/src/main/java/com/rj/service/biz/ICustomerProfileAnalysisService.java +++ b/src/main/java/com/rj/service/biz/ICustomerProfileAnalysisService.java @@ -66,6 +66,17 @@ public interface ICustomerProfileAnalysisService extends IService> requestEntity = new HttpEntity<>(requestBody, headers); - - // 发送请求 - ResponseEntity response = restTemplate.exchange( - API_URL, - HttpMethod.POST, - requestEntity, - String.class - ); - log.info("发送视频模板检测请求,请求ID: {}, 视频URL: {}", requestId, videoUrl); + // 发送请求 + ResponseEntity response = restTemplate.postForEntity(API_URL, requestEntity, String.class); + + + + log.info("发送视频模板检测请求,response: {}, 视频URL: {}", response, videoUrl); // 计算处理时间 long endTime = System.currentTimeMillis(); @@ -121,48 +131,35 @@ public class DetectVideoTemplateServiceImpl extends ServiceImpl getPageList( Integer current, Integer size, Boolean success, String startTime, String endTime, - String ownerName, String ownerPhone, String avatarName) { + String ownerName, String ownerPhone, String templateName) { com.baomidou.mybatisplus.extension.plugins.pagination.Page page = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(current, size); @@ -276,8 +273,8 @@ public class DetectVideoTemplateServiceImpl extends ServiceImpl 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; + } } diff --git a/src/main/java/com/rj/service/impl/FaceDetectLogServiceImpl.java b/src/main/java/com/rj/service/impl/FaceDetectLogServiceImpl.java index 700e2be..016e686 100644 --- a/src/main/java/com/rj/service/impl/FaceDetectLogServiceImpl.java +++ b/src/main/java/com/rj/service/impl/FaceDetectLogServiceImpl.java @@ -96,7 +96,7 @@ public class FaceDetectLogServiceImpl extends ServiceImpl requestBody = new HashMap<>(); requestBody.put("model", modelName); - if (modelName.equals("emo-detect-v1")){ + if (modelName.equals("emo-detect-v1") || modelName.equals("emoji-detect-v1")){ Map ratioo = new HashMap<>(); ratioo.put("ratio", "1:1"); requestBody.put("parameters", ratioo); @@ -131,7 +131,7 @@ public class FaceDetectLogServiceImpl extends ServiceImpl + implements IVideoImageAnalysisService { + + @Override + public boolean saveVideoImageAnalysis(VideoImageAnalysis analysis) { + try { + log.info("保存AI视频图像分析记录开始,参数:{}", analysis); + boolean result = this.save(analysis); + if (result) { + log.info("AI视频图像分析记录保存成功,ID: {}", analysis.getId()); + } else { + log.error("AI视频图像分析记录保存失败"); + } + return result; + } catch (Exception e) { + log.error("保存AI视频图像分析记录异常", e); + return false; + } + } + + @Override + public VideoImageAnalysis getByUserName(String userName) { + try { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(VideoImageAnalysis::getUserName, userName) + .eq(VideoImageAnalysis::getStatus, 1) + .orderByDesc(VideoImageAnalysis::getCreatedAt) + .last("LIMIT 1"); + + return this.getOne(queryWrapper); + } catch (Exception e) { + log.error("根据用户姓名查询AI视频图像分析记录异常", e); + return null; + } + } + + @Override + public VideoImageAnalysis getByUserPhone(String userPhone) { + try { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(VideoImageAnalysis::getUserPhone, userPhone) + .eq(VideoImageAnalysis::getStatus, 1) + .orderByDesc(VideoImageAnalysis::getCreatedAt) + .last("LIMIT 1"); + + return this.getOne(queryWrapper); + } catch (Exception e) { + log.error("根据用户电话查询AI视频图像分析记录异常", e); + return null; + } + } + + @Override + public VideoImageAnalysis getBySessionName(String sessionName) { + try { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(VideoImageAnalysis::getSessionName, sessionName) + .eq(VideoImageAnalysis::getStatus, 1) + .orderByDesc(VideoImageAnalysis::getCreatedAt) + .last("LIMIT 1"); + + return this.getOne(queryWrapper); + } catch (Exception e) { + log.error("根据会话名称查询AI视频图像分析记录异常", e); + return null; + } + } + + @Override + public VideoImageAnalysis getByModel(String model) { + try { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(VideoImageAnalysis::getModel, model) + .eq(VideoImageAnalysis::getStatus, 1) + .orderByDesc(VideoImageAnalysis::getCreatedAt) + .last("LIMIT 1"); + + return this.getOne(queryWrapper); + } catch (Exception e) { + log.error("根据模型查询AI视频图像分析记录异常", e); + return null; + } + } +} diff --git a/src/main/java/com/rj/service/impl/VideoSynthesisServiceImpl.java b/src/main/java/com/rj/service/impl/VideoSynthesisServiceImpl.java index 0911377..4cd850e 100644 --- a/src/main/java/com/rj/service/impl/VideoSynthesisServiceImpl.java +++ b/src/main/java/com/rj/service/impl/VideoSynthesisServiceImpl.java @@ -52,7 +52,7 @@ public class VideoSynthesisServiceImpl implements IVideoSynthesisService { synthesisLog.setRequestId(requestId); synthesisLog.setRequestTime(startTime); synthesisLog.setSuccess(false); - + // 使用BeanUtils复制相同名称的属性 BeanUtils.copyProperties(request, synthesisLog); @@ -76,8 +76,10 @@ public class VideoSynthesisServiceImpl implements IVideoSynthesisService { if (request.getExtBbox() != null && !request.getExtBbox().trim().isEmpty()) { synthesisLog.setExtBbox(request.getExtBbox()); log.info("设置动态区域坐标: {}", request.getExtBbox()); + } + try { log.info("开始视频合成,请求ID: {}, 图片URL: {}, 音频URL: {}", requestId, request.getImageUrl(), request.getAudioUrl()); @@ -90,7 +92,7 @@ public class VideoSynthesisServiceImpl implements IVideoSynthesisService { request.buildParam(); // 构建请求体 HttpEntity entity = new HttpEntity<>(request, headers); - + log.info("开始视频合成,entity : {} ",entity); // 发送请求 ResponseEntity response = restTemplate.postForEntity(API_URL, entity, String.class); @@ -110,6 +112,7 @@ public class VideoSynthesisServiceImpl implements IVideoSynthesisService { // 使用fastjson解析JSON响应 JSONObject responseJson = JSON.parseObject(responseBody); + // 提取task_id if (responseJson.containsKey("output")) { JSONObject output = responseJson.getJSONObject("output"); @@ -119,7 +122,6 @@ public class VideoSynthesisServiceImpl implements IVideoSynthesisService { log.info("提取到任务ID: {}", taskId); } } - // 设置基本状态 synthesisLog.setTaskStatus("PENDING"); synthesisLog.setSuccess(true); diff --git a/src/main/java/com/rj/service/sys/IMenuService.java b/src/main/java/com/rj/service/sys/IMenuService.java index 6e1e830..3adb173 100644 --- a/src/main/java/com/rj/service/sys/IMenuService.java +++ b/src/main/java/com/rj/service/sys/IMenuService.java @@ -73,6 +73,13 @@ public interface IMenuService extends IService { + + + + + + + diff --git a/src/main/java/com/rj/service/sys/impl/MenuServiceImpl.java b/src/main/java/com/rj/service/sys/impl/MenuServiceImpl.java index ac3d79f..c29ff34 100644 --- a/src/main/java/com/rj/service/sys/impl/MenuServiceImpl.java +++ b/src/main/java/com/rj/service/sys/impl/MenuServiceImpl.java @@ -73,6 +73,17 @@ public class MenuServiceImpl extends ServiceImpl implements IM + + + + + + + + + + + diff --git a/src/main/java/com/rj/service/sys/impl/RoleServiceImpl.java b/src/main/java/com/rj/service/sys/impl/RoleServiceImpl.java index 39b0722..015d335 100644 --- a/src/main/java/com/rj/service/sys/impl/RoleServiceImpl.java +++ b/src/main/java/com/rj/service/sys/impl/RoleServiceImpl.java @@ -73,6 +73,17 @@ public class RoleServiceImpl extends ServiceImpl implements IR + + + + + + + + + + + diff --git a/src/main/java/com/rj/service/sys/impl/UserRoleServiceImpl.java b/src/main/java/com/rj/service/sys/impl/UserRoleServiceImpl.java index 73dccfa..7e8b7fa 100644 --- a/src/main/java/com/rj/service/sys/impl/UserRoleServiceImpl.java +++ b/src/main/java/com/rj/service/sys/impl/UserRoleServiceImpl.java @@ -73,6 +73,17 @@ public class UserRoleServiceImpl extends ServiceImpl i + + + + + + + + + + + diff --git a/src/main/sql/video_image_analysis.sql b/src/main/sql/video_image_analysis.sql new file mode 100644 index 0000000..6d83ca2 --- /dev/null +++ b/src/main/sql/video_image_analysis.sql @@ -0,0 +1,28 @@ + -- 创建AI图像分析记录表(合并请求和响应) +CREATE TABLE `video_image_analysis` ( + `id` VARCHAR(36) NOT NULL COMMENT '主键UUID', + `model` varchar(50) NOT NULL COMMENT '使用的AI模型', + `video_image_url` varchar(500) NOT NULL COMMENT '图像URL', + `user_question` text COMMENT '用户问题文本', + `response_content` text COMMENT 'AI返回的内容', + `role` varchar(20) DEFAULT 'assistant' COMMENT '角色', + `finish_reason` varchar(20) COMMENT '完成原因', + `choice_index` int(11) DEFAULT 0 COMMENT '选择索引', + `usage_prompt_tokens` int(11) COMMENT '提示词token数量', + `usage_completion_tokens` int(11) COMMENT '完成token数量', + `usage_total_tokens` int(11) COMMENT '总token数量', + `request_data` json COMMENT '完整请求参数JSON', + `response_data` json COMMENT '完整响应数据JSON', + `user_name` varchar(100) COMMENT '用户姓名', + `user_phone` varchar(20) COMMENT '用户电话', + `session_name` varchar(200) COMMENT '会话名称', + `status` tinyint(4) DEFAULT 1 COMMENT '状态:1-成功,0-失败', + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_model` (`model`), + KEY `idx_status` (`status`), + KEY `idx_created_at` (`updated_at`), + KEY `idx_model_created` (`model`, `created_at`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='AI图像视频分析记录表'; + diff --git a/src/main/sql/应用场景/阿里百炼.md b/src/main/sql/应用场景/阿里百炼.md new file mode 100644 index 0000000..0458277 --- /dev/null +++ b/src/main/sql/应用场景/阿里百炼.md @@ -0,0 +1,46 @@ + +### 语音播报 +#### 一、个人生活与日常便捷场景 +此类场景的核心是 “解放双手 / 双眼”,让用户在不专注于设备操作时(如移动、家务)仍能获取信息,提升日常效率与体验。 +移动出行场景: +导航语音播报:地图软件(如高德、百度地图)实时播报路线(“前方 500 米左转,进入主路”)、路况(“前方路段拥堵,预计延误 10 分钟”),避免驾驶员频繁看屏,保障行车安全。 +交通站点提示:公交 / 地铁 APP 播报车辆动态(“您乘坐的 301 路公交已到站,距离本站还有 2 分钟”)、共享单车解锁 / 还车提示(“解锁成功,骑行时长已开始计算”)。 +智能家居交互: +设备状态播报:智能音箱(如小爱同学、天猫精灵)播报指令反馈(“已为您打开客厅灯光,当前亮度 70%”)、家电故障提示(“洗衣机进水异常,请检查水龙头”);智能门锁播报开锁结果(“指纹识别成功,门已开启”)。 +生活提醒播报:闹钟语音(“早上好,今日气温 25℃,有小雨,建议携带雨伞”)、日程提醒(“下午 3 点有会议,地点在 1 号会议室”)、待办事项播报(“记得今晚 8 点取快递”)。 +信息获取场景: +碎片化信息播报:新闻 APP “语音读新闻”(“今日热点:我国首条跨海高铁开通运营”)、电子书 / 文章 “听读模式”(适合通勤时 “听” 小说、公众号文章);天气 APP 播报未来 24 小时天气(“夜间最低气温 18℃,风力 3-4 级”)。 +设备操作反馈:手机验证码播报(“您的验证码是 654321,有效期 5 分钟”)、支付成功提示(“微信支付收款 19.9 元”)、电量低提醒(“手机电量已不足 20%,请及时充电”)。 +#### 二、公共服务与社会保障场景 +此类场景聚焦 “信息普惠” 与 “公共安全”,需向大量人群(或特定群体)快速、清晰传递关键信息,覆盖公共空间、特殊需求人群服务等。 +公共空间提示场景: +交通枢纽播报:机场 / 火车站的登机 / 检票提示(“前往北京的 CA1234 次航班开始登机,请旅客前往 3 号登机口”)、站台安全提醒(“列车即将进站,请站在安全线内候车”);地铁站播报换乘信息(“下一站人民广场,可换乘 2 号线,往徐泾东方向”)。 +公共场馆提示:商场 / 超市的广播(“请失主到服务台领取遗失的黑色钱包”“生鲜区今日特价活动即将结束”);医院的叫号播报(“请 305 号患者到内科 3 诊室就诊”);图书馆的闭馆提醒(“距离闭馆还有 30 分钟,请尽快整理借阅书籍”)。 +特殊群体关怀场景: +视障人士辅助:屏幕阅读器(如 NVDA、VoiceOver)播报手机 / 电脑界面内容(“当前打开微信,未读消息 3 条,联系人列表第 1 位是‘家人’”)、盲道导航播报(“前方 10 米有十字路口,左侧是超市入口”)。 +老年群体适配:简化版 APP 语音播报(“您的养老金已到账,金额 5000 元”)、智能家电语音指引(“按‘开始’键启动洗衣机,水温已设为 40℃”),降低操作门槛。 +#### 三、工作与生产效率场景 +此类场景的核心是 “减少信息处理成本”,让用户在专注工作(如办公、生产)时,通过语音快速接收任务、状态反馈,避免频繁中断流程。 +办公协作场景: +即时通讯播报:企业微信 / 钉钉语音提醒(“张经理发送了新的项目文件,请查收”“明天 9 点的项目会已提前至 8 点 30 分”)。 +日程与任务播报:日历软件播报当日工作安排(“上午 10 点与客户沟通需求,下午 2 点提交方案初稿”)、待办工具播报紧急任务(“距离‘周报提交’截止时间还有 1 小时,请尽快完成”)。 +数据与通知播报:Excel 表格语音反馈(“数据计算完成,总销售额为 85 万元”)、邮件 APP 播报重要邮件(“收到来自合作方的‘合同修改意见’邮件”)。 +生产与作业场景: +工业设备播报:工厂生产线设备实时播报运行状态(“机床 A 转速异常,当前转速低于标准值 10%,请检查”)、仓储系统播报货物位置(“您查找的‘零件 A’位于 3 号货架第 2 层,库存剩余 50 件”)。 +物流与配送播报:快递员 APP 播报订单信息(“下一单收件地址:XX 小区 3 号楼 2 单元 501,联系人李女士”)、物流系统播报运输状态(“您的货物已到达上海转运中心,预计明日送达”)。 +#### 四、安全与应急警示场景 +此类场景是 “生命与财产安全的重要保障”,需在紧急情况下(如灾害、危险)快速传递警示信息,要求语音播报 “及时、清晰、有穿透力”。 +灾害预警场景: +自然灾害播报:气象部门的台风 / 暴雨预警(“预计今晚 8 点发布台风橙色预警,风力可达 10 级,请居民减少外出,加固门窗”)、地震预警系统播报(“10 秒后将发生轻微地震,震感较弱,请保持冷静,远离窗户”)。 +公共安全预警:火灾报警系统播报(“3 楼东侧发生火情,烟雾浓度超标,请立即通过安全通道疏散,不要乘坐电梯”)、燃气泄漏报警器播报(“检测到燃气泄漏,浓度已超过安全阈值,请立即关闭阀门,打开门窗通风,禁止使用明火”)。 +危险提示场景: +交通危险播报:车辆 ADAS 系统(高级驾驶辅助)播报(“前方车辆急刹,距离过近,请立即减速”“您已偏离车道,请注意回正方向”);铁路道口播报(“火车即将通过,请关闭道口栏杆,禁止行人车辆通行”)。 +工业安全播报:化工厂车间播报(“检测到有毒气体泄漏,浓度超标,请立即启动应急预案,撤离至安全区域”)、建筑工地播报(“塔吊作业范围内有人员靠近,请立即离开危险区域”)。 +#### 五、商业与服务营销场景 +此类场景通过语音播报 “传递商业信息”,兼顾服务告知与营销转化,常见于线下消费、线上服务触达等场景。 +线下消费场景: +零售门店播报:便利店 / 服装店的促销提示(“今日新品奶茶第二杯半价,活动仅限今日”“夏季服装全场 8 折,会员可享折上折”);餐饮店的取餐播报(“12 号桌的汉堡套餐已做好,请前来取餐”)。 +服务场所播报:酒店前台播报(“您的房间已打扫完毕,可前往 5 楼 508 房入住”);加油站播报(“当前 92 号汽油单价 8.5 元 / 升,加满可参与‘满 200 减 20’活动”)。 +线上服务场景: +电商售后播报:网购 APP 播报订单进度(“您购买的商品已发货,快递单号为 YT123456789”)、售后处理结果(“您的退款申请已通过,金额将在 1-3 个工作日内到账”)。 +会员服务播报:视频 / 音乐 APP 播报会员权益(“您的 VIP 会员还有 7 天到期,续费可享 8 折优惠”)、专属活动(“会员专享演唱会直播将于今晚 7 点开始,记得准时观看”)。 \ No newline at end of file