Files
web_huangguo2_h5/app/router/gin.go
T
2026-09-29 15:30:33 +07:00

224 lines
7.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package router
import (
"encoding/json"
"html/template"
"net/http"
"os"
"strings"
"videoweb/app/api/web"
"videoweb/app/appg"
"videoweb/app/service/commentser"
"videoweb/app/service/playletser"
"videoweb/app/service/seoser"
"videoweb/app/service/translationser"
"videoweb/app/service/videoser"
staticassets "videoweb/app/static"
"github.com/gin-gonic/gin"
)
// Init 初始化 Gin,注册静态资源与业务路由。
func Init() *gin.Engine {
setGinMode()
engine := gin.Default()
engine.HandleMethodNotAllowed = true
setupEngine(engine)
registerM3U8Routes(engine)
registerSitemapRoutes(engine)
registerRoutes(engine)
engine.NoMethod(web.NotFound)
return engine
}
// DHInit 导航站独立端口:仅暴露官方导航页及静态资源。
func DHInit() *gin.Engine {
setGinMode()
engine := gin.Default()
engine.HandleMethodNotAllowed = true
setupEngine(engine)
engine.GET("/", web.Nav)
engine.GET("/nav/", web.Nav)
engine.GET("/dh/", web.Nav)
engine.NoRoute(web.NotFound)
engine.NoMethod(web.NotFound)
return engine
}
func setGinMode() {
mode := strings.ToLower(strings.TrimSpace(appg.Conf.RunMod))
if envMode := strings.ToLower(strings.TrimSpace(os.Getenv(gin.EnvGinMode))); envMode != "" {
mode = envMode
}
switch mode {
case "test":
gin.SetMode(gin.TestMode)
case "prod", "production", "release":
gin.SetMode(gin.ReleaseMode)
default:
gin.SetMode(gin.DebugMode)
}
}
func setupEngine(engine *gin.Engine) {
tplFuncs := playletser.TemplateFuncs()
for name, fn := range commentser.TemplateFuncs() {
tplFuncs[name] = fn
}
tplFuncs["InJSONScript"] = inJSONScript
tplFuncs["navHref"] = translationser.NavHref
tplFuncs["navIsDownload"] = translationser.NavIsDownload
tplFuncs["navIsActive"] = translationser.NavIsActive
tplFuncs["navDrawerIcon"] = translationser.NavDrawerIcon
tplFuncs["navHasMegaMenu"] = translationser.NavHasMegaMenu
tplFuncs["listThemeSegment"] = translationser.ListThemeSegmentFromPath
tplFuncs["listUpdateStatus"] = translationser.ListUpdateStatusFromPath
tplFuncs["seriesListURL"] = videoser.SeriesListURL
tplFuncs["videoDetailURL"] = videoser.VideoDetailURL
tplFuncs["videoPlayURL"] = videoser.VideoPlayURL
tplFuncs["mediaTypeLabel"] = videoser.MediaTypeLabel
tplFuncs["episodeLabel"] = videoser.EpisodeLabel
engine.SetFuncMap(tplFuncs)
engine.SetHTMLTemplate(mustHTMLTemplate(tplFuncs))
engine.Use(strictTransportSecurity(), staticCacheHeaders(), crawlerHeaders())
engine.StaticFS("/static", http.FS(staticassets.FS))
}
// strictTransportSecurity 仅在请求实际通过 HTTPS 到达,或可信代理标记首个协议为 HTTPS 时启用 HSTS。
// 不声明 includeSubDomains/preload,避免把尚未确认支持 HTTPS 的子域一并纳入。
func strictTransportSecurity() gin.HandlerFunc {
return func(c *gin.Context) {
if isHTTPSRequest(c.Request) {
c.Header("Strict-Transport-Security", "max-age=31536000")
}
c.Next()
}
}
func isHTTPSRequest(r *http.Request) bool {
if r == nil {
return false
}
if r.TLS != nil {
return true
}
proto := r.Header.Get("X-Forwarded-Proto")
if i := strings.IndexByte(proto, ','); i >= 0 {
proto = proto[:i]
}
return strings.EqualFold(strings.TrimSpace(proto), "https")
}
// crawlerHeaders 防止接口和媒体工具响应进入搜索索引。
func crawlerHeaders() gin.HandlerFunc {
return func(c *gin.Context) {
path := c.Request.URL.Path
if strings.HasPrefix(path, "/api/") || strings.HasPrefix(path, "/h5/") ||
path == "/enkey" || path == "/newenkey" || path == "/healthz" {
c.Header("X-Robots-Tag", "noindex, nofollow")
}
c.Next()
}
}
// inJSONScript 将任意数据序列化为可嵌入 <script> 的 JSON。
func inJSONScript(v any) template.JS {
b, err := json.Marshal(v)
if err != nil {
return template.JS("null")
}
return template.JS(b)
}
// staticCacheHeaders 为静态资源设置浏览器缓存。
func staticCacheHeaders() gin.HandlerFunc {
return func(c *gin.Context) {
if strings.HasPrefix(c.Request.URL.Path, "/static/") {
c.Header("Cache-Control", "public, max-age=604800, stale-while-revalidate=86400")
}
c.Next()
}
}
func registerRoutes(r *gin.Engine) {
// 页面路由与 template/pages 文件名对应:camelCase → kebab-case(如 popularRecommend → /popular-recommend)
r.GET("/", web.Index)
r.GET("/search-mobile", web.SearchMobile)
r.GET("/search", web.Search)
r.GET("/search/:q", web.Search)
r.GET("/search/:q/page/:page", web.Search)
r.GET("/about", web.About)
r.GET("/help", web.Help)
r.GET("/terms", web.Terms)
r.GET("/privacy", web.Privacy)
r.GET("/2257", web.Compliance2257)
r.GET("/dmca", web.DMCA)
r.GET("/download-app", web.DownloadApp)
r.GET("/nav", web.Nav)
r.GET("/nav/", web.Nav)
r.GET("/dh", web.Nav)
r.GET("/dh/", web.Nav)
r.GET("/list", web.List)
r.GET("/list/:theme", web.List)
r.GET("/list/:theme/:updateStatus/:sortType", web.List)
r.GET("/list/:theme/:updateStatus/:sortType/page/:page", web.List)
r.GET("/more-list/:configId/:timeRange/:theme/page/:page", web.MoreList)
r.GET("/more-list/:configId/:timeRange/:theme", web.MoreList)
r.GET("/more-list/:configId/page/:page", web.MoreList)
r.GET("/more-list/:configId", web.MoreList)
r.GET("/more-series-list/:configId/:timeRange/:theme/page/:page", web.MoreSeriesList)
r.GET("/more-series-list/:configId/:timeRange/:theme", web.MoreSeriesList)
r.GET("/more-series-list/:configId/page/:page", web.MoreSeriesList)
r.GET("/more-series-list/:configId", web.MoreSeriesList)
r.GET("/series", web.Series)
r.GET("/series/:media/:topic/:sortType", web.Series)
r.GET("/series/:media/:topic/:sortType/page/:page", web.Series)
r.GET("/topic", web.Topic)
r.GET("/topic/page/:page", web.Topic)
r.GET("/topic/sort/:sortType", web.Topic)
r.GET("/topic/sort/:sortType/page/:page", web.Topic)
r.GET("/topic-detail/:slug/sort/:sortType/page/:page", web.TopicDetail)
r.GET("/topic-detail/:slug/sort/:sortType", web.TopicDetail)
r.GET("/topic-detail/:slug/page/:page", web.TopicDetail)
r.GET("/topic-detail/:slug", web.TopicDetail)
r.GET("/rank", web.Rank)
r.GET("/rank/page/:page", web.Rank)
r.GET("/rank/:sortOrder", web.Rank)
r.GET("/rank/:sortOrder/page/:page", web.Rank)
r.GET("/tags/:slug/sort/:sortType/page/:page", web.Tags)
r.GET("/tags/:slug/sort/:sortType", web.Tags)
r.GET("/tags/:slug/page/:page", web.Tags)
r.GET("/tags/:slug", web.Tags)
r.GET("/recently-added", web.RecentlyAdded)
r.GET("/recently-added/page/:page", web.RecentlyAdded)
r.GET("/popular-recommend", web.PopularRecommend)
r.GET("/popular-recommend/page/:page", web.PopularRecommend)
r.GET("/popular-recommend/:rankType", web.PopularRecommend)
r.GET("/popular-recommend/:rankType/page/:page", web.PopularRecommend)
r.GET("/play/:slug", web.Play)
r.GET("/play/:slug/:ep", web.Play)
r.GET("/play/:slug/recommendations/page/:guessPage", web.Play)
r.GET("/play/:slug/:ep/recommendations/page/:guessPage", web.Play)
r.GET("/series-play/:slug", web.SeriesPlay)
r.GET("/series-play/:slug/recommendations/page/:guessPage", web.SeriesPlay)
r.GET("/feedback", web.Feedback)
// 意见反馈(含截图上传)
r.POST("/api/upload/image", web.UploadImage)
r.POST("/api/feedback/add", web.FeedbackAdd)
// 播放页 BFF
r.GET("/api/playlet/guess-like/list", web.GuessLikeList)
r.GET("/api/comment/list", web.CommentList)
r.GET("/api/web/search/suggest", web.SearchSuggest)
// 搜索引擎抓取、发现与站点所有权验证。
r.GET("/robots.txt", web.RobotsTXT)
r.GET("/llms.txt", web.LLMSTXT)
if key := seoser.VerifyFromConfig().IndexNowKey; key != "" {
r.GET("/"+key+".txt", web.SEOVerifyFile)
}
r.NoRoute(web.NotFound)
}