package handler import ( "fmt" "net/http" "strconv" "strings" "github.com/gin-gonic/gin" "soul-api/internal/config" "soul-api/internal/database" "soul-api/internal/model" ) // AdminCKBPushContent POST /api/admin/ckb/push-content // 将选中章节推送到存客宝内容库(当前为内部标记 + 生成可拉取摘要;存客宝侧通过 GET /api/ckb/content 拉取) func AdminCKBPushContent(c *gin.Context) { var body struct { ChapterIDs []string `json:"chapterIds" binding:"required"` } if err := c.ShouldBindJSON(&body); err != nil || len(body.ChapterIDs) == 0 { c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "请选择要推送的章节"}) return } db := database.DB() var chapters []model.Chapter if err := db.Where("id IN ?", body.ChapterIDs).Find(&chapters).Error; err != nil { c.JSON(http.StatusOK, gin.H{"success": false, "error": "查询章节失败: " + err.Error()}) return } if len(chapters) == 0 { c.JSON(http.StatusOK, gin.H{"success": false, "error": "未找到指定章节"}) return } results := make([]gin.H, 0, len(chapters)) for _, ch := range chapters { status := "published" if ch.Status != nil { status = *ch.Status } if err := db.Model(&model.Chapter{}).Where("id = ?", ch.ID). Update("status", "published").Error; err != nil { results = append(results, gin.H{"id": ch.ID, "title": ch.SectionTitle, "status": "failed", "error": err.Error()}) continue } _ = status results = append(results, gin.H{"id": ch.ID, "title": ch.SectionTitle, "status": "success"}) } c.JSON(http.StatusOK, gin.H{ "success": true, "message": fmt.Sprintf("已推送 %d 条内容", len(results)), "results": results, }) } // CKBContentList GET /api/ckb/content // 供存客宝拉取内容列表(通过 X-API-Key 或 apiKey query 鉴权) func CKBContentList(c *gin.Context) { apiKey := c.GetHeader("X-API-Key") if apiKey == "" { apiKey = c.Query("apiKey") } expectedKey := getCkbLeadApiKey() if apiKey == "" || apiKey != expectedKey { c.JSON(http.StatusUnauthorized, gin.H{"success": false, "error": "无效的 API Key"}) return } page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "20")) if page < 1 { page = 1 } if pageSize < 1 || pageSize > 100 { pageSize = 20 } db := database.DB() var total int64 db.Model(&model.Chapter{}).Where("status = ? OR status IS NULL", "published").Count(&total) var chapters []model.Chapter db.Select("id, section_title, content, created_at, updated_at"). Where("status = ? OR status IS NULL", "published"). Order("sort_order ASC, mid ASC"). Offset((page - 1) * pageSize). Limit(pageSize). Find(&chapters) baseURL := "https://soulapi.quwanzhi.com" if cfg := config.Get(); cfg != nil && cfg.BaseURL != "" { baseURL = cfg.BaseURL } data := make([]gin.H, 0, len(chapters)) for _, ch := range chapters { summary := strings.TrimSpace(ch.Content) summary = stripHTMLTags(summary) if len([]rune(summary)) > 200 { summary = string([]rune(summary)[:200]) + "..." } data = append(data, gin.H{ "id": ch.ID, "title": ch.SectionTitle, "summary": summary, "url": baseURL + "/read/" + ch.ID, "createdAt": ch.CreatedAt, "updatedAt": ch.UpdatedAt, }) } c.JSON(http.StatusOK, gin.H{ "success": true, "data": data, "total": total, "page": page, "pageSize": pageSize, }) } func stripHTMLTags(s string) string { var out strings.Builder inTag := false for _, r := range s { if r == '<' { inTag = true continue } if r == '>' { inTag = false continue } if !inTag { out.WriteRune(r) } } return out.String() }