- 删除 embed_static(内嵌 SPA 静态资源)与 handler/ssr_home.go、ssr_icons.go - router 移除前端页面服务,NoRoute 统一返回 404 JSON - seo.go 仅保留 robots.txt / sitemap.xml / favicon.ico - 数据聚合逻辑迁移到 handler/home_boot.go(/api/home-boot) - 启动日志改为提示前端由 Node 渲染服务器提供
180 lines
4.5 KiB
Go
180 lines
4.5 KiB
Go
package handler
|
||
|
||
import (
|
||
"net/http"
|
||
"strings"
|
||
"time"
|
||
|
||
"git.iioio.com/freefire/jiang13-forum/service"
|
||
"github.com/gin-gonic/gin"
|
||
)
|
||
|
||
const seoSitemapLimit = 5000
|
||
|
||
// RobotsTxt 搜索引擎抓取规则
|
||
func (h *Handlers) RobotsTxt(c *gin.Context) {
|
||
base := h.publicBaseURL(c)
|
||
var b strings.Builder
|
||
b.WriteString("User-agent: *\n")
|
||
b.WriteString("Allow: /\n")
|
||
b.WriteString("Disallow: /api/\n")
|
||
b.WriteString("Disallow: /admin\n")
|
||
b.WriteString("Disallow: /compose\n")
|
||
b.WriteString("Disallow: /login\n")
|
||
b.WriteString("Disallow: /register\n")
|
||
b.WriteString("Disallow: /profile\n")
|
||
b.WriteString("Disallow: /favorites\n")
|
||
b.WriteString("Disallow: /oauth/\n")
|
||
b.WriteString("Disallow: /media/\n")
|
||
b.WriteString("Disallow: /*/edit\n")
|
||
if base != "" {
|
||
b.WriteString("\nSitemap: ")
|
||
b.WriteString(base)
|
||
b.WriteString("/sitemap.xml\n")
|
||
}
|
||
c.Data(http.StatusOK, "text/plain; charset=utf-8", []byte(b.String()))
|
||
}
|
||
|
||
// FaviconICO 约定路径 /favicon.ico:有品牌图标则 302 到实际上传 URL,否则 404
|
||
func (h *Handlers) FaviconICO(c *gin.Context) {
|
||
href := strings.TrimSpace(h.Settings.SiteBranding().Favicon)
|
||
if href == "" {
|
||
c.Status(http.StatusNotFound)
|
||
return
|
||
}
|
||
// 相对路径保持站内跳转;绝对 URL 也可 Redirect
|
||
c.Header("Cache-Control", "public, max-age=86400")
|
||
c.Redirect(http.StatusFound, href)
|
||
}
|
||
|
||
// SitemapXML 公开页面站点地图
|
||
func (h *Handlers) SitemapXML(c *gin.Context) {
|
||
base := h.publicBaseURL(c)
|
||
if base == "" {
|
||
c.String(http.StatusServiceUnavailable, "未配置站点 ROOT_URL,无法生成 sitemap")
|
||
return
|
||
}
|
||
|
||
now := time.Now().UTC()
|
||
permalink := h.Settings.Permalink()
|
||
urls := []service.SitemapURL{
|
||
{Loc: base + "/", LastMod: now, ChangeFreq: "hourly", Priority: "1.0"},
|
||
{Loc: base + "/projects", LastMod: now, ChangeFreq: "daily", Priority: "0.6"},
|
||
{Loc: base + "/links", LastMod: now, ChangeFreq: "weekly", Priority: "0.6"},
|
||
}
|
||
|
||
if boards, err := h.Board.List(); err == nil {
|
||
for _, board := range boards {
|
||
urls = append(urls, service.SitemapURL{
|
||
Loc: base + service.QueryBoardHome(board.ID, permalink),
|
||
LastMod: board.UpdatedAt.UTC(),
|
||
ChangeFreq: "daily",
|
||
Priority: "0.7",
|
||
})
|
||
}
|
||
}
|
||
|
||
if posts, e1 := h.Post.ListSitemap(seoSitemapLimit); e1 == nil {
|
||
for _, p := range posts {
|
||
lm := p.UpdatedAt
|
||
if lm.IsZero() {
|
||
lm = p.CreatedAt
|
||
}
|
||
urls = append(urls, service.SitemapURL{
|
||
Loc: base + permalink.PostPath(p.ID),
|
||
LastMod: lm.UTC(),
|
||
ChangeFreq: "weekly",
|
||
Priority: "0.8",
|
||
})
|
||
}
|
||
}
|
||
|
||
if users, e2 := h.User.ListSitemap(seoSitemapLimit); e2 == nil {
|
||
for _, u := range users {
|
||
urls = append(urls, service.SitemapURL{
|
||
Loc: base + permalink.UserPath(u.ID),
|
||
LastMod: u.UpdatedAt.UTC(),
|
||
ChangeFreq: "weekly",
|
||
Priority: "0.5",
|
||
})
|
||
}
|
||
}
|
||
|
||
if pages, e3 := h.SitePage.ListSitemap(seoSitemapLimit); e3 == nil {
|
||
for _, p := range pages {
|
||
lm := p.UpdatedAt
|
||
if lm.IsZero() {
|
||
lm = p.CreatedAt
|
||
}
|
||
urls = append(urls, service.SitemapURL{
|
||
Loc: base + permalink.PagePath(p.Slug),
|
||
LastMod: lm.UTC(),
|
||
ChangeFreq: "monthly",
|
||
Priority: "0.5",
|
||
})
|
||
}
|
||
}
|
||
|
||
var b strings.Builder
|
||
b.WriteString(`<?xml version="1.0" encoding="UTF-8"?>`)
|
||
b.WriteString(`<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">`)
|
||
for _, u := range urls {
|
||
b.WriteString("<url>")
|
||
b.WriteString("<loc>")
|
||
b.WriteString(xmlEscape(u.Loc))
|
||
b.WriteString("</loc>")
|
||
if !u.LastMod.IsZero() {
|
||
b.WriteString("<lastmod>")
|
||
b.WriteString(u.LastMod.Format("2006-01-02"))
|
||
b.WriteString("</lastmod>")
|
||
}
|
||
if u.ChangeFreq != "" {
|
||
b.WriteString("<changefreq>")
|
||
b.WriteString(u.ChangeFreq)
|
||
b.WriteString("</changefreq>")
|
||
}
|
||
if u.Priority != "" {
|
||
b.WriteString("<priority>")
|
||
b.WriteString(u.Priority)
|
||
b.WriteString("</priority>")
|
||
}
|
||
b.WriteString("</url>")
|
||
}
|
||
b.WriteString("</urlset>")
|
||
c.Data(http.StatusOK, "application/xml; charset=utf-8", []byte(b.String()))
|
||
}
|
||
|
||
func (h *Handlers) publicBaseURL(c *gin.Context) string {
|
||
return h.Settings.SitePublicBaseURL(requestOrigin(c))
|
||
}
|
||
|
||
func requestOrigin(c *gin.Context) string {
|
||
proto := c.GetHeader("X-Forwarded-Proto")
|
||
if proto == "" {
|
||
if c.Request.TLS != nil {
|
||
proto = "https"
|
||
} else {
|
||
proto = "http"
|
||
}
|
||
}
|
||
host := c.GetHeader("X-Forwarded-Host")
|
||
if host == "" {
|
||
host = c.Request.Host
|
||
}
|
||
if host == "" {
|
||
return ""
|
||
}
|
||
return proto + "://" + host
|
||
}
|
||
|
||
func xmlEscape(s string) string {
|
||
r := strings.NewReplacer(
|
||
`&`, "&",
|
||
`<`, "<",
|
||
`>`, ">",
|
||
`"`, """,
|
||
`'`, "'",
|
||
)
|
||
return r.Replace(s)
|
||
}
|