package com.wok.supportbot.controller; import com.wok.supportbot.entity.FaqFeedbackLink; import com.wok.supportbot.entity.KnowledgeFaq; import com.wok.supportbot.service.FaqService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; 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; /** * FAQ 知识库管理控制器 * 提供 FAQ 的 CRUD、批量导入/导出、匹配统计等功能 */ @RestController @RequestMapping("/faq") @Slf4j public class FaqController { @Autowired private FaqService faqService; // ==================== 分页列表 ==================== /** * 分页查询 FAQ 列表 */ @GetMapping("/list") @PreAuthorize("hasAnyRole('admin','kb_operator')") public ResponseEntity> list( @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "20") int size, @RequestParam(required = false) String keyword, @RequestParam(required = false) Long categoryId, @RequestParam(required = false) String status) { try { Map data = faqService.list(page, size, keyword, categoryId, status); return ResponseEntity.ok(Map.of( "success", true, "message", "查询成功", "data", data )); } catch (Exception e) { log.error("FAQ 列表查询失败", e); return ResponseEntity.status(500).body(Map.of( "success", false, "message", "查询失败:" + e.getMessage() )); } } /** * 从反馈创建新 FAQ */ @PostMapping("/from-feedback") @PreAuthorize("hasAnyRole('admin','kb_operator')") public ResponseEntity> createFromFeedback(@RequestBody Map body) { try { Long feedbackId = body.get("feedbackId") != null ? Long.valueOf(body.get("feedbackId").toString()) : null; String question = body.get("question") != null ? body.get("question").toString() : null; String answer = body.get("answer") != null ? body.get("answer").toString() : null; Long categoryId = body.get("categoryId") != null ? Long.valueOf(body.get("categoryId").toString()) : null; Integer priority = body.get("priority") != null ? Integer.valueOf(body.get("priority").toString()) : 0; String source = body.get("source") != null ? body.get("source").toString() : "feedback"; String originalQuestion = body.get("originalQuestion") != null ? body.get("originalQuestion").toString() : question; String originalAnswer = body.get("originalAnswer") != null ? body.get("originalAnswer").toString() : answer; Long operatorId = body.get("operatorId") != null ? Long.valueOf(body.get("operatorId").toString()) : null; String remark = body.get("remark") != null ? body.get("remark").toString() : null; if (feedbackId == null) { return ResponseEntity.status(400).body(Map.of("success", false, "message", "feedbackId 不能为空")); } if (question == null || question.isBlank()) { return ResponseEntity.status(400).body(Map.of("success", false, "message", "问题内容不能为空")); } if (answer == null || answer.isBlank()) { return ResponseEntity.status(400).body(Map.of("success", false, "message", "答案内容不能为空")); } KnowledgeFaq faq = new KnowledgeFaq(); faq.setQuestion(question.trim()); faq.setAnswer(answer.trim()); faq.setCategoryId(categoryId); faq.setPriority(priority); faq.setSource(source); faq.setCreatedFromFeedbackId(feedbackId); faq.setStatus("ENABLED"); KnowledgeFaq created = faqService.createFromFeedback(faq, feedbackId, originalQuestion, originalAnswer, operatorId, remark); return ResponseEntity.ok(Map.of( "success", true, "message", "从反馈创建 FAQ 成功", "data", created )); } catch (IllegalArgumentException e) { return ResponseEntity.status(400).body(Map.of("success", false, "message", e.getMessage())); } catch (Exception e) { log.error("从反馈创建 FAQ 失败", e); return ResponseEntity.status(500).body(Map.of("success", false, "message", "创建失败:" + e.getMessage())); } } /** * 将用户真实问题追加到现有 FAQ 的 similarQuestions */ @PostMapping("/{id}/append-from-feedback") @PreAuthorize("hasAnyRole('admin','kb_operator')") public ResponseEntity> appendFromFeedback( @PathVariable Long id, @RequestBody Map body) { try { Long feedbackId = body.get("feedbackId") != null ? Long.valueOf(body.get("feedbackId").toString()) : null; String similarQuestion = body.get("similarQuestion") != null ? body.get("similarQuestion").toString() : null; String originalAnswer = body.get("originalAnswer") != null ? body.get("originalAnswer").toString() : null; Long operatorId = body.get("operatorId") != null ? Long.valueOf(body.get("operatorId").toString()) : null; if (feedbackId == null) { return ResponseEntity.status(400).body(Map.of("success", false, "message", "feedbackId 不能为空")); } if (similarQuestion == null || similarQuestion.isBlank()) { return ResponseEntity.status(400).body(Map.of("success", false, "message", "相似问题不能为空")); } String remark = body.get("remark") != null ? body.get("remark").toString() : null; KnowledgeFaq updated = faqService.appendSimilarFromFeedback( id, feedbackId, similarQuestion, originalAnswer, operatorId, remark); return ResponseEntity.ok(Map.of( "success", true, "message", "追加相似问题成功", "data", updated )); } catch (IllegalArgumentException e) { return ResponseEntity.status(400).body(Map.of("success", false, "message", e.getMessage())); } catch (Exception e) { log.error("追加相似问题失败: id={}", id, e); return ResponseEntity.status(500).body(Map.of("success", false, "message", "追加失败:" + e.getMessage())); } } /** * 基于反馈修改 FAQ 答案 */ @PostMapping("/{id}/edit-from-feedback") @PreAuthorize("hasAnyRole('admin','kb_operator')") public ResponseEntity> editFromFeedback( @PathVariable Long id, @RequestBody Map body) { try { Long feedbackId = body.get("feedbackId") != null ? Long.valueOf(body.get("feedbackId").toString()) : null; String answer = body.get("answer") != null ? body.get("answer").toString() : null; String originalQuestion = body.get("originalQuestion") != null ? body.get("originalQuestion").toString() : null; Long operatorId = body.get("operatorId") != null ? Long.valueOf(body.get("operatorId").toString()) : null; String remark = body.get("remark") != null ? body.get("remark").toString() : null; if (feedbackId == null) { return ResponseEntity.status(400).body(Map.of("success", false, "message", "feedbackId 不能为空")); } if (answer == null || answer.isBlank()) { return ResponseEntity.status(400).body(Map.of("success", false, "message", "答案内容不能为空")); } KnowledgeFaq updated = faqService.editAnswerFromFeedback( id, feedbackId, answer, originalQuestion, operatorId, remark); return ResponseEntity.ok(Map.of( "success", true, "message", "修改答案成功", "data", updated )); } catch (IllegalArgumentException e) { return ResponseEntity.status(400).body(Map.of("success", false, "message", e.getMessage())); } catch (Exception e) { log.error("基于反馈修改答案失败: id={}", id, e); return ResponseEntity.status(500).body(Map.of("success", false, "message", "修改失败:" + e.getMessage())); } } /** * 查询 FAQ 关联的反馈维护记录 */ @GetMapping("/{id}/feedback-links") @PreAuthorize("hasAnyRole('admin','kb_operator')") public ResponseEntity> getFeedbackLinks(@PathVariable Long id) { try { List links = faqService.getFeedbackLinksByFaqId(id); return ResponseEntity.ok(Map.of( "success", true, "message", "查询成功", "data", links )); } catch (Exception e) { log.error("查询 FAQ 反馈关联失败: id={}", id, e); return ResponseEntity.status(500).body(Map.of("success", false, "message", "查询失败:" + e.getMessage())); } } // ==================== 新增 ==================== /** * 新增 FAQ */ @PostMapping @PreAuthorize("hasAnyRole('admin','kb_operator')") public ResponseEntity> create(@RequestBody KnowledgeFaq faq) { try { if (faq.getQuestion() == null || faq.getQuestion().isBlank()) { return ResponseEntity.status(400).body(Map.of( "success", false, "message", "问题内容不能为空" )); } if (faq.getAnswer() == null || faq.getAnswer().isBlank()) { return ResponseEntity.status(400).body(Map.of( "success", false, "message", "答案内容不能为空" )); } KnowledgeFaq created = faqService.create(faq); return ResponseEntity.ok(Map.of( "success", true, "message", "创建成功", "data", created )); } catch (Exception e) { log.error("FAQ 创建失败", e); return ResponseEntity.status(500).body(Map.of( "success", false, "message", "创建失败:" + e.getMessage() )); } } // ==================== 修改 ==================== /** * 修改 FAQ */ @PutMapping("/{id}") @PreAuthorize("hasAnyRole('admin','kb_operator')") public ResponseEntity> update(@PathVariable Long id, @RequestBody KnowledgeFaq faq) { try { KnowledgeFaq updated = faqService.update(id, faq); return ResponseEntity.ok(Map.of( "success", true, "message", "更新成功", "data", updated )); } catch (IllegalArgumentException e) { return ResponseEntity.status(400).body(Map.of( "success", false, "message", e.getMessage() )); } catch (Exception e) { log.error("FAQ 更新失败: id={}", id, e); return ResponseEntity.status(500).body(Map.of( "success", false, "message", "更新失败:" + e.getMessage() )); } } // ==================== 删除 ==================== /** * 删除 FAQ(逻辑删除) */ @DeleteMapping("/{id}") @PreAuthorize("hasAnyRole('admin','kb_operator')") public ResponseEntity> delete(@PathVariable Long id) { try { faqService.delete(id); return ResponseEntity.ok(Map.of( "success", true, "message", "删除成功" )); } catch (IllegalArgumentException e) { return ResponseEntity.status(400).body(Map.of( "success", false, "message", e.getMessage() )); } catch (Exception e) { log.error("FAQ 删除失败: id={}", id, e); return ResponseEntity.status(500).body(Map.of( "success", false, "message", "删除失败:" + e.getMessage() )); } } // ==================== 启用/禁用 ==================== /** * 切换 FAQ 启用/禁用状态 */ @PutMapping("/{id}/toggle") @PreAuthorize("hasAnyRole('admin','kb_operator')") public ResponseEntity> toggleStatus( @PathVariable Long id, @RequestParam String status) { try { faqService.toggleStatus(id, status); return ResponseEntity.ok(Map.of( "success", true, "message", "状态切换成功" )); } catch (IllegalArgumentException e) { return ResponseEntity.status(400).body(Map.of( "success", false, "message", e.getMessage() )); } catch (Exception e) { log.error("FAQ 状态切换失败: id={}", id, e); return ResponseEntity.status(500).body(Map.of( "success", false, "message", "状态切换失败:" + e.getMessage() )); } } // ==================== 批量导入 ==================== /** * 批量导入 FAQ * 请求体格式: {"faqs": [{"question":"...", "answer":"...", "similarQuestions":"[...]", "category":"...", "priority":0}]} */ @PostMapping("/batch-import") @PreAuthorize("hasAnyRole('admin','kb_operator')") public ResponseEntity> batchImport(@RequestBody Map> body) { try { List faqs = body.get("faqs"); if (faqs == null || faqs.isEmpty()) { return ResponseEntity.status(400).body(Map.of( "success", false, "message", "导入数据不能为空" )); } int count = faqService.batchImport(faqs); return ResponseEntity.ok(Map.of( "success", true, "message", "批量导入成功,共导入 " + count + " 条", "data", Map.of("importedCount", count) )); } catch (Exception e) { log.error("FAQ 批量导入失败", e); return ResponseEntity.status(500).body(Map.of( "success", false, "message", "批量导入失败:" + e.getMessage() )); } } // ==================== 导出 ==================== /** * 导出所有启用的 FAQ(返回 JSON 列表,后续可扩展为 Excel) */ @GetMapping("/export") @PreAuthorize("hasAnyRole('admin','kb_operator')") public ResponseEntity> exportAll() { try { List faqs = faqService.exportAll(); return ResponseEntity.ok(Map.of( "success", true, "message", "导出成功", "data", faqs )); } catch (Exception e) { log.error("FAQ 导出失败", e); return ResponseEntity.status(500).body(Map.of( "success", false, "message", "导出失败:" + e.getMessage() )); } } // ==================== 统计 ==================== /** * 获取 FAQ 匹配统计信息 */ @GetMapping("/stats") @PreAuthorize("hasAnyRole('admin','kb_operator')") public ResponseEntity> getStats() { try { Map stats = faqService.getStats(); return ResponseEntity.ok(Map.of( "success", true, "message", "查询成功", "data", stats )); } catch (Exception e) { log.error("FAQ 统计查询失败", e); return ResponseEntity.status(500).body(Map.of( "success", false, "message", "统计查询失败:" + e.getMessage() )); } } // ==================== 手动重算向量 ==================== /** * 手动重新计算某条 FAQ 的向量嵌入 */ @PostMapping("/{id}/recompute-embedding") @PreAuthorize("hasAnyRole('admin','kb_operator')") public ResponseEntity> recomputeEmbedding(@PathVariable Long id) { try { faqService.recomputeEmbedding(id); return ResponseEntity.ok(Map.of( "success", true, "message", "向量重新计算完成" )); } catch (IllegalArgumentException e) { return ResponseEntity.status(400).body(Map.of( "success", false, "message", e.getMessage() )); } catch (Exception e) { log.error("FAQ 向量重算失败: id={}", id, e); return ResponseEntity.status(500).body(Map.of( "success", false, "message", "向量重算失败:" + e.getMessage() )); } } }