package httpx import ( "net/http" "strings" "time" "github.com/gin-gonic/gin" "senlinai-agent/backend/internal/config" "senlinai-agent/backend/internal/logic/auth" ) type RouteRegistrar interface { Register(router gin.IRouter) } type PublicRouteRegistrar interface { RegisterPublic(router gin.IRouter) } // NewRouter 创建仅暴露 /api/v1 业务契约的公开路由。 func NewRouter(cfg config.Config, registrars ...RouteRegistrar) *gin.Engine { return newRouter(cfg, nil, registrars...) } // NewProtectedRouter 为 /api/v1 业务路由启用签名 session 校验。 func NewProtectedRouter(cfg config.Config, tokenVerifier func(string) (uint, error), registrars ...RouteRegistrar) *gin.Engine { return newRouter(cfg, tokenVerifier, registrars...) } func newRouter(cfg config.Config, tokenVerifier func(string) (uint, error), registrars ...RouteRegistrar) *gin.Engine { if cfg.Env == "test" { gin.SetMode(gin.TestMode) } router := gin.New() router.Use(gin.Recovery()) router.Use(cors(cfg)) router.GET("/healthz", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"status": "ok"}) }) api := router.Group("/api/v1") api.GET("/status", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"timestamp": time.Now().UTC().Format(time.RFC3339Nano)}) }) for _, registrar := range registrars { if publicRegistrar, ok := registrar.(PublicRouteRegistrar); ok { publicRegistrar.RegisterPublic(api) } } if tokenVerifier != nil { api.Use(auth.RequireUser(tokenVerifier)) } for _, registrar := range registrars { registrar.Register(api) } return router } func cors(cfg config.Config) gin.HandlerFunc { origins := cfg.AllowedOrigins if len(origins) == 0 && (cfg.Env == "development" || cfg.Env == "dev" || cfg.Env == "test") { origins = []string{ "http://localhost:5173", "http://127.0.0.1:5173", "http://localhost:5180", "http://127.0.0.1:5180", "http://tauri.localhost", } } // 非开发环境在未配置来源时默认拒绝跨域,防止意外开放浏览器访问。 allowedOrigins := make(map[string]struct{}, len(origins)) for _, origin := range origins { if origin = strings.TrimSpace(origin); origin != "" { allowedOrigins[origin] = struct{}{} } } return func(c *gin.Context) { origin := c.GetHeader("Origin") if _, ok := allowedOrigins[origin]; ok { c.Header("Access-Control-Allow-Origin", origin) c.Header("Vary", "Origin") c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS") c.Header("Access-Control-Allow-Headers", "Authorization, Content-Type") c.Header("Access-Control-Expose-Headers", "Content-Disposition") c.Header("Access-Control-Max-Age", "86400") } if c.Request.Method == http.MethodOptions { c.AbortWithStatus(http.StatusNoContent) return } c.Next() } }