feat: 支持章节通过 mid 进行访问,优化阅读跳转逻辑。新增章节数据结构,包含章节的 mid 信息,提升用户体验。更新 API 以支持通过 mid 查询章节内容,确保兼容性与灵活性。
This commit is contained in:
@@ -22,16 +22,40 @@ func BookAllChapters(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "data": list})
|
||||
}
|
||||
|
||||
// BookChapterByID GET /api/book/chapter/:id
|
||||
// 同时兼容小程序:将 content/chapterTitle/partTitle 等放到顶层,便于 miniprogram 直接 res.content
|
||||
// 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
|
||||
if err := database.DB().Where("id = ?", id).First(&ch).Error; err != nil {
|
||||
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
|
||||
@@ -39,14 +63,14 @@ func BookChapterByID(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()})
|
||||
return
|
||||
}
|
||||
// 返回格式:顶层携带 content/chapterTitle/partTitle 等,供小程序 read 页直接使用
|
||||
out := gin.H{
|
||||
"success": true,
|
||||
"data": ch,
|
||||
"content": ch.Content,
|
||||
"success": true,
|
||||
"data": ch,
|
||||
"content": ch.Content,
|
||||
"chapterTitle": ch.ChapterTitle,
|
||||
"partTitle": ch.PartTitle,
|
||||
"id": ch.ID,
|
||||
"partTitle": ch.PartTitle,
|
||||
"id": ch.ID,
|
||||
"mid": ch.MID,
|
||||
"sectionTitle": ch.SectionTitle,
|
||||
}
|
||||
if ch.IsFree != nil {
|
||||
@@ -193,7 +217,7 @@ func BookSearch(c *gin.Context) {
|
||||
matchType = "title"
|
||||
}
|
||||
results = append(results, gin.H{
|
||||
"id": ch.ID, "title": ch.SectionTitle, "part": ch.PartTitle, "chapter": ch.ChapterTitle,
|
||||
"id": ch.ID, "mid": ch.MID, "title": ch.SectionTitle, "part": ch.PartTitle, "chapter": ch.ChapterTitle,
|
||||
"isFree": ch.IsFree, "matchType": matchType,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -268,12 +268,21 @@ func UserPurchaseStatus(c *gin.Context) {
|
||||
c.JSON(http.StatusNotFound, gin.H{"success": false, "error": "用户不存在"})
|
||||
return
|
||||
}
|
||||
var orderRows []struct{ ProductID string }
|
||||
db.Raw("SELECT DISTINCT product_id FROM orders WHERE user_id = ? AND status = ? AND product_type = ?", userId, "paid", "section").Scan(&orderRows)
|
||||
var orderRows []struct {
|
||||
ProductID string
|
||||
MID int
|
||||
}
|
||||
db.Raw(`SELECT DISTINCT o.product_id, c.mid FROM orders o
|
||||
LEFT JOIN chapters c ON c.id = o.product_id
|
||||
WHERE o.user_id = ? AND o.status = ? AND o.product_type = ?`, userId, "paid", "section").Scan(&orderRows)
|
||||
purchasedSections := make([]string, 0, len(orderRows))
|
||||
sectionMidMap := make(map[string]int)
|
||||
for _, r := range orderRows {
|
||||
if r.ProductID != "" {
|
||||
purchasedSections = append(purchasedSections, r.ProductID)
|
||||
if r.MID > 0 {
|
||||
sectionMidMap[r.ProductID] = r.MID
|
||||
}
|
||||
}
|
||||
}
|
||||
// 匹配次数配额:纯计算(订单 + match_records)
|
||||
@@ -290,6 +299,7 @@ func UserPurchaseStatus(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "data": gin.H{
|
||||
"hasFullBook": user.HasFullBook != nil && *user.HasFullBook,
|
||||
"purchasedSections": purchasedSections,
|
||||
"sectionMidMap": sectionMidMap,
|
||||
"purchasedCount": len(purchasedSections),
|
||||
"matchCount": matchQuota.PurchasedTotal,
|
||||
"matchQuota": gin.H{
|
||||
|
||||
@@ -2,9 +2,10 @@ package model
|
||||
|
||||
import "time"
|
||||
|
||||
// Chapter 对应表 chapters(与 Prisma 一致),JSON 小写驼峰
|
||||
// Chapter 对应表 chapters(mid 为自增主键,id 保留业务标识如 1.1、preface)
|
||||
type Chapter struct {
|
||||
ID string `gorm:"column:id;primaryKey;size:20" json:"id"`
|
||||
MID int `gorm:"column:mid;primaryKey;autoIncrement" json:"mid"`
|
||||
ID string `gorm:"column:id;size:20;uniqueIndex" json:"id"`
|
||||
PartID string `gorm:"column:part_id;size:20" json:"partId"`
|
||||
PartTitle string `gorm:"column:part_title;size:100" json:"partTitle"`
|
||||
ChapterID string `gorm:"column:chapter_id;size:20" json:"chapterId"`
|
||||
|
||||
@@ -75,6 +75,7 @@ func Setup(cfg *config.Config) *gin.Engine {
|
||||
// ----- 书籍/章节 -----
|
||||
api.GET("/book/all-chapters", handler.BookAllChapters)
|
||||
api.GET("/book/chapter/:id", handler.BookChapterByID)
|
||||
api.GET("/book/chapter/by-mid/:mid", handler.BookChapterByMID)
|
||||
api.GET("/book/chapters", handler.BookChapters)
|
||||
api.POST("/book/chapters", handler.BookChapters)
|
||||
api.PUT("/book/chapters", handler.BookChapters)
|
||||
@@ -218,6 +219,7 @@ func Setup(cfg *config.Config) *gin.Engine {
|
||||
miniprogram.GET("/qrcode/image", handler.MiniprogramQrcodeImage)
|
||||
miniprogram.GET("/book/all-chapters", handler.BookAllChapters)
|
||||
miniprogram.GET("/book/chapter/:id", handler.BookChapterByID)
|
||||
miniprogram.GET("/book/chapter/by-mid/:mid", handler.BookChapterByMID)
|
||||
miniprogram.GET("/book/hot", handler.BookHot)
|
||||
miniprogram.GET("/book/search", handler.BookSearch)
|
||||
miniprogram.GET("/book/stats", handler.BookStats)
|
||||
|
||||
Reference in New Issue
Block a user