package handler import ( "encoding/json" "html" "regexp" "strings" "unicode/utf8" "soul-api/internal/model" "gorm.io/gorm" ) var ( reHTMLScript = regexp.MustCompile(`(?is)]*>.*?`) reHTMLStyle = regexp.MustCompile(`(?is)]*>.*?`) reHTMLBr = regexp.MustCompile(`(?i)<\s*br\s*/?>`) reHTMLPClose = regexp.MustCompile(`(?i)`) reHTMLTags = regexp.MustCompile(`<[^>]+>`) ) // stripHTMLToPlainPreview 将正文 HTML 转为纯文本再截取,避免预览截在 = 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": "", "fullLockedProgressText": "", "fullPaywallTip": "转发给需要的人,一起学习还能赚佣金", "notLoginUnlockDesc": "", "notLoginPaywallTip": "转发给需要的人,一起学习还能赚佣金", "shareTipLine": "转发给需要的人,一起学习还能赚佣金", "momentsModalTitle": "分享到朋友圈", "momentsModalContent": "已复制发圈文案(非分享给好友)。\n\n请点击右上角「···」→「分享到朋友圈」粘贴发布。", "momentsClipboardFooter": "\n\n—— 以上为正文预览约 {percent}% ,搜「卡若创业派对」小程序阅读全文 ——", "timelineImageUrl": "", } } // mergeReadPreviewUI 合并 system_config.read_preview_ui(JSON 对象)与默认文案;占位符 {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 }