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(`([^<]+)`) // 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("" + html.EscapeString(u.String()) + "") }) } 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 }