Files
Mycontent/soul-api/internal/handler/chapter_preview.go
Alex-larget ace61a7290 refactor: update API base URL and enhance read page functionality
- Changed API base URL from localhost:8080 to https://soulapi.quwanzhi.com for production.
- Added new read page configuration for "阅读详情" with specific query parameters.
- Implemented a fallback mechanism for read page UI to handle cases where cloud data is unavailable.
- Updated read page content and clipboard text formatting for better user experience.
- Removed deprecated gift sharing UI elements to streamline the interface.
2026-03-30 18:47:43 +08:00

122 lines
4.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package handler
import (
"encoding/json"
"html"
"regexp"
"strings"
"unicode/utf8"
"soul-api/internal/model"
"gorm.io/gorm"
)
var (
reHTMLScript = regexp.MustCompile(`(?is)<script[^>]*>.*?</script>`)
reHTMLStyle = regexp.MustCompile(`(?is)<style[^>]*>.*?</style>`)
reHTMLBr = regexp.MustCompile(`(?i)<\s*br\s*/?>`)
reHTMLPClose = regexp.MustCompile(`(?i)</\s*p\s*>`)
reHTMLTags = regexp.MustCompile(`<[^>]+>`)
)
// stripHTMLToPlainPreview 将正文 HTML 转为纯文本再截取,避免预览截在 <img 中间导致用户看到半截标签
func stripHTMLToPlainPreview(s string) string {
s = reHTMLScript.ReplaceAllString(s, " ")
s = reHTMLStyle.ReplaceAllString(s, " ")
s = reHTMLBr.ReplaceAllString(s, "\n")
s = reHTMLPClose.ReplaceAllString(s, "\n")
s = reHTMLTags.ReplaceAllString(s, "")
s = html.UnescapeString(s)
s = strings.NewReplacer("\r\n", "\n", "\r", "\n").Replace(s)
return strings.TrimSpace(s)
}
// chapterPreviewPercent 章节单独 preview_percent 覆盖全局 unpaid_preview_percent
func chapterPreviewPercent(db *gorm.DB, ch *model.Chapter) int {
g := getUnpaidPreviewPercent(db)
if ch != nil && ch.PreviewPercent != nil {
p := *ch.PreviewPercent
if p >= 1 && p <= 100 {
return p
}
}
return g
}
// previewContent 取内容的前 percent%(按纯文本 rune 计;原 HTML 先剥标签)——用于 H5 落地页等;小程序读章节接口应返回全文并由端上按渲染高度裁切。
func previewContent(content string, percent int) string {
work := content
if strings.Contains(content, "<") && strings.Contains(content, ">") {
work = stripHTMLToPlainPreview(content)
}
total := utf8.RuneCountInString(work)
if total == 0 {
return ""
}
if percent < 1 {
percent = 1
}
if percent > 100 {
percent = 100
}
limit := total * percent / 100
if limit < 100 {
limit = 100
}
const maxPreview = 500
if limit > maxPreview {
limit = maxPreview
}
if limit > total {
limit = total
}
runes := []rune(work)
return string(runes[:limit]) + "\n\n……"
}
func defaultReadPreviewUI() map[string]string {
return map[string]string{
"singlePageUnlockTitle": "解锁完整内容",
"singlePagePayButtonText": "支付 ¥{price} 解锁全文",
"singlePageExpandedHint": "预览页不能直接付款,务必先点底栏「前往小程序」。",
"payTapModalTitle": "解锁说明",
"payTapModalContent": "全文 ¥{price}。预览里无法完成支付:请先点屏幕底部「前往小程序」进入完整版,登录后再付款解锁。",
"fullUnlockTitle": "解锁完整内容",
"fullUnlockDesc": "可先上滑阅读预览;需要全文时,点下方「支付¥{price}」查看说明",
"fullLockedProgressText": "已阅读约 {percent}% ,购买后继续阅读",
"fullPaywallTip": "转发给需要的人,一起学习还能赚佣金",
"notLoginUnlockDesc": "已预览约 {percent}% 内容,登录并支付 ¥{price} 后阅读全文",
"notLoginPaywallTip": "分享给好友一起学习,还能赚取佣金",
"shareTipLine": "好友经你分享购买,你可获得约 90% 收益",
"momentsModalTitle": "分享到朋友圈",
"momentsModalContent": "已复制发圈文案(非分享给好友)。\n\n请点击右上角「···」→「分享到朋友圈」粘贴发布。",
"momentsClipboardFooter": "\n\n—— 以上为正文预览约 {percent}% ,搜「卡若创业派对」小程序阅读全文 ——",
"timelineTitleSuffix": "(预览{percent}%",
}
}
// mergeReadPreviewUI 合并 system_config.read_preview_uiJSON 对象)与默认文案;占位符 {percent}/{price} 由小程序替换
func mergeReadPreviewUI(db *gorm.DB) map[string]string {
out := defaultReadPreviewUI()
var row model.SystemConfig
if err := db.Where("config_key = ?", "read_preview_ui").First(&row).Error; err != nil || len(row.ConfigValue) == 0 {
return out
}
var raw map[string]interface{}
if err := json.Unmarshal(row.ConfigValue, &raw); err != nil {
return out
}
for k, v := range raw {
switch t := v.(type) {
case string:
if strings.TrimSpace(t) != "" {
out[k] = strings.TrimSpace(t)
}
default:
// 兼容数字等被误存:忽略非字符串
}
}
return out
}