AdminKnowledgeController.java 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. package com.pharmacopoeia.controller;
  2. import com.fasterxml.jackson.databind.ObjectMapper;
  3. import com.pharmacopoeia.config.UnifiedResponse;
  4. import com.pharmacopoeia.dto.*;
  5. import com.pharmacopoeia.entity.QuickAsk;
  6. import com.pharmacopoeia.service.AdminKnowledgeService;
  7. import com.pharmacopoeia.service.BrandRecommendService;
  8. import org.springframework.http.ResponseEntity;
  9. import org.springframework.web.bind.annotation.*;
  10. import java.util.*;
  11. @UnifiedResponse
  12. @RestController
  13. @RequestMapping("/api/v1/admin/knowledge")
  14. public class AdminKnowledgeController {
  15. private final AdminKnowledgeService service;
  16. private final BrandRecommendService brandRecommendService;
  17. private final ObjectMapper mapper = new ObjectMapper();
  18. public AdminKnowledgeController(AdminKnowledgeService service,
  19. BrandRecommendService brandRecommendService) {
  20. this.service = service;
  21. this.brandRecommendService = brandRecommendService;
  22. }
  23. // ==================== 药品管理 ====================
  24. @GetMapping("/drugs")
  25. public ResponseEntity<Map<String, Object>> listDrugs(
  26. @RequestParam(required = false) String keyword,
  27. @RequestParam(required = false) String category,
  28. @RequestParam(defaultValue = "1") int page,
  29. @RequestParam(defaultValue = "20") int pageSize) {
  30. return ResponseEntity.ok(service.listDrugs(keyword, category, page, pageSize));
  31. }
  32. @GetMapping("/drugs/{drugId}")
  33. public ResponseEntity<Map<String, Object>> getDrug(@PathVariable String drugId) {
  34. return ResponseEntity.ok(service.getDrug(drugId)
  35. .map(d -> Map.of("data", (Object) d))
  36. .orElse(Map.of("message", "数据入库后可用", "drug_id", drugId)));
  37. }
  38. @PostMapping("/drugs")
  39. public ResponseEntity<Map<String, Object>> createDrug(@RequestBody DrugCreateRequest req) {
  40. return ResponseEntity.ok(service.createDrug(objectToMap(req)));
  41. }
  42. @PutMapping("/drugs/{drugId}")
  43. public ResponseEntity<Map<String, Object>> updateDrug(@PathVariable String drugId,
  44. @RequestBody DrugUpdateRequest req) {
  45. return ResponseEntity.ok(service.updateDrug(drugId, objectToMap(req)));
  46. }
  47. @DeleteMapping("/drugs/{drugId}")
  48. public ResponseEntity<Map<String, Object>> deleteDrug(@PathVariable String drugId) {
  49. return ResponseEntity.ok(service.deleteDrug(drugId));
  50. }
  51. @PostMapping("/drugs/import")
  52. public ResponseEntity<Map<String, Object>> importDrugs(@RequestBody ImportRequest req) {
  53. int count = parseCount(req);
  54. return ResponseEntity.ok(Map.of(
  55. "message", "导入任务已提交",
  56. "total", count,
  57. "success", 0,
  58. "failed", 0,
  59. "note", "Phase 2 实现异步批量写入 + 向量化"
  60. ));
  61. }
  62. @PostMapping("/drugs/reindex")
  63. public ResponseEntity<Map<String, Object>> reindexDrugs(@RequestBody ReindexRequest req) {
  64. return ResponseEntity.ok(Map.of(
  65. "message", "重建索引任务已提交",
  66. "collection", req.getCollection(),
  67. "drug_ids", req.getDrugIds(),
  68. "status", "pending",
  69. "note", "Phase 2 实现:重新 chunk → 向量化 → 写入 pgvector"
  70. ));
  71. }
  72. // ==================== 知识点管理 ====================
  73. @GetMapping("/knowledge-points")
  74. public ResponseEntity<Map<String, Object>> listKnowledgePoints(
  75. @RequestParam(required = false) String subject,
  76. @RequestParam(required = false) String chapterId,
  77. @RequestParam(required = false) Integer difficulty,
  78. @RequestParam(required = false) String frequency,
  79. @RequestParam(defaultValue = "1") int page,
  80. @RequestParam(defaultValue = "20") int pageSize) {
  81. return ResponseEntity.ok(service.listKnowledgePoints(subject, chapterId, difficulty, frequency, page, pageSize));
  82. }
  83. @GetMapping("/knowledge-points/{pointId}")
  84. public ResponseEntity<Map<String, Object>> getKnowledgePoint(@PathVariable String pointId) {
  85. return ResponseEntity.ok(service.getKnowledgePoint(pointId)
  86. .<Map<String, Object>>map(kp -> Map.of(
  87. "point_id", kp.getPointId(),
  88. "subject", kp.getSubject(),
  89. "title", kp.getTitle(),
  90. "content", kp.getContent()
  91. ))
  92. .orElse(Map.of("message", "数据入库后可用", "point_id", pointId)));
  93. }
  94. @PostMapping("/knowledge-points")
  95. public ResponseEntity<Map<String, Object>> createKnowledgePoint(@RequestBody KnowledgePointCreateRequest req) {
  96. return ResponseEntity.ok(service.createKnowledgePoint(objectToMap(req)));
  97. }
  98. @PutMapping("/knowledge-points/{pointId}")
  99. public ResponseEntity<Map<String, Object>> updateKnowledgePoint(@PathVariable String pointId,
  100. @RequestBody KnowledgePointUpdateRequest req) {
  101. return ResponseEntity.ok(service.updateKnowledgePoint(pointId, objectToMap(req)));
  102. }
  103. @DeleteMapping("/knowledge-points/{pointId}")
  104. public ResponseEntity<Map<String, Object>> deleteKnowledgePoint(@PathVariable String pointId) {
  105. return ResponseEntity.ok(service.deleteKnowledgePoint(pointId));
  106. }
  107. @PostMapping("/knowledge-points/import")
  108. public ResponseEntity<Map<String, Object>> importKnowledgePoints(@RequestBody ImportRequest req) {
  109. int count = parseCount(req);
  110. return ResponseEntity.ok(Map.of(
  111. "message", "导入任务已提交",
  112. "total", count,
  113. "success", 0,
  114. "failed", 0
  115. ));
  116. }
  117. // ==================== 题库管理 ====================
  118. @GetMapping("/questions")
  119. public ResponseEntity<Map<String, Object>> listQuestions(
  120. @RequestParam(required = false) String subject,
  121. @RequestParam(required = false) String chapterId,
  122. @RequestParam(required = false) String questionType,
  123. @RequestParam(required = false) Integer difficulty,
  124. @RequestParam(required = false) Boolean audited,
  125. @RequestParam(defaultValue = "1") int page,
  126. @RequestParam(defaultValue = "20") int pageSize) {
  127. return ResponseEntity.ok(service.listQuestions(subject, chapterId, questionType, difficulty, audited, page, pageSize));
  128. }
  129. @GetMapping("/questions/{questionId}")
  130. public ResponseEntity<Map<String, Object>> getQuestion(@PathVariable String questionId) {
  131. return ResponseEntity.ok(service.getQuestion(questionId)
  132. .<Map<String, Object>>map(q -> Map.of(
  133. "question_id", q.getQuestionId(),
  134. "content", q.getContent(),
  135. "answer", q.getAnswer()
  136. ))
  137. .orElse(Map.of("message", "数据入库后可用", "question_id", questionId)));
  138. }
  139. @PostMapping("/questions")
  140. public ResponseEntity<Map<String, Object>> createQuestion(@RequestBody QuestionCreateRequest req) {
  141. return ResponseEntity.ok(service.createQuestion(objectToMap(req)));
  142. }
  143. @PutMapping("/questions/{questionId}")
  144. public ResponseEntity<Map<String, Object>> updateQuestion(@PathVariable String questionId,
  145. @RequestBody QuestionUpdateRequest req) {
  146. return ResponseEntity.ok(service.updateQuestion(questionId, objectToMap(req)));
  147. }
  148. @DeleteMapping("/questions/{questionId}")
  149. public ResponseEntity<Map<String, Object>> deleteQuestion(@PathVariable String questionId) {
  150. return ResponseEntity.ok(service.deleteQuestion(questionId));
  151. }
  152. @PostMapping("/questions/batch-audit")
  153. public ResponseEntity<Map<String, Object>> batchAudit(@RequestBody BatchAuditRequest req) {
  154. return ResponseEntity.ok(service.batchAudit(req.getQuestionIds(), req.getAudited()));
  155. }
  156. @PostMapping("/questions/import")
  157. public ResponseEntity<Map<String, Object>> importQuestions(@RequestBody ImportRequest req) {
  158. int count = parseCount(req);
  159. return ResponseEntity.ok(Map.of(
  160. "message", "导入任务已提交",
  161. "total", count,
  162. "success", 0,
  163. "failed", 0
  164. ));
  165. }
  166. @PostMapping("/questions/generate")
  167. public ResponseEntity<Map<String, Object>> generateQuestions(
  168. @RequestParam String pointId,
  169. @RequestParam(defaultValue = "10") int count,
  170. @RequestParam(defaultValue = "A") String questionType) {
  171. return ResponseEntity.ok(Map.of(
  172. "message", "AI 出题任务已提交",
  173. "point_id", pointId,
  174. "count", count,
  175. "question_type", questionType,
  176. "status", "pending",
  177. "note", "Phase 2 实现:LLM 根据知识点生成题目"
  178. ));
  179. }
  180. // ==================== 快捷提问标签管理(管理端,需要 JWT) ====================
  181. @GetMapping("/quick-asks")
  182. public ResponseEntity<List<QuickAsk>> listAllQuickAsks() {
  183. return ResponseEntity.ok(service.listAllQuickAsks());
  184. }
  185. @PostMapping("/quick-asks")
  186. public ResponseEntity<Map<String, Object>> createQuickAsk(@RequestBody Map<String, Object> body) {
  187. String label = (String) body.getOrDefault("label", "");
  188. String question = (String) body.getOrDefault("question", label);
  189. int sortOrder = body.get("sort_order") instanceof Integer i ? i : 0;
  190. return ResponseEntity.ok(service.createQuickAsk(label, question, sortOrder));
  191. }
  192. @PutMapping("/quick-asks/{id}")
  193. public ResponseEntity<Map<String, Object>> updateQuickAsk(
  194. @PathVariable int id, @RequestBody Map<String, Object> body) {
  195. String label = body.containsKey("label") ? (String) body.get("label") : null;
  196. String question = body.containsKey("question") ? (String) body.get("question") : null;
  197. Integer sortOrder = body.get("sort_order") instanceof Integer i ? i : null;
  198. Boolean isActive = body.get("is_active") instanceof Boolean b ? b : null;
  199. return ResponseEntity.ok(service.updateQuickAsk(id, label, question, sortOrder, isActive));
  200. }
  201. @DeleteMapping("/quick-asks/{id}")
  202. public ResponseEntity<Map<String, Object>> deleteQuickAsk(@PathVariable int id) {
  203. return ResponseEntity.ok(service.deleteQuickAsk(id));
  204. }
  205. @PutMapping("/quick-asks/reorder")
  206. public ResponseEntity<Map<String, Object>> reorderQuickAsks(@RequestBody List<Map<String, Object>> items) {
  207. return ResponseEntity.ok(service.reorderQuickAsks(items));
  208. }
  209. @GetMapping("/quick-asks/audit-log")
  210. public ResponseEntity<Map<String, Object>> listQuickAskAuditLog(
  211. @RequestParam(defaultValue = "1") int page,
  212. @RequestParam(defaultValue = "20") int pageSize) {
  213. return ResponseEntity.ok(service.listQuickAskAuditLog(page, pageSize));
  214. }
  215. // ==================== 品牌管理 ====================
  216. @GetMapping("/brands")
  217. public ResponseEntity<Map<String, Object>> listBrands(
  218. @RequestParam(required = false) String keyword,
  219. @RequestParam(defaultValue = "1") int page,
  220. @RequestParam(defaultValue = "20") int pageSize) {
  221. return ResponseEntity.ok(brandRecommendService.listBrands(keyword, page, pageSize));
  222. }
  223. @GetMapping("/brands/{id}")
  224. public ResponseEntity<Map<String, Object>> getBrand(@PathVariable int id) {
  225. return ResponseEntity.ok(brandRecommendService.getBrand(id));
  226. }
  227. @PostMapping("/brands")
  228. public ResponseEntity<Map<String, Object>> createBrand(@RequestBody Map<String, Object> body) {
  229. return ResponseEntity.ok(brandRecommendService.createBrand(body));
  230. }
  231. @PutMapping("/brands/{id}")
  232. public ResponseEntity<Map<String, Object>> updateBrand(
  233. @PathVariable int id, @RequestBody Map<String, Object> body) {
  234. return ResponseEntity.ok(brandRecommendService.updateBrand(id, body));
  235. }
  236. @DeleteMapping("/brands/{id}")
  237. public ResponseEntity<Map<String, Object>> deleteBrand(@PathVariable int id) {
  238. return ResponseEntity.ok(brandRecommendService.deleteBrand(id));
  239. }
  240. @PostMapping("/brands/import")
  241. public ResponseEntity<Map<String, Object>> importBrands(
  242. @RequestBody Map<String, String> body) {
  243. String csv = body.get("csv");
  244. return ResponseEntity.ok(brandRecommendService.importBrandsCsv(csv));
  245. }
  246. @GetMapping("/brands/export")
  247. public ResponseEntity<Map<String, Object>> exportBrands() {
  248. String csv = brandRecommendService.exportBrandsCsv();
  249. return ResponseEntity.ok(Map.of("csv", csv));
  250. }
  251. @GetMapping("/brands/template")
  252. public ResponseEntity<String> brandTemplate() {
  253. return ResponseEntity.ok("name,function_indication,usage_dosage,contraindication,ingredients,properties,specification,adverse_reactions,precautions,execution_standard,storage,jump_url,description,sort_order");
  254. }
  255. // ==================== 品牌推荐规则管理 ====================
  256. @GetMapping("/brand-recommend-rules")
  257. public ResponseEntity<Map<String, Object>> listBrandRecommendRules(
  258. @RequestParam(required = false) String keyword,
  259. @RequestParam(defaultValue = "1") int page,
  260. @RequestParam(defaultValue = "20") int pageSize) {
  261. return ResponseEntity.ok(brandRecommendService.listRules(keyword, page, pageSize));
  262. }
  263. @PostMapping("/brand-recommend-rules")
  264. public ResponseEntity<Map<String, Object>> createBrandRecommendRule(
  265. @RequestBody Map<String, Object> body) {
  266. String keyword = (String) body.get("keyword");
  267. int brandId = body.get("brand_id") instanceof Number n ? n.intValue() : 0;
  268. int tier = body.get("tier") instanceof Number n ? n.intValue() : 1;
  269. return ResponseEntity.ok(brandRecommendService.createRule(keyword, brandId, tier));
  270. }
  271. @PutMapping("/brand-recommend-rules/{id}")
  272. public ResponseEntity<Map<String, Object>> updateBrandRecommendRule(
  273. @PathVariable int id,
  274. @RequestBody Map<String, Object> body) {
  275. String keyword = (String) body.get("keyword");
  276. Integer brandId = body.get("brand_id") instanceof Number n ? n.intValue() : null;
  277. Integer tier = body.get("tier") instanceof Number n ? n.intValue() : null;
  278. Boolean isActive = body.get("is_active") instanceof Boolean b ? b : null;
  279. return ResponseEntity.ok(brandRecommendService.updateRule(id, keyword, brandId, tier, isActive));
  280. }
  281. @DeleteMapping("/brand-recommend-rules/{id}")
  282. public ResponseEntity<Map<String, Object>> deleteBrandRecommendRule(@PathVariable int id) {
  283. return ResponseEntity.ok(brandRecommendService.deleteRule(id));
  284. }
  285. @PatchMapping("/brand-recommend-rules/{id}/toggle")
  286. public ResponseEntity<Map<String, Object>> toggleBrandRecommendRule(@PathVariable int id,
  287. @RequestBody(required = false) Map<String, Boolean> body) {
  288. Boolean isActive = body != null ? body.get("is_active") : null;
  289. return ResponseEntity.ok(brandRecommendService.toggleRule(id, isActive));
  290. }
  291. @PostMapping("/brand-recommend-rules/import")
  292. public ResponseEntity<Map<String, Object>> importBrandRecommendRules(
  293. @RequestBody Map<String, String> body) {
  294. String csv = body.get("csv");
  295. return ResponseEntity.ok(brandRecommendService.importRulesCsv(csv));
  296. }
  297. @GetMapping("/brand-recommend-rules/export")
  298. public ResponseEntity<Map<String, Object>> exportBrandRecommendRules() {
  299. String csv = brandRecommendService.exportRulesCsv();
  300. return ResponseEntity.ok(Map.of("csv", csv));
  301. }
  302. @GetMapping("/brand-recommend-rules/template")
  303. public ResponseEntity<String> brandRuleTemplate() {
  304. return ResponseEntity.ok("keyword,brand_id,tier");
  305. }
  306. // ==================== Helpers ====================
  307. private int parseCount(ImportRequest req) {
  308. try {
  309. if ("json".equals(req.getFormat()) && req.getData() != null) {
  310. var items = mapper.readTree(req.getData());
  311. return items.isArray() ? items.size() : 0;
  312. }
  313. if ("csv".equals(req.getFormat()) && req.getData() != null) {
  314. return req.getData().split("\n").length - 1; // rough count
  315. }
  316. } catch (Exception ignored) {}
  317. return 0;
  318. }
  319. private Map<String, Object> objectToMap(Object obj) {
  320. try {
  321. @SuppressWarnings("unchecked")
  322. Map<String, Object> map = mapper.convertValue(obj, Map.class);
  323. return map;
  324. } catch (Exception e) {
  325. return Map.of();
  326. }
  327. }
  328. }