From 23fe2e70b1f8914e211d6769b63b3d49ea9d8afe Mon Sep 17 00:00:00 2001 From: ZLI263 Date: Sat, 22 Nov 2025 17:53:18 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=B6=E5=B1=85=E5=A4=A7=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E8=81=94=E8=B0=83=EF=BC=8C=E5=A4=A7=E6=A8=A1=E5=9E=8B=E7=94=9F?= =?UTF-8?q?=E6=88=90=E6=99=BA=E8=83=BD=E5=BE=85=E5=8A=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AudioTextAnalysisFurnitureController.java | 178 ++++++++- .../com/rj/controller/TodoItemController.java | 359 ++++++++++++++++++ src/main/java/com/rj/entity/TodoItem.java | 64 ++++ .../java/com/rj/mapper/TodoItemMapper.java | 17 + .../java/com/rj/service/ITodoItemService.java | 17 + .../rj/service/impl/TodoItemServiceImpl.java | 21 + src/main/resources/mapper/TodoItemMapper.xml | 23 ++ .../audio_text_analysis_furniture_system.txt | 1 + .../audio_text_analysis_furniture_user.txt | 189 +++++++++ src/main/sql/todo_item.sql | 14 + 10 files changed, 874 insertions(+), 9 deletions(-) create mode 100644 src/main/java/com/rj/controller/TodoItemController.java create mode 100644 src/main/java/com/rj/entity/TodoItem.java create mode 100644 src/main/java/com/rj/mapper/TodoItemMapper.java create mode 100644 src/main/java/com/rj/service/ITodoItemService.java create mode 100644 src/main/java/com/rj/service/impl/TodoItemServiceImpl.java create mode 100644 src/main/resources/mapper/TodoItemMapper.xml create mode 100644 src/main/resources/prompts/audio_text_analysis_furniture_system.txt create mode 100644 src/main/resources/prompts/audio_text_analysis_furniture_user.txt create mode 100644 src/main/sql/todo_item.sql diff --git a/src/main/java/com/rj/controller/AudioTextAnalysisFurnitureController.java b/src/main/java/com/rj/controller/AudioTextAnalysisFurnitureController.java index 999f132..f8ebf4f 100644 --- a/src/main/java/com/rj/controller/AudioTextAnalysisFurnitureController.java +++ b/src/main/java/com/rj/controller/AudioTextAnalysisFurnitureController.java @@ -14,8 +14,10 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.rj.entity.AudioManagement; import com.rj.entity.AudioTextAnalysisFurniture; +import com.rj.entity.TodoItem; import com.rj.service.IAudioManagementService; import com.rj.service.IAudioTextAnalysisFurnitureService; +import com.rj.service.ITodoItemService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; @@ -25,11 +27,14 @@ import org.springframework.format.annotation.DateTimeFormat; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Scanner; import java.util.UUID; /** @@ -47,9 +52,20 @@ public class AudioTextAnalysisFurnitureController { @Autowired private IAudioManagementService audioManagementService; + @Autowired + private ITodoItemService todoItemService; + @Autowired private ObjectMapper objectMapper; + // 提示词文件路径 + private static final String SYSTEM_PROMPT_PATH = "prompts/audio_text_analysis_furniture_system.txt"; + private static final String USER_PROMPT_PATH = "prompts/audio_text_analysis_furniture_user.txt"; + + // 缓存提示词内容,避免重复读取文件 + private static String cachedSystemPrompt; + private static String cachedUserPromptTemplate; + @PostMapping("/add") @Operation(summary = "新增记录", description = "新增一条家具意向分析记录") public ResponseEntity> add(@RequestBody AudioTextAnalysisFurniture furniture) { @@ -222,8 +238,8 @@ public class AudioTextAnalysisFurnitureController { } AudioTextAnalysisFurniture furniture = new AudioTextAnalysisFurniture(); furniture.setRecordingText(recordingText); - // 4. 调用大模型生成总结 - generateSummaryByLLM(furniture); + // 4. 调用大模型生成总结,传递待办事项所需的关联信息 + generateSummaryByLLM(furniture, id, audioManagement.getSalesName(), audioManagement.getSalesPhone()); audioManagement = new AudioManagement(); @@ -267,14 +283,20 @@ public class AudioTextAnalysisFurnitureController { /** * 调用大模型生成总结 * - * @param - * @return 生成的总结 + * @param furniture 家具意向分析对象 + * @param parentId 父ID(AudioManagement的ID) + * @param ownerName 所属人姓名 + * @param ownerPhone 所属人电话 */ - private void generateSummaryByLLM(AudioTextAnalysisFurniture furniture) { + private void generateSummaryByLLM(AudioTextAnalysisFurniture furniture, String parentId, String ownerName, String ownerPhone) { + // 记录开始时间 + long startTime = System.currentTimeMillis(); + log.info("开始调用大模型生成总结"); + Generation gen = new Generation(); Message systemMsg = Message.builder() .role(Role.SYSTEM.getValue()) - .content("你是一个专业的家居销售顾问助手,需要从录音文本中抽取结构化信息并输出JSON。必须严格按照要求输出JSON,不能包含额外文字。") + .content(getSystemPrompt()) .build(); Message userMsg = Message.builder() .role(Role.USER.getValue()) @@ -292,7 +314,7 @@ public class AudioTextAnalysisFurnitureController { GenerationResult call = gen.call(param); String rawContent = call.getOutput().getChoices().get(0).getMessage().getContent(); log.info("大模型生成总结完成,结果长度: {},内容是:{}", rawContent.length(), rawContent); - applyStructuredResult(furniture, rawContent); + applyStructuredResult(furniture, rawContent, parentId, ownerName, ownerPhone); } catch (NoApiKeyException e) { log.error("API密钥未配置", e); throw new RuntimeException("API密钥未配置: " + e.getMessage(), e); @@ -302,10 +324,83 @@ public class AudioTextAnalysisFurnitureController { } catch (Exception e) { log.error("调用大模型生成总结失败", e); throw new RuntimeException("调用大模型失败: " + e.getMessage(), e); + } finally { + // 记录结束时间并计算耗时 + long endTime = System.currentTimeMillis(); + long durationMillis = endTime - startTime; + double durationSeconds = durationMillis / 1000.0; + double durationMinutes = durationSeconds / 60.0; + log.info("调用大模型耗时: {} 毫秒, {} 秒, {} 分钟", durationMillis, String.format("%.2f", durationSeconds), String.format("%.2f", durationMinutes)); } } private String buildPrompt(String recordingText) { + String template = getUserPromptTemplate(); + // 替换占位符 + return template.replace("{RECORDING_TEXT}", recordingText != null ? recordingText : ""); + } + + /** + * 读取系统提示词 + * @return 系统提示词内容 + */ + private String getSystemPrompt() { + if (cachedSystemPrompt == null) { + cachedSystemPrompt = readResourceFile(SYSTEM_PROMPT_PATH); + // 如果读取失败,使用默认值作为fallback + if (cachedSystemPrompt == null || cachedSystemPrompt.trim().isEmpty()) { + log.warn("无法读取系统提示词文件,使用默认值"); + cachedSystemPrompt = "你是一个专业的家居销售顾问助手,需要从录音文本中抽取结构化信息并输出JSON。必须严格按照要求输出JSON,不能包含额外文字。"; + } + } + return cachedSystemPrompt; + } + + /** + * 读取用户提示词模板 + * @return 用户提示词模板内容 + */ + private String getUserPromptTemplate() { + if (cachedUserPromptTemplate == null) { + cachedUserPromptTemplate = readResourceFile(USER_PROMPT_PATH); + // 如果读取失败,使用默认值作为fallback + if (cachedUserPromptTemplate == null || cachedUserPromptTemplate.trim().isEmpty()) { + log.warn("无法读取用户提示词模板文件,使用默认值"); + cachedUserPromptTemplate = buildDefaultUserPrompt(); + } + } + return cachedUserPromptTemplate; + } + + /** + * 读取资源文件内容 + * @param resourcePath 资源文件路径 + * @return 文件内容,如果读取失败返回null + */ + private String readResourceFile(String resourcePath) { + try { + ClassLoader classLoader = getClass().getClassLoader(); + InputStream inputStream = classLoader.getResourceAsStream(resourcePath); + if (inputStream == null) { + log.error("无法找到资源文件: {}", resourcePath); + return null; + } + try (Scanner scanner = new Scanner(inputStream, StandardCharsets.UTF_8.name()).useDelimiter("\\A")) { + String content = scanner.hasNext() ? scanner.next() : ""; + log.info("成功读取提示词文件: {}, 长度: {}", resourcePath, content.length()); + return content; + } + } catch (Exception e) { + log.error("读取资源文件失败: {}", resourcePath, e); + return null; + } + } + + /** + * 构建默认的用户提示词(作为fallback) + * @return 默认提示词 + */ + private String buildDefaultUserPrompt() { StringBuilder prompt = new StringBuilder(); prompt.append("请阅读以下录音文本,从中提取客户信息并生成 JSON。必须覆盖所有字段,缺失信息请基于语境合理推断或标注\"暂无信息\"。\n\n"); prompt.append("字段要求:\n"); @@ -483,12 +578,12 @@ public class AudioTextAnalysisFurnitureController { prompt.append("2. 每个产品维度必须是一个完整的JSON对象,包含上述10个维度。\n"); prompt.append("3. 如果录音文本中未提及某个产品,该产品的JSON对象中所有维度应填写\"暂无信息\"或基于语境合理推断。\n"); prompt.append("4. 所有字段值必须基于录音文本内容,合理推断,不能随意编造。\n\n"); - prompt.append("录音文本:\n").append(recordingText); + prompt.append("录音文本:\n{RECORDING_TEXT}"); return prompt.toString(); } - private void applyStructuredResult(AudioTextAnalysisFurniture furniture, String rawContent) { + private void applyStructuredResult(AudioTextAnalysisFurniture furniture, String rawContent, String parentId, String ownerName, String ownerPhone) { String normalized = normalizeJson(rawContent); try { JsonNode root = objectMapper.readTree(normalized); @@ -511,11 +606,76 @@ public class AudioTextAnalysisFurnitureController { furniture.setSecondaryBedroomCabinet(jsonObjectToString(root, "secondary_bedroom_cabinet")); furniture.setShoeCabinet(jsonObjectToString(root, "shoe_cabinet")); furniture.setBedAndMattress(jsonObjectToString(root, "bed_and_mattress")); + + // 解析并保存待办事项 + saveTodoItems(root, parentId, ownerName, ownerPhone); } catch (JsonProcessingException e) { log.warn("解析大模型返回JSON失败,使用原始内容作为总结: {}", e.getMessage()); furniture.setSummarySentence(rawContent); } } + + /** + * 解析并保存待办事项 + * + * @param root JSON根节点 + * @param parentId 父ID(AudioManagement的ID) + * @param ownerName 所属人姓名 + * @param ownerPhone 所属人电话 + */ + private void saveTodoItems(JsonNode root, String parentId, String ownerName, String ownerPhone) { + try { + JsonNode todoItemsNode = root.get("todo_item"); + if (todoItemsNode == null || !todoItemsNode.isArray()) { + log.info("未找到待办事项数组或格式不正确"); + return; + } + + LocalDateTime now = LocalDateTime.now(); + int savedCount = 0; + + for (JsonNode todoItemNode : todoItemsNode) { + if (!todoItemNode.isObject()) { + log.warn("待办事项项不是JSON对象,跳过"); + continue; + } + + String todoTitle = textValue(todoItemNode, "todo_title"); + String todoDetail = textValue(todoItemNode, "todo_detail"); + + // 如果标题和详情都为空,跳过 + if ((todoTitle == null || todoTitle.trim().isEmpty()) && + (todoDetail == null || todoDetail.trim().isEmpty())) { + log.warn("待办事项标题和详情都为空,跳过"); + continue; + } + + TodoItem todoItem = new TodoItem(); + todoItem.setId(UUID.randomUUID().toString()); + todoItem.setParentId(parentId); + todoItem.setTodoTitle(todoTitle != null ? todoTitle.trim() : ""); + todoItem.setTodoDetail(todoDetail != null ? todoDetail.trim() : ""); + todoItem.setOwnerName(ownerName); + todoItem.setOwnerPhone(ownerPhone); + todoItem.setStatus("待处理"); // 默认状态 + todoItem.setCreateTime(now); + todoItem.setUpdateTime(now); + + boolean success = todoItemService.save(todoItem); + if (success) { + savedCount++; + log.info("保存待办事项成功,ID: {}, 标题: {}", todoItem.getId(), todoItem.getTodoTitle()); + } else { + log.warn("保存待办事项失败,标题: {}", todoItem.getTodoTitle()); + } + } + + log.info("待办事项保存完成,共保存 {} 条", savedCount); + } catch (Exception e) { + log.error("保存待办事项时发生异常", e); + // 不抛出异常,避免影响主流程 + } + } private String normalizeJson(String content) { String trimmed = content.trim(); diff --git a/src/main/java/com/rj/controller/TodoItemController.java b/src/main/java/com/rj/controller/TodoItemController.java new file mode 100644 index 0000000..f8f62a5 --- /dev/null +++ b/src/main/java/com/rj/controller/TodoItemController.java @@ -0,0 +1,359 @@ +package com.rj.controller; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.rj.entity.TodoItem; +import com.rj.service.ITodoItemService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +/** + *

+ * 待办事项表 前端控制器 + *

+ * + * @author system + * @since 2025-01-XX + */ +@RestController +@RequestMapping("/api/todoItem") +@Tag(name = "待办事项管理", description = "待办事项相关接口") +public class TodoItemController { + + @Autowired + private ITodoItemService todoItemService; + + /** + * 新增待办事项 + */ + @PostMapping("/add") + @Operation(summary = "新增待办事项", description = "添加新的待办事项") + public ResponseEntity> addTodoItem( + @Parameter(description = "待办事项信息", required = true) + @RequestBody TodoItem todoItem) { + Map result = new HashMap<>(); + try { + if (todoItem.getId() == null || todoItem.getId().trim().isEmpty()) { + todoItem.setId(UUID.randomUUID().toString()); + } + todoItem.setCreateTime(LocalDateTime.now()); + todoItem.setUpdateTime(LocalDateTime.now()); + boolean success = todoItemService.save(todoItem); + if (success) { + result.put("success", true); + result.put("message", "待办事项添加成功"); + result.put("data", todoItem); + return ResponseEntity.ok(result); + } else { + result.put("success", false); + result.put("message", "待办事项添加失败"); + return ResponseEntity.badRequest().body(result); + } + } catch (Exception e) { + result.put("success", false); + result.put("message", "待办事项添加异常:" + e.getMessage()); + return ResponseEntity.internalServerError().body(result); + } + } + + /** + * 根据ID查询待办事项 + */ + @GetMapping("/get/{id}") + @Operation(summary = "根据ID查询待办事项", description = "根据待办事项ID获取详细信息") + public ResponseEntity> getTodoItemById( + @Parameter(description = "待办事项ID", required = true) + @PathVariable String id) { + Map result = new HashMap<>(); + try { + TodoItem todoItem = todoItemService.getById(id); + if (todoItem != null) { + result.put("success", true); + result.put("message", "查询成功"); + result.put("data", todoItem); + return ResponseEntity.ok(result); + } else { + result.put("success", false); + result.put("message", "待办事项不存在"); + return ResponseEntity.notFound().build(); + } + } catch (Exception e) { + result.put("success", false); + result.put("message", "查询异常:" + e.getMessage()); + return ResponseEntity.internalServerError().body(result); + } + } + + /** + * 根据父ID查询待办事项列表 + */ + @GetMapping("/getByParentId") + @Operation(summary = "根据父ID查询待办事项", description = "根据父ID查询子待办事项列表") + public ResponseEntity> getTodoItemsByParentId( + @Parameter(description = "父ID", required = true) + @RequestParam String parentId) { + Map result = new HashMap<>(); + try { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(TodoItem::getParentId, parentId); + queryWrapper.orderByDesc(TodoItem::getCreateTime); + List todoItems = todoItemService.list(queryWrapper); + result.put("success", true); + result.put("message", "查询成功"); + result.put("data", todoItems); + result.put("count", todoItems.size()); + return ResponseEntity.ok(result); + } catch (Exception e) { + result.put("success", false); + result.put("message", "查询异常:" + e.getMessage()); + return ResponseEntity.internalServerError().body(result); + } + } + + /** + * 分页查询待办事项列表 + */ + @GetMapping("/list") + @Operation(summary = "分页查询待办事项列表", description = "分页查询待办事项信息列表") + public ResponseEntity> getTodoItemList( + @Parameter(description = "页码", example = "1") + @RequestParam(defaultValue = "1") Integer current, + @Parameter(description = "每页大小", example = "10") + @RequestParam(defaultValue = "10") Integer size, + @Parameter(description = "父ID(精确查询)") + @RequestParam(required = false) String parentId, + @Parameter(description = "状态(模糊查询)") + @RequestParam(required = false) String status, + @Parameter(description = "待办事项简称(模糊查询)") + @RequestParam(required = false) String todoTitle, + @Parameter(description = "所属人姓名(模糊查询)") + @RequestParam(required = false) String ownerName, + @Parameter(description = "所属人电话(模糊查询)") + @RequestParam(required = false) String ownerPhone, + @Parameter(description = "创建开始时间", example = "2025-01-01 00:00:00") + @RequestParam(required = false) String createStartTime, + @Parameter(description = "创建结束时间", example = "2025-12-31 23:59:59") + @RequestParam(required = false) String createEndTime, + @Parameter(description = "修改开始时间", example = "2025-01-01 00:00:00") + @RequestParam(required = false) String updateStartTime, + @Parameter(description = "修改结束时间", example = "2025-12-31 23:59:59") + @RequestParam(required = false) String updateEndTime) { + Map result = new HashMap<>(); + try { + Page page = new Page<>(current, size); + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + + // 添加查询条件 + if (parentId != null && !parentId.trim().isEmpty()) { + queryWrapper.eq(TodoItem::getParentId, parentId); + } + if (status != null && !status.trim().isEmpty()) { + queryWrapper.like(TodoItem::getStatus, status); + } + if (todoTitle != null && !todoTitle.trim().isEmpty()) { + queryWrapper.like(TodoItem::getTodoTitle, todoTitle); + } + if (ownerName != null && !ownerName.trim().isEmpty()) { + queryWrapper.like(TodoItem::getOwnerName, ownerName); + } + if (ownerPhone != null && !ownerPhone.trim().isEmpty()) { + queryWrapper.like(TodoItem::getOwnerPhone, ownerPhone); + } + + // 添加时间范围查询条件 + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + if (createStartTime != null && !createStartTime.trim().isEmpty()) { + try { + LocalDateTime startTime = LocalDateTime.parse(createStartTime, formatter); + queryWrapper.ge(TodoItem::getCreateTime, startTime); + } catch (Exception e) { + result.put("success", false); + result.put("message", "创建开始时间格式错误,请使用格式:yyyy-MM-dd HH:mm:ss"); + return ResponseEntity.badRequest().body(result); + } + } + if (createEndTime != null && !createEndTime.trim().isEmpty()) { + try { + LocalDateTime endTime = LocalDateTime.parse(createEndTime, formatter); + queryWrapper.le(TodoItem::getCreateTime, endTime); + } catch (Exception e) { + result.put("success", false); + result.put("message", "创建结束时间格式错误,请使用格式:yyyy-MM-dd HH:mm:ss"); + return ResponseEntity.badRequest().body(result); + } + } + if (updateStartTime != null && !updateStartTime.trim().isEmpty()) { + try { + LocalDateTime startTime = LocalDateTime.parse(updateStartTime, formatter); + queryWrapper.ge(TodoItem::getUpdateTime, startTime); + } catch (Exception e) { + result.put("success", false); + result.put("message", "修改开始时间格式错误,请使用格式:yyyy-MM-dd HH:mm:ss"); + return ResponseEntity.badRequest().body(result); + } + } + if (updateEndTime != null && !updateEndTime.trim().isEmpty()) { + try { + LocalDateTime endTime = LocalDateTime.parse(updateEndTime, formatter); + queryWrapper.le(TodoItem::getUpdateTime, endTime); + } catch (Exception e) { + result.put("success", false); + result.put("message", "修改结束时间格式错误,请使用格式:yyyy-MM-dd HH:mm:ss"); + return ResponseEntity.badRequest().body(result); + } + } + + // 按修改时间倒序排列 + queryWrapper.orderByDesc(TodoItem::getUpdateTime); + + Page todoItemPage = todoItemService.page(page, queryWrapper); + + result.put("success", true); + result.put("message", "查询成功"); + result.put("data", todoItemPage.getRecords()); + result.put("total", todoItemPage.getTotal()); + result.put("current", todoItemPage.getCurrent()); + result.put("size", todoItemPage.getSize()); + result.put("pages", todoItemPage.getPages()); + + return ResponseEntity.ok(result); + } catch (Exception e) { + result.put("success", false); + result.put("message", "查询异常:" + e.getMessage()); + return ResponseEntity.internalServerError().body(result); + } + } + + /** + * 更新待办事项信息 + */ + @PutMapping("/update") + @Operation(summary = "更新待办事项信息", description = "更新待办事项详细信息") + public ResponseEntity> updateTodoItem( + @Parameter(description = "待办事项信息", required = true) + @RequestBody TodoItem todoItem) { + Map result = new HashMap<>(); + try { + if (todoItem.getId() == null || todoItem.getId().trim().isEmpty()) { + result.put("success", false); + result.put("message", "待办事项ID不能为空"); + return ResponseEntity.badRequest().body(result); + } + + todoItem.setUpdateTime(LocalDateTime.now()); + boolean success = todoItemService.updateById(todoItem); + + if (success) { + result.put("success", true); + result.put("message", "待办事项信息更新成功"); + result.put("data", todoItem); + return ResponseEntity.ok(result); + } else { + result.put("success", false); + result.put("message", "待办事项信息更新失败"); + return ResponseEntity.badRequest().body(result); + } + } catch (Exception e) { + result.put("success", false); + result.put("message", "更新异常:" + e.getMessage()); + return ResponseEntity.internalServerError().body(result); + } + } + + /** + * 根据ID删除待办事项 + */ + @DeleteMapping("/delete/{id}") + @Operation(summary = "删除待办事项", description = "根据待办事项ID删除待办事项信息") + public ResponseEntity> deleteTodoItem( + @Parameter(description = "待办事项ID", required = true) + @PathVariable String id) { + Map result = new HashMap<>(); + try { + boolean success = todoItemService.removeById(id); + if (success) { + result.put("success", true); + result.put("message", "待办事项删除成功"); + return ResponseEntity.ok(result); + } else { + result.put("success", false); + result.put("message", "待办事项删除失败"); + return ResponseEntity.badRequest().body(result); + } + } catch (Exception e) { + result.put("success", false); + result.put("message", "删除异常:" + e.getMessage()); + return ResponseEntity.internalServerError().body(result); + } + } + + /** + * 批量删除待办事项 + */ + @DeleteMapping("/batchDelete") + @Operation(summary = "批量删除待办事项", description = "根据待办事项ID列表批量删除待办事项信息") + public ResponseEntity> batchDeleteTodoItems( + @Parameter(description = "待办事项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 = todoItemService.removeByIds(ids); + if (success) { + result.put("success", true); + result.put("message", "批量删除成功,共删除 " + ids.size() + " 条记录"); + return ResponseEntity.ok(result); + } else { + result.put("success", false); + result.put("message", "批量删除失败"); + return ResponseEntity.badRequest().body(result); + } + } catch (Exception e) { + result.put("success", false); + result.put("message", "批量删除异常:" + e.getMessage()); + return ResponseEntity.internalServerError().body(result); + } + } + + /** + * 获取待办事项统计信息 + */ + @GetMapping("/statistics") + @Operation(summary = "获取待办事项统计信息", description = "获取待办事项总数等统计信息") + public ResponseEntity> getTodoItemStatistics() { + Map result = new HashMap<>(); + try { + long totalCount = todoItemService.count(); + + Map statistics = new HashMap<>(); + statistics.put("totalTodoItems", totalCount); + + result.put("success", true); + result.put("message", "统计信息获取成功"); + result.put("data", statistics); + + return ResponseEntity.ok(result); + } catch (Exception e) { + result.put("success", false); + result.put("message", "统计信息获取异常:" + e.getMessage()); + return ResponseEntity.internalServerError().body(result); + } + } +} + diff --git a/src/main/java/com/rj/entity/TodoItem.java b/src/main/java/com/rj/entity/TodoItem.java new file mode 100644 index 0000000..23e07af --- /dev/null +++ b/src/main/java/com/rj/entity/TodoItem.java @@ -0,0 +1,64 @@ +package com.rj.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableField; +import java.time.LocalDateTime; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + *

+ * 待办事项表 + *

+ * + * @author system + * @since 2025-01-XX + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("todo_item") +@Schema(description = "待办事项表") +public class TodoItem implements Serializable { + + private static final long serialVersionUID = 1L; + + @Schema(description = "主键,UUID") + @TableId("id") + private String id; + + @Schema(description = "父ID,UUID,用于树状结构关联") + @TableField("parent_id") + private String parentId; + + @Schema(description = "状态") + @TableField("status") + private String status; + + @Schema(description = "待办事项简称") + @TableField("todo_title") + private String todoTitle; + + @Schema(description = "待办事项详情") + @TableField("todo_detail") + private String todoDetail; + + @Schema(description = "所属人姓名") + @TableField("owner_name") + private String ownerName; + + @Schema(description = "所属人电话") + @TableField("owner_phone") + private String ownerPhone; + + @Schema(description = "创建时间") + @TableField("create_time") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + @TableField("update_time") + private LocalDateTime updateTime; +} + diff --git a/src/main/java/com/rj/mapper/TodoItemMapper.java b/src/main/java/com/rj/mapper/TodoItemMapper.java new file mode 100644 index 0000000..e577ef3 --- /dev/null +++ b/src/main/java/com/rj/mapper/TodoItemMapper.java @@ -0,0 +1,17 @@ +package com.rj.mapper; + +import com.rj.entity.TodoItem; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 待办事项表 Mapper 接口 + *

+ * + * @author system + * @since 2025-01-XX + */ +public interface TodoItemMapper extends BaseMapper { + +} + diff --git a/src/main/java/com/rj/service/ITodoItemService.java b/src/main/java/com/rj/service/ITodoItemService.java new file mode 100644 index 0000000..b84286d --- /dev/null +++ b/src/main/java/com/rj/service/ITodoItemService.java @@ -0,0 +1,17 @@ +package com.rj.service; + +import com.rj.entity.TodoItem; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 待办事项表 服务类 + *

+ * + * @author system + * @since 2025-01-XX + */ +public interface ITodoItemService extends IService { + +} + diff --git a/src/main/java/com/rj/service/impl/TodoItemServiceImpl.java b/src/main/java/com/rj/service/impl/TodoItemServiceImpl.java new file mode 100644 index 0000000..6699ebf --- /dev/null +++ b/src/main/java/com/rj/service/impl/TodoItemServiceImpl.java @@ -0,0 +1,21 @@ +package com.rj.service.impl; + +import com.rj.entity.TodoItem; +import com.rj.mapper.TodoItemMapper; +import com.rj.service.ITodoItemService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + *

+ * 待办事项表 服务实现类 + *

+ * + * @author system + * @since 2025-01-XX + */ +@Service +public class TodoItemServiceImpl extends ServiceImpl implements ITodoItemService { + +} + diff --git a/src/main/resources/mapper/TodoItemMapper.xml b/src/main/resources/mapper/TodoItemMapper.xml new file mode 100644 index 0000000..36d79d5 --- /dev/null +++ b/src/main/resources/mapper/TodoItemMapper.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + id, parent_id, status, todo_title, todo_detail, owner_name, owner_phone, create_time, update_time + + + diff --git a/src/main/resources/prompts/audio_text_analysis_furniture_system.txt b/src/main/resources/prompts/audio_text_analysis_furniture_system.txt new file mode 100644 index 0000000..d3f30c2 --- /dev/null +++ b/src/main/resources/prompts/audio_text_analysis_furniture_system.txt @@ -0,0 +1 @@ +你是一个专业的家居销售顾问助手,需要从录音文本中抽取结构化信息并输出JSON。必须严格按照要求输出JSON,不能包含额外文字。 diff --git a/src/main/resources/prompts/audio_text_analysis_furniture_user.txt b/src/main/resources/prompts/audio_text_analysis_furniture_user.txt new file mode 100644 index 0000000..947c930 --- /dev/null +++ b/src/main/resources/prompts/audio_text_analysis_furniture_user.txt @@ -0,0 +1,189 @@ +请阅读以下录音文本,从中提取客户信息并生成 JSON。必须覆盖所有字段,缺失信息请基于语境合理推断或标注"暂无信息"。 + +字段要求: +1. customer_type:客户类型,仅可填「新房装修」「二次翻修」「补充家居」。 +2. family_structure:家庭结构,描述家庭几口人、几个孩子、几个老人,重点反应家庭成员。 +3. intention_products:意向产品清单,比如:床、柜子、沙发等,仅限家居,不能扩展到其它产品,用顿号或逗号分隔。 +4. summary:一句话总结,应该简洁明了地概括客户的需求和关键信息,重点关注客户预算(如果未提及,默认预算3万),关注的还有产品信息等关键信息。 +5. decoration_style:装修风格,总结偏好的装修/家居风格,风格类型,并推测主人相关信息。 +6. sofa:沙发需求或配置说明,大小、风格、数量、种类等相关信息。 +7. tea_table:茶几/茶桌需求或配置,材质、风格。 +8. dining_table:餐桌椅需求或配置。 +9. study_desk:学习桌/书桌需求。 +10. tv_cabinet:电视柜需求说明。 +11. cabinet:橱柜或厨房柜体说明。 +12. wine_cabinet:酒柜需求说明。 +13. master_bedroom_cabinet:主卧衣柜/储物柜配置。 +14. secondary_bedroom_cabinet:次卧衣柜/储物柜配置。 +15. shoe_cabinet:鞋柜需求或配置。 +16. bed_and_mattress:床及床垫配置。 +17.todo_item: 待办事项,通过分析对话内容,把所有待办的事, 都放到这个字段属性里 。 + +重要:每个产品维度(sofa、tea_table、dining_table、study_desk、tv_cabinet、cabinet、wine_cabinet、master_bedroom_cabinet、secondary_bedroom_cabinet、shoe_cabinet、bed_and_mattress)必须是一个JSON对象,包含以下10个维度: +1. preferenceStyleAndColor:偏好风格与颜色 +2. materialPreference:材质偏好 +3. customerFocusAreas:客户重点关注 +4. priceSensitivity:价格敏感度 +5. activityParticipation:活动参与度 +6. customerConcerns:客户疑虑点 +7. deliveryService:交付服务 +8. competitorFeedback:竞品反馈 +9. deliveryCycle:交付周期 +10. productAppearance:产品颜值 + +严格输出 JSON 格式,示例: +{ + "customer_type": "二次翻修", + "family_structure": "三口之家,一个小男孩", + "intention_products": "床、沙发、酒柜、厨房柜子", + "summary": "客户是二次翻修,对价格比较敏感,预算在10万以内", + "decoration_style": "总结偏好的装修/家居风格,风格类型,并推测主人相关信息", + "sofa": { + "preferenceStyleAndColor": "偏好风格与颜色", + "materialPreference": "材质偏好", + "customerFocusAreas": "客户重点关注", + "priceSensitivity": "价格敏感度", + "activityParticipation": "活动参与度", + "customerConcerns": "客户疑虑点", + "deliveryService": "交付服务", + "competitorFeedback": "竞品反馈", + "deliveryCycle": "交付周期", + "productAppearance": "产品颜值" + }, + "tea_table": { + "preferenceStyleAndColor": "偏好风格与颜色", + "materialPreference": "材质偏好", + "customerFocusAreas": "客户重点关注", + "priceSensitivity": "价格敏感度", + "activityParticipation": "活动参与度", + "customerConcerns": "客户疑虑点", + "deliveryService": "交付服务", + "competitorFeedback": "竞品反馈", + "deliveryCycle": "交付周期", + "productAppearance": "产品颜值" + }, + "dining_table": { + "preferenceStyleAndColor": "偏好风格与颜色", + "materialPreference": "材质偏好", + "customerFocusAreas": "客户重点关注", + "priceSensitivity": "价格敏感度", + "activityParticipation": "活动参与度", + "customerConcerns": "客户疑虑点", + "deliveryService": "交付服务", + "competitorFeedback": "竞品反馈", + "deliveryCycle": "交付周期", + "productAppearance": "产品颜值" + }, + "study_desk": { + "preferenceStyleAndColor": "偏好风格与颜色", + "materialPreference": "材质偏好", + "customerFocusAreas": "客户重点关注", + "priceSensitivity": "价格敏感度", + "activityParticipation": "活动参与度", + "customerConcerns": "客户疑虑点", + "deliveryService": "交付服务", + "competitorFeedback": "竞品反馈", + "deliveryCycle": "交付周期", + "productAppearance": "产品颜值" + }, + "tv_cabinet": { + "preferenceStyleAndColor": "偏好风格与颜色", + "materialPreference": "材质偏好", + "customerFocusAreas": "客户重点关注", + "priceSensitivity": "价格敏感度", + "activityParticipation": "活动参与度", + "customerConcerns": "客户疑虑点", + "deliveryService": "交付服务", + "competitorFeedback": "竞品反馈", + "deliveryCycle": "交付周期", + "productAppearance": "产品颜值" + }, + "cabinet": { + "preferenceStyleAndColor": "偏好风格与颜色", + "materialPreference": "材质偏好", + "customerFocusAreas": "客户重点关注", + "priceSensitivity": "价格敏感度", + "activityParticipation": "活动参与度", + "customerConcerns": "客户疑虑点", + "deliveryService": "交付服务", + "competitorFeedback": "竞品反馈", + "deliveryCycle": "交付周期", + "productAppearance": "产品颜值" + }, + "wine_cabinet": { + "preferenceStyleAndColor": "偏好风格与颜色", + "materialPreference": "材质偏好", + "customerFocusAreas": "客户重点关注", + "priceSensitivity": "价格敏感度", + "activityParticipation": "活动参与度", + "customerConcerns": "客户疑虑点", + "deliveryService": "交付服务", + "competitorFeedback": "竞品反馈", + "deliveryCycle": "交付周期", + "productAppearance": "产品颜值" + }, + "master_bedroom_cabinet": { + "preferenceStyleAndColor": "偏好风格与颜色", + "materialPreference": "材质偏好", + "customerFocusAreas": "客户重点关注", + "priceSensitivity": "价格敏感度", + "activityParticipation": "活动参与度", + "customerConcerns": "客户疑虑点", + "deliveryService": "交付服务", + "competitorFeedback": "竞品反馈", + "deliveryCycle": "交付周期", + "productAppearance": "产品颜值" + }, + "secondary_bedroom_cabinet": { + "preferenceStyleAndColor": "偏好风格与颜色", + "materialPreference": "材质偏好", + "customerFocusAreas": "客户重点关注", + "priceSensitivity": "价格敏感度", + "activityParticipation": "活动参与度", + "customerConcerns": "客户疑虑点", + "deliveryService": "交付服务", + "competitorFeedback": "竞品反馈", + "deliveryCycle": "交付周期", + "productAppearance": "产品颜值" + }, + "shoe_cabinet": { + "preferenceStyleAndColor": "偏好风格与颜色", + "materialPreference": "材质偏好", + "customerFocusAreas": "客户重点关注", + "priceSensitivity": "价格敏感度", + "activityParticipation": "活动参与度", + "customerConcerns": "客户疑虑点", + "deliveryService": "交付服务", + "competitorFeedback": "竞品反馈", + "deliveryCycle": "交付周期", + "productAppearance": "产品颜值" + }, + "bed_and_mattress": { + "preferenceStyleAndColor": "偏好风格与颜色", + "materialPreference": "材质偏好", + "customerFocusAreas": "客户重点关注", + "priceSensitivity": "价格敏感度", + "activityParticipation": "活动参与度", + "customerConcerns": "客户疑虑点", + "deliveryService": "交付服务", + "competitorFeedback": "竞品反馈", + "deliveryCycle": "交付周期", + "productAppearance": "产品颜值" + }, + "todo_item":[{ + "todo_detail": "待办事项详细说明" , + "todo_title":"待办概要" + },{ + "todo_detail": "待办事项详细说明" , + "todo_title":"待办概要" + }] +} + +注意: +1. 必须严格按照JSON格式输出,不要添加任何额外文字或markdown代码块标记。 +2. 每个产品维度必须是一个完整的JSON对象,包含上述10个维度。 +3. 如果录音文本中未提及某个产品,该产品的JSON对象中所有维度应填写"暂无信息"或基于语境合理推断。 +4. 所有字段值必须基于录音文本内容,合理推断,不能随意编造。 + +录音文本: +{RECORDING_TEXT} diff --git a/src/main/sql/todo_item.sql b/src/main/sql/todo_item.sql new file mode 100644 index 0000000..d653bcf --- /dev/null +++ b/src/main/sql/todo_item.sql @@ -0,0 +1,14 @@ +CREATE TABLE todo_item ( + id CHAR(36) NOT NULL COMMENT '主键,UUID', + parent_id CHAR(36) NULL COMMENT '父ID,UUID,用于树状结构关联', + status VARCHAR(50) NULL COMMENT '状态', + todo_title VARCHAR(200) NULL COMMENT '待办事项简称', + todo_detail TEXT NULL COMMENT '待办事项详情', + owner_name VARCHAR(100) NULL COMMENT '所属人姓名', + owner_phone VARCHAR(50) NULL COMMENT '所属人电话', + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (id), + KEY idx_todo_item_parent_id (parent_id) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '待办事项表'; +