You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
432 lines
18 KiB
432 lines
18 KiB
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<Map<String, Object>> 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<String, Object> 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<Map<String, Object>> createFromFeedback(@RequestBody Map<String, Object> 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<Map<String, Object>> appendFromFeedback(
|
|
@PathVariable Long id,
|
|
@RequestBody Map<String, Object> 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<Map<String, Object>> editFromFeedback(
|
|
@PathVariable Long id,
|
|
@RequestBody Map<String, Object> 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<Map<String, Object>> getFeedbackLinks(@PathVariable Long id) {
|
|
try {
|
|
List<FaqFeedbackLink> 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<Map<String, Object>> 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<Map<String, Object>> 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<Map<String, Object>> 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<Map<String, Object>> 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<Map<String, Object>> batchImport(@RequestBody Map<String, List<KnowledgeFaq>> body) {
|
|
try {
|
|
List<KnowledgeFaq> 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<Map<String, Object>> exportAll() {
|
|
try {
|
|
List<KnowledgeFaq> 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<Map<String, Object>> getStats() {
|
|
try {
|
|
Map<String, Object> 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<Map<String, Object>> 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()
|
|
));
|
|
}
|
|
}
|
|
}
|