144 lines
3.6 KiB
Go
144 lines
3.6 KiB
Go
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()
|
|
}
|