更新小程序首页精选推荐逻辑,调整展示的章节数据源为排名接口,优化展开功能以支持动态加载更多章节。修复图标组件的SVG映射,确保图标显示一致性。更新开发环境配置为本地地址,提升开发体验。
This commit is contained in:
@@ -50,7 +50,7 @@ var allChaptersSelectCols = []string{
|
||||
var chapterMetaCols = []string{
|
||||
"mid", "id", "part_id", "part_title", "chapter_id", "chapter_title",
|
||||
"section_title", "word_count", "is_free", "price", "sort_order", "status",
|
||||
"is_new", "edition_standard", "edition_premium", "hot_score", "created_at", "updated_at",
|
||||
"is_new", "edition_standard", "edition_premium", "hot_score", "preview_percent", "created_at", "updated_at",
|
||||
}
|
||||
|
||||
// allChaptersCache 内存缓存,减轻 DB 压力,30 秒 TTL
|
||||
@@ -621,12 +621,19 @@ func findChapterAndRespond(c *gin.Context, whereFn func(*gorm.DB) *gorm.DB) {
|
||||
userID := c.Query("userId")
|
||||
isPremium := ch.EditionPremium != nil && *ch.EditionPremium
|
||||
var returnContent string
|
||||
effectivePreviewPercent := 20 // 默认,小程序付费墙「已阅读X%」展示用
|
||||
if isFree {
|
||||
returnContent = ch.Content
|
||||
effectivePreviewPercent = 100
|
||||
} else if checkUserChapterAccess(db, userID, ch.ID, isPremium) {
|
||||
returnContent = ch.Content
|
||||
effectivePreviewPercent = 100
|
||||
} else {
|
||||
percent := getUnpaidPreviewPercent(db)
|
||||
if ch.PreviewPercent != nil && *ch.PreviewPercent > 0 {
|
||||
percent = *ch.PreviewPercent
|
||||
}
|
||||
effectivePreviewPercent = percent
|
||||
returnContent = previewContent(ch.Content, percent)
|
||||
}
|
||||
|
||||
@@ -635,15 +642,16 @@ func findChapterAndRespond(c *gin.Context, whereFn func(*gorm.DB) *gorm.DB) {
|
||||
chForResponse.Content = returnContent
|
||||
|
||||
out := gin.H{
|
||||
"success": true,
|
||||
"data": chForResponse,
|
||||
"content": returnContent,
|
||||
"chapterTitle": ch.ChapterTitle,
|
||||
"partTitle": ch.PartTitle,
|
||||
"id": ch.ID,
|
||||
"mid": ch.MID,
|
||||
"sectionTitle": ch.SectionTitle,
|
||||
"isFree": isFree,
|
||||
"success": true,
|
||||
"data": chForResponse,
|
||||
"content": returnContent,
|
||||
"chapterTitle": ch.ChapterTitle,
|
||||
"partTitle": ch.PartTitle,
|
||||
"id": ch.ID,
|
||||
"mid": ch.MID,
|
||||
"sectionTitle": ch.SectionTitle,
|
||||
"isFree": isFree,
|
||||
"previewPercent": effectivePreviewPercent, // 小程序付费墙「已阅读X%」展示用
|
||||
}
|
||||
// 文章详情内直接输出上一篇/下一篇,省去单独请求
|
||||
if list := getOrderedChapterList(); len(list) > 0 {
|
||||
@@ -808,8 +816,8 @@ func bookHotChaptersSorted(db *gorm.DB, limit int) []model.Chapter {
|
||||
}
|
||||
// 按阅读量降序、同量按 updated_at 降序
|
||||
type withSort struct {
|
||||
ch model.Chapter
|
||||
cnt int64
|
||||
ch model.Chapter
|
||||
cnt int64
|
||||
}
|
||||
withCnt := make([]withSort, 0, len(all))
|
||||
for _, c := range all {
|
||||
@@ -883,9 +891,53 @@ func BookRecommended(c *gin.Context) {
|
||||
if i < len(tags) {
|
||||
tag = tags[i]
|
||||
}
|
||||
// 与管理端内容排行榜 sectionListItem 字段一致,便于两端数据对齐
|
||||
out = append(out, gin.H{
|
||||
"id": s.ID,
|
||||
"mid": s.MID,
|
||||
"title": s.Title,
|
||||
"sectionTitle": s.Title, // 兼容小程序旧字段
|
||||
"partTitle": s.PartTitle,
|
||||
"chapterTitle": s.ChapterTitle,
|
||||
"tag": tag,
|
||||
"isFree": s.IsFree,
|
||||
"price": s.Price,
|
||||
"isNew": s.IsNew,
|
||||
"clickCount": s.ClickCount,
|
||||
"payCount": s.PayCount,
|
||||
"hotScore": s.HotScore,
|
||||
"isPinned": s.IsPinned,
|
||||
})
|
||||
}
|
||||
cache.Set(context.Background(), cache.KeyBookRecommended, out, cache.BookRelatedTTL)
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "data": out})
|
||||
}
|
||||
|
||||
// BookRanking GET /api/miniprogram/book/ranking 内容排行榜(与 recommended 同算法,支持 limit,供精选推荐展开用)
|
||||
func BookRanking(c *gin.Context) {
|
||||
limit := 50
|
||||
if l := c.Query("limit"); l != "" {
|
||||
if n, err := strconv.Atoi(l); err == nil && n > 0 && n <= 50 {
|
||||
limit = n
|
||||
}
|
||||
}
|
||||
sections, err := computeArticleRankingSections(database.DB())
|
||||
if err != nil || len(sections) == 0 {
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "data": []gin.H{}})
|
||||
return
|
||||
}
|
||||
if len(sections) < limit {
|
||||
limit = len(sections)
|
||||
}
|
||||
tags := []string{"热门", "推荐", "精选"}
|
||||
out := make([]gin.H, 0, limit)
|
||||
for i := 0; i < limit; i++ {
|
||||
s := sections[i]
|
||||
tag := tags[i%len(tags)]
|
||||
out = append(out, gin.H{
|
||||
"id": s.ID,
|
||||
"mid": s.MID,
|
||||
"title": s.Title,
|
||||
"sectionTitle": s.Title,
|
||||
"partTitle": s.PartTitle,
|
||||
"chapterTitle": s.ChapterTitle,
|
||||
@@ -893,9 +945,12 @@ func BookRecommended(c *gin.Context) {
|
||||
"isFree": s.IsFree,
|
||||
"price": s.Price,
|
||||
"isNew": s.IsNew,
|
||||
"clickCount": s.ClickCount,
|
||||
"payCount": s.PayCount,
|
||||
"hotScore": s.HotScore,
|
||||
"isPinned": s.IsPinned,
|
||||
})
|
||||
}
|
||||
cache.Set(context.Background(), cache.KeyBookRecommended, out, cache.BookRelatedTTL)
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "data": out})
|
||||
}
|
||||
|
||||
|
||||
@@ -700,6 +700,10 @@ func DBConfigPost(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
cache.InvalidateConfig()
|
||||
// 排名权重或置顶配置变更时,使精选推荐缓存失效,与管理端内容排行榜保持一致
|
||||
if body.Key == "article_ranking_weights" || body.Key == "pinned_section_ids" {
|
||||
cache.InvalidateBookCache()
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "message": "配置保存成功"})
|
||||
}
|
||||
|
||||
|
||||
@@ -377,6 +377,7 @@ func DBBookAction(c *gin.Context) {
|
||||
"chapterTitle": ch.ChapterTitle,
|
||||
"editionStandard": ch.EditionStandard,
|
||||
"editionPremium": ch.EditionPremium,
|
||||
"previewPercent": ch.PreviewPercent,
|
||||
},
|
||||
})
|
||||
return
|
||||
@@ -517,6 +518,7 @@ func DBBookAction(c *gin.Context) {
|
||||
TargetPartTitle string `json:"targetPartTitle"`
|
||||
TargetChapterTitle string `json:"targetChapterTitle"`
|
||||
ID string `json:"id"`
|
||||
NewID string `json:"newId"` // 修改章节 ID 时传入,后端会更新 id 列
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
Price *float64 `json:"price"`
|
||||
@@ -524,6 +526,7 @@ func DBBookAction(c *gin.Context) {
|
||||
IsNew *bool `json:"isNew"` // stitch_soul:标记最新新增
|
||||
EditionStandard *bool `json:"editionStandard"` // 是否属于普通版
|
||||
EditionPremium *bool `json:"editionPremium"` // 是否属于增值版
|
||||
PreviewPercent *int `json:"previewPercent"` // 未解锁显示前 N%,空则用全局
|
||||
PartID string `json:"partId"`
|
||||
PartTitle string `json:"partTitle"`
|
||||
ChapterID string `json:"chapterId"`
|
||||
@@ -684,6 +687,14 @@ func DBBookAction(c *gin.Context) {
|
||||
if body.HotScore != nil {
|
||||
updates["hot_score"] = *body.HotScore
|
||||
}
|
||||
if body.PreviewPercent != nil {
|
||||
p := *body.PreviewPercent
|
||||
if p <= 0 || p > 100 {
|
||||
updates["preview_percent"] = nil // 无效值则清空,用全局
|
||||
} else {
|
||||
updates["preview_percent"] = p
|
||||
}
|
||||
}
|
||||
if body.PartID != "" {
|
||||
updates["part_id"] = body.PartID
|
||||
}
|
||||
@@ -696,6 +707,9 @@ func DBBookAction(c *gin.Context) {
|
||||
if body.ChapterTitle != "" {
|
||||
updates["chapter_title"] = body.ChapterTitle
|
||||
}
|
||||
if body.NewID != "" && body.NewID != body.ID {
|
||||
updates["id"] = body.NewID
|
||||
}
|
||||
var existing model.Chapter
|
||||
err = db.Where("id = ?", body.ID).First(&existing).Error
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
@@ -741,6 +755,9 @@ func DBBookAction(c *gin.Context) {
|
||||
if body.IsNew != nil {
|
||||
ch.IsNew = body.IsNew
|
||||
}
|
||||
if body.PreviewPercent != nil && *body.PreviewPercent > 0 && *body.PreviewPercent <= 100 {
|
||||
ch.PreviewPercent = body.PreviewPercent
|
||||
}
|
||||
if err := db.Create(&ch).Error; err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()})
|
||||
return
|
||||
@@ -761,7 +778,12 @@ func DBBookAction(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()})
|
||||
return
|
||||
}
|
||||
cache.InvalidateChapterContentByID(body.ID)
|
||||
// id 变更后需按 mid 失效缓存(按 id 查会失败)
|
||||
if body.NewID != "" && body.NewID != body.ID {
|
||||
cache.InvalidateChapterContent(existing.MID)
|
||||
} else {
|
||||
cache.InvalidateChapterContentByID(body.ID)
|
||||
}
|
||||
cache.InvalidateBookParts()
|
||||
InvalidateChaptersByPartCache()
|
||||
cache.InvalidateBookCache()
|
||||
|
||||
Reference in New Issue
Block a user