Files
NetworkAuth/server/routes.go

163 lines
4.4 KiB
Go
Raw Normal View History

2026-03-28 23:30:02 +08:00
package server
import (
"NetworkAuth/utils"
2026-03-29 00:44:30 +08:00
"io"
"io/fs"
"net/http"
"net/http/httputil"
"net/url"
"os"
"path/filepath"
2026-03-29 00:44:30 +08:00
"strings"
2026-03-28 23:30:02 +08:00
"github.com/gin-gonic/gin"
"github.com/spf13/viper"
2026-03-28 23:30:02 +08:00
)
2026-05-25 02:07:05 +08:00
var frontendFS fs.FS
func SetFrontendFS(fsys fs.FS) {
frontendFS = fsys
}
2026-03-28 23:30:02 +08:00
// RegisterRoutes 聚合注册所有路由
func RegisterRoutes(r *gin.Engine) {
2026-03-29 00:44:30 +08:00
// 1. 所有接口路由基于 /api
2026-03-28 23:30:02 +08:00
apiGroup := r.Group("/api")
RegisterInstallRoutes(apiGroup)
RegisterDefaultRoutes(apiGroup)
RegisterAdminRoutes(apiGroup)
2026-03-29 00:44:30 +08:00
// 2. 注册前端静态资源及兜底路由
registerFrontendRoutes(r)
}
// registerFrontendRoutes 注册前端静态资源及兜底路由
func registerFrontendRoutes(r *gin.Engine) {
distConfig := viper.GetString("server.dist")
var fileServer http.Handler
// 判断是否配置了外部 dist (支持 http 反向代理或本地目录)
if distConfig != "" {
if strings.HasPrefix(distConfig, "http://") || strings.HasPrefix(distConfig, "https://") {
// 反向代理到前端开发服务器
r.Use(func(c *gin.Context) {
if !strings.HasPrefix(c.Request.URL.Path, "/api") {
proxy := httputil.NewSingleHostReverseProxy(&url.URL{
Scheme: strings.Split(distConfig, "://")[0],
Host: strings.TrimPrefix(distConfig, strings.Split(distConfig, "://")[0]+"://"),
})
proxy.ServeHTTP(c.Writer, c.Request)
c.Abort()
}
})
return // 反向代理接管了所有非 API 路由,直接返回
} else {
// 使用本地外部目录
if !filepath.IsAbs(distConfig) {
distConfig = filepath.Join(utils.GetRootDir(), distConfig)
}
fileServer = http.FileServer(http.Dir(distConfig))
// 拦截并处理静态资源请求
r.Use(func(c *gin.Context) {
path := c.Request.URL.Path
if strings.HasPrefix(path, "/api") {
c.Next()
return
}
cleanPath := strings.TrimPrefix(path, "/")
if cleanPath == "" {
cleanPath = "index.html"
}
fullPath := distConfig + "/" + cleanPath
if stat, err := os.Stat(fullPath); err == nil && !stat.IsDir() {
if strings.HasPrefix(path, "/static/") || strings.HasPrefix(path, "/assets/") {
c.Header("Cache-Control", "public, max-age=31536000")
}
fileServer.ServeHTTP(c.Writer, c.Request)
c.Abort()
return
}
c.Next()
})
// SPA 前端路由兜底
r.NoRoute(func(c *gin.Context) {
if strings.HasPrefix(c.Request.URL.Path, "/api") {
c.JSON(http.StatusNotFound, gin.H{"code": 404, "msg": "API Not Found"})
return
}
c.Header("Content-Type", "text/html; charset=utf-8")
c.File(distConfig + "/index.html")
})
return
}
}
2026-05-25 02:07:05 +08:00
if frontendFS == nil {
panic("Failed to initialize embedded static files: frontend fs is nil")
2026-03-29 00:44:30 +08:00
}
// 提供静态文件服务器
2026-05-25 02:07:05 +08:00
fileServer = http.FileServer(http.FS(frontendFS))
2026-03-29 00:44:30 +08:00
// 拦截并处理静态资源请求
r.Use(func(c *gin.Context) {
path := c.Request.URL.Path
// 如果是 API 请求,直接放行
if strings.HasPrefix(path, "/api") {
c.Next()
return
}
// 检查静态文件中是否存在该路径
// 移除开头的 "/"
cleanPath := strings.TrimPrefix(path, "/")
if cleanPath == "" {
cleanPath = "index.html"
}
2026-05-25 02:07:05 +08:00
// 尝试在嵌入的文件系统中查找文件
if _, err := fs.Stat(frontendFS, cleanPath); err == nil {
2026-03-29 00:44:30 +08:00
// 文件存在,交由 FileServer 处理
// 设置一些常见的缓存头
if strings.HasPrefix(path, "/static/") || strings.HasPrefix(path, "/assets/") {
c.Header("Cache-Control", "public, max-age=31536000")
}
fileServer.ServeHTTP(c.Writer, c.Request)
c.Abort()
return
}
c.Next()
})
// SPA 前端路由兜底 (处理 History 模式)
r.NoRoute(func(c *gin.Context) {
// 如果是 API 请求找不到路由,返回 404 JSON
if strings.HasPrefix(c.Request.URL.Path, "/api") {
c.JSON(http.StatusNotFound, gin.H{
"code": 404,
"msg": "API Not Found",
})
return
}
// 其他所有非 API 请求,都返回 index.html 交给前端 Vue Router 处理
c.Header("Content-Type", "text/html; charset=utf-8")
2026-05-25 02:07:05 +08:00
indexFile, err := frontendFS.Open("index.html")
2026-03-29 00:44:30 +08:00
if err != nil {
c.String(http.StatusInternalServerError, "Failed to load index.html")
return
}
defer indexFile.Close()
stat, _ := indexFile.Stat()
http.ServeContent(c.Writer, c.Request, "index.html", stat.ModTime(), indexFile.(io.ReadSeeker))
})
2026-03-28 23:30:02 +08:00
}