package main import ( "flag" "fmt" "net/http" "os" "strings" "github.com/gin-gonic/gin" "github.com/spf13/viper" "github.com/lineup/app-server/modules" ) func main() { var cfgFile string flag.StringVar(&cfgFile, "config", "configs/lineup.yaml", "config file") flag.Parse() vp := viper.New() vp.SetConfigFile(cfgFile) if err := vp.ReadInConfig(); err != nil { fmt.Println("读取配置失败:", err) os.Exit(1) } vp.SetEnvPrefix("LINEUP") vp.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) vp.AutomaticEnv() gin.SetMode(gin.ReleaseMode) if vp.GetString("mode") == "debug" { gin.SetMode(gin.DebugMode) } r := gin.Default() r.Use(corsMiddleware(vp.GetStringSlice("client.allowedOrigins"))) // 静态文件服务 r.Static("/chat", "./assets/chat") // Markdown dependencies are served locally so the chat remains available on // a private network and never executes content from a third-party CDN. // They intentionally use their own prefix: /chat already owns a wildcard // static route, which Gin cannot combine with nested exact file routes. r.Static("/vendor/marked", "./node_modules/marked/lib") r.Static("/vendor/dompurify", "./node_modules/dompurify/dist") r.StaticFile("/", "./assets/chat/index.html") // 注册模块路由 modules.SetupRoutes(r, vp) fmt.Printf("LineUp App Server 启动 → %s\n", vp.GetString("addr")) fmt.Printf("WuKongIM API → %s\n", vp.GetString("wukongim.apiURL")) r.Run(vp.GetString("addr")) } // corsMiddleware permits only configured LineUp hosts to call the JSON API. // The Web reference host is served same-origin and does not need an Origin // header. Tauri uses its own WebView origin, so it must be explicitly allowed. func corsMiddleware(origins []string) gin.HandlerFunc { allowed := make(map[string]struct{}, len(origins)) for _, origin := range origins { if origin = strings.TrimSpace(origin); origin != "" { allowed[origin] = struct{}{} } } return func(c *gin.Context) { origin := c.GetHeader("Origin") if origin != "" { if _, ok := allowed[origin]; !ok { if c.Request.Method == http.MethodOptions { c.AbortWithStatus(http.StatusForbidden) return } c.Next() return } c.Header("Access-Control-Allow-Origin", origin) c.Header("Access-Control-Allow-Methods", "GET, POST, OPTIONS") c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization, X-LineUp-Agent-Secret") c.Header("Vary", "Origin") } if c.Request.Method == http.MethodOptions { c.AbortWithStatus(http.StatusNoContent) return } c.Next() } }