调用dify,解决时间问题,源语料为空的问题
This commit is contained in:
60
src/main/java/com/rj/config/AliyunConfig.java
Normal file
60
src/main/java/com/rj/config/AliyunConfig.java
Normal file
@@ -0,0 +1,60 @@
|
||||
package com.rj.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 阿里云配置类
|
||||
*
|
||||
* @author Li Zhonghua
|
||||
* @since 2025-08-22
|
||||
*/
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "aliyun")
|
||||
public class AliyunConfig {
|
||||
|
||||
/**
|
||||
* 阿里云API Key
|
||||
*/
|
||||
private String apiKey;
|
||||
|
||||
/**
|
||||
* 阿里云区域
|
||||
*/
|
||||
private String region = "cn-beijing";
|
||||
|
||||
/**
|
||||
* 是否启用阿里云服务
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
public String getApiKey() {
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
public void setApiKey(String apiKey) {
|
||||
this.apiKey = apiKey;
|
||||
}
|
||||
|
||||
public String getRegion() {
|
||||
return region;
|
||||
}
|
||||
|
||||
public void setRegion(String region) {
|
||||
this.region = region;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
329
src/main/java/com/rj/controller/AudioFileController.java
Normal file
329
src/main/java/com/rj/controller/AudioFileController.java
Normal file
@@ -0,0 +1,329 @@
|
||||
package com.rj.controller;
|
||||
|
||||
import com.rj.service.IFileUploadService;
|
||||
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.beans.factory.annotation.Value;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.UrlResource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 音频文件访问控制器
|
||||
* 音频文件大小和时长
|
||||
*
|
||||
* 音频文件不超过2GB;时长在12小时以内。
|
||||
*
|
||||
* @author 李中华 ,spllzh
|
||||
* @since 2025-08-07
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/audio")
|
||||
@Tag(name = "音频文件", description = "音频文件上传和访问相关接口")
|
||||
@Slf4j
|
||||
public class AudioFileController {
|
||||
|
||||
@Autowired
|
||||
private IFileUploadService fileUploadService;
|
||||
|
||||
@Value("${app.audio.upload.path:uploads/audio}")
|
||||
private String uploadPath;
|
||||
|
||||
/**
|
||||
* 上传音频文件
|
||||
*/
|
||||
@PostMapping("/upload")
|
||||
@Operation(summary = "上传音频文件", description = "上传音频文件并关联到录音记录")
|
||||
public ResponseEntity<Map<String, Object>> uploadAudioFile(
|
||||
@Parameter(description = "音频文件", required = true)
|
||||
@RequestParam("file") MultipartFile file,
|
||||
@Parameter(description = "录音ID", required = true)
|
||||
@RequestParam("audioId") String audioId) {
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
try {
|
||||
Map<String, Object> uploadResult = fileUploadService.uploadAudioFileAndUpdateRecord(file, audioId);
|
||||
|
||||
if ((Boolean) uploadResult.get("success")) {
|
||||
result.put("success", true);
|
||||
result.put("message", "音频文件上传成功");
|
||||
result.put("data", uploadResult);
|
||||
return ResponseEntity.ok(result);
|
||||
} else {
|
||||
result.put("success", false);
|
||||
result.put("message", uploadResult.get("message"));
|
||||
return ResponseEntity.badRequest().body(result);
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
log.error("音频文件上传失败:audioId={}, error={}", audioId, e.getMessage(), e);
|
||||
result.put("success", false);
|
||||
result.put("message", "文件上传失败:" + e.getMessage());
|
||||
return ResponseEntity.internalServerError().body(result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取音频文件
|
||||
*/
|
||||
@GetMapping("/{fileName}")
|
||||
@Operation(summary = "获取音频文件", description = "根据文件名获取音频文件")
|
||||
public ResponseEntity<Resource> getAudioFile(
|
||||
@Parameter(description = "文件名", required = true)
|
||||
@PathVariable String fileName) {
|
||||
|
||||
try {
|
||||
Path filePath = Paths.get(uploadPath, fileName);
|
||||
File file = filePath.toFile();
|
||||
|
||||
if (!file.exists() || !file.isFile()) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
// 检查文件是否为音频文件
|
||||
if (!isAudioFile(fileName)) {
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
|
||||
Resource resource = new FileSystemResource(file);
|
||||
|
||||
// 设置响应头
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.parseMediaType(getContentType(fileName)));
|
||||
headers.setContentLength(file.length());
|
||||
headers.set("Accept-Ranges", "bytes");
|
||||
|
||||
return ResponseEntity.ok()
|
||||
.headers(headers)
|
||||
.body(resource);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("获取音频文件失败:fileName={}, error={}", fileName, e.getMessage(), e);
|
||||
return ResponseEntity.internalServerError().build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除音频文件
|
||||
*/
|
||||
@DeleteMapping("/{audioId}")
|
||||
@Operation(summary = "删除音频文件", description = "根据录音ID删除音频文件")
|
||||
public ResponseEntity<Map<String, Object>> deleteAudioFile(
|
||||
@Parameter(description = "录音ID", required = true)
|
||||
@PathVariable String audioId) {
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
try {
|
||||
boolean deleted = fileUploadService.deleteAudioFile(audioId);
|
||||
|
||||
if (deleted) {
|
||||
result.put("success", true);
|
||||
result.put("message", "音频文件删除成功");
|
||||
return ResponseEntity.ok(result);
|
||||
} else {
|
||||
result.put("success", false);
|
||||
result.put("message", "音频文件不存在或删除失败");
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("删除音频文件失败:audioId={}, error={}", audioId, e.getMessage(), e);
|
||||
result.put("success", false);
|
||||
result.put("message", "删除失败:" + e.getMessage());
|
||||
return ResponseEntity.internalServerError().body(result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查音频文件是否存在
|
||||
*/
|
||||
@GetMapping("/exists/{audioId}")
|
||||
@Operation(summary = "检查音频文件是否存在", description = "根据录音ID检查音频文件是否存在")
|
||||
public ResponseEntity<Map<String, Object>> checkAudioFileExists(
|
||||
@Parameter(description = "录音ID", required = true)
|
||||
@PathVariable String audioId) {
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
try {
|
||||
boolean exists = fileUploadService.audioFileExists(audioId);
|
||||
String fileUrl = null;
|
||||
|
||||
if (exists) {
|
||||
fileUrl = fileUploadService.getAudioFileUrl(audioId);
|
||||
}
|
||||
|
||||
result.put("success", true);
|
||||
result.put("exists", exists);
|
||||
result.put("fileUrl", fileUrl);
|
||||
|
||||
return ResponseEntity.ok(result);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("检查音频文件失败:audioId={}, error={}", audioId, e.getMessage(), e);
|
||||
result.put("success", false);
|
||||
result.put("message", "检查失败:" + e.getMessage());
|
||||
return ResponseEntity.internalServerError().body(result);
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/download/{filename:.+}") // 使用 {.+} 匹配包含点的完整文件名
|
||||
public ResponseEntity<Resource> serveAudio(@PathVariable String filename) throws IOException {
|
||||
// 假设你的音频文件存储在这个目录
|
||||
Path audioLocation = Paths.get("path/to/your/audio/files");
|
||||
// 1. 构建文件路径,防止路径遍历攻击
|
||||
// Path filePath = audioLocation.resolve(filename).normalize();
|
||||
// if (!filePath.startsWith(audioLocation)) {
|
||||
// return ResponseEntity.badRequest().build(); // 安全检查
|
||||
// }
|
||||
|
||||
// 2. 检查文件是否存在
|
||||
// if (!Files.exists(filePath) || !Files.isRegularFile(filePath)) {
|
||||
// return ResponseEntity.notFound().build();
|
||||
// }
|
||||
|
||||
// 3. 创建 Resource 对象
|
||||
Resource resource = new UrlResource(filename);
|
||||
|
||||
// 4. 设置响应头:关键!让浏览器下载而不是播放
|
||||
String downloadFilename = "downloaded_audio.mp3"; // 你希望用户保存的文件名
|
||||
return ResponseEntity.ok()
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + downloadFilename + "\"")
|
||||
.contentType(MediaType.APPLICATION_OCTET_STREAM) // 通用二进制流,确保下载
|
||||
// 或者使用音频 MIME 类型,但配合 attachment 头也能下载
|
||||
// .contentType(MediaType.parseMediaType("audio/mpeg"))
|
||||
.body(resource);
|
||||
}
|
||||
/**
|
||||
* 获取音频文件信息
|
||||
*/
|
||||
@GetMapping("/info/{audioId}")
|
||||
@Operation(summary = "获取音频文件信息", description = "根据录音ID获取音频文件详细信息")
|
||||
public ResponseEntity<Map<String, Object>> getAudioFileInfo(
|
||||
@Parameter(description = "录音ID", required = true)
|
||||
@PathVariable String audioId) {
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
try {
|
||||
boolean exists = fileUploadService.audioFileExists(audioId);
|
||||
|
||||
if (!exists) {
|
||||
result.put("success", false);
|
||||
result.put("message", "音频文件不存在");
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
String fileUrl = fileUploadService.getAudioFileUrl(audioId);
|
||||
Path uploadDir = Paths.get(uploadPath);
|
||||
File dir = uploadDir.toFile();
|
||||
|
||||
File[] files = dir.listFiles((d, name) -> name.startsWith(audioId + "."));
|
||||
if (files != null && files.length > 0) {
|
||||
File file = files[0];
|
||||
|
||||
Map<String, Object> fileInfo = new HashMap<>();
|
||||
fileInfo.put("fileName", file.getName());
|
||||
fileInfo.put("fileSize", file.length());
|
||||
fileInfo.put("fileUrl", fileUrl);
|
||||
fileInfo.put("lastModified", file.lastModified());
|
||||
fileInfo.put("contentType", getContentType(file.getName()));
|
||||
|
||||
result.put("success", true);
|
||||
result.put("data", fileInfo);
|
||||
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
|
||||
result.put("success", false);
|
||||
result.put("message", "音频文件信息获取失败");
|
||||
return ResponseEntity.internalServerError().body(result);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("获取音频文件信息失败:audioId={}, error={}", audioId, e.getMessage(), e);
|
||||
result.put("success", false);
|
||||
result.put("message", "获取文件信息失败:" + e.getMessage());
|
||||
return ResponseEntity.internalServerError().body(result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为音频文件
|
||||
*/
|
||||
private boolean isAudioFile(String fileName) {
|
||||
if (fileName == null || fileName.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String extension = getFileExtension(fileName).toLowerCase();
|
||||
String[] audioExtensions = {"mp3", "wav", "m4a", "aac", "ogg", "flac", "wma"};
|
||||
|
||||
for (String ext : audioExtensions) {
|
||||
if (ext.equals(extension)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件扩展名
|
||||
*/
|
||||
private String getFileExtension(String fileName) {
|
||||
if (fileName == null || fileName.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
int lastDotIndex = fileName.lastIndexOf('.');
|
||||
if (lastDotIndex > 0 && lastDotIndex < fileName.length() - 1) {
|
||||
return fileName.substring(lastDotIndex + 1);
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Content-Type
|
||||
*/
|
||||
private String getContentType(String fileName) {
|
||||
String extension = getFileExtension(fileName).toLowerCase();
|
||||
|
||||
switch (extension) {
|
||||
case "mp3":
|
||||
return "audio/mpeg";
|
||||
case "wav":
|
||||
return "audio/wav";
|
||||
case "m4a":
|
||||
return "audio/mp4";
|
||||
case "aac":
|
||||
return "audio/aac";
|
||||
case "ogg":
|
||||
return "audio/ogg";
|
||||
case "flac":
|
||||
return "audio/flac";
|
||||
case "wma":
|
||||
return "audio/x-ms-wma";
|
||||
default:
|
||||
return "application/octet-stream";
|
||||
}
|
||||
}
|
||||
}
|
||||
135
src/main/java/com/rj/controller/DifyWorkflowController.java
Normal file
135
src/main/java/com/rj/controller/DifyWorkflowController.java
Normal file
@@ -0,0 +1,135 @@
|
||||
package com.rj.controller;
|
||||
|
||||
import com.rj.dto.DifyWorkflowRequestDto;
|
||||
import com.rj.dto.DifyWorkflowResponseDto;
|
||||
import com.rj.service.DifyWorkflowService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Dify工作流测试控制器
|
||||
*
|
||||
* @author 李中华
|
||||
* @date 2025/1/3
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/dify")
|
||||
@Tag(name = "Dify工作流测试", description = "Dify工作流API测试接口")
|
||||
public class DifyWorkflowController {
|
||||
|
||||
@Autowired
|
||||
private DifyWorkflowService difyWorkflowService;
|
||||
|
||||
/**
|
||||
* 测试企微对话分析工作流
|
||||
*/
|
||||
@PostMapping("/workflow/consulting-scenario")
|
||||
@Operation(summary = "企微对话分析工作流测试", description = "调用Dify企微对话分析工作流进行测试")
|
||||
public ResponseEntity<DifyWorkflowResponseDto> testConsultingScenarioWorkflow(
|
||||
@Valid @RequestBody DifyWorkflowRequestDto requestDto) {
|
||||
|
||||
try {
|
||||
log.info("开始调用企微对话分析工作流, 参数: "+requestDto.toString());
|
||||
// 构建工作流输入参数
|
||||
Map<String, Object> inputs = new HashMap<>();
|
||||
inputs.put("unionId", requestDto.getUnionId());
|
||||
inputs.put("consultantId", requestDto.getConsultantId());
|
||||
inputs.put("communicateDate", requestDto.getCommunicateDate());
|
||||
inputs.put("analysisScene", requestDto.getAnalysisScene());
|
||||
inputs.put("aiAnalysisRequestId", requestDto.getAiAnalysisRequestId());
|
||||
inputs.put("version", requestDto.getVersion());
|
||||
inputs.put("chat", requestDto.getChat());
|
||||
|
||||
// 创建请求对象
|
||||
DifyWorkflowService.DifyWorkflowRequest request =
|
||||
new DifyWorkflowService.DifyWorkflowRequest(inputs, requestDto.getConsultantId());
|
||||
|
||||
// 调用工作流(Service层会自动保存数据到数据库)
|
||||
DifyWorkflowService.DifyWorkflowResponse response =
|
||||
difyWorkflowService.callConsultingScenarioWorkflow(request);
|
||||
|
||||
// 构建返回结果
|
||||
DifyWorkflowResponseDto result = DifyWorkflowResponseDto.success(
|
||||
response.getWorkflowRunId(),
|
||||
response.getTaskId(),
|
||||
response.getData(),
|
||||
response.getMetadata()
|
||||
);
|
||||
|
||||
return ResponseEntity.ok(result);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("调用企微对话分析工作流失败", e);
|
||||
|
||||
DifyWorkflowResponseDto errorResult = DifyWorkflowResponseDto.failure(
|
||||
"工作流调用失败: " + e.getMessage(),
|
||||
e.getClass().getSimpleName()
|
||||
);
|
||||
|
||||
return ResponseEntity.status(500).body(errorResult);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用预设测试数据测试工作流
|
||||
*/
|
||||
@PostMapping("/workflow/consulting-scenario/test")
|
||||
@Operation(summary = "使用预设数据测试企微对话分析工作流", description = "使用预设的测试数据调用工作流")
|
||||
public ResponseEntity<DifyWorkflowResponseDto> testWithPresetData() {
|
||||
|
||||
// 创建预设测试数据
|
||||
DifyWorkflowRequestDto requestDto = new DifyWorkflowRequestDto();
|
||||
requestDto.setUnionId("test_union_12345");
|
||||
requestDto.setConsultantId("consultant_67890");
|
||||
requestDto.setCommunicateDate("2025-01-03 10:30:00");
|
||||
requestDto.setAnalysisScene("sales_consultation");
|
||||
requestDto.setAiAnalysisRequestId("ai_req_20250103_001");
|
||||
requestDto.setVersion(1);
|
||||
|
||||
// 模拟企微对话内容
|
||||
String chat = "客户:你好,我想了解一下沃尔沃XC60这款车。\n" +
|
||||
"顾问:您好!很高兴为您介绍沃尔沃XC60。这是一款非常优秀的中型SUV,请问您主要关注哪些方面呢?\n" +
|
||||
"客户:我比较关心安全性能和油耗表现。\n" +
|
||||
"顾问:XC60在安全方面表现非常出色,配备了City Safety城市安全系统,还有Pilot Assist领航辅助系统。油耗方面,2.0T发动机百公里综合油耗约8.5L。\n" +
|
||||
"客户:价格大概是多少?\n" +
|
||||
"顾问:XC60的指导价在37.39-47.49万元之间,目前有优惠活动,可以优惠3万元左右。\n" +
|
||||
"客户:我预算在40万以内,有什么推荐的配置吗?\n" +
|
||||
"顾问:根据您的预算,我推荐智逸豪华版,指导价39.69万,优惠后36.69万,完全符合您的预算。\n" +
|
||||
"客户:好的,我考虑一下,什么时候可以试驾?\n" +
|
||||
"顾问:明天下午2点可以安排试驾,您方便吗?\n" +
|
||||
"客户:可以的,我明天下午过去。\n" +
|
||||
"顾问:好的,我为您预约明天下午2点的试驾,地址是...";
|
||||
requestDto.setChat(chat);
|
||||
|
||||
return testConsultingScenarioWorkflow(requestDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取工作流配置信息
|
||||
*/
|
||||
@GetMapping("/workflow/config")
|
||||
@Operation(summary = "获取工作流配置信息", description = "获取当前Dify工作流的配置信息")
|
||||
public ResponseEntity<Map<String, Object>> getWorkflowConfig() {
|
||||
Map<String, Object> config = new HashMap<>();
|
||||
config.put("message", "工作流配置信息");
|
||||
config.put("workflowName", "企微对话分析工作流");
|
||||
config.put("description", "对企微对话内容进行深度剖析,输出客户需求、顾问方案等信息");
|
||||
config.put("inputParameters", new String[]{
|
||||
"unionId", "consultantId", "communicateDate",
|
||||
"analysisScene", "aiAnalysisRequestId", "version", "chat"
|
||||
});
|
||||
config.put("outputFormat", "JSON格式,包含analysisResult和analysisDetail");
|
||||
|
||||
return ResponseEntity.ok(config);
|
||||
}
|
||||
|
||||
}
|
||||
46
src/main/java/com/rj/dto/DifyWorkflowRequestDto.java
Normal file
46
src/main/java/com/rj/dto/DifyWorkflowRequestDto.java
Normal file
@@ -0,0 +1,46 @@
|
||||
package com.rj.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* Dify工作流请求DTO
|
||||
*
|
||||
* @author 李中华
|
||||
* @date 2025/1/3
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "Dify企微对话分析工作流请求参数")
|
||||
public class DifyWorkflowRequestDto {
|
||||
|
||||
@NotBlank(message = "企微客户unionId不能为空")
|
||||
@Schema(description = "企微客户unionId", example = "test_union_12345", required = true)
|
||||
private String unionId;
|
||||
|
||||
@NotBlank(message = "B端认证中心userId不能为空")
|
||||
@Schema(description = "B端认证中心userId", example = "consultant_67890", required = true)
|
||||
private String consultantId;
|
||||
|
||||
@NotBlank(message = "沟通时间不能为空")
|
||||
@Schema(description = "沟通时间", example = "2025-01-03 10:30:00", required = true)
|
||||
private String communicateDate;
|
||||
|
||||
@NotBlank(message = "分析场景不能为空")
|
||||
@Schema(description = "分析场景", example = "sales_consultation", required = true)
|
||||
private String analysisScene;
|
||||
|
||||
@NotBlank(message = "AI分析请求ID不能为空")
|
||||
@Schema(description = "AI分析请求ID", example = "ai_req_20250103_001", required = true)
|
||||
private String aiAnalysisRequestId;
|
||||
|
||||
@NotNull(message = "版本号不能为空")
|
||||
@Schema(description = "版本号", example = "1", required = true)
|
||||
private Integer version;
|
||||
|
||||
@NotBlank(message = "企微对话内容不能为空")
|
||||
@Schema(description = "企微对话内容", example = "客户:你好,我想了解一下沃尔沃XC60这款车。\n顾问:您好!很高兴为您介绍沃尔沃XC60。", required = true)
|
||||
private String chat;
|
||||
}
|
||||
62
src/main/java/com/rj/dto/DifyWorkflowResponseDto.java
Normal file
62
src/main/java/com/rj/dto/DifyWorkflowResponseDto.java
Normal file
@@ -0,0 +1,62 @@
|
||||
package com.rj.dto;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Dify工作流响应DTO
|
||||
*
|
||||
* @author 李中华
|
||||
* @date 2025/1/3
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "Dify企微对话分析工作流响应结果")
|
||||
public class DifyWorkflowResponseDto {
|
||||
|
||||
@Schema(description = "是否成功", example = "true")
|
||||
private Boolean success;
|
||||
|
||||
@Schema(description = "响应消息", example = "工作流调用成功")
|
||||
private String message;
|
||||
|
||||
@Schema(description = "工作流运行ID", example = "workflow_run_12345")
|
||||
private String workflowRunId;
|
||||
|
||||
@Schema(description = "任务ID", example = "task_67890")
|
||||
private String taskId;
|
||||
|
||||
@Schema(description = "工作流响应数据")
|
||||
private JSONObject data;
|
||||
|
||||
@Schema(description = "元数据")
|
||||
private JSONObject metadata;
|
||||
|
||||
@Schema(description = "错误类型(仅在失败时返回)", example = "RuntimeException")
|
||||
private String error;
|
||||
|
||||
/**
|
||||
* 创建成功响应
|
||||
*/
|
||||
public static DifyWorkflowResponseDto success(String workflowRunId, String taskId, JSONObject data, JSONObject metadata) {
|
||||
DifyWorkflowResponseDto response = new DifyWorkflowResponseDto();
|
||||
response.setSuccess(true);
|
||||
response.setMessage("工作流调用成功");
|
||||
response.setWorkflowRunId(workflowRunId);
|
||||
response.setTaskId(taskId);
|
||||
response.setData(data);
|
||||
response.setMetadata(metadata);
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建失败响应
|
||||
*/
|
||||
public static DifyWorkflowResponseDto failure(String message, String error) {
|
||||
DifyWorkflowResponseDto response = new DifyWorkflowResponseDto();
|
||||
response.setSuccess(false);
|
||||
response.setMessage(message);
|
||||
response.setError(error);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
258
src/main/java/com/rj/example/AudioFileUploadExample.java
Normal file
258
src/main/java/com/rj/example/AudioFileUploadExample.java
Normal file
@@ -0,0 +1,258 @@
|
||||
package com.rj.example;
|
||||
|
||||
import com.rj.entity.AudioManagement;
|
||||
import com.rj.service.IAudioManagementService;
|
||||
import com.rj.service.IFileUploadService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 音频文件上传功能使用示例
|
||||
*
|
||||
* @author 李中华 ,spllzh
|
||||
* @since 2025-08-07
|
||||
*/
|
||||
@Component
|
||||
public class AudioFileUploadExample {
|
||||
|
||||
@Autowired
|
||||
private IAudioManagementService audioManagementService;
|
||||
|
||||
@Autowired
|
||||
private IFileUploadService fileUploadService;
|
||||
|
||||
/**
|
||||
* 示例1:创建录音记录并上传音频文件
|
||||
*/
|
||||
public void exampleCreateAudioWithFile() {
|
||||
System.out.println("=== 示例1:创建录音记录并上传音频文件 ===");
|
||||
|
||||
try {
|
||||
// 1. 创建录音记录
|
||||
AudioManagement audio = new AudioManagement();
|
||||
audio.setId(UUID.randomUUID().toString());
|
||||
audio.setRecordingName("测试录音-" + System.currentTimeMillis());
|
||||
audio.setRecordingTime(LocalDateTime.now());
|
||||
audio.setSalesId("sales001");
|
||||
audio.setSalesName("张三");
|
||||
audio.setCustomerId("customer001");
|
||||
audio.setCustomerName("李四");
|
||||
audio.setCustomerPhone("13800138000");
|
||||
audio.setDealershipId("dealership001");
|
||||
audio.setDealershipName("北京门店");
|
||||
audio.setProjectId("project001");
|
||||
audio.setProjectName("汽车销售项目");
|
||||
audio.setDuration(new java.math.BigDecimal("5.5"));
|
||||
audio.setIntentionLevel("高意向");
|
||||
audio.setCreateTime(LocalDateTime.now());
|
||||
audio.setUpdateTime(LocalDateTime.now());
|
||||
|
||||
boolean saved = audioManagementService.save(audio);
|
||||
if (saved) {
|
||||
System.out.println("录音记录创建成功,ID: " + audio.getId());
|
||||
|
||||
// 2. 这里可以上传音频文件(需要实际的MultipartFile)
|
||||
System.out.println("音频文件上传URL: POST /api/audio/upload");
|
||||
System.out.println("参数: file (音频文件), audioId (" + audio.getId() + ")");
|
||||
System.out.println("注意:上传成功后会自动更新录音记录中的文件信息");
|
||||
|
||||
// 3. 检查音频文件是否存在
|
||||
boolean fileExists = fileUploadService.audioFileExists(audio.getId());
|
||||
System.out.println("音频文件是否存在: " + fileExists);
|
||||
|
||||
if (fileExists) {
|
||||
String fileUrl = fileUploadService.getAudioFileUrl(audio.getId());
|
||||
System.out.println("音频文件访问URL: " + fileUrl);
|
||||
}
|
||||
} else {
|
||||
System.out.println("录音记录创建失败");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("创建录音记录失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例2:获取录音的音频文件信息
|
||||
*/
|
||||
public void exampleGetAudioFileInfo() {
|
||||
System.out.println("=== 示例2:获取录音的音频文件信息 ===");
|
||||
|
||||
// 假设有一个录音ID
|
||||
String audioId = "test-audio-id";
|
||||
|
||||
try {
|
||||
// 检查音频文件是否存在
|
||||
boolean fileExists = fileUploadService.audioFileExists(audioId);
|
||||
System.out.println("音频文件是否存在: " + fileExists);
|
||||
|
||||
if (fileExists) {
|
||||
// 获取音频文件URL
|
||||
String fileUrl = fileUploadService.getAudioFileUrl(audioId);
|
||||
System.out.println("音频文件URL: " + fileUrl);
|
||||
|
||||
// 获取录音信息
|
||||
AudioManagement audio = audioManagementService.getById(audioId);
|
||||
if (audio != null) {
|
||||
System.out.println("录音信息: " + audio.getRecordingName());
|
||||
System.out.println("录音时长: " + audio.getDuration() + " 分钟");
|
||||
System.out.println("客户姓名: " + audio.getCustomerName());
|
||||
System.out.println("销售人员: " + audio.getSalesName());
|
||||
}
|
||||
} else {
|
||||
System.out.println("音频文件不存在,可以上传音频文件");
|
||||
System.out.println("上传URL: POST /api/audio/upload");
|
||||
System.out.println("参数: file (音频文件), audioId (" + audioId + ")");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("获取音频文件信息失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例3:删除录音及其音频文件
|
||||
*/
|
||||
public void exampleDeleteAudioWithFile() {
|
||||
System.out.println("=== 示例3:删除录音及其音频文件 ===");
|
||||
|
||||
String audioId = "test-audio-id";
|
||||
|
||||
try {
|
||||
// 检查录音是否存在
|
||||
AudioManagement audio = audioManagementService.getById(audioId);
|
||||
if (audio == null) {
|
||||
System.out.println("录音记录不存在: " + audioId);
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.println("准备删除录音: " + audio.getRecordingName());
|
||||
|
||||
// 检查音频文件是否存在
|
||||
boolean fileExists = fileUploadService.audioFileExists(audioId);
|
||||
System.out.println("音频文件是否存在: " + fileExists);
|
||||
|
||||
// 删除音频文件
|
||||
if (fileExists) {
|
||||
boolean fileDeleted = fileUploadService.deleteAudioFile(audioId);
|
||||
System.out.println("音频文件删除结果: " + fileDeleted);
|
||||
}
|
||||
|
||||
// 删除录音记录
|
||||
boolean recordDeleted = audioManagementService.removeById(audioId);
|
||||
System.out.println("录音记录删除结果: " + recordDeleted);
|
||||
|
||||
if (recordDeleted) {
|
||||
System.out.println("录音及其音频文件删除成功");
|
||||
} else {
|
||||
System.out.println("录音删除失败");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("删除录音失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例4:批量处理音频文件
|
||||
*/
|
||||
public void exampleBatchProcessAudioFiles() {
|
||||
System.out.println("=== 示例4:批量处理音频文件 ===");
|
||||
|
||||
try {
|
||||
// 获取所有录音记录
|
||||
var audioList = audioManagementService.list();
|
||||
System.out.println("总录音数量: " + audioList.size());
|
||||
|
||||
int withFileCount = 0;
|
||||
int withoutFileCount = 0;
|
||||
|
||||
for (AudioManagement audio : audioList) {
|
||||
boolean fileExists = fileUploadService.audioFileExists(audio.getId());
|
||||
if (fileExists) {
|
||||
withFileCount++;
|
||||
String fileUrl = fileUploadService.getAudioFileUrl(audio.getId());
|
||||
System.out.println("录音 " + audio.getRecordingName() + " 有音频文件: " + fileUrl);
|
||||
} else {
|
||||
withoutFileCount++;
|
||||
System.out.println("录音 " + audio.getRecordingName() + " 没有音频文件");
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("有音频文件的录音数量: " + withFileCount);
|
||||
System.out.println("没有音频文件的录音数量: " + withoutFileCount);
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("批量处理音频文件失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例5:查询包含音频文件信息的录音记录
|
||||
*/
|
||||
public void exampleQueryAudioWithFileInfo() {
|
||||
System.out.println("=== 示例5:查询包含音频文件信息的录音记录 ===");
|
||||
|
||||
try {
|
||||
// 获取所有录音记录
|
||||
var audioList = audioManagementService.list();
|
||||
System.out.println("总录音数量: " + audioList.size());
|
||||
|
||||
for (AudioManagement audio : audioList) {
|
||||
System.out.println("录音ID: " + audio.getId());
|
||||
System.out.println("录音名称: " + audio.getRecordingName());
|
||||
System.out.println("音频文件URL: " + audio.getAudioFileUrl());
|
||||
System.out.println("音频文件大小: " + audio.getAudioFileSize() + " 字节");
|
||||
System.out.println("原始文件名: " + audio.getAudioFileOriginalName());
|
||||
System.out.println("文件扩展名: " + audio.getAudioFileExtension());
|
||||
System.out.println("---");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("查询录音文件信息失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行所有示例
|
||||
*/
|
||||
public void runAllExamples() {
|
||||
System.out.println("开始运行音频文件上传功能示例...");
|
||||
System.out.println();
|
||||
|
||||
try {
|
||||
exampleCreateAudioWithFile();
|
||||
System.out.println();
|
||||
|
||||
exampleGetAudioFileInfo();
|
||||
System.out.println();
|
||||
|
||||
exampleBatchProcessAudioFiles();
|
||||
System.out.println();
|
||||
|
||||
exampleQueryAudioWithFileInfo();
|
||||
System.out.println();
|
||||
|
||||
// 注意:删除示例需要谨慎使用
|
||||
// exampleDeleteAudioWithFile();
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("示例运行失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
System.out.println("示例运行完成!");
|
||||
}
|
||||
}
|
||||
308
src/main/java/com/rj/service/DifyWorkflowService.java
Normal file
308
src/main/java/com/rj/service/DifyWorkflowService.java
Normal file
@@ -0,0 +1,308 @@
|
||||
package com.rj.service;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.rj.entity.bz.DifyWorkflowAnalysis;
|
||||
import com.rj.service.biz.IDifyWorkflowAnalysisService;
|
||||
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 java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Dify工作流服务类
|
||||
* 用于调用Dify平台的工作流API
|
||||
*
|
||||
* @author 李中华
|
||||
* @date 2025/1/3
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class DifyWorkflowService {
|
||||
|
||||
@Value("${dify.api.base-url}")
|
||||
private String difyBaseUrl;
|
||||
|
||||
@Value("${dify.api.workflow-endpoint-consultingScenar}")
|
||||
private String workflowEndpoint;
|
||||
|
||||
@Value("${dify.api.summary-qiwei-token}")// 企微(一句话+分类别) app-WPuiaYg0iVLc2ws0iOfsAUC6
|
||||
private String summaryQiweiToken;
|
||||
@Value("${dify.api.summary-ddc-token}")// DDC(一句话+分类别) app-rgaQbIir7vrVb1473Z3Puz6w
|
||||
private String summaryddcToken;
|
||||
@Value("${dify.api.summary-nameplate-token}")// 铭牌(一句话+分类别) //app-cv5glaYrY4zgjq0XIidSoJea
|
||||
private String summaryNameplateToken;
|
||||
|
||||
|
||||
|
||||
private final RestTemplate restTemplate;
|
||||
|
||||
@Autowired
|
||||
private IDifyWorkflowAnalysisService difyWorkflowAnalysisService;
|
||||
|
||||
public DifyWorkflowService() {
|
||||
this.restTemplate = new RestTemplate();
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用企微对话分析工作流
|
||||
*
|
||||
* @param request 工作流请求参数
|
||||
* @return 工作流响应结果
|
||||
*/
|
||||
public DifyWorkflowResponse callConsultingScenarioWorkflow(DifyWorkflowRequest request) {
|
||||
try {
|
||||
String url = difyBaseUrl + workflowEndpoint;
|
||||
|
||||
// 构建请求头
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.set("Authorization", "Bearer " + summaryQiweiToken);
|
||||
|
||||
// 构建请求体
|
||||
Map<String, Object> requestBody = new HashMap<>();
|
||||
requestBody.put("inputs", request.getInputs());
|
||||
requestBody.put("response_mode", "blocking");
|
||||
requestBody.put("user", request.getUserId());
|
||||
|
||||
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(requestBody, headers);
|
||||
|
||||
log.info("调用Dify工作流API: {}", url);
|
||||
log.info("请求参数: {}", JSON.toJSONString(requestBody));
|
||||
|
||||
ResponseEntity<String> response = restTemplate.exchange(
|
||||
url,
|
||||
HttpMethod.POST,
|
||||
entity,
|
||||
String.class
|
||||
);
|
||||
|
||||
log.info("Dify工作流响应状态: {}", response.getStatusCode());
|
||||
log.info("Dify工作流响应内容: {}", response.getBody());
|
||||
|
||||
if (response.getStatusCode() == HttpStatus.OK) {
|
||||
JSONObject responseJson = JSON.parseObject(response.getBody());
|
||||
return parseWorkflowResponse(responseJson, request);
|
||||
} else {
|
||||
throw new RuntimeException("Dify工作流调用失败,状态码: " + response.getStatusCode());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("调用Dify工作流异常", e);
|
||||
throw new RuntimeException("调用Dify工作流异常: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析工作流响应并保存数据
|
||||
*/
|
||||
private DifyWorkflowResponse parseWorkflowResponse(JSONObject responseJson, DifyWorkflowRequest request) {
|
||||
DifyWorkflowResponse response = new DifyWorkflowResponse();
|
||||
|
||||
if (responseJson.containsKey("data")) {
|
||||
JSONObject data = responseJson.getJSONObject("data");
|
||||
response.setWorkflowRunId(data.getString("workflow_run_id"));
|
||||
response.setTaskId(data.getString("task_id"));
|
||||
response.setData(data);
|
||||
|
||||
// 解析并保存分析结果到数据库
|
||||
if (data.containsKey("outputs")) {
|
||||
JSONObject outputs = data.getJSONObject("outputs");
|
||||
log.info("Dify工作流响应数据 outputs : {}", outputs);
|
||||
|
||||
// 保存分析结果到数据库
|
||||
saveAnalysisResultToDatabase(outputs, request);
|
||||
}
|
||||
}
|
||||
|
||||
if (responseJson.containsKey("metadata")) {
|
||||
response.setMetadata(responseJson.getJSONObject("metadata"));
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存分析结果到数据库
|
||||
*/
|
||||
private void saveAnalysisResultToDatabase(JSONObject outputs, DifyWorkflowRequest request) {
|
||||
try {
|
||||
DifyWorkflowAnalysis analysis = new DifyWorkflowAnalysis();
|
||||
|
||||
// 从outputs中提取数据
|
||||
if (outputs.containsKey("data")) {
|
||||
JSONObject analysisData = outputs.getJSONObject("data");
|
||||
|
||||
// 提取分析结果摘要
|
||||
if (analysisData.containsKey("analysisResult")) {
|
||||
analysis.setAnalysisResult(analysisData.getString("analysisResult"));
|
||||
}
|
||||
|
||||
// 解析详细分析结果
|
||||
if (analysisData.containsKey("analysisDetail")) {
|
||||
JSONObject analysisDetail = analysisData.getJSONObject("analysisDetail");
|
||||
|
||||
// 解析客户需求
|
||||
if (analysisDetail.containsKey("customerNeeds")) {
|
||||
JSONObject customerNeeds = analysisDetail.getJSONObject("customerNeeds");
|
||||
analysis.setCustomerSource(customerNeeds.getString("customerSource"));
|
||||
analysis.setCustomerOccupation(customerNeeds.getString("customerOccupation"));
|
||||
analysis.setCustomerHobbies(customerNeeds.getString("customerHobbies"));
|
||||
analysis.setHomeAddress(customerNeeds.getString("homeAddress"));
|
||||
analysis.setCarPurchaseNeed(customerNeeds.getString("carPurchase"));
|
||||
analysis.setPurchaseType(customerNeeds.getString("purchaseType"));
|
||||
analysis.setPurchaseBuyer(customerNeeds.getString("purchaseBuyer"));
|
||||
analysis.setCarUser(customerNeeds.getString("carUser"));
|
||||
analysis.setIntendedCarModel(customerNeeds.getString("intendedCarModel"));
|
||||
analysis.setCarQualifications(customerNeeds.getString("carQualifications"));
|
||||
analysis.setCarBudget(customerNeeds.getString("carBudget"));
|
||||
analysis.setFinancialInstallment(customerNeeds.getString("financialInstallment"));
|
||||
analysis.setPurchaseCycle(customerNeeds.getString("purchaseCycle"));
|
||||
analysis.setFocusPoints(customerNeeds.getString("focus"));
|
||||
analysis.setConcerns(customerNeeds.getString("concerns"));
|
||||
analysis.setCarContrast(customerNeeds.getString("carContrast"));
|
||||
}
|
||||
|
||||
// 解析顾问服务
|
||||
if (analysisDetail.containsKey("customerService")) {
|
||||
JSONObject customerService = analysisDetail.getJSONObject("customerService");
|
||||
analysis.setProductDescription(customerService.getString("productDesc"));
|
||||
analysis.setSolution(customerService.getString("solution"));
|
||||
analysis.setQuotation(customerService.getString("quotation"));
|
||||
}
|
||||
|
||||
// 解析后续行动和未解决问题
|
||||
analysis.setAgreedFollowUpActions(analysisDetail.getString("agreedFollowUpActions"));
|
||||
analysis.setUnresolvedIssues(analysisDetail.getString("unresolvedIssues"));
|
||||
}
|
||||
}
|
||||
|
||||
// 从outputs中提取其他字段
|
||||
if (outputs.containsKey("unionId")) {
|
||||
analysis.setUnionId(outputs.getString("unionId"));
|
||||
}
|
||||
if (outputs.containsKey("consultantId")) {
|
||||
analysis.setConsultantId(outputs.getString("consultantId"));
|
||||
}
|
||||
if (outputs.containsKey("communicateDate")) {
|
||||
try {
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
analysis.setCommunicateDate(LocalDateTime.parse(outputs.getString("communicateDate"), formatter));
|
||||
} catch (Exception e) {
|
||||
log.warn("解析沟通时间失败: {}", outputs.getString("communicateDate"), e);
|
||||
analysis.setCommunicateDate(LocalDateTime.now());
|
||||
}
|
||||
}
|
||||
if (outputs.containsKey("analysisScene")) {
|
||||
analysis.setAnalysisScene(outputs.getString("analysisScene"));
|
||||
}
|
||||
if (outputs.containsKey("analysisRecordId")) {
|
||||
analysis.setAnalysisRecordId(outputs.getString("analysisRecordId"));
|
||||
}
|
||||
if (outputs.containsKey("version")) {
|
||||
analysis.setVersion(outputs.getInteger("version"));
|
||||
}
|
||||
|
||||
// 从request的inputs中提取原始对话内容
|
||||
if (request.getInputs() != null && request.getInputs().containsKey("chat")) {
|
||||
analysis.setOriginalCorpus((String) request.getInputs().get("chat"));
|
||||
}
|
||||
|
||||
// 设置创建时间和更新时间
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
analysis.setCreatedAt(now);
|
||||
analysis.setUpdatedAt(now);
|
||||
|
||||
// 保存到数据库
|
||||
boolean saveResult = difyWorkflowAnalysisService.save(analysis);
|
||||
if (saveResult) {
|
||||
log.info("分析结果保存成功,ID: {}", analysis.getId());
|
||||
} else {
|
||||
log.error("分析结果保存失败");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("保存分析结果到数据库失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dify工作流请求参数
|
||||
*/
|
||||
public static class DifyWorkflowRequest {
|
||||
private Map<String, Object> inputs;
|
||||
private String userId;
|
||||
|
||||
public DifyWorkflowRequest() {}
|
||||
|
||||
public DifyWorkflowRequest(Map<String, Object> inputs, String userId) {
|
||||
this.inputs = inputs;
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public Map<String, Object> getInputs() {
|
||||
return inputs;
|
||||
}
|
||||
|
||||
public void setInputs(Map<String, Object> inputs) {
|
||||
this.inputs = inputs;
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(String userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dify工作流响应结果
|
||||
*/
|
||||
public static class DifyWorkflowResponse {
|
||||
private String workflowRunId;
|
||||
private String taskId;
|
||||
private JSONObject data;
|
||||
private JSONObject metadata;
|
||||
|
||||
public String getWorkflowRunId() {
|
||||
return workflowRunId;
|
||||
}
|
||||
|
||||
public void setWorkflowRunId(String workflowRunId) {
|
||||
this.workflowRunId = workflowRunId;
|
||||
}
|
||||
|
||||
public String getTaskId() {
|
||||
return taskId;
|
||||
}
|
||||
|
||||
public void setTaskId(String taskId) {
|
||||
this.taskId = taskId;
|
||||
}
|
||||
|
||||
public JSONObject getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(JSONObject data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public JSONObject getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
public void setMetadata(JSONObject metadata) {
|
||||
this.metadata = metadata;
|
||||
}
|
||||
}
|
||||
}
|
||||
52
src/main/java/com/rj/service/IFileUploadService.java
Normal file
52
src/main/java/com/rj/service/IFileUploadService.java
Normal file
@@ -0,0 +1,52 @@
|
||||
package com.rj.service;
|
||||
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 文件上传服务接口
|
||||
*
|
||||
* @author 李中华 ,spllzh
|
||||
* @since 2025-08-07
|
||||
*/
|
||||
public interface IFileUploadService {
|
||||
|
||||
/**
|
||||
* 上传音频文件
|
||||
* @param file 音频文件
|
||||
* @param audioId 录音ID
|
||||
* @return 上传结果
|
||||
*/
|
||||
Map<String, Object> uploadAudioFile(MultipartFile file, String audioId) throws IOException;
|
||||
|
||||
/**
|
||||
* 上传音频文件并更新录音记录
|
||||
* @param file 音频文件
|
||||
* @param audioId 录音ID
|
||||
* @return 上传结果
|
||||
*/
|
||||
Map<String, Object> uploadAudioFileAndUpdateRecord(MultipartFile file, String audioId) throws IOException;
|
||||
|
||||
/**
|
||||
* 获取音频文件访问URL
|
||||
* @param audioId 录音ID
|
||||
* @return 音频文件访问URL
|
||||
*/
|
||||
String getAudioFileUrl(String audioId);
|
||||
|
||||
/**
|
||||
* 删除音频文件
|
||||
* @param audioId 录音ID
|
||||
* @return 删除结果
|
||||
*/
|
||||
boolean deleteAudioFile(String audioId);
|
||||
|
||||
/**
|
||||
* 检查音频文件是否存在
|
||||
* @param audioId 录音ID
|
||||
* @return 是否存在
|
||||
*/
|
||||
boolean audioFileExists(String audioId);
|
||||
}
|
||||
302
src/main/java/com/rj/service/impl/FileUploadServiceImpl.java
Normal file
302
src/main/java/com/rj/service/impl/FileUploadServiceImpl.java
Normal file
@@ -0,0 +1,302 @@
|
||||
package com.rj.service.impl;
|
||||
|
||||
import com.rj.entity.AudioManagement;
|
||||
import com.rj.service.IFileUploadService;
|
||||
import com.rj.service.IAudioManagementService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 文件上传服务实现类
|
||||
*
|
||||
* @author 李中华 ,spllzh
|
||||
* @since 2025-08-07
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class FileUploadServiceImpl implements IFileUploadService {
|
||||
|
||||
@Autowired
|
||||
private IAudioManagementService audioManagementService;
|
||||
|
||||
@Value("${app.audio.upload.path:uploads/audio}")
|
||||
private String uploadPath;
|
||||
|
||||
@Value("${app.audio.access.url:/api/audio/}")
|
||||
private String accessUrl;
|
||||
|
||||
@Value("${app.audio.max.size:100MB}")
|
||||
private String maxFileSize;
|
||||
|
||||
// 支持的音频格式
|
||||
private static final String[] SUPPORTED_AUDIO_FORMATS = {
|
||||
"mp3", "wav", "m4a", "aac", "ogg", "flac", "wma"
|
||||
};
|
||||
|
||||
@Override
|
||||
public Map<String, Object> uploadAudioFile(MultipartFile file, String audioId) throws IOException {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
try {
|
||||
// 验证文件
|
||||
if (file == null || file.isEmpty()) {
|
||||
result.put("success", false);
|
||||
result.put("message", "文件不能为空");
|
||||
return result;
|
||||
}
|
||||
|
||||
// 验证文件大小
|
||||
long maxSize = parseFileSize(maxFileSize);
|
||||
if (file.getSize() > maxSize) {
|
||||
result.put("success", false);
|
||||
result.put("message", "文件大小超过限制,最大允许 " + maxFileSize);
|
||||
return result;
|
||||
}
|
||||
|
||||
// 验证文件格式
|
||||
String originalFilename = file.getOriginalFilename();
|
||||
if (!isValidAudioFormat(originalFilename)) {
|
||||
result.put("success", false);
|
||||
result.put("message", "不支持的音频格式,支持的格式:" + String.join(", ", SUPPORTED_AUDIO_FORMATS));
|
||||
return result;
|
||||
}
|
||||
|
||||
// 创建上传目录
|
||||
Path uploadDir = Paths.get(uploadPath);
|
||||
if (!Files.exists(uploadDir)) {
|
||||
Files.createDirectories(uploadDir);
|
||||
}
|
||||
|
||||
// 生成文件名
|
||||
String fileExtension = getFileExtension(originalFilename);
|
||||
String fileName = audioId+"_"+UUID.randomUUID() + "." + fileExtension;
|
||||
Path filePath = uploadDir.resolve(fileName);
|
||||
|
||||
// 保存文件
|
||||
Files.copy(file.getInputStream(), filePath);
|
||||
|
||||
// 生成访问URL
|
||||
String fileUrl = accessUrl + fileName;
|
||||
|
||||
result.put("success", true);
|
||||
result.put("message", "文件上传成功");
|
||||
result.put("fileName", fileName);
|
||||
result.put("fileUrl", fileUrl);
|
||||
result.put("fileSize", file.getSize());
|
||||
result.put("originalName", originalFilename);
|
||||
|
||||
log.info("音频文件上传成功:audioId={}, fileName={}, size={}", audioId, fileName, file.getSize());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("音频文件上传失败:audioId={}, error={}", audioId, e.getMessage(), e);
|
||||
result.put("success", false);
|
||||
result.put("message", "文件上传失败:" + e.getMessage());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> uploadAudioFileAndUpdateRecord(MultipartFile file, String audioId) throws IOException {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
try {
|
||||
// 先上传文件
|
||||
Map<String, Object> uploadResult = uploadAudioFile(file, audioId);
|
||||
|
||||
if (!(Boolean) uploadResult.get("success")) {
|
||||
return uploadResult;
|
||||
}
|
||||
|
||||
// 获取录音记录
|
||||
AudioManagement audio = audioManagementService.getById(audioId);
|
||||
if (audio == null) {
|
||||
result.put("success", false);
|
||||
result.put("message", "录音记录不存在");
|
||||
return result;
|
||||
}
|
||||
|
||||
// 更新录音记录的文件信息
|
||||
String fileName = (String) uploadResult.get("fileName");
|
||||
String fileUrl = (String) uploadResult.get("fileUrl");
|
||||
Long fileSize = (Long) uploadResult.get("fileSize");
|
||||
String originalName = (String) uploadResult.get("originalName");
|
||||
String fileExtension = getFileExtension(originalName);
|
||||
|
||||
audio.setAudioFilePath(uploadPath + "/" + fileName);
|
||||
audio.setAudioFileUrl(fileUrl);
|
||||
audio.setAudioFileSize(fileSize);
|
||||
audio.setAudioFileOriginalName(originalName);
|
||||
audio.setAudioFileExtension(fileExtension);
|
||||
audio.setUploadTime(java.time.LocalDateTime.now());
|
||||
audio.setUpdateTime(java.time.LocalDateTime.now());
|
||||
|
||||
// 保存更新
|
||||
boolean updated = audioManagementService.updateById(audio);
|
||||
|
||||
if (updated) {
|
||||
result.put("success", true);
|
||||
result.put("message", "音频文件上传成功并更新录音记录");
|
||||
result.put("data", uploadResult);
|
||||
result.put("audioRecord", audio);
|
||||
|
||||
log.info("音频文件上传并更新录音记录成功:audioId={}, fileName={}", audioId, fileName);
|
||||
} else {
|
||||
result.put("success", false);
|
||||
result.put("message", "文件上传成功但更新录音记录失败");
|
||||
result.put("data", uploadResult);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("音频文件上传并更新录音记录失败:audioId={}, error={}", audioId, e.getMessage(), e);
|
||||
result.put("success", false);
|
||||
result.put("message", "文件上传并更新录音记录失败:" + e.getMessage());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAudioFileUrl(String audioId) {
|
||||
if (StringUtils.isEmpty(audioId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Path uploadDir = Paths.get(uploadPath);
|
||||
File dir = uploadDir.toFile();
|
||||
|
||||
if (!dir.exists() || !dir.isDirectory()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 查找对应的音频文件
|
||||
File[] files = dir.listFiles((d, name) -> name.startsWith(audioId + "."));
|
||||
if (files != null && files.length > 0) {
|
||||
String fileName = files[0].getName();
|
||||
return accessUrl + fileName;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteAudioFile(String audioId) {
|
||||
if (StringUtils.isEmpty(audioId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
Path uploadDir = Paths.get(uploadPath);
|
||||
File dir = uploadDir.toFile();
|
||||
|
||||
if (!dir.exists() || !dir.isDirectory()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 查找对应的音频文件
|
||||
File[] files = dir.listFiles((d, name) -> name.startsWith(audioId + "."));
|
||||
if (files != null && files.length > 0) {
|
||||
boolean deleted = files[0].delete();
|
||||
if (deleted) {
|
||||
log.info("音频文件删除成功:audioId={}, fileName={}", audioId, files[0].getName());
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
log.error("音频文件删除失败:audioId={}, error={}", audioId, e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean audioFileExists(String audioId) {
|
||||
if (StringUtils.isEmpty(audioId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Path uploadDir = Paths.get(uploadPath);
|
||||
File dir = uploadDir.toFile();
|
||||
|
||||
if (!dir.exists() || !dir.isDirectory()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
File[] files = dir.listFiles((d, name) -> name.startsWith(audioId + "."));
|
||||
return files != null && files.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证音频文件格式
|
||||
*/
|
||||
private boolean isValidAudioFormat(String filename) {
|
||||
if (StringUtils.isEmpty(filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String extension = getFileExtension(filename).toLowerCase();
|
||||
for (String format : SUPPORTED_AUDIO_FORMATS) {
|
||||
if (format.equals(extension)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件扩展名
|
||||
*/
|
||||
private String getFileExtension(String filename) {
|
||||
if (StringUtils.isEmpty(filename)) {
|
||||
return "";
|
||||
}
|
||||
int lastDotIndex = filename.lastIndexOf('.');
|
||||
if (lastDotIndex > 0 && lastDotIndex < filename.length() - 1) {
|
||||
return filename.substring(lastDotIndex + 1);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析文件大小字符串
|
||||
*/
|
||||
private long parseFileSize(String sizeStr) {
|
||||
if (StringUtils.isEmpty(sizeStr)) {
|
||||
return 100 * 1024 * 1024; // 默认100MB
|
||||
}
|
||||
|
||||
sizeStr = sizeStr.trim().toUpperCase();
|
||||
long multiplier = 1;
|
||||
|
||||
if (sizeStr.endsWith("KB")) {
|
||||
multiplier = 1024;
|
||||
sizeStr = sizeStr.substring(0, sizeStr.length() - 2);
|
||||
} else if (sizeStr.endsWith("MB")) {
|
||||
multiplier = 1024 * 1024;
|
||||
sizeStr = sizeStr.substring(0, sizeStr.length() - 2);
|
||||
} else if (sizeStr.endsWith("GB")) {
|
||||
multiplier = 1024 * 1024 * 1024;
|
||||
sizeStr = sizeStr.substring(0, sizeStr.length() - 2);
|
||||
}
|
||||
|
||||
try {
|
||||
return Long.parseLong(sizeStr) * multiplier;
|
||||
} catch (NumberFormatException e) {
|
||||
return 100 * 1024 * 1024; // 默认100MB
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user