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