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 将任意数据序列化为可嵌入