// Package router 负责路由注册与中间件装配。 package router import ( "log/slog" "net/http" "time" "github.com/gin-gonic/gin" "ws_usernode/internal/api" "ws_usernode/internal/auth" "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, sessions auth.SessionStore, 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") { authGrp := v1.Group("/auth") { authGrp.GET("/captcha", h.Auth.Captcha) authGrp.POST("/otp/send", h.Auth.OTPSend) authGrp.POST("/otp/login", h.Auth.OTPLogin) authGrp.POST("/admin/login", h.Auth.AdminLogin) authGrp.POST("/admin/forgot", h.Auth.AdminForgot) authGrp.POST("/admin/reset", h.Auth.AdminReset) // 需要会话(管理员或外部用户) authed := authGrp.Group("", sessionMiddleware(sessions)) authed.POST("/logout", h.Auth.Logout) authed.GET("/me", h.Auth.Me) } // 用户管理(admin) users := v1.Group("/users", sessionMiddleware(sessions), requireUserType(auth.SessionUserAdmin)) { users.GET("", h.User.List) users.POST("", h.User.Create) users.GET("/:id", h.User.Get) users.PATCH("/:id", h.User.Update) users.POST("/:id/disable", h.User.Disable) users.POST("/:id/enable", h.User.Enable) users.POST("/:id/extend", h.User.Extend) users.DELETE("/:id", h.User.Delete) users.GET("/:id/keys", h.Key.ListForUser) } // 我的密钥(外部用户,自助管理;仅用户上传,管理员不代签) me := v1.Group("/me", sessionMiddleware(sessions), requireUserType(auth.SessionUserUser)) { me.GET("/keys", h.Key.ListMine) me.POST("/keys", h.Key.Create) me.PATCH("/keys/:id", h.Key.Rename) me.DELETE("/keys/:id", h.Key.Revoke) } // 后续里程碑 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(), ) } }