package handler import ( "net/http" "strconv" "strings" "soul-api/internal/database" "soul-api/internal/model" "github.com/gin-gonic/gin" "gorm.io/gorm" ) // BookAllChapters GET /api/book/all-chapters 返回所有章节(列表,来自 chapters 表) func BookAllChapters(c *gin.Context) { var list []model.Chapter if err := database.DB().Order("sort_order ASC, id ASC").Find(&list).Error; err != nil { c.JSON(http.StatusOK, gin.H{"success": true, "data": []interface{}{}}) return } c.JSON(http.StatusOK, gin.H{"success": true, "data": list}) } // BookChapterByID GET /api/book/chapter/:id 按业务 id 查询(兼容旧链接) func BookChapterByID(c *gin.Context) { id := c.Param("id") if id == "" { c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "缺少 id"}) return } findChapterAndRespond(c, func(db *gorm.DB) *gorm.DB { return db.Where("id = ?", id) }) } // BookChapterByMID GET /api/book/chapter/by-mid/:mid 按自增主键 mid 查询(新链接推荐) func BookChapterByMID(c *gin.Context) { midStr := c.Param("mid") if midStr == "" { c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "缺少 mid"}) return } mid, err := strconv.Atoi(midStr) if err != nil || mid < 1 { c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "mid 必须为正整数"}) return } findChapterAndRespond(c, func(db *gorm.DB) *gorm.DB { return db.Where("mid = ?", mid) }) } // findChapterAndRespond 按条件查章节并返回统一格式 func findChapterAndRespond(c *gin.Context, whereFn func(*gorm.DB) *gorm.DB) { var ch model.Chapter db := database.DB() if err := whereFn(db).First(&ch).Error; err != nil { if err == gorm.ErrRecordNotFound { c.JSON(http.StatusNotFound, gin.H{"success": false, "error": "章节不存在"}) return } c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()}) return } out := gin.H{ "success": true, "data": ch, "content": ch.Content, "chapterTitle": ch.ChapterTitle, "partTitle": ch.PartTitle, "id": ch.ID, "mid": ch.MID, "sectionTitle": ch.SectionTitle, } if ch.IsFree != nil { out["isFree"] = *ch.IsFree } if ch.Price != nil { out["price"] = *ch.Price } c.JSON(http.StatusOK, out) } // BookChapters GET/POST/PUT/DELETE /api/book/chapters(与 app/api/book/chapters 一致,用 GORM) func BookChapters(c *gin.Context) { db := database.DB() switch c.Request.Method { case http.MethodGet: partId := c.Query("partId") status := c.Query("status") if status == "" { status = "published" } page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "100")) if page < 1 { page = 1 } if pageSize < 1 || pageSize > 500 { pageSize = 100 } q := db.Model(&model.Chapter{}) if partId != "" { q = q.Where("part_id = ?", partId) } if status != "" && status != "all" { q = q.Where("status = ?", status) } var total int64 q.Count(&total) var list []model.Chapter q.Order("sort_order ASC, id ASC").Offset((page - 1) * pageSize).Limit(pageSize).Find(&list) totalPages := int(total) / pageSize if int(total)%pageSize > 0 { totalPages++ } c.JSON(http.StatusOK, gin.H{ "success": true, "data": gin.H{ "list": list, "total": total, "page": page, "pageSize": pageSize, "totalPages": totalPages, }, }) return case http.MethodPost: var body model.Chapter if err := c.ShouldBindJSON(&body); err != nil { c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "请求体无效"}) return } if body.ID == "" || body.PartID == "" || body.ChapterID == "" { c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "缺少必要字段 id/partId/chapterId"}) return } if err := db.Create(&body).Error; err != nil { c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"success": true, "data": body}) return case http.MethodPut: var body model.Chapter if err := c.ShouldBindJSON(&body); err != nil || body.ID == "" { c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "缺少 id"}) return } if err := db.Model(&model.Chapter{}).Where("id = ?", body.ID).Updates(map[string]interface{}{ "part_title": body.PartTitle, "chapter_title": body.ChapterTitle, "section_title": body.SectionTitle, "content": body.Content, "word_count": body.WordCount, "is_free": body.IsFree, "price": body.Price, "sort_order": body.SortOrder, "status": body.Status, }).Error; err != nil { c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"success": true}) return case http.MethodDelete: id := c.Query("id") if id == "" { c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "缺少 id"}) return } if err := db.Where("id = ?", id).Delete(&model.Chapter{}).Error; err != nil { c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"success": true}) return } c.JSON(http.StatusOK, gin.H{"success": false, "error": "不支持的请求方法"}) } // BookHot GET /api/book/hot func BookHot(c *gin.Context) { var list []model.Chapter database.DB().Order("sort_order ASC, id ASC").Limit(10).Find(&list) c.JSON(http.StatusOK, gin.H{"success": true, "data": list}) } // BookLatestChapters GET /api/book/latest-chapters func BookLatestChapters(c *gin.Context) { var list []model.Chapter database.DB().Order("updated_at DESC, id ASC").Limit(20).Find(&list) c.JSON(http.StatusOK, gin.H{"success": true, "data": list}) } func escapeLikeBook(s string) string { s = strings.ReplaceAll(s, "\\", "\\\\") s = strings.ReplaceAll(s, "%", "\\%") s = strings.ReplaceAll(s, "_", "\\_") return s } // BookSearch GET /api/book/search?q= 章节搜索(与 /api/search 逻辑一致) func BookSearch(c *gin.Context) { q := strings.TrimSpace(c.Query("q")) if q == "" { c.JSON(http.StatusOK, gin.H{"success": true, "results": []interface{}{}, "total": 0, "keyword": ""}) return } pattern := "%" + escapeLikeBook(q) + "%" var list []model.Chapter err := database.DB().Model(&model.Chapter{}). Where("section_title LIKE ? OR content LIKE ?", pattern, pattern). Order("sort_order ASC, id ASC"). Limit(20). Find(&list).Error if err != nil { c.JSON(http.StatusOK, gin.H{"success": true, "results": []interface{}{}, "total": 0, "keyword": q}) return } lowerQ := strings.ToLower(q) results := make([]gin.H, 0, len(list)) for _, ch := range list { matchType := "content" if strings.Contains(strings.ToLower(ch.SectionTitle), lowerQ) { matchType = "title" } results = append(results, gin.H{ "id": ch.ID, "mid": ch.MID, "title": ch.SectionTitle, "part": ch.PartTitle, "chapter": ch.ChapterTitle, "isFree": ch.IsFree, "matchType": matchType, }) } c.JSON(http.StatusOK, gin.H{"success": true, "results": results, "total": len(results), "keyword": q}) } // BookStats GET /api/book/stats func BookStats(c *gin.Context) { var total int64 database.DB().Model(&model.Chapter{}).Count(&total) c.JSON(http.StatusOK, gin.H{"success": true, "data": gin.H{"totalChapters": total}}) } // BookSync GET/POST /api/book/sync func BookSync(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"success": true, "message": "同步由 DB 维护"}) }