// Package api 为 HTTP handler 层(RESTful v1)。 // M1 覆盖认证(管理员/外部用户登录、会话、OTP)与用户管理 CRUD; // M2 覆盖 SSH 公钥管理(上传/重命名/吊销/列表); // M3 覆盖申请审批(公开提交 + 管理员审批 + 邮件通知)。 package api import ( "net/http" "runtime" "strconv" "time" "github.com/gin-gonic/gin" "ws_usernode/internal/auth" "ws_usernode/internal/config" "ws_usernode/internal/service" ) // Handler 聚合各模块 handler,作为路由注册的挂载点。 type Handler struct { Health *HealthHandler Auth *AuthHandler User *UserHandler Key *KeyHandler Approval *ApprovalHandler authSvc *service.AuthService auditSvc *service.AuditService } // New 创建 handler 集合。 func New(cfg *config.Config, authSvc *service.AuthService, userSvc *service.UserService, keySvc *service.KeyService, approvalSvc *service.ApprovalService, auditSvc *service.AuditService) *Handler { h := &Handler{ Health: &HealthHandler{startedAt: time.Now()}, authSvc: authSvc, auditSvc: auditSvc, } h.Auth = &AuthHandler{svc: authSvc, cfg: cfg} h.User = &UserHandler{svc: userSvc, cfg: cfg, h: h} h.Key = &KeyHandler{svc: keySvc, cfg: cfg, h: h} h.Approval = &ApprovalHandler{svc: approvalSvc, cfg: cfg, h: h} return h } // audit 记录管理操作审计(append-only)。actor 来自会话中间件。 func (h *Handler) audit(c *gin.Context, action, resourceType, resourceID string, detail any, result string) { var actorID uint var actorName string if sess := sessionFrom(c); sess != nil { actorID = sess.RefID if info, err := h.authSvc.Me(c.Request.Context(), sess.ID); err == nil { actorName = info.Username } else { actorName = sess.UserType + "#" + strconv.FormatUint(uint64(sess.RefID), 10) } } _ = h.auditSvc.Record(c.Request.Context(), actorID, actorName, action, resourceType, resourceID, detail, c.ClientIP(), result) } // HealthHandler 健康检查。 type HealthHandler struct { startedAt time.Time } func (h *HealthHandler) Healthz(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "status": "ok", "version": "0.3.0-m3", "uptime": time.Since(h.startedAt).String(), "go": runtime.Version(), "timestamp": time.Now().UTC().Format(time.RFC3339), }) } // sessionFrom 返回会话中间件注入的会话(未登录时为 nil)。 func sessionFrom(c *gin.Context) *auth.Session { if v, ok := c.Get(SessionContextKey); ok { if s, ok := v.(*auth.Session); ok { return s } } return nil } // ok 统一成功响应。 func ok(c *gin.Context, data any) { c.JSON(http.StatusOK, gin.H{"data": data}) } // fail 统一错误响应。 func fail(c *gin.Context, status int, msg string) { c.JSON(status, gin.H{"error": msg}) }