package com.pharmacopoeia.controller; import com.fasterxml.jackson.databind.ObjectMapper; import com.pharmacopoeia.config.UnifiedResponse; import com.pharmacopoeia.dto.*; import com.pharmacopoeia.entity.QuickAsk; import com.pharmacopoeia.service.AdminKnowledgeService; import com.pharmacopoeia.service.BrandRecommendService; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.*; @UnifiedResponse @RestController @RequestMapping("/api/v1/admin/knowledge") public class AdminKnowledgeController { private final AdminKnowledgeService service; private final BrandRecommendService brandRecommendService; private final ObjectMapper mapper = new ObjectMapper(); public AdminKnowledgeController(AdminKnowledgeService service, BrandRecommendService brandRecommendService) { this.service = service; this.brandRecommendService = brandRecommendService; } // ==================== 药品管理 ==================== @GetMapping("/drugs") public ResponseEntity> listDrugs( @RequestParam(required = false) String keyword, @RequestParam(required = false) String category, @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "20") int pageSize) { return ResponseEntity.ok(service.listDrugs(keyword, category, page, pageSize)); } @GetMapping("/drugs/{drugId}") public ResponseEntity> getDrug(@PathVariable String drugId) { return ResponseEntity.ok(service.getDrug(drugId) .map(d -> Map.of("data", (Object) d)) .orElse(Map.of("message", "数据入库后可用", "drug_id", drugId))); } @PostMapping("/drugs") public ResponseEntity> createDrug(@RequestBody DrugCreateRequest req) { return ResponseEntity.ok(service.createDrug(objectToMap(req))); } @PutMapping("/drugs/{drugId}") public ResponseEntity> updateDrug(@PathVariable String drugId, @RequestBody DrugUpdateRequest req) { return ResponseEntity.ok(service.updateDrug(drugId, objectToMap(req))); } @DeleteMapping("/drugs/{drugId}") public ResponseEntity> deleteDrug(@PathVariable String drugId) { return ResponseEntity.ok(service.deleteDrug(drugId)); } @PostMapping("/drugs/import") public ResponseEntity> importDrugs(@RequestBody ImportRequest req) { int count = parseCount(req); return ResponseEntity.ok(Map.of( "message", "导入任务已提交", "total", count, "success", 0, "failed", 0, "note", "Phase 2 实现异步批量写入 + 向量化" )); } @PostMapping("/drugs/reindex") public ResponseEntity> reindexDrugs(@RequestBody ReindexRequest req) { return ResponseEntity.ok(Map.of( "message", "重建索引任务已提交", "collection", req.getCollection(), "drug_ids", req.getDrugIds(), "status", "pending", "note", "Phase 2 实现:重新 chunk → 向量化 → 写入 pgvector" )); } // ==================== 知识点管理 ==================== @GetMapping("/knowledge-points") public ResponseEntity> listKnowledgePoints( @RequestParam(required = false) String subject, @RequestParam(required = false) String chapterId, @RequestParam(required = false) Integer difficulty, @RequestParam(required = false) String frequency, @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "20") int pageSize) { return ResponseEntity.ok(service.listKnowledgePoints(subject, chapterId, difficulty, frequency, page, pageSize)); } @GetMapping("/knowledge-points/{pointId}") public ResponseEntity> getKnowledgePoint(@PathVariable String pointId) { return ResponseEntity.ok(service.getKnowledgePoint(pointId) .>map(kp -> Map.of( "point_id", kp.getPointId(), "subject", kp.getSubject(), "title", kp.getTitle(), "content", kp.getContent() )) .orElse(Map.of("message", "数据入库后可用", "point_id", pointId))); } @PostMapping("/knowledge-points") public ResponseEntity> createKnowledgePoint(@RequestBody KnowledgePointCreateRequest req) { return ResponseEntity.ok(service.createKnowledgePoint(objectToMap(req))); } @PutMapping("/knowledge-points/{pointId}") public ResponseEntity> updateKnowledgePoint(@PathVariable String pointId, @RequestBody KnowledgePointUpdateRequest req) { return ResponseEntity.ok(service.updateKnowledgePoint(pointId, objectToMap(req))); } @DeleteMapping("/knowledge-points/{pointId}") public ResponseEntity> deleteKnowledgePoint(@PathVariable String pointId) { return ResponseEntity.ok(service.deleteKnowledgePoint(pointId)); } @PostMapping("/knowledge-points/import") public ResponseEntity> importKnowledgePoints(@RequestBody ImportRequest req) { int count = parseCount(req); return ResponseEntity.ok(Map.of( "message", "导入任务已提交", "total", count, "success", 0, "failed", 0 )); } // ==================== 题库管理 ==================== @GetMapping("/questions") public ResponseEntity> listQuestions( @RequestParam(required = false) String subject, @RequestParam(required = false) String chapterId, @RequestParam(required = false) String questionType, @RequestParam(required = false) Integer difficulty, @RequestParam(required = false) Boolean audited, @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "20") int pageSize) { return ResponseEntity.ok(service.listQuestions(subject, chapterId, questionType, difficulty, audited, page, pageSize)); } @GetMapping("/questions/{questionId}") public ResponseEntity> getQuestion(@PathVariable String questionId) { return ResponseEntity.ok(service.getQuestion(questionId) .>map(q -> Map.of( "question_id", q.getQuestionId(), "content", q.getContent(), "answer", q.getAnswer() )) .orElse(Map.of("message", "数据入库后可用", "question_id", questionId))); } @PostMapping("/questions") public ResponseEntity> createQuestion(@RequestBody QuestionCreateRequest req) { return ResponseEntity.ok(service.createQuestion(objectToMap(req))); } @PutMapping("/questions/{questionId}") public ResponseEntity> updateQuestion(@PathVariable String questionId, @RequestBody QuestionUpdateRequest req) { return ResponseEntity.ok(service.updateQuestion(questionId, objectToMap(req))); } @DeleteMapping("/questions/{questionId}") public ResponseEntity> deleteQuestion(@PathVariable String questionId) { return ResponseEntity.ok(service.deleteQuestion(questionId)); } @PostMapping("/questions/batch-audit") public ResponseEntity> batchAudit(@RequestBody BatchAuditRequest req) { return ResponseEntity.ok(service.batchAudit(req.getQuestionIds(), req.getAudited())); } @PostMapping("/questions/import") public ResponseEntity> importQuestions(@RequestBody ImportRequest req) { int count = parseCount(req); return ResponseEntity.ok(Map.of( "message", "导入任务已提交", "total", count, "success", 0, "failed", 0 )); } @PostMapping("/questions/generate") public ResponseEntity> generateQuestions( @RequestParam String pointId, @RequestParam(defaultValue = "10") int count, @RequestParam(defaultValue = "A") String questionType) { return ResponseEntity.ok(Map.of( "message", "AI 出题任务已提交", "point_id", pointId, "count", count, "question_type", questionType, "status", "pending", "note", "Phase 2 实现:LLM 根据知识点生成题目" )); } // ==================== 快捷提问标签管理(管理端,需要 JWT) ==================== @GetMapping("/quick-asks") public ResponseEntity> listAllQuickAsks() { return ResponseEntity.ok(service.listAllQuickAsks()); } @PostMapping("/quick-asks") public ResponseEntity> createQuickAsk(@RequestBody Map body) { String label = (String) body.getOrDefault("label", ""); String question = (String) body.getOrDefault("question", label); int sortOrder = body.get("sort_order") instanceof Integer i ? i : 0; return ResponseEntity.ok(service.createQuickAsk(label, question, sortOrder)); } @PutMapping("/quick-asks/{id}") public ResponseEntity> updateQuickAsk( @PathVariable int id, @RequestBody Map body) { String label = body.containsKey("label") ? (String) body.get("label") : null; String question = body.containsKey("question") ? (String) body.get("question") : null; Integer sortOrder = body.get("sort_order") instanceof Integer i ? i : null; Boolean isActive = body.get("is_active") instanceof Boolean b ? b : null; return ResponseEntity.ok(service.updateQuickAsk(id, label, question, sortOrder, isActive)); } @DeleteMapping("/quick-asks/{id}") public ResponseEntity> deleteQuickAsk(@PathVariable int id) { return ResponseEntity.ok(service.deleteQuickAsk(id)); } @PutMapping("/quick-asks/reorder") public ResponseEntity> reorderQuickAsks(@RequestBody List> items) { return ResponseEntity.ok(service.reorderQuickAsks(items)); } @GetMapping("/quick-asks/audit-log") public ResponseEntity> listQuickAskAuditLog( @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "20") int pageSize) { return ResponseEntity.ok(service.listQuickAskAuditLog(page, pageSize)); } // ==================== 品牌管理 ==================== @GetMapping("/brands") public ResponseEntity> listBrands( @RequestParam(required = false) String keyword, @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "20") int pageSize) { return ResponseEntity.ok(brandRecommendService.listBrands(keyword, page, pageSize)); } @GetMapping("/brands/{id}") public ResponseEntity> getBrand(@PathVariable int id) { return ResponseEntity.ok(brandRecommendService.getBrand(id)); } @PostMapping("/brands") public ResponseEntity> createBrand(@RequestBody Map body) { return ResponseEntity.ok(brandRecommendService.createBrand(body)); } @PutMapping("/brands/{id}") public ResponseEntity> updateBrand( @PathVariable int id, @RequestBody Map body) { return ResponseEntity.ok(brandRecommendService.updateBrand(id, body)); } @DeleteMapping("/brands/{id}") public ResponseEntity> deleteBrand(@PathVariable int id) { return ResponseEntity.ok(brandRecommendService.deleteBrand(id)); } @PostMapping("/brands/import") public ResponseEntity> importBrands( @RequestBody Map body) { String csv = body.get("csv"); return ResponseEntity.ok(brandRecommendService.importBrandsCsv(csv)); } @GetMapping("/brands/export") public ResponseEntity> exportBrands() { String csv = brandRecommendService.exportBrandsCsv(); return ResponseEntity.ok(Map.of("csv", csv)); } @GetMapping("/brands/template") public ResponseEntity brandTemplate() { return ResponseEntity.ok("name,function_indication,usage_dosage,contraindication,ingredients,properties,specification,adverse_reactions,precautions,execution_standard,storage,jump_url,description,sort_order"); } // ==================== 品牌推荐规则管理 ==================== @GetMapping("/brand-recommend-rules") public ResponseEntity> listBrandRecommendRules( @RequestParam(required = false) String keyword, @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "20") int pageSize) { return ResponseEntity.ok(brandRecommendService.listRules(keyword, page, pageSize)); } @PostMapping("/brand-recommend-rules") public ResponseEntity> createBrandRecommendRule( @RequestBody Map body) { String keyword = (String) body.get("keyword"); int brandId = body.get("brand_id") instanceof Number n ? n.intValue() : 0; int tier = body.get("tier") instanceof Number n ? n.intValue() : 1; return ResponseEntity.ok(brandRecommendService.createRule(keyword, brandId, tier)); } @PutMapping("/brand-recommend-rules/{id}") public ResponseEntity> updateBrandRecommendRule( @PathVariable int id, @RequestBody Map body) { String keyword = (String) body.get("keyword"); Integer brandId = body.get("brand_id") instanceof Number n ? n.intValue() : null; Integer tier = body.get("tier") instanceof Number n ? n.intValue() : null; Boolean isActive = body.get("is_active") instanceof Boolean b ? b : null; return ResponseEntity.ok(brandRecommendService.updateRule(id, keyword, brandId, tier, isActive)); } @DeleteMapping("/brand-recommend-rules/{id}") public ResponseEntity> deleteBrandRecommendRule(@PathVariable int id) { return ResponseEntity.ok(brandRecommendService.deleteRule(id)); } @PatchMapping("/brand-recommend-rules/{id}/toggle") public ResponseEntity> toggleBrandRecommendRule(@PathVariable int id, @RequestBody(required = false) Map body) { Boolean isActive = body != null ? body.get("is_active") : null; return ResponseEntity.ok(brandRecommendService.toggleRule(id, isActive)); } @PostMapping("/brand-recommend-rules/import") public ResponseEntity> importBrandRecommendRules( @RequestBody Map body) { String csv = body.get("csv"); return ResponseEntity.ok(brandRecommendService.importRulesCsv(csv)); } @GetMapping("/brand-recommend-rules/export") public ResponseEntity> exportBrandRecommendRules() { String csv = brandRecommendService.exportRulesCsv(); return ResponseEntity.ok(Map.of("csv", csv)); } @GetMapping("/brand-recommend-rules/template") public ResponseEntity brandRuleTemplate() { return ResponseEntity.ok("keyword,brand_id,tier"); } // ==================== Helpers ==================== private int parseCount(ImportRequest req) { try { if ("json".equals(req.getFormat()) && req.getData() != null) { var items = mapper.readTree(req.getData()); return items.isArray() ? items.size() : 0; } if ("csv".equals(req.getFormat()) && req.getData() != null) { return req.getData().split("\n").length - 1; // rough count } } catch (Exception ignored) {} return 0; } private Map objectToMap(Object obj) { try { @SuppressWarnings("unchecked") Map map = mapper.convertValue(obj, Map.class); return map; } catch (Exception e) { return Map.of(); } } }