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
167 lines
4.4 KiB
Go
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()
|
|
}
|
|
}
|