移除旧版 HTML 模板与兼容层,并完善私信、举报、媒体存储与 SEO。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-03 16:37:11 +08:00
parent 060b7707cb
commit 48db333272
121 changed files with 11147 additions and 3225 deletions

140
service/permalink.go Normal file
View File

@@ -0,0 +1,140 @@
package service
import (
"fmt"
"regexp"
"strconv"
"strings"
)
const (
SettingPermalinkEnabled = "permalink_enabled"
SettingPermalinkExt = "permalink_ext"
DefaultPermalinkExt = "html"
)
var (
permalinkExtRe = regexp.MustCompile(`(?i)^[a-z0-9]{1,16}$`)
// /post/123 或 /post/123.html
postPermalinkRe = regexp.MustCompile(`^/post/(\d+)(?:\.([A-Za-z0-9]{1,16}))?/?$`)
userPermalinkRe = regexp.MustCompile(`^/user/(\d+)(?:\.([A-Za-z0-9]{1,16}))?/?$`)
)
// PermalinkConfig 伪静态(固定链接)配置
type PermalinkConfig struct {
Enabled bool `json:"permalink_enabled"`
Ext string `json:"permalink_ext"` // 不含点,如 html / htm
}
// NormalizePermalinkExt 规范化后缀:去点、小写、仅字母数字
func NormalizePermalinkExt(raw string) (string, bool) {
ext := strings.TrimSpace(raw)
ext = strings.TrimPrefix(ext, ".")
ext = strings.ToLower(ext)
if ext == "" {
ext = DefaultPermalinkExt
}
if !permalinkExtRe.MatchString(ext) {
return "", false
}
return ext, true
}
// Permalink 读取伪静态配置
func (s *ForumSettingsService) Permalink() PermalinkConfig {
ext, ok := NormalizePermalinkExt(s.getString(SettingPermalinkExt, DefaultPermalinkExt))
if !ok {
ext = DefaultPermalinkExt
}
return PermalinkConfig{
Enabled: s.getString(SettingPermalinkEnabled, "0") == "1",
Ext: ext,
}
}
// Suffix 返回带点后缀(未启用时为空)
func (p PermalinkConfig) Suffix() string {
if !p.Enabled {
return ""
}
ext, ok := NormalizePermalinkExt(p.Ext)
if !ok {
ext = DefaultPermalinkExt
}
return "." + ext
}
// PostPath 帖子规范路径
func (p PermalinkConfig) PostPath(id uint) string {
return fmt.Sprintf("/post/%d%s", id, p.Suffix())
}
// UserPath 用户规范路径
func (p PermalinkConfig) UserPath(id uint) string {
return fmt.Sprintf("/user/%d%s", id, p.Suffix())
}
// PermalinkMatch 路径解析结果
type PermalinkMatch struct {
ID uint
Ext string // 请求里的后缀(无点);无后缀为空
Canonical string // 当前配置下的规范路径
OK bool
}
// MatchPostPath 解析帖子公开路径(不含 /edit
func (p PermalinkConfig) MatchPostPath(path string) PermalinkMatch {
m := postPermalinkRe.FindStringSubmatch(path)
if len(m) < 2 {
return PermalinkMatch{}
}
id64, err := strconv.ParseUint(m[1], 10, 64)
if err != nil || id64 == 0 {
return PermalinkMatch{}
}
ext := ""
if len(m) > 2 {
ext = strings.ToLower(m[2])
}
id := uint(id64)
return PermalinkMatch{
ID: id,
Ext: ext,
Canonical: p.PostPath(id),
OK: true,
}
}
// MatchUserPath 解析用户公开路径
func (p PermalinkConfig) MatchUserPath(path string) PermalinkMatch {
m := userPermalinkRe.FindStringSubmatch(path)
if len(m) < 2 {
return PermalinkMatch{}
}
id64, err := strconv.ParseUint(m[1], 10, 64)
if err != nil || id64 == 0 {
return PermalinkMatch{}
}
ext := ""
if len(m) > 2 {
ext = strings.ToLower(m[2])
}
id := uint(id64)
return PermalinkMatch{
ID: id,
Ext: ext,
Canonical: p.UserPath(id),
OK: true,
}
}
// NeedsCanonicalRedirect 当前请求路径是否应 301 到规范 URL
func (m PermalinkMatch) NeedsCanonicalRedirect(requestPath string) bool {
if !m.OK {
return false
}
// 去掉末尾 / 再比
req := strings.TrimSuffix(requestPath, "/")
can := strings.TrimSuffix(m.Canonical, "/")
return req != can
}