Files
Home-Vue-Go/main.go
T
admin_gitea 6f07849a53
Build Packages / Test (push) Has been cancelled
Build Packages / Build macOS package (push) Has been cancelled
Build Packages / Build Linux package (push) Has been cancelled
Build Packages / Build Windows package (push) Has been cancelled
Build Packages / Publish GitHub Release (push) Has been cancelled
feat: unify service port and add GitHub packages workflow
2026-08-05 15:13:29 +08:00

167 lines
4.4 KiB
Go

package main
import (
"embed"
"io/fs"
"log"
"mime"
"net/http"
"os"
"path/filepath"
"strings"
"time"
_ "time/tzdata"
"home-vue-go/internal/api"
"home-vue-go/internal/config"
"home-vue-go/internal/database"
"github.com/gin-gonic/gin"
)
//go:embed dist/*
var distFS embed.FS
func main() {
loc, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
log.Fatal("无法加载时区:", err)
}
time.Local = loc
exePath, err := os.Executable()
if err != nil {
log.Fatal("无法获取可执行文件路径:", err)
}
dataDir := filepath.Join(filepath.Dir(exePath), "data")
if err := os.MkdirAll(dataDir, 0755); err != nil {
log.Fatal("无法创建data目录:", err)
}
cfg := config.New(dataDir)
db, err := database.Init(cfg.DatabasePath, cfg)
if err != nil {
log.Fatal("数据库初始化失败:", err)
}
defer db.Close()
if os.Getenv("GIN_MODE") == "" {
gin.SetMode(gin.ReleaseMode)
}
port := os.Getenv("PORT")
if port == "" {
port = "1552"
}
router := gin.New()
router.Use(gin.Recovery(), corsMiddleware(port))
api.SetupRoutes(router, db, cfg)
distRoot, err := fs.Sub(distFS, "dist")
if err != nil {
log.Fatal("无法加载嵌入的前端文件:", err)
}
router.NoRoute(serveFrontend(distRoot))
log.Println("使用嵌入的前端文件(单一可执行文件模式)")
firstRunFile := filepath.Join(dataDir, ".first_run")
if _, err := os.Stat(firstRunFile); os.IsNotExist(err) {
if err := os.WriteFile(firstRunFile, []byte(""), 0644); err != nil {
log.Printf("记录首次启动状态失败: %v", err)
}
log.Printf("默认管理员账号: admin, 密码: admin123")
log.Printf("提示: 首次启动后,请及时修改默认密码以确保安全")
}
log.Printf("========================================")
log.Printf("服务器启动成功!")
log.Printf("访问端点: http://localhost:%s", port)
log.Printf("========================================")
server := &http.Server{
Addr: ":" + port,
Handler: router,
}
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("服务器启动失败: %v", err)
}
}
func serveFrontend(root fs.FS) gin.HandlerFunc {
return func(c *gin.Context) {
requestPath := strings.TrimPrefix(c.Request.URL.Path, "/")
if requestPath == "api" || strings.HasPrefix(requestPath, "api/") ||
requestPath == "uploads" || strings.HasPrefix(requestPath, "uploads/") {
c.Status(http.StatusNotFound)
return
}
if c.Request.Method != http.MethodGet && c.Request.Method != http.MethodHead {
c.Status(http.StatusNotFound)
return
}
filePath := requestPath
if filePath == "" {
filePath = "index.html"
}
if serveEmbeddedFile(c, root, filePath) {
return
}
if !serveEmbeddedFile(c, root, "index.html") {
c.Status(http.StatusNotFound)
}
}
}
func serveEmbeddedFile(c *gin.Context, root fs.FS, filePath string) bool {
file, err := root.Open(filePath)
if err != nil {
return false
}
defer file.Close()
stat, err := file.Stat()
if err != nil || stat.IsDir() {
return false
}
content, err := fs.ReadFile(root, filePath)
if err != nil {
return false
}
contentType := mime.TypeByExtension(filepath.Ext(filePath))
if contentType == "" {
contentType = http.DetectContentType(content)
}
c.Data(http.StatusOK, contentType, content)
return true
}
func corsMiddleware(port string) gin.HandlerFunc {
allowedOrigins := make(map[string]struct{})
for _, origin := range strings.Split(os.Getenv("CORS_ALLOWED_ORIGINS"), ",") {
if value := strings.TrimSpace(origin); value != "" {
allowedOrigins[value] = struct{}{}
}
}
if len(allowedOrigins) == 0 {
allowedOrigins["http://localhost:"+port] = struct{}{}
allowedOrigins["http://127.0.0.1:"+port] = struct{}{}
}
return func(c *gin.Context) {
origin := c.GetHeader("Origin")
if _, ok := allowedOrigins[origin]; ok {
c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
c.Writer.Header().Add("Vary", "Origin")
}
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE, PATCH")
if c.Request.Method == http.MethodOptions {
c.AbortWithStatus(http.StatusNoContent)
return
}
c.Next()
}
}