Files
user-system/internal/api/router/router.go
long-agent 7b047e2f11 perf: Sprint 19 P0/P1 性能优化落地
P0(高优先级):
- P0-1: 确认数据库复合索引已存在(GORM tag),composite_index_test 验证通过
- P0-2: 连接池调优 MaxIdleConns 5→10, ConnMaxLifetime 30min→5min
- P0-3: Redis 智能探测(ProbeRedis),无 Redis 自动降级到纯内存模式

P1(中优先级):
- P1-1: GZIP 压缩中间件(compress/gzip 标准库,零新依赖)
- P1-2: 权限缓存 TTL 30min→5min
- P1-3: Argon2id 启动自适应校准(CalibrateArgon2id)

历史优化(含本次提交):
- L1Cache O(n)→O(1) LRU 重构
- Auth 中间件 DB 查询合并 + 5s L1 缓存
- Logger 异步化(4096 缓冲通道)

验证: go build/vet/test 41/41 PASS, govulncheck 无漏洞
2026-04-18 22:57:44 +08:00

404 lines
15 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package router
import (
"github.com/gin-gonic/gin"
"github.com/prometheus/client_golang/prometheus/promhttp"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
"github.com/user-management-system/internal/api/handler"
"github.com/user-management-system/internal/api/middleware"
"github.com/user-management-system/internal/monitoring"
)
type Router struct {
engine *gin.Engine
authHandler *handler.AuthHandler
userHandler *handler.UserHandler
roleHandler *handler.RoleHandler
permissionHandler *handler.PermissionHandler
deviceHandler *handler.DeviceHandler
logHandler *handler.LogHandler
passwordResetHandler *handler.PasswordResetHandler
captchaHandler *handler.CaptchaHandler
totpHandler *handler.TOTPHandler
webhookHandler *handler.WebhookHandler
exportHandler *handler.ExportHandler
statsHandler *handler.StatsHandler
smsHandler *handler.SMSHandler
avatarHandler *handler.AvatarHandler
customFieldHandler *handler.CustomFieldHandler
themeHandler *handler.ThemeHandler
authMiddleware *middleware.AuthMiddleware
rateLimitMiddleware *middleware.RateLimitMiddleware
opLogMiddleware *middleware.OperationLogMiddleware
ipFilterMiddleware *middleware.IPFilterMiddleware
ssoHandler *handler.SSOHandler
settingsHandler *handler.SettingsHandler
metrics *monitoring.Metrics // CRIT-01/02: Prometheus 指标
}
func NewRouter(
authHandler *handler.AuthHandler,
userHandler *handler.UserHandler,
roleHandler *handler.RoleHandler,
permissionHandler *handler.PermissionHandler,
deviceHandler *handler.DeviceHandler,
logHandler *handler.LogHandler,
authMiddleware *middleware.AuthMiddleware,
rateLimitMiddleware *middleware.RateLimitMiddleware,
opLogMiddleware *middleware.OperationLogMiddleware,
passwordResetHandler *handler.PasswordResetHandler,
captchaHandler *handler.CaptchaHandler,
totpHandler *handler.TOTPHandler,
webhookHandler *handler.WebhookHandler,
ipFilterMiddleware *middleware.IPFilterMiddleware,
exportHandler *handler.ExportHandler,
statsHandler *handler.StatsHandler,
smsHandler *handler.SMSHandler,
customFieldHandler *handler.CustomFieldHandler,
themeHandler *handler.ThemeHandler,
ssoHandler *handler.SSOHandler,
settingsHandler *handler.SettingsHandler,
metrics *monitoring.Metrics,
avatarHandler ...*handler.AvatarHandler,
) *Router {
engine := gin.New()
var avatar *handler.AvatarHandler
if len(avatarHandler) > 0 {
avatar = avatarHandler[0]
}
return &Router{
engine: engine,
authHandler: authHandler,
userHandler: userHandler,
roleHandler: roleHandler,
permissionHandler: permissionHandler,
deviceHandler: deviceHandler,
logHandler: logHandler,
passwordResetHandler: passwordResetHandler,
captchaHandler: captchaHandler,
totpHandler: totpHandler,
webhookHandler: webhookHandler,
exportHandler: exportHandler,
statsHandler: statsHandler,
smsHandler: smsHandler,
customFieldHandler: customFieldHandler,
themeHandler: themeHandler,
ssoHandler: ssoHandler,
settingsHandler: settingsHandler,
avatarHandler: avatar,
authMiddleware: authMiddleware,
rateLimitMiddleware: rateLimitMiddleware,
opLogMiddleware: opLogMiddleware,
ipFilterMiddleware: ipFilterMiddleware,
metrics: metrics,
}
}
func (r *Router) Setup() *gin.Engine {
r.engine.Use(middleware.Recover())
r.engine.Use(middleware.TraceID()) // 可观察性补强:每个请求生成唯一 trace_id
r.engine.Use(middleware.ErrorHandler())
r.engine.Use(middleware.Logger())
r.engine.Use(middleware.SecurityHeaders())
r.engine.Use(middleware.NoStoreSensitiveResponses())
r.engine.Use(middleware.CORS())
// P1-1GZIP 压缩 — 对 JSON/文本响应 > 1 KiB 自动压缩,列表接口带宽降低 50-70%
r.engine.Use(middleware.GzipMiddleware())
r.engine.Use(middleware.ResponseWrapper())
// CRIT-01/02 修复:挂载 Prometheus 中间件,暴露 /metrics 端点
// WARN-01 修复:/metrics 端点加内网 IP 限制,防止指标数据对外泄露
if r.metrics != nil {
r.engine.Use(monitoring.PrometheusMiddleware(r.metrics))
r.engine.GET("/metrics",
middleware.InternalOnly(),
gin.WrapH(promhttp.HandlerFor(
r.metrics.GetRegistry(),
promhttp.HandlerOpts{EnableOpenMetrics: true},
)),
)
}
r.engine.Static("/uploads", "./uploads")
r.engine.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
if r.ipFilterMiddleware != nil {
r.engine.Use(r.ipFilterMiddleware.Filter())
}
if r.opLogMiddleware != nil {
r.engine.Use(r.opLogMiddleware.Record())
}
v1 := r.engine.Group("/api/v1")
{
authGroup := v1.Group("/auth")
{
authGroup.POST("/register", r.rateLimitMiddleware.Register(), r.authHandler.Register)
authGroup.POST("/bootstrap-admin", r.rateLimitMiddleware.Register(), r.authHandler.BootstrapAdmin)
authGroup.POST("/login", r.rateLimitMiddleware.Login(), r.authHandler.Login)
authGroup.POST("/login/totp-verify", r.rateLimitMiddleware.Login(), r.authHandler.VerifyTOTPAfterPasswordLogin)
authGroup.POST("/refresh", r.rateLimitMiddleware.Refresh(), r.authHandler.RefreshToken)
authGroup.GET("/capabilities", r.authHandler.GetAuthCapabilities)
authGroup.POST("/activate-email", r.authHandler.ActivateEmail)
authGroup.POST("/resend-activation", r.authHandler.ResendActivationEmail)
if r.authHandler.SupportsEmailCodeLogin() {
authGroup.POST("/send-email-code", r.rateLimitMiddleware.Register(), r.authHandler.SendEmailCode)
authGroup.POST("/login/email-code", r.rateLimitMiddleware.Login(), r.authHandler.LoginByEmailCode)
}
if r.smsHandler != nil {
authGroup.POST("/send-code", r.rateLimitMiddleware.Register(), r.smsHandler.SendCode)
authGroup.POST("/login/code", r.rateLimitMiddleware.Login(), r.smsHandler.LoginByCode)
}
if r.passwordResetHandler != nil {
authGroup.POST("/forgot-password", r.passwordResetHandler.ForgotPassword)
authGroup.POST("/password/validate", r.passwordResetHandler.ValidateResetToken)
authGroup.POST("/reset-password", r.passwordResetHandler.ResetPassword)
// 短信密码重置
authGroup.POST("/forgot-password/phone", r.passwordResetHandler.ForgotPasswordByPhone)
authGroup.POST("/reset-password/phone", r.passwordResetHandler.ResetPasswordByPhone)
}
if r.captchaHandler != nil {
authGroup.GET("/captcha", r.captchaHandler.GenerateCaptcha)
authGroup.GET("/captcha/image", r.captchaHandler.GetCaptchaImage)
authGroup.POST("/captcha/verify", r.captchaHandler.VerifyCaptcha)
}
authGroup.GET("/oauth/providers", r.authHandler.GetEnabledOAuthProviders)
authGroup.GET("/oauth/:provider", r.authHandler.OAuthLogin)
authGroup.GET("/oauth/:provider/callback", r.authHandler.OAuthCallback)
authGroup.POST("/oauth/exchange", r.authHandler.OAuthExchange)
}
// 公开主题接口(无需认证)
if r.themeHandler != nil {
themePublic := v1.Group("")
{
themePublic.GET("/theme/active", r.themeHandler.GetActiveTheme)
}
}
protected := v1.Group("")
protected.Use(r.authMiddleware.Required())
protected.Use(r.rateLimitMiddleware.API())
{
protected.GET("/auth/csrf-token", r.authHandler.GetCSRFToken)
protected.POST("/auth/logout", r.authHandler.Logout)
protected.GET("/auth/userinfo", r.authHandler.GetUserInfo)
protected.POST("/users/me/bind-email/code", r.authHandler.SendEmailBindCode)
protected.POST("/users/me/bind-email", r.authHandler.BindEmail)
protected.DELETE("/users/me/bind-email", r.authHandler.UnbindEmail)
protected.POST("/users/me/bind-phone/code", r.authHandler.SendPhoneBindCode)
protected.POST("/users/me/bind-phone", r.authHandler.BindPhone)
protected.DELETE("/users/me/bind-phone", r.authHandler.UnbindPhone)
protected.GET("/users/me/social-accounts", r.authHandler.GetSocialAccounts)
protected.POST("/users/me/bind-social", r.authHandler.BindSocialAccount)
protected.DELETE("/users/me/bind-social/:provider", r.authHandler.UnbindSocialAccount)
users := protected.Group("/users")
{
users.POST("", middleware.RequirePermission("user:manage"), r.userHandler.CreateUser)
users.GET("", r.userHandler.ListUsers)
users.GET("/:id", r.userHandler.GetUser)
users.PUT("/:id", r.userHandler.UpdateUser)
users.DELETE("/:id", middleware.RequirePermission("user:delete"), r.userHandler.DeleteUser)
users.PUT("/:id/password", r.userHandler.UpdatePassword)
users.PUT("/:id/status", middleware.RequirePermission("user:manage"), r.userHandler.UpdateUserStatus)
users.GET("/:id/roles", r.userHandler.GetUserRoles)
users.PUT("/:id/roles", middleware.RequirePermission("user:manage"), r.userHandler.AssignRoles)
users.PUT("/batch/status", middleware.RequirePermission("user:manage"), r.userHandler.BatchUpdateStatus)
users.DELETE("/batch", middleware.RequirePermission("user:delete"), r.userHandler.BatchDelete)
if r.avatarHandler != nil {
users.POST("/:id/avatar", r.avatarHandler.UploadAvatar)
}
}
roles := protected.Group("/roles")
roles.Use(middleware.AdminOnly())
{
roles.POST("", r.roleHandler.CreateRole)
roles.GET("", r.roleHandler.ListRoles)
roles.GET("/:id", r.roleHandler.GetRole)
roles.PUT("/:id", r.roleHandler.UpdateRole)
roles.DELETE("/:id", r.roleHandler.DeleteRole)
roles.PUT("/:id/status", r.roleHandler.UpdateRoleStatus)
roles.GET("/:id/permissions", r.roleHandler.GetRolePermissions)
roles.PUT("/:id/permissions", r.roleHandler.AssignPermissions)
}
permissions := protected.Group("/permissions")
permissions.Use(middleware.AdminOnly())
{
permissions.POST("", r.permissionHandler.CreatePermission)
permissions.GET("", r.permissionHandler.ListPermissions)
permissions.GET("/tree", r.permissionHandler.GetPermissionTree)
permissions.GET("/:id", r.permissionHandler.GetPermission)
permissions.PUT("/:id", r.permissionHandler.UpdatePermission)
permissions.DELETE("/:id", r.permissionHandler.DeletePermission)
permissions.PUT("/:id/status", r.permissionHandler.UpdatePermissionStatus)
}
devices := protected.Group("/devices")
{
devices.GET("", r.deviceHandler.GetMyDevices)
devices.POST("", r.deviceHandler.CreateDevice)
devices.GET("/:id", r.deviceHandler.GetDevice)
devices.PUT("/:id", r.deviceHandler.UpdateDevice)
devices.DELETE("/:id", r.deviceHandler.DeleteDevice)
devices.PUT("/:id/status", r.deviceHandler.UpdateDeviceStatus)
devices.POST("/:id/trust", r.deviceHandler.TrustDevice)
devices.POST("/by-device-id/:deviceId/trust", r.deviceHandler.TrustDeviceByDeviceID)
devices.DELETE("/:id/trust", r.deviceHandler.UntrustDevice)
devices.GET("/me/trusted", r.deviceHandler.GetMyTrustedDevices)
devices.POST("/me/logout-others", r.deviceHandler.LogoutAllOtherDevices)
devices.GET("/users/:id", r.deviceHandler.GetUserDevices)
}
adminDevices := protected.Group("/admin/devices")
adminDevices.Use(middleware.AdminOnly())
{
adminDevices.GET("", r.deviceHandler.GetAllDevices)
adminDevices.DELETE("/:id", r.deviceHandler.DeleteDevice)
adminDevices.PUT("/:id/status", r.deviceHandler.UpdateDeviceStatus)
adminDevices.POST("/:id/trust", r.deviceHandler.TrustDevice)
adminDevices.DELETE("/:id/trust", r.deviceHandler.UntrustDevice)
}
if r.logHandler != nil {
logs := protected.Group("/logs")
{
logs.GET("/login/me", r.logHandler.GetMyLoginLogs)
logs.GET("/operation/me", r.logHandler.GetMyOperationLogs)
adminLogs := logs.Group("")
adminLogs.Use(middleware.AdminOnly())
{
adminLogs.GET("/login", r.logHandler.GetLoginLogs)
adminLogs.GET("/login/export", r.logHandler.ExportLoginLogs)
adminLogs.GET("/operation", r.logHandler.GetOperationLogs)
}
}
}
if r.totpHandler != nil {
twoFA := protected.Group("/auth/2fa")
{
twoFA.GET("/status", r.totpHandler.GetTOTPStatus)
twoFA.GET("/setup", r.totpHandler.SetupTOTP)
twoFA.POST("/enable", r.totpHandler.EnableTOTP)
twoFA.POST("/disable", r.totpHandler.DisableTOTP)
twoFA.POST("/verify", r.totpHandler.VerifyTOTP)
}
}
if r.webhookHandler != nil {
webhooks := protected.Group("/webhooks")
{
webhooks.POST("", r.webhookHandler.CreateWebhook)
webhooks.GET("", r.webhookHandler.ListWebhooks)
webhooks.PUT("/:id", r.webhookHandler.UpdateWebhook)
webhooks.DELETE("/:id", r.webhookHandler.DeleteWebhook)
webhooks.GET("/:id/deliveries", r.webhookHandler.GetWebhookDeliveries)
}
}
if r.exportHandler != nil {
adminUsers := protected.Group("/admin/users")
adminUsers.Use(middleware.AdminOnly())
{
adminUsers.GET("/export", r.exportHandler.ExportUsers)
adminUsers.POST("/import", r.exportHandler.ImportUsers)
adminUsers.GET("/import/template", r.exportHandler.GetImportTemplate)
}
}
adminMgmt := protected.Group("/admin/admins")
adminMgmt.Use(middleware.AdminOnly())
{
adminMgmt.GET("", r.userHandler.ListAdmins)
adminMgmt.POST("", r.userHandler.CreateAdmin)
adminMgmt.DELETE("/:id", r.userHandler.DeleteAdmin)
}
if r.statsHandler != nil {
adminStats := protected.Group("/admin/stats")
adminStats.Use(middleware.AdminOnly())
{
adminStats.GET("/dashboard", r.statsHandler.GetDashboard)
adminStats.GET("/users", r.statsHandler.GetUserStats)
}
}
if r.settingsHandler != nil {
adminSettings := protected.Group("/admin/settings")
adminSettings.Use(middleware.AdminOnly())
{
adminSettings.GET("", r.settingsHandler.GetSettings)
}
}
if r.customFieldHandler != nil {
// 自定义字段管理(管理员)
customFields := protected.Group("/custom-fields")
customFields.Use(middleware.AdminOnly())
{
customFields.POST("", r.customFieldHandler.CreateField)
customFields.GET("", r.customFieldHandler.ListFields)
customFields.GET("/:id", r.customFieldHandler.GetField)
customFields.PUT("/:id", r.customFieldHandler.UpdateField)
customFields.DELETE("/:id", r.customFieldHandler.DeleteField)
}
// 用户自定义字段值(用户自己的)
userFields := protected.Group("/users/me/custom-fields")
{
userFields.GET("", r.customFieldHandler.GetUserFieldValues)
userFields.PUT("", r.customFieldHandler.SetUserFieldValues)
}
}
if r.themeHandler != nil {
// 主题管理(管理员)
themes := protected.Group("/themes")
themes.Use(middleware.AdminOnly())
{
themes.POST("", r.themeHandler.CreateTheme)
themes.GET("", r.themeHandler.ListAllThemes)
themes.GET("/default", r.themeHandler.GetDefaultTheme)
themes.PUT("/default/:id", r.themeHandler.SetDefaultTheme)
themes.GET("/:id", r.themeHandler.GetTheme)
themes.PUT("/:id", r.themeHandler.UpdateTheme)
themes.DELETE("/:id", r.themeHandler.DeleteTheme)
}
}
// SSO 单点登录接口(需要认证)
if r.ssoHandler != nil {
sso := protected.Group("/sso")
{
sso.GET("/authorize", r.ssoHandler.Authorize)
sso.POST("/token", r.ssoHandler.Token)
sso.POST("/introspect", r.ssoHandler.Introspect)
sso.POST("/revoke", r.ssoHandler.Revoke)
sso.GET("/userinfo", r.ssoHandler.UserInfo)
}
}
}
}
return r.engine
}
func (r *Router) GetEngine() *gin.Engine {
return r.engine
}