package com.wok.supportbot.controller; import com.wok.supportbot.app.SuggestionGenerator; import com.wok.supportbot.cache.SuggestionCache; import com.wok.supportbot.service.ConversationService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.Map; /** * 会话管理控制器 * 提供会话列表、详情、消息、删除、导出等 API */ @RestController @Slf4j public class ConversationController { @Autowired private ConversationService conversationService; @Autowired private SuggestionCache suggestionCache; @Autowired private SuggestionGenerator suggestionGenerator; // ==================== 会话列表 ==================== /** * 获取会话列表(分页) * 仅限管理员和客服人员使用,SDK 端应调用 /ai/sdk/conversation/list * * @param page 页码(默认1) * @param size 每页大小(默认10) * @param keyword 搜索关键词(可选,按会话ID或消息内容模糊匹配) * @return 分页会话列表 */ @GetMapping("/conversation/list") @PreAuthorize("hasAnyRole('admin','cs_agent','viewer')") public ResponseEntity> listConversations( @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int size, @RequestParam(required = false) String keyword, @RequestParam(required = false) String accountId, @RequestParam(required = false) Long roleId, @RequestParam(required = false) String sortField, @RequestParam(required = false) String sortOrder) { try { Map result = conversationService.listConversations(page, size, keyword, accountId, roleId, sortField, sortOrder); Map data = new java.util.HashMap<>(); data.put("success", true); data.put("data", result.get("records")); data.put("total", result.get("total")); data.put("page", result.get("page")); data.put("size", result.get("size")); data.put("pages", result.get("pages")); return ResponseEntity.ok(data); } catch (Exception e) { return ResponseEntity.status(500).body(Map.of( "success", false, "message", "查询失败:" + e.getMessage() )); } } // ==================== 会话详情 ==================== /** * 截断会话:删除指定用户消息及其之后的全部消息(用于"编辑历史消息并重发")。 * 仅限管理员和客服操作,SDK 端应调用 /ai/sdk/conversation/{id}/truncate * * @param conversationId 会话ID * @param body { userTurn: 第几条用户消息(1-based) } * @return 逻辑删除的消息条数 */ @PostMapping("/conversation/{id}/truncate") @PreAuthorize("hasAnyRole('admin','cs_agent')") public ResponseEntity> truncateConversation( @PathVariable("id") String conversationId, @RequestBody Map body) { try { Object raw = body.get("userTurn"); int userTurn = raw instanceof Number number ? number.intValue() : Integer.parseInt(String.valueOf(raw)); int deleted = conversationService.truncateFromUserTurn(conversationId, userTurn); return ResponseEntity.ok(Map.of( "success", true, "deletedMessages", deleted )); } catch (Exception e) { return ResponseEntity.status(500).body(Map.of( "success", false, "message", "截断失败:" + e.getMessage() )); } } /** * 获取会话详情 * * @param conversationId 会话ID * @return 会话详情 */ @GetMapping("/conversation/{id}") @PreAuthorize("hasAnyRole('admin','cs_agent','viewer','kb_operator')") public ResponseEntity> getConversationDetail(@PathVariable("id") String conversationId) { try { Map detail = conversationService.getConversationDetail(conversationId); if ((int) detail.get("messageCount") == 0) { return ResponseEntity.status(404).body(Map.of( "success", false, "message", "会话不存在或无任何消息" )); } return ResponseEntity.ok(Map.of( "success", true, "data", detail )); } catch (Exception e) { return ResponseEntity.status(500).body(Map.of( "success", false, "message", "查询失败:" + e.getMessage() )); } } /** * 获取会话消息列表 * 仅限管理员和客服查看,SDK 端应调用 /ai/sdk/conversation/{id}/messages * * @param conversationId 会话ID * @return 消息列表 */ @GetMapping("/conversation/{id}/messages") @PreAuthorize("hasAnyRole('admin','cs_agent','viewer')") public ResponseEntity> getConversationMessages(@PathVariable("id") String conversationId) { try { List> messages = conversationService.getConversationMessages(conversationId); return ResponseEntity.ok(Map.of( "success", true, "data", messages, "total", messages.size() )); } catch (Exception e) { return ResponseEntity.status(500).body(Map.of( "success", false, "message", "查询失败:" + e.getMessage() )); } } /** * 获取会话的 AI 推荐问题列表(管理后台 ChatPanel 用,JwtAuthFilter 守卫)。 *

* 与 /ai/suggestions 和 /open-api/suggestions 共享同一 SuggestionCache 实例, * AI 主回复不再同步生成 suggestions,本接口按需异步生成。 * * @param conversationId 会话 ID(即 chatId) * @return { success: true, data: { suggestions: [...] } } */ @GetMapping("/conversation/{id}/suggestions") @PreAuthorize("hasAnyRole('admin','cs_agent','viewer')") public ResponseEntity> getConversationSuggestions(@PathVariable("id") String conversationId) { try { List suggestions = suggestionGenerator.generate(conversationId); return ResponseEntity.ok(Map.of( "success", true, "data", Map.of("suggestions", suggestions) )); } catch (Exception e) { log.error("获取会话建议问题失败: conversationId={}", conversationId, e); return ResponseEntity.ok(Map.of( "success", true, "data", Map.of("suggestions", List.of()) )); } } // ==================== 删除会话 ==================== /** * 删除会话(逻辑删除该会话下的所有消息) * 仅限管理员操作 * * @param conversationId 会话ID * @return 删除结果 */ @DeleteMapping("/conversation/{id}") @PreAuthorize("hasAnyRole('admin','cs_agent')") public ResponseEntity> deleteConversation(@PathVariable("id") String conversationId) { try { int count = conversationService.deleteConversation(conversationId); // 删除会话时同步清理推荐问题缓存 suggestionCache.evict(conversationId); return ResponseEntity.ok(Map.of( "success", true, "message", "会话删除成功", "deletedMessages", count )); } catch (Exception e) { return ResponseEntity.status(500).body(Map.of( "success", false, "message", "删除失败:" + e.getMessage() )); } } // ==================== 导出会话 ==================== /** * 导出会话记录为文本 * 仅限管理员和客服使用 * * @param conversationId 会话ID * @return 格式化后的会话文本 */ @GetMapping(value = "/conversation/{id}/export", produces = MediaType.TEXT_PLAIN_VALUE + ";charset=UTF-8") @PreAuthorize("hasAnyRole('admin','cs_agent','viewer')") public ResponseEntity exportConversation(@PathVariable("id") String conversationId) { try { String content = conversationService.exportConversation(conversationId); return ResponseEntity.ok() .header("Content-Disposition", "attachment; filename=conversation_" + conversationId + ".txt") .body(content); } catch (Exception e) { return ResponseEntity.status(500).body("导出失败:" + e.getMessage()); } } // ==================== 统计 ==================== /** * 获取会话统计信息 * 仅限管理员和客服使用 * * @return 统计信息 */ @GetMapping("/conversation/stats") @PreAuthorize("hasAnyRole('admin','cs_agent','viewer')") public ResponseEntity> getConversationStats() { try { Map stats = conversationService.getConversationStats(); return ResponseEntity.ok(Map.of( "success", true, "data", stats )); } catch (Exception e) { return ResponseEntity.status(500).body(Map.of( "success", false, "message", "查询统计失败:" + e.getMessage() )); } } }