224 lines
7.5 KiB
Go
224 lines
7.5 KiB
Go
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)
|
||
}
|