refactor: Gitea 式目录改组,移除本分支 SPA 与杂项产物

将 model/service/handler/middleware 迁至 models/services/routers/api/modules/auth,并删除 frontend、embed_static、scripts 及误入库缓存/二进制。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-29 03:54:56 +08:00
parent 1414c71dec
commit 9fe299a45f
449 changed files with 0 additions and 52779 deletions

130
services/seo.go Normal file
View File

@@ -0,0 +1,130 @@
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 中的 ROOT_URL否则根据请求 Host 推断
func (s *ForumSettingsService) SitePublicBaseURL(requestOrigin string) string {
root := normalizeRootURL(s.getString(SettingOIDCRootURL, ""))
if root == "" {
root = normalizeRootURL(requestOrigin)
}
return strings.TrimRight(root, "/")
}
// AbsoluteURL 将相对路径拼成绝对 URL。
// base 为空或非 http(s) 时返回空串,避免邮件等场景出现无法点击的相对路径。
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(strings.TrimSpace(base), "/")
if base == "" || (!strings.HasPrefix(base, "http://") && !strings.HasPrefix(base, "https://")) {
return ""
}
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, p PermalinkConfig) string {
if boardID == 0 {
return "/"
}
return p.BoardPath(boardID)
}
// LegacyQueryBoardHome 旧版 query 形式(/?board=id仅用于 301 重定向
func LegacyQueryBoardHome(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:])
}