Files
sub2api/backend/internal/web/embed.go
NepetaLemon c6b3de1199 ci(backend): 添加 github actions (#10)
## 变更内容

### CI/CD
- 添加 GitHub Actions 工作流(test + golangci-lint)
- 添加 golangci-lint 配置,启用 errcheck/govet/staticcheck/unused/depguard
- 通过 depguard 强制 service 层不能直接导入 repository

### 错误处理修复
- 修复 CSV 写入、SSE 流式输出、随机数生成等未处理的错误
- GenerateRedeemCode() 现在返回 error

### 资源泄露修复
- 统一使用 defer func() { _ = xxx.Close() }() 模式

### 代码清理
- 移除未使用的常量
- 简化 nil map 检查
- 统一代码格式
2025-12-20 02:29:52 -05:00

80 lines
1.7 KiB
Go

package web
import (
"embed"
"io"
"io/fs"
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
//go:embed all:dist
var frontendFS embed.FS
// ServeEmbeddedFrontend returns a Gin handler that serves embedded frontend assets
// and handles SPA routing by falling back to index.html for non-API routes.
func ServeEmbeddedFrontend() gin.HandlerFunc {
distFS, err := fs.Sub(frontendFS, "dist")
if err != nil {
panic("failed to get dist subdirectory: " + err.Error())
}
fileServer := http.FileServer(http.FS(distFS))
return func(c *gin.Context) {
path := c.Request.URL.Path
// Skip API and gateway routes
if strings.HasPrefix(path, "/api/") ||
strings.HasPrefix(path, "/v1/") ||
strings.HasPrefix(path, "/setup/") ||
path == "/health" {
c.Next()
return
}
// Try to serve static file
cleanPath := strings.TrimPrefix(path, "/")
if cleanPath == "" {
cleanPath = "index.html"
}
if file, err := distFS.Open(cleanPath); err == nil {
_ = file.Close()
fileServer.ServeHTTP(c.Writer, c.Request)
c.Abort()
return
}
// SPA fallback: serve index.html for all other routes
serveIndexHTML(c, distFS)
}
}
func serveIndexHTML(c *gin.Context, fsys fs.FS) {
file, err := fsys.Open("index.html")
if err != nil {
c.String(http.StatusNotFound, "Frontend not found")
c.Abort()
return
}
defer func() { _ = file.Close() }()
content, err := io.ReadAll(file)
if err != nil {
c.String(http.StatusInternalServerError, "Failed to read index.html")
c.Abort()
return
}
c.Data(http.StatusOK, "text/html; charset=utf-8", content)
c.Abort()
}
// HasEmbeddedFrontend checks if frontend assets are embedded
func HasEmbeddedFrontend() bool {
_, err := frontendFS.ReadFile("dist/index.html")
return err == nil
}