mirror of
https://git.fightbot.fun/hxuanyu/BingPaper.git
synced 2026-02-15 14:49:34 +08:00
支持嵌入式静态资源服务,移除 web 包,调整路由逻辑
This commit is contained in:
@@ -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 输出欢迎信息和快速跳转地址
|
||||||
|
|||||||
@@ -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
12
main.go
@@ -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()
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
package web
|
|
||||||
|
|
||||||
import "embed"
|
|
||||||
|
|
||||||
//go:embed index.html
|
|
||||||
var FS embed.FS
|
|
||||||
Reference in New Issue
Block a user