Files
jiang13-forum/service/seo.go

122 lines
2.6 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 service
import (
"net/url"
"regexp"
"strings"
"time"
"unicode/utf8"
"git.iioio.com/freefire/jiang13-forum/model"
)
var (
imgSrcRe = regexp.MustCompile(`(?i)<img[^>]+src=["']([^"']+)["']`)
)
// SitemapURL 站点地图条目
type SitemapURL struct {
Loc string
LastMod time.Time
ChangeFreq string
Priority string
}
// SitePublicBaseURL 公开站点根地址(无尾斜杠)
// 优先 OIDC / 配置中的 RootURL否则根据请求 Host 推断
func (s *ForumSettingsService) SitePublicBaseURL(cfgRoot, requestOrigin string) string {
root := normalizeRootURL(s.getString(SettingOIDCRootURL, ""))
if root == "" {
root = normalizeRootURL(cfgRoot)
}
if root == "" {
root = normalizeRootURL(requestOrigin)
}
return strings.TrimRight(root, "/")
}
// AbsoluteURL 将相对路径拼成绝对 URL
func AbsoluteURL(base, pathOrURL string) string {
pathOrURL = strings.TrimSpace(pathOrURL)
if pathOrURL == "" {
return ""
}
if strings.HasPrefix(pathOrURL, "http://") || strings.HasPrefix(pathOrURL, "https://") {
return pathOrURL
}
base = strings.TrimRight(base, "/")
if !strings.HasPrefix(pathOrURL, "/") {
pathOrURL = "/" + pathOrURL
}
return base + pathOrURL
}
// TruncateRunes 按 rune 截断并加省略号
func TruncateRunes(s string, max int) string {
s = strings.TrimSpace(s)
if max <= 0 || s == "" {
return s
}
if utf8.RuneCountInString(s) <= max {
return s
}
runes := []rune(s)
if max < 2 {
return string(runes[:max])
}
return string(runes[:max-1]) + "…"
}
// ExcerptFromHTML 从 HTML 生成摘要(剥离标签)
func ExcerptFromHTML(htmlContent string, maxRunes int) string {
plain := StripHTMLForSearch(htmlContent)
return TruncateRunes(plain, maxRunes)
}
// FirstImageURL 提取正文中第一张图片的 src
func FirstImageURL(htmlContent string) string {
m := imgSrcRe.FindStringSubmatch(htmlContent)
if len(m) < 2 {
return ""
}
src := strings.TrimSpace(m[1])
// 忽略 data: 内联图
if strings.HasPrefix(src, "data:") {
return ""
}
return src
}
// DisplayName 用户展示名
func DisplayName(u *model.User) string {
if u == nil {
return ""
}
if n := strings.TrimSpace(u.Nickname); n != "" {
return n
}
return strings.TrimSpace(u.Username)
}
// QueryBoardHome 板块首页相对路径
func QueryBoardHome(boardID uint) string {
if boardID == 0 {
return "/"
}
return "/?board=" + url.QueryEscape(itoaUint(boardID))
}
func itoaUint(n uint) string {
if n == 0 {
return "0"
}
var buf [20]byte
i := len(buf)
for n > 0 {
i--
buf[i] = byte('0' + n%10)
n /= 10
}
return string(buf[i:])
}