Files
app-server/main.go
T
2026-08-07 13:36:56 +08:00

91 lines
2.5 KiB
Go

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()
}
}