// Package router 负责路由注册与中间件装配。 package router import ( "log/slog" "net/http" "time" "github.com/gin-gonic/gin" "ws_usernode/internal/api" "ws_usernode/internal/config" "ws_usernode/internal/webui" ) // New 构建根 router:API v1 + 前端静态资源(go:embed)。 // production 模式启用 gin.ReleaseMode;否则启用调试模式与开发日志。 func New(cfg *config.Config, h *api.Handler, log *slog.Logger) *gin.Engine { if cfg.App.Env == "production" { gin.SetMode(gin.ReleaseMode) } r := gin.New() r.Use(gin.Recovery(), requestLogger(log)) // 健康检查(不进 /api/v1 前缀,便于负载均衡/探针) r.GET("/healthz", h.Health.Healthz) // RESTful API v1 v1 := r.Group("/api/v1") { auth := v1.Group("/auth") { // M1:captcha / otp/send / otp/login / admin/login / logout / me auth.GET("/captcha", notImplemented("图形验证码(M1)")) auth.POST("/otp/send", notImplemented("OTP 发送(M1)")) auth.POST("/otp/login", notImplemented("OTP 登录(M1)")) auth.POST("/admin/login", notImplemented("管理员登录(M1)")) auth.POST("/logout", notImplemented("登出(M1)")) auth.GET("/me", notImplemented("当前会话(M1)")) } v1.GET("/users", h.User.List) v1.POST("/users", h.User.Create) v1.POST("/users/:id/disable", notImplemented("禁用用户(M1)")) v1.POST("/users/:id/enable", notImplemented("启用用户(M1)")) v1.POST("/users/:id/extend", notImplemented("延期(M1)")) v1.POST("/approvals", notImplemented("提交申请(M3)")) v1.GET("/approvals", notImplemented("申请列表(M3)")) v1.POST("/approvals/:id/review", notImplemented("审批(M3)")) v1.GET("/audit", notImplemented("审计查询(M4)")) v1.GET("/audit/export", notImplemented("审计导出(M4)")) v1.GET("/settings", notImplemented("设置(M4)")) v1.PUT("/settings", notImplemented("设置(M4)")) } // 前端静态资源(go:embed;dev 阶段由 Vite dev server 代理,见 Makefile dev) r.NoRoute(gin.WrapH(webui.NewHandler())) return r } // notImplemented 返回 501 占位 handler,标注里程碑。 func notImplemented(what string) gin.HandlerFunc { return func(c *gin.Context) { c.JSON(http.StatusNotImplemented, gin.H{"error": "接口 " + what + " 尚未实现"}) } } // requestLogger 以 slog 输出结构化请求日志。 func requestLogger(log *slog.Logger) gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() c.Next() log.Info("http", "method", c.Request.Method, "path", c.Request.URL.Path, "status", c.Writer.Status(), "ip", c.ClientIP(), "latency_ms", time.Since(start).Milliseconds(), ) } }