支持嵌入式静态资源服务,移除 web 包,调整路由逻辑

This commit is contained in:
2026-01-26 23:17:20 +08:00
parent ca67914cb5
commit 4795960fad
4 changed files with 84 additions and 33 deletions

View File

@@ -2,6 +2,7 @@ package bootstrap
import ( import (
"context" "context"
"embed"
"fmt" "fmt"
"log" "log"
"os" "os"
@@ -22,7 +23,7 @@ import (
) )
// Init 初始化应用各项服务 // Init 初始化应用各项服务
func Init() *gin.Engine { func Init(webFS embed.FS) *gin.Engine {
// 0. 确保数据目录存在 // 0. 确保数据目录存在
_ = os.MkdirAll("data/picture", 0755) _ = os.MkdirAll("data/picture", 0755)
@@ -80,7 +81,7 @@ func Init() *gin.Engine {
}() }()
// 7. 设置路由 // 7. 设置路由
return http.SetupRouter() return http.SetupRouter(webFS)
} }
// LogWelcomeInfo 输出欢迎信息和快速跳转地址 // LogWelcomeInfo 输出欢迎信息和快速跳转地址

View File

@@ -1,22 +1,25 @@
package http package http
import ( import (
"embed"
"io"
"io/fs"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"strings"
_ "BingPaper/docs" _ "BingPaper/docs"
"BingPaper/internal/config" "BingPaper/internal/config"
"BingPaper/internal/http/handlers" "BingPaper/internal/http/handlers"
"BingPaper/internal/http/middleware" "BingPaper/internal/http/middleware"
"BingPaper/web"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
swaggerFiles "github.com/swaggo/files" swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger" ginSwagger "github.com/swaggo/gin-swagger"
) )
func SetupRouter() *gin.Engine { func SetupRouter(webFS embed.FS) *gin.Engine {
r := gin.Default() r := gin.Default()
// Swagger // Swagger
@@ -25,28 +28,6 @@ func SetupRouter() *gin.Engine {
// 静态文件 // 静态文件
r.Static("/static", "./static") r.Static("/static", "./static")
webPath := config.GetConfig().Web.Path
indexPath := filepath.Join(webPath, "index.html")
serveIndex := func(c *gin.Context) {
// 1. 优先尝试从配置的路径读取
if _, err := os.Stat(indexPath); err == nil {
c.File(indexPath)
return
}
// 2. 如果外部文件不存在,则使用内置嵌入的文件
data, err := web.FS.ReadFile("index.html")
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "web files not found"})
return
}
c.Data(http.StatusOK, "text/html; charset=utf-8", data)
}
r.GET("/", serveIndex)
r.GET("/admin", serveIndex)
r.GET("/login", serveIndex)
api := r.Group("/api/v1") api := r.Group("/api/v1")
{ {
// 公共接口 // 公共接口
@@ -86,5 +67,70 @@ func SetupRouter() *gin.Engine {
} }
} }
// 静态资源服务与 SPA 路由 (放在最后,确保 API 路由优先)
webSub, _ := fs.Sub(webFS, "web")
r.NoRoute(func(c *gin.Context) {
path := c.Request.URL.Path
// 如果请求的是 API 或 Swagger则不处理静态资源 (让其返回 404)
if strings.HasPrefix(path, "/api") || strings.HasPrefix(path, "/swagger") {
return
}
// 辅助函数:尝试从外部或嵌入服务文件
serveFile := func(relPath string, allowExternal bool) bool {
// 1. 优先尝试外部路径
webPath := config.GetConfig().Web.Path
if allowExternal && webPath != "" {
fullPath := filepath.Join(webPath, relPath)
if info, err := os.Stat(fullPath); err == nil && !info.IsDir() {
c.File(fullPath)
return true
}
}
// 2. 尝试嵌入式文件
f, err := webSub.Open(relPath)
if err == nil {
defer f.Close()
stat, err := f.Stat()
if err == nil && !stat.IsDir() {
if rs, ok := f.(io.ReadSeeker); ok {
http.ServeContent(c.Writer, c.Request, stat.Name(), stat.ModTime(), rs)
return true
}
// 兜底:直接读取并输出
data, err := io.ReadAll(f)
if err == nil {
c.Data(http.StatusOK, "", data)
return true
}
}
}
return false
}
// 1. 尝试直接请求的文件 (如果是 / 则尝试 index.html)
requestedPath := strings.TrimPrefix(path, "/")
if requestedPath == "" {
requestedPath = "index.html"
}
if serveFile(requestedPath, true) {
return
}
// 2. SPA 支持:对于非文件请求(没有后缀或不包含点),尝试返回 index.html
isAsset := strings.Contains(requestedPath, ".")
if !isAsset || requestedPath == "index.html" {
if serveFile("index.html", true) {
return
}
}
c.Status(http.StatusNotFound)
})
return r return r
} }

12
main.go
View File

@@ -1,7 +1,9 @@
package main package main
import ( import (
"embed"
"fmt" "fmt"
"mime"
"BingPaper/internal/bootstrap" "BingPaper/internal/bootstrap"
"BingPaper/internal/config" "BingPaper/internal/config"
@@ -10,6 +12,9 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
) )
//go:embed all:web
var webFS embed.FS
// @title BingPaper API // @title BingPaper API
// @version 1.0 // @version 1.0
// @description 必应每日一图抓取、存储、管理与公共 API 服务。 // @description 必应每日一图抓取、存储、管理与公共 API 服务。
@@ -21,8 +26,13 @@ import (
// @name Authorization // @name Authorization
func main() { func main() {
// 注册常用 MIME 类型,确保嵌入式资源能被正确识别
mime.AddExtensionType(".js", "application/javascript")
mime.AddExtensionType(".css", "text/css")
mime.AddExtensionType(".svg", "image/svg+xml")
// 1. 初始化 // 1. 初始化
r := bootstrap.Init() r := bootstrap.Init(webFS)
// 2. 输出欢迎信息 // 2. 输出欢迎信息
bootstrap.LogWelcomeInfo() bootstrap.LogWelcomeInfo()

View File

@@ -1,6 +0,0 @@
package web
import "embed"
//go:embed index.html
var FS embed.FS