package handler import ( "net/http" "github.com/gin-gonic/gin" "kefu-sys/server/internal/middleware" "kefu-sys/server/internal/model" ) type KnowledgeHandler struct{} func NewKnowledgeHandler() *KnowledgeHandler { return &KnowledgeHandler{} } func requireKnowledgeManager(c *gin.Context) bool { if middleware.HasAnyRole(c, "admin", "supervisor") { return true } c.JSON(http.StatusForbidden, gin.H{"code": 403, "message": "仅主管或管理员可管理知识库"}) return false } func hasKnowledgeCapacity(tenantID uint) (bool, error) { var tenant model.Tenant if err := model.DB.First(&tenant, tenantID).Error; err != nil { return false, err } if tenant.PlanID == nil { return true, nil } var plan model.Plan if err := model.DB.First(&plan, *tenant.PlanID).Error; err != nil { return false, err } if plan.KBLimit == 0 { return true, nil } var count int64 if err := model.DB.Model(&model.KnowledgeEntry{}).Where("tenant_id = ?", tenantID).Count(&count).Error; err != nil { return false, err } return count < int64(plan.KBLimit), nil } type CategoryListItem struct { model.Category EntryCount int64 `json:"entry_count"` } func (h *KnowledgeHandler) ListCategories(c *gin.Context) { tenantID := middleware.GetTenantID(c) var categories []model.Category model.DB.Where("tenant_id = ?", tenantID).Order("id asc").Find(&categories) type countRow struct { CategoryID uint Cnt int64 } var rows []countRow model.DB.Model(&model.KnowledgeEntry{}). Select("category_id, COUNT(*) as cnt"). Where("tenant_id = ?", tenantID). Group("category_id"). Scan(&rows) countMap := map[uint]int64{} var total int64 for _, r := range rows { countMap[r.CategoryID] = r.Cnt total += r.Cnt } items := make([]CategoryListItem, 0, len(categories)) for _, cat := range categories { items = append(items, CategoryListItem{Category: cat, EntryCount: countMap[cat.ID]}) } // total 放在额外字段便于侧栏「全部知识库」 middleware.JSON(c, gin.H{"list": items, "total_entries": total}) } func (h *KnowledgeHandler) CreateCategory(c *gin.Context) { if !requireKnowledgeManager(c) { return } var category model.Category if err := c.ShouldBindJSON(&category); err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "参数错误"}) return } category.TenantID = middleware.GetTenantID(c) if err := model.DB.Create(&category).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "创建失败"}) return } middleware.JSON(c, category) } type EntryListItem struct { model.KnowledgeEntry CategoryName string `json:"category_name"` } func (h *KnowledgeHandler) ListEntries(c *gin.Context) { tenantID := middleware.GetTenantID(c) page, pageSize := middleware.GetPageParams(c) categoryID := c.Query("category_id") search := c.Query("search") status := c.Query("status") sortBy := c.Query("sort") // usage | updated(default) var entries []model.KnowledgeEntry var total int64 query := model.DB.Where("tenant_id = ?", tenantID) if categoryID != "" { query = query.Where("category_id = ?", categoryID) } if search != "" { query = query.Where("title LIKE ? OR content LIKE ?", "%"+search+"%", "%"+search+"%") } if status != "" { query = query.Where("status = ?", status) } query.Model(&model.KnowledgeEntry{}).Count(&total) order := "updated_at desc" if sortBy == "usage" { order = "usage_count desc, updated_at desc" } else if sortBy == "title" { order = "title asc" } query.Order(order).Offset((page - 1) * pageSize).Limit(pageSize).Find(&entries) catIDs := make([]uint, 0, len(entries)) for _, e := range entries { catIDs = append(catIDs, e.CategoryID) } catMap := map[uint]string{} if len(catIDs) > 0 { seen := map[uint]struct{}{} unique := make([]uint, 0) for _, id := range catIDs { if _, ok := seen[id]; ok { continue } seen[id] = struct{}{} unique = append(unique, id) } var cats []model.Category model.DB.Where("id IN ?", unique).Find(&cats) for _, cat := range cats { catMap[cat.ID] = cat.Name } } items := make([]EntryListItem, 0, len(entries)) for _, e := range entries { items = append(items, EntryListItem{ KnowledgeEntry: e, CategoryName: catMap[e.CategoryID], }) } middleware.JSONList(c, items, total, page, pageSize) } func (h *KnowledgeHandler) CreateEntry(c *gin.Context) { if !requireKnowledgeManager(c) { return } var entry model.KnowledgeEntry if err := c.ShouldBindJSON(&entry); err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "参数错误"}) return } entry.TenantID = middleware.GetTenantID(c) available, err := hasKnowledgeCapacity(entry.TenantID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "校验知识库容量失败"}) return } if !available { c.JSON(http.StatusConflict, gin.H{"code": 409, "message": "当前套餐知识库容量已达上限"}) return } var category model.Category if err := model.DB.Where("id = ? AND tenant_id = ?", entry.CategoryID, entry.TenantID).First(&category).Error; err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "知识分类不存在"}) return } if err := model.DB.Create(&entry).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "创建失败"}) return } middleware.JSON(c, entry) } func (h *KnowledgeHandler) UpdateEntry(c *gin.Context) { if !requireKnowledgeManager(c) { return } tenantID := middleware.GetTenantID(c) id := c.Param("id") var entry model.KnowledgeEntry if err := model.DB.Where("id = ? AND tenant_id = ?", id, tenantID).First(&entry).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"code": 404, "message": "条目不存在"}) return } var updates map[string]interface{} if err := c.ShouldBindJSON(&updates); err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "参数错误"}) return } allowed := map[string]bool{"category_id": true, "title": true, "content": true, "status": true} for key := range updates { if !allowed[key] { delete(updates, key) } } if categoryID, exists := updates["category_id"]; exists { var category model.Category if err := model.DB.Where("id = ? AND tenant_id = ?", categoryID, tenantID).First(&category).Error; err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "知识分类不存在"}) return } } if len(updates) == 0 { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "没有可更新字段"}) return } if err := model.DB.Model(&entry).Updates(updates).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "更新失败"}) return } model.DB.First(&entry, entry.ID) middleware.JSON(c, entry) } func (h *KnowledgeHandler) DeleteEntry(c *gin.Context) { if !requireKnowledgeManager(c) { return } tenantID := middleware.GetTenantID(c) id := c.Param("id") result := model.DB.Where("id = ? AND tenant_id = ?", id, tenantID).Delete(&model.KnowledgeEntry{}) if result.RowsAffected == 0 { c.JSON(http.StatusNotFound, gin.H{"code": 404, "message": "条目不存在"}) return } middleware.JSON(c, gin.H{"message": "已删除"}) }