Files
fish03andClaude Opus 5.5 bef9f8b4ed Initial commit
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 14:43:16 +07:00

91 lines
2.4 KiB
Go

package router
import (
"html"
"net/http"
"net/url"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/gin-gonic/gin"
)
const sitemapDir = "./templates/page/sitemaps"
var sitemapLocPattern = regexp.MustCompile(`<loc>([^<]+)</loc>`)
// registerSitemapRoutes 暴露 hgdj_server app 生成的 sitemap 索引及分片文件。
func registerSitemapRoutes(r *gin.Engine) {
r.StaticFile("/sitemap.xml", filepath.Join(sitemapDir, "sitemap.xml"))
r.GET("/sitemaps/:name", serveSitemapShard)
}
func serveSitemapShard(c *gin.Context) {
name := c.Param("name")
if !isValidSitemapShardName(name) {
renderNotFound(c)
return
}
raw, err := os.ReadFile(filepath.Join(sitemapDir, name))
if err != nil {
renderNotFound(c)
return
}
c.Data(http.StatusOK, "application/xml; charset=utf-8", normalizeSitemapPageURLs(raw))
}
// normalizeSitemapPageURLs 让运行时生成的旧 sitemap 与当前 canonical 路由保持一致。
// sitemap 索引和静态文件地址不经过这里,仍保留文件型 URL(无尾斜杠)。
func normalizeSitemapPageURLs(raw []byte) []byte {
return sitemapLocPattern.ReplaceAllFunc(raw, func(match []byte) []byte {
parts := sitemapLocPattern.FindSubmatch(match)
if len(parts) != 2 {
return match
}
loc := html.UnescapeString(strings.TrimSpace(string(parts[1])))
u, err := url.Parse(loc)
if err != nil || u.Path == "" {
return match
}
u.Path = canonicalSitemapPagePath(u.Path)
return []byte("<loc>" + html.EscapeString(u.String()) + "</loc>")
})
}
func canonicalSitemapPagePath(path string) string {
switch {
case path == "/more":
path = "/topic"
case strings.HasPrefix(path, "/more/"):
path = "/topic/" + strings.TrimPrefix(path, "/more/")
case path == "/tag-list":
path = "/tag"
case strings.HasPrefix(path, "/tag-list/"):
path = "/tag/" + strings.TrimPrefix(path, "/tag-list/")
}
if path != "/" && !strings.HasSuffix(path, "/") {
path += "/"
}
return path
}
// isValidSitemapShardName 校验分片文件名,防止路径遍历。
// 合法示例:sitemap-pages.xml、sitemap-playletdetail-0.xml
func isValidSitemapShardName(name string) bool {
if !strings.HasPrefix(name, "sitemap-") || !strings.HasSuffix(name, ".xml") {
return false
}
base := strings.TrimSuffix(name, ".xml")
if len(base) <= len("sitemap-") {
return false
}
for _, ch := range base[len("sitemap-"):] {
if !((ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '-') {
return false
}
}
return true
}