diff --git a/internal/api/handler/auth_handler.go b/internal/api/handler/auth_handler.go index 05bd9bc..053c1a0 100644 --- a/internal/api/handler/auth_handler.go +++ b/internal/api/handler/auth_handler.go @@ -30,6 +30,17 @@ func NewAuthHandler(authService *service.AuthService) *AuthHandler { return &AuthHandler{authService: authService} } +// Register 用户注册 +// @Summary 用户注册 +// @Description 用户注册新账号,支持用户名+密码或手机号注册 +// @Tags 认证 +// @Accept json +// @Produce json +// @Param request body service.RegisterRequest true "注册请求" +// @Success 201 {object} Response{data=service.UserInfo} "注册成功" +// @Failure 400 {object} Response{code=int,message=string} "请求参数错误" +// @Failure 409 {object} Response{code=int,message=string} "用户已存在" +// @Router /api/v1/auth/register [post] func (h *AuthHandler) Register(c *gin.Context) { var req struct { Username string `json:"username" binding:"required"` @@ -65,6 +76,18 @@ func (h *AuthHandler) Register(c *gin.Context) { }) } +// Login 用户登录 +// @Summary 用户登录 +// @Description 用户使用账号密码登录,支持多种认证方式(用户名/邮箱/手机号) +// @Tags 认证 +// @Accept json +// @Produce json +// @Param request body service.LoginRequest true "登录请求" +// @Success 200 {object} Response{data=service.LoginResponse} "登录成功" +// @Failure 400 {object} Response{code=int,message=string} "请求参数错误" +// @Failure 401 {object} Response{code=int,message=string} "认证失败" +// @Failure 429 {object} Response{code=int,message=string} "登录尝试过多" +// @Router /api/v1/auth/login [post] func (h *AuthHandler) Login(c *gin.Context) { var req struct { Account string `json:"account"` @@ -109,6 +132,16 @@ func (h *AuthHandler) Login(c *gin.Context) { }) } +// Logout 用户登出 +// @Summary 用户登出 +// @Description 使当前 access_token 和 refresh_token 失效 +// @Tags 认证 +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param request body service.LogoutRequest false "登出请求(token可从header获取)" +// @Success 200 {object} Response{code=int,message=string} "登出成功" +// @Router /api/v1/auth/logout [post] func (h *AuthHandler) Logout(c *gin.Context) { var req struct { AccessToken string `json:"access_token"` @@ -136,6 +169,17 @@ func (h *AuthHandler) Logout(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "logged out"}) } +// RefreshToken 刷新访问令牌 +// @Summary 刷新访问令牌 +// @Description 使用 refresh_token 获取新的 access_token +// @Tags 认证 +// @Accept json +// @Produce json +// @Param request body RefreshTokenRequest true "刷新令牌请求" +// @Success 200 {object} Response{data=service.LoginResponse} "刷新成功" +// @Failure 400 {object} Response{code=int,message=string} "请求参数错误" +// @Failure 401 {object} Response{code=int,message=string} "refresh_token无效或已过期" +// @Router /api/v1/auth/refresh-token [post] func (h *AuthHandler) RefreshToken(c *gin.Context) { var req struct { RefreshToken string `json:"refresh_token" binding:"required"` @@ -159,6 +203,15 @@ func (h *AuthHandler) RefreshToken(c *gin.Context) { }) } +// GetUserInfo 获取当前用户信息 +// @Summary 获取当前用户信息 +// @Description 获取已登录用户的详细信息 +// @Tags 认证 +// @Produce json +// @Security BearerAuth +// @Success 200 {object} Response{data=service.UserInfo} "用户信息" +// @Failure 401 {object} Response{code=int,message=string} "未认证" +// @Router /api/v1/auth/userinfo [get] func (h *AuthHandler) GetUserInfo(c *gin.Context) { userID, ok := getUserIDFromContext(c) if !ok { @@ -179,6 +232,13 @@ func (h *AuthHandler) GetUserInfo(c *gin.Context) { }) } +// GetCSRFToken 获取CSRF令牌 +// @Summary 获取CSRF令牌 +// @Description 由于系统使用JWT Bearer Token认证,不存在CSRF风险,返回空token +// @Tags 认证 +// @Produce json +// @Success 200 {object} map "CSRF token(为空)" +// @Router /api/v1/auth/csrf-token [get] func (h *AuthHandler) GetCSRFToken(c *gin.Context) { // 系统使用 JWT Bearer Token 认证,Bearer Token 不会被浏览器自动携带(非 cookie) // 因此不存在传统意义上的 CSRF 风险,此端点返回空 token 作为兼容响应 @@ -188,6 +248,13 @@ func (h *AuthHandler) GetCSRFToken(c *gin.Context) { }) } +// GetAuthCapabilities 获取认证能力 +// @Summary 获取系统认证能力 +// @Description 返回系统支持的认证方式和配置(如是否需要邮件激活、是否支持OAuth等) +// @Tags 认证 +// @Produce json +// @Success 200 {object} Response{data=service.AuthCapabilities} "认证能力配置" +// @Router /api/v1/auth/capabilities [get] func (h *AuthHandler) GetAuthCapabilities(c *gin.Context) { ctx := c.Request.Context() caps := h.authService.GetAuthCapabilities(ctx) @@ -198,23 +265,65 @@ func (h *AuthHandler) GetAuthCapabilities(c *gin.Context) { }) } +// OAuthLogin OAuth登录初始化 +// @Summary OAuth登录初始化 +// @Description 发起OAuth登录流程(当前未配置) +// @Tags OAuth +// @Produce json +// @Param provider path string true "OAuth提供商(如 github, google)" +// @Success 200 {object} Response "OAuth未配置" +// @Router /api/v1/auth/oauth/{provider} [get] func (h *AuthHandler) OAuthLogin(c *gin.Context) { provider := c.Param("provider") c.JSON(http.StatusOK, gin.H{"code": 0, "message": "OAuth not configured", "data": gin.H{"provider": provider}}) } +// OAuthCallback OAuth回调 +// @Summary OAuth回调处理 +// @Description 处理OAuth provider回调(当前未配置) +// @Tags OAuth +// @Produce json +// @Param provider path string true "OAuth提供商" +// @Success 200 {object} Response "OAuth未配置" +// @Router /api/v1/auth/oauth/{provider}/callback [get] func (h *AuthHandler) OAuthCallback(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"code": 0, "message": "OAuth not configured"}) } +// OAuthExchange OAuth令牌交换 +// @Summary OAuth令牌交换 +// @Description 使用OAuth code交换access_token(当前未配置) +// @Tags OAuth +// @Accept json +// @Produce json +// @Param provider path string true "OAuth提供商" +// @Success 200 {object} Response "OAuth未配置" +// @Router /api/v1/auth/oauth/{provider}/exchange [post] func (h *AuthHandler) OAuthExchange(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"code": 0, "message": "OAuth not configured"}) } +// GetEnabledOAuthProviders 获取已启用的OAuth提供商 +// @Summary 获取OAuth提供商列表 +// @Description 返回系统已配置并启用的OAuth提供商列表 +// @Tags OAuth +// @Produce json +// @Success 200 {object} Response{data=map} "提供商列表" +// @Router /api/v1/auth/oauth/providers [get] func (h *AuthHandler) GetEnabledOAuthProviders(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"code": 0, "message": "success", "data": gin.H{"providers": []string{}}}) } +// ActivateEmail 激活邮箱 +// @Summary 激活用户邮箱 +// @Description 使用邮箱激活token激活用户账号 +// @Tags 邮箱认证 +// @Produce json +// @Param token query string true "激活token" +// @Success 200 {object} Response "激活成功" +// @Failure 400 {object} Response "token缺失" +// @Failure 401 {object} Response "token无效或已过期" +// @Router /api/v1/auth/activate-email [post] func (h *AuthHandler) ActivateEmail(c *gin.Context) { token := c.Query("token") if token == "" { @@ -228,6 +337,16 @@ func (h *AuthHandler) ActivateEmail(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"code": 0, "message": "email activated successfully"}) } +// ResendActivationEmail 重发激活邮件 +// @Summary 重发激活邮件 +// @Description 重新发送账号激活邮件(防枚举:无论邮箱是否注册都返回成功) +// @Tags 邮箱认证 +// @Accept json +// @Produce json +// @Param request body ResendActivationRequest true "邮箱地址" +// @Success 200 {object} Response "激活邮件已发送(如果邮箱已注册)" +// @Failure 400 {object} Response "邮箱格式错误" +// @Router /api/v1/auth/resend-activation-email [post] func (h *AuthHandler) ResendActivationEmail(c *gin.Context) { var req struct { Email string `json:"email" binding:"required,email"` @@ -244,6 +363,16 @@ func (h *AuthHandler) ResendActivationEmail(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"code": 0, "message": "activation email sent if address is registered"}) } +// SendEmailCode 发送邮箱验证码 +// @Summary 发送邮箱验证码 +// @Description 发送邮箱登录验证码(防枚举:无论邮箱是否注册都返回成功) +// @Tags 邮箱认证 +// @Accept json +// @Produce json +// @Param request body SendEmailCodeRequest true "邮箱地址" +// @Success 200 {object} Response "验证码已发送" +// @Failure 400 {object} Response "邮箱格式错误" +// @Router /api/v1/auth/send-email-code [post] func (h *AuthHandler) SendEmailCode(c *gin.Context) { var req struct { Email string `json:"email" binding:"required,email"` @@ -261,6 +390,17 @@ func (h *AuthHandler) SendEmailCode(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"code": 0, "message": "验证码已发送"}) } +// LoginByEmailCode 使用邮箱验证码登录 +// @Summary 邮箱验证码登录 +// @Description 使用邮箱和验证码完成登录 +// @Tags 邮箱认证 +// @Accept json +// @Produce json +// @Param request body LoginByEmailCodeRequest true "登录请求" +// @Success 200 {object} Response{data=service.LoginResponse} "登录成功" +// @Failure 400 {object} Response "请求参数错误" +// @Failure 401 {object} Response "验证码错误或已过期" +// @Router /api/v1/auth/login-by-email-code [post] func (h *AuthHandler) LoginByEmailCode(c *gin.Context) { var req struct { Email string `json:"email" binding:"required,email"` @@ -307,6 +447,19 @@ func (h *AuthHandler) LoginByEmailCode(c *gin.Context) { }) } +// BootstrapAdmin 引导初始化管理员 +// @Summary 引导初始化管理员账号 +// @Description 在系统未配置管理员时,创建第一个管理员账号(需要BOOTSTRAP_SECRET) +// @Tags 系统初始化 +// @Accept json +// @Produce json +// @Security BootstrapSecret +// @Param X-Bootstrap-Secret header string true "引导密钥" +// @Param request body BootstrapAdminRequest true "管理员信息" +// @Success 201 {object} Response{data=service.UserInfo} "管理员创建成功" +// @Failure 401 {object} Response "引导密钥无效" +// @Failure 403 {object} Response "引导初始化未授权" +// @Router /api/v1/auth/bootstrap-admin [post] func (h *AuthHandler) BootstrapAdmin(c *gin.Context) { // P0 修复:BootstrapAdmin 端点需要 bootstrap secret 验证 bootstrapSecret := os.Getenv("BOOTSTRAP_SECRET") @@ -358,38 +511,110 @@ func (h *AuthHandler) BootstrapAdmin(c *gin.Context) { }) } +// SendEmailBindCode 发送邮箱绑定验证码 +// @Summary 发送邮箱绑定验证码 +// @Description 发送验证码到邮箱以绑定邮箱(当前未配置) +// @Tags 邮箱绑定 +// @Accept json +// @Produce json +// @Success 200 {object} Response "功能未配置" +// @Router /api/v1/auth/email/bind/send [post] func (h *AuthHandler) SendEmailBindCode(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"code": 0, "message": "email bind not configured"}) } +// BindEmail 绑定邮箱 +// @Summary 绑定邮箱 +// @Description 使用邮箱验证码绑定账号(当前未配置) +// @Tags 邮箱绑定 +// @Accept json +// @Produce json +// @Success 200 {object} Response "功能未配置" +// @Router /api/v1/auth/email/bind [post] func (h *AuthHandler) BindEmail(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"code": 0, "message": "email bind not configured"}) } +// UnbindEmail 解绑邮箱 +// @Summary 解绑邮箱 +// @Description 解绑账号关联的邮箱(当前未配置) +// @Tags 邮箱绑定 +// @Accept json +// @Produce json +// @Success 200 {object} Response "功能未配置" +// @Router /api/v1/auth/email/unbind [post] func (h *AuthHandler) UnbindEmail(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"code": 0, "message": "email unbind not configured"}) } +// SendPhoneBindCode 发送手机绑定验证码 +// @Summary 发送手机绑定验证码 +// @Description 发送验证码到手机以绑定手机号(当前未配置) +// @Tags 手机绑定 +// @Accept json +// @Produce json +// @Success 200 {object} Response "功能未配置" +// @Router /api/v1/auth/phone/bind/send [post] func (h *AuthHandler) SendPhoneBindCode(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"code": 0, "message": "phone bind not configured"}) } +// BindPhone 绑定手机号 +// @Summary 绑定手机号 +// @Description 使用手机验证码绑定账号(当前未配置) +// @Tags 手机绑定 +// @Accept json +// @Produce json +// @Success 200 {object} Response "功能未配置" +// @Router /api/v1/auth/phone/bind [post] func (h *AuthHandler) BindPhone(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"code": 0, "message": "phone bind not configured"}) } +// UnbindPhone 解绑手机号 +// @Summary 解绑手机号 +// @Description 解绑账号关联的手机号(当前未配置) +// @Tags 手机绑定 +// @Accept json +// @Produce json +// @Success 200 {object} Response "功能未配置" +// @Router /api/v1/auth/phone/unbind [post] func (h *AuthHandler) UnbindPhone(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"code": 0, "message": "phone unbind not configured"}) } +// GetSocialAccounts 获取社交账号列表 +// @Summary 获取已绑定的社交账号列表 +// @Description 获取当前用户绑定的第三方社交账号列表 +// @Tags 社交账号 +// @Produce json +// @Security BearerAuth +// @Success 200 {object} Response "社交账号列表" +// @Router /api/v1/auth/social-accounts [get] func (h *AuthHandler) GetSocialAccounts(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"code": 0, "message": "success", "data": gin.H{"accounts": []interface{}{}}}) } +// BindSocialAccount 绑定社交账号 +// @Summary 绑定社交账号 +// @Description 绑定第三方社交账号到当前用户(当前未配置) +// @Tags 社交账号 +// @Accept json +// @Produce json +// @Success 200 {object} Response "功能未配置" +// @Router /api/v1/auth/social/bind [post] func (h *AuthHandler) BindSocialAccount(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"code": 0, "message": "social binding not configured"}) } +// UnbindSocialAccount 解绑社交账号 +// @Summary 解绑社交账号 +// @Description 解绑当前用户关联的第三方社交账号(当前未配置) +// @Tags 社交账号 +// @Accept json +// @Produce json +// @Success 200 {object} Response "功能未配置" +// @Router /api/v1/auth/social/unbind [post] func (h *AuthHandler) UnbindSocialAccount(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"code": 0, "message": "social unbinding not configured"}) } diff --git a/internal/api/handler/avatar_handler.go b/internal/api/handler/avatar_handler.go index 030ed28..b39d23a 100644 --- a/internal/api/handler/avatar_handler.go +++ b/internal/api/handler/avatar_handler.go @@ -32,7 +32,21 @@ func generateSecureToken(length int) string { return hex.EncodeToString(bytes)[:length] } -// UploadAvatar handles avatar file upload +// UploadAvatar 上传用户头像 +// @Summary 上传用户头像 +// @Description 上传并更新用户头像(仅本人或管理员) +// @Tags 用户头像 +// @Accept multipart/form-data +// @Produce json +// @Security BearerAuth +// @Param id path int true "用户ID" +// @Param avatar formData file true "头像文件(最大5MB,支持jpg/jpeg/png/gif/webp)" +// @Success 200 {object} Response{data=AvatarResponse} "上传成功" +// @Failure 400 {object} Response "文件无效或大小超限" +// @Failure 401 {object} Response "未认证" +// @Failure 403 {object} Response "无权限" +// @Failure 404 {object} Response "用户不存在" +// @Router /api/v1/users/{id}/avatar [post] func (h *AvatarHandler) UploadAvatar(c *gin.Context) { userIDStr := c.Param("id") userID, err := strconv.ParseInt(userIDStr, 10, 64) diff --git a/internal/api/handler/captcha_handler.go b/internal/api/handler/captcha_handler.go index 7299c56..9bc4ffb 100644 --- a/internal/api/handler/captcha_handler.go +++ b/internal/api/handler/captcha_handler.go @@ -18,6 +18,13 @@ func NewCaptchaHandler(captchaService *service.CaptchaService) *CaptchaHandler { return &CaptchaHandler{captchaService: captchaService} } +// GenerateCaptcha 生成验证码 +// @Summary 生成验证码 +// @Description 生成图形验证码 +// @Tags 验证码 +// @Produce json +// @Success 200 {object} Response{data=CaptchaResponse} "验证码信息" +// @Router /api/v1/captcha/generate [get] func (h *CaptchaHandler) GenerateCaptcha(c *gin.Context) { result, err := h.captchaService.Generate(c.Request.Context()) if err != nil { @@ -35,10 +42,28 @@ func (h *CaptchaHandler) GenerateCaptcha(c *gin.Context) { }) } +// GetCaptchaImage 获取验证码图片 +// @Summary 获取验证码图片 +// @Description 根据captcha_id获取验证码图片(当前未实现) +// @Tags 验证码 +// @Produce json +// @Param captcha_id query string false "验证码ID" +// @Success 200 {object} Response "验证码图片" +// @Router /api/v1/captcha/image [get] func (h *CaptchaHandler) GetCaptchaImage(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"code": 0, "message": "success"}) } +// VerifyCaptcha 验证验证码 +// @Summary 验证验证码 +// @Description 验证用户输入的验证码是否正确 +// @Tags 验证码 +// @Accept json +// @Produce json +// @Param request body VerifyCaptchaRequest true "验证码信息" +// @Success 200 {object} Response{data=VerifyResponse} "验证成功" +// @Failure 400 {object} Response "验证码无效" +// @Router /api/v1/captcha/verify [post] func (h *CaptchaHandler) VerifyCaptcha(c *gin.Context) { var req struct { CaptchaID string `json:"captcha_id" binding:"required"` diff --git a/internal/api/handler/custom_field_handler.go b/internal/api/handler/custom_field_handler.go index 113a655..77cd4cd 100644 --- a/internal/api/handler/custom_field_handler.go +++ b/internal/api/handler/custom_field_handler.go @@ -20,6 +20,17 @@ func NewCustomFieldHandler(customFieldService *service.CustomFieldService) *Cust } // CreateField 创建自定义字段 +// @Summary 创建自定义字段 +// @Description 创建新的自定义字段定义(仅管理员) +// @Tags 自定义字段 +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param request body service.CreateFieldRequest true "字段定义" +// @Success 201 {object} Response{data=domain.CustomField} "创建成功" +// @Failure 400 {object} Response "请求参数错误" +// @Failure 403 {object} Response "无权限" +// @Router /api/v1/fields [post] func (h *CustomFieldHandler) CreateField(c *gin.Context) { var req service.CreateFieldRequest if err := c.ShouldBindJSON(&req); err != nil { @@ -41,6 +52,19 @@ func (h *CustomFieldHandler) CreateField(c *gin.Context) { } // UpdateField 更新自定义字段 +// @Summary 更新自定义字段 +// @Description 更新自定义字段定义(仅管理员) +// @Tags 自定义字段 +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "字段ID" +// @Param request body service.UpdateFieldRequest true "更新信息" +// @Success 200 {object} Response{data=domain.CustomField} "更新成功" +// @Failure 400 {object} Response "请求参数错误" +// @Failure 403 {object} Response "无权限" +// @Failure 404 {object} Response "字段不存在" +// @Router /api/v1/fields/{id} [put] func (h *CustomFieldHandler) UpdateField(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { @@ -68,6 +92,16 @@ func (h *CustomFieldHandler) UpdateField(c *gin.Context) { } // DeleteField 删除自定义字段 +// @Summary 删除自定义字段 +// @Description 删除自定义字段定义(仅管理员) +// @Tags 自定义字段 +// @Produce json +// @Security BearerAuth +// @Param id path int true "字段ID" +// @Success 200 {object} Response "删除成功" +// @Failure 403 {object} Response "无权限" +// @Failure 404 {object} Response "字段不存在" +// @Router /api/v1/fields/{id} [delete] func (h *CustomFieldHandler) DeleteField(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { @@ -87,6 +121,15 @@ func (h *CustomFieldHandler) DeleteField(c *gin.Context) { } // GetField 获取自定义字段 +// @Summary 获取自定义字段详情 +// @Description 根据ID获取自定义字段定义 +// @Tags 自定义字段 +// @Produce json +// @Security BearerAuth +// @Param id path int true "字段ID" +// @Success 200 {object} Response{data=domain.CustomField} "字段信息" +// @Failure 404 {object} Response "字段不存在" +// @Router /api/v1/fields/{id} [get] func (h *CustomFieldHandler) GetField(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { @@ -108,6 +151,13 @@ func (h *CustomFieldHandler) GetField(c *gin.Context) { } // ListFields 获取所有自定义字段 +// @Summary 获取自定义字段列表 +// @Description 获取所有自定义字段定义列表 +// @Tags 自定义字段 +// @Produce json +// @Security BearerAuth +// @Success 200 {object} Response{data=[]domain.CustomField} "字段列表" +// @Router /api/v1/fields [get] func (h *CustomFieldHandler) ListFields(c *gin.Context) { fields, err := h.customFieldService.ListFields(c.Request.Context()) if err != nil { @@ -123,6 +173,17 @@ func (h *CustomFieldHandler) ListFields(c *gin.Context) { } // SetUserFieldValues 设置用户自定义字段值 +// @Summary 设置用户自定义字段值 +// @Description 设置当前用户的自定义字段值 +// @Tags 自定义字段 +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param request body SetUserFieldValuesRequest true "字段值" +// @Success 200 {object} Response "设置成功" +// @Failure 400 {object} Response "请求参数错误" +// @Failure 401 {object} Response "未认证" +// @Router /api/v1/users/me/fields [put] func (h *CustomFieldHandler) SetUserFieldValues(c *gin.Context) { userID, ok := getUserIDFromContext(c) if !ok { @@ -151,6 +212,14 @@ func (h *CustomFieldHandler) SetUserFieldValues(c *gin.Context) { } // GetUserFieldValues 获取用户自定义字段值 +// @Summary 获取用户自定义字段值 +// @Description 获取当前用户的自定义字段值 +// @Tags 自定义字段 +// @Produce json +// @Security BearerAuth +// @Success 200 {object} Response{data=map} "字段值" +// @Failure 401 {object} Response "未认证" +// @Router /api/v1/users/me/fields [get] func (h *CustomFieldHandler) GetUserFieldValues(c *gin.Context) { userID, ok := getUserIDFromContext(c) if !ok { diff --git a/internal/api/handler/device_handler.go b/internal/api/handler/device_handler.go index 8a37635..2043932 100644 --- a/internal/api/handler/device_handler.go +++ b/internal/api/handler/device_handler.go @@ -22,6 +22,17 @@ func NewDeviceHandler(deviceService *service.DeviceService) *DeviceHandler { return &DeviceHandler{deviceService: deviceService} } +// CreateDevice 创建设备 +// @Summary 创建设备记录 +// @Description 当前用户创建设备记录 +// @Tags 设备管理 +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param request body service.CreateDeviceRequest true "设备信息" +// @Success 201 {object} Response{data=domain.Device} "设备创建成功" +// @Failure 401 {object} Response "未认证" +// @Router /api/v1/devices [post] func (h *DeviceHandler) CreateDevice(c *gin.Context) { userID, ok := getUserIDFromContext(c) if !ok { @@ -48,6 +59,17 @@ func (h *DeviceHandler) CreateDevice(c *gin.Context) { }) } +// GetMyDevices 获取我的设备列表 +// @Summary 获取当前用户的设备列表 +// @Description 获取当前用户的所有设备记录 +// @Tags 设备管理 +// @Produce json +// @Security BearerAuth +// @Param page query int false "页码" +// @Param page_size query int false "每页数量" +// @Success 200 {object} Response{data=DeviceListResponse} "设备列表" +// @Failure 401 {object} Response "未认证" +// @Router /api/v1/devices [get] func (h *DeviceHandler) GetMyDevices(c *gin.Context) { userID, ok := getUserIDFromContext(c) if !ok { @@ -76,6 +98,16 @@ func (h *DeviceHandler) GetMyDevices(c *gin.Context) { }) } +// GetDevice 获取设备详情 +// @Summary 获取设备详情 +// @Description 根据ID获取设备详细信息 +// @Tags 设备管理 +// @Produce json +// @Security BearerAuth +// @Param id path int true "设备ID" +// @Success 200 {object} Response{data=domain.Device} "设备信息" +// @Failure 404 {object} Response "设备不存在" +// @Router /api/v1/devices/{id} [get] func (h *DeviceHandler) GetDevice(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { @@ -96,6 +128,19 @@ func (h *DeviceHandler) GetDevice(c *gin.Context) { }) } +// UpdateDevice 更新设备 +// @Summary 更新设备信息 +// @Description 更新设备的基本信息 +// @Tags 设备管理 +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "设备ID" +// @Param request body service.UpdateDeviceRequest true "更新信息" +// @Success 200 {object} Response{data=domain.Device} "更新成功" +// @Failure 400 {object} Response "请求参数错误" +// @Failure 404 {object} Response "设备不存在" +// @Router /api/v1/devices/{id} [put] func (h *DeviceHandler) UpdateDevice(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { @@ -122,6 +167,16 @@ func (h *DeviceHandler) UpdateDevice(c *gin.Context) { }) } +// DeleteDevice 删除设备 +// @Summary 删除设备 +// @Description 删除设备记录 +// @Tags 设备管理 +// @Produce json +// @Security BearerAuth +// @Param id path int true "设备ID" +// @Success 200 {object} Response "删除成功" +// @Failure 404 {object} Response "设备不存在" +// @Router /api/v1/devices/{id} [delete] func (h *DeviceHandler) DeleteDevice(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { @@ -140,6 +195,19 @@ func (h *DeviceHandler) DeleteDevice(c *gin.Context) { }) } +// UpdateDeviceStatus 更新设备状态 +// @Summary 更新设备状态 +// @Description 更新设备状态(active/inactive) +// @Tags 设备管理 +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "设备ID" +// @Param request body UpdateDeviceStatusRequest true "状态信息" +// @Success 200 {object} Response "状态更新成功" +// @Failure 400 {object} Response "无效的状态值" +// @Failure 404 {object} Response "设备不存在" +// @Router /api/v1/devices/{id}/status [put] func (h *DeviceHandler) UpdateDeviceStatus(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { @@ -178,6 +246,18 @@ func (h *DeviceHandler) UpdateDeviceStatus(c *gin.Context) { }) } +// GetUserDevices 获取指定用户的设备列表 +// @Summary 获取用户设备列表 +// @Description 获取指定用户的设备列表(仅本人或管理员) +// @Tags 设备管理 +// @Produce json +// @Security BearerAuth +// @Param id path int true "用户ID" +// @Param page query int false "页码" +// @Param page_size query int false "每页数量" +// @Success 200 {object} Response{data=DeviceListResponse} "设备列表" +// @Failure 403 {object} Response "无权限" +// @Router /api/v1/users/{id}/devices [get] func (h *DeviceHandler) GetUserDevices(c *gin.Context) { // IDOR 修复:检查当前用户是否有权限查看指定用户的设备 currentUserID, ok := getUserIDFromContext(c) @@ -232,7 +312,19 @@ func (h *DeviceHandler) GetUserDevices(c *gin.Context) { }) } -// GetAllDevices 获取所有设备列表(管理员) +// GetAllDevices 获取所有设备列表 +// @Summary 获取所有设备列表 +// @Description 获取所有设备列表(仅管理员),支持游标分页和偏移分页 +// @Tags 设备管理 +// @Produce json +// @Security BearerAuth +// @Param cursor query string false "游标分页游标" +// @Param size query int false "每页数量(游标模式)" +// @Param page query int false "页码" +// @Param page_size query int false "每页数量" +// @Success 200 {object} Response{data=DeviceListResponse} "设备列表" +// @Failure 403 {object} Response "无权限" +// @Router /api/v1/admin/devices [get] func (h *DeviceHandler) GetAllDevices(c *gin.Context) { var req service.GetAllDevicesRequest if err := c.ShouldBindQuery(&req); err != nil { @@ -280,6 +372,17 @@ type TrustDeviceRequest struct { } // TrustDevice 设置设备为信任设备 +// @Summary 设置设备为信任设备 +// @Description 将指定设备设置为信任设备,在信任期内免二次验证 +// @Tags 设备管理 +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "设备ID" +// @Param request body TrustDeviceRequest true "信任配置" +// @Success 200 {object} Response "设置成功" +// @Failure 404 {object} Response "设备不存在" +// @Router /api/v1/devices/{id}/trust [post] func (h *DeviceHandler) TrustDevice(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { @@ -307,7 +410,18 @@ func (h *DeviceHandler) TrustDevice(c *gin.Context) { }) } -// TrustDeviceByDeviceID 根据设备标识字符串设置设备为信任状态 +// TrustDeviceByDeviceID 根据设备标识设置设备为信任状态 +// @Summary 根据设备标识设置信任 +// @Description 根据设备唯一标识字符串设置设备为信任状态 +// @Tags 设备管理 +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param deviceId path string true "设备唯一标识" +// @Param request body TrustDeviceRequest true "信任配置" +// @Success 200 {object} Response "设置成功" +// @Failure 401 {object} Response "未认证" +// @Router /api/v1/devices/trust/{deviceId} [post] func (h *DeviceHandler) TrustDeviceByDeviceID(c *gin.Context) { userID, ok := getUserIDFromContext(c) if !ok { @@ -342,6 +456,15 @@ func (h *DeviceHandler) TrustDeviceByDeviceID(c *gin.Context) { } // UntrustDevice 取消设备信任状态 +// @Summary 取消设备信任 +// @Description 取消设备的信任状态 +// @Tags 设备管理 +// @Produce json +// @Security BearerAuth +// @Param id path int true "设备ID" +// @Success 200 {object} Response "取消成功" +// @Failure 404 {object} Response "设备不存在" +// @Router /api/v1/devices/{id}/trust [delete] func (h *DeviceHandler) UntrustDevice(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { @@ -361,6 +484,14 @@ func (h *DeviceHandler) UntrustDevice(c *gin.Context) { } // GetMyTrustedDevices 获取我的信任设备列表 +// @Summary 获取信任设备列表 +// @Description 获取当前用户的信任设备列表 +// @Tags 设备管理 +// @Produce json +// @Security BearerAuth +// @Success 200 {object} Response{data=[]domain.Device} "信任设备列表" +// @Failure 401 {object} Response "未认证" +// @Router /api/v1/devices/trusted [get] func (h *DeviceHandler) GetMyTrustedDevices(c *gin.Context) { userID, ok := getUserIDFromContext(c) if !ok { @@ -382,6 +513,16 @@ func (h *DeviceHandler) GetMyTrustedDevices(c *gin.Context) { } // LogoutAllOtherDevices 登出所有其他设备 +// @Summary 登出其他设备 +// @Description 登出当前用户除指定设备外的所有其他设备 +// @Tags 设备管理 +// @Produce json +// @Security BearerAuth +// @Param X-Device-ID header string true "当前设备ID" +// @Success 200 {object} Response "登出成功" +// @Failure 400 {object} Response "无效的设备ID" +// @Failure 401 {object} Response "未认证" +// @Router /api/v1/devices/logout-others [post] func (h *DeviceHandler) LogoutAllOtherDevices(c *gin.Context) { userID, ok := getUserIDFromContext(c) if !ok { diff --git a/internal/api/handler/log_handler.go b/internal/api/handler/log_handler.go index 1eeaf62..e66603d 100644 --- a/internal/api/handler/log_handler.go +++ b/internal/api/handler/log_handler.go @@ -24,6 +24,17 @@ func NewLogHandler(loginLogService *service.LoginLogService, operationLogService } } +// GetMyLoginLogs 获取我的登录日志 +// @Summary 获取登录日志 +// @Description 获取当前用户的登录日志 +// @Tags 日志 +// @Produce json +// @Security BearerAuth +// @Param page query int false "页码" +// @Param page_size query int false "每页数量" +// @Success 200 {object} Response{data=LoginLogListResponse} "登录日志列表" +// @Failure 401 {object} Response "未认证" +// @Router /api/v1/users/me/login-logs [get] func (h *LogHandler) GetMyLoginLogs(c *gin.Context) { userID, ok := getUserIDFromContext(c) if !ok { @@ -52,6 +63,17 @@ func (h *LogHandler) GetMyLoginLogs(c *gin.Context) { }) } +// GetMyOperationLogs 获取我的操作日志 +// @Summary 获取操作日志 +// @Description 获取当前用户的操作日志 +// @Tags 日志 +// @Produce json +// @Security BearerAuth +// @Param page query int false "页码" +// @Param page_size query int false "每页数量" +// @Success 200 {object} Response{data=OperationLogListResponse} "操作日志列表" +// @Failure 401 {object} Response "未认证" +// @Router /api/v1/users/me/operation-logs [get] func (h *LogHandler) GetMyOperationLogs(c *gin.Context) { userID, ok := getUserIDFromContext(c) if !ok { @@ -80,6 +102,19 @@ func (h *LogHandler) GetMyOperationLogs(c *gin.Context) { }) } +// GetLoginLogs 获取登录日志列表 +// @Summary 获取登录日志列表 +// @Description 获取所有登录日志(仅管理员),支持游标分页和偏移分页 +// @Tags 日志 +// @Produce json +// @Security BearerAuth +// @Param cursor query string false "游标分页游标" +// @Param size query int false "每页数量(游标模式)" +// @Param page query int false "页码" +// @Param page_size query int false "每页数量" +// @Success 200 {object} Response{data=LoginLogListResponse} "登录日志列表" +// @Failure 403 {object} Response "无权限" +// @Router /api/v1/admin/logs/login [get] func (h *LogHandler) GetLoginLogs(c *gin.Context) { var req service.ListLoginLogRequest if err := c.ShouldBindQuery(&req); err != nil { diff --git a/internal/api/handler/password_reset_handler.go b/internal/api/handler/password_reset_handler.go index f71a462..586697d 100644 --- a/internal/api/handler/password_reset_handler.go +++ b/internal/api/handler/password_reset_handler.go @@ -27,6 +27,16 @@ func NewPasswordResetHandlerWithSMS(passwordResetService *service.PasswordResetS } } +// ForgotPassword 忘记密码 +// @Summary 忘记密码 +// @Description 请求密码重置邮件 +// @Tags 密码重置 +// @Accept json +// @Produce json +// @Param request body ForgotPasswordRequest true "邮箱地址" +// @Success 200 {object} Response "密码重置邮件已发送" +// @Failure 400 {object} Response "请求参数错误" +// @Router /api/v1/auth/password/forgot [post] func (h *PasswordResetHandler) ForgotPassword(c *gin.Context) { var req struct { Email string `json:"email" binding:"required"` diff --git a/internal/api/handler/permission_handler.go b/internal/api/handler/permission_handler.go index f696f88..751c19f 100644 --- a/internal/api/handler/permission_handler.go +++ b/internal/api/handler/permission_handler.go @@ -20,6 +20,18 @@ func NewPermissionHandler(permissionService *service.PermissionService) *Permiss return &PermissionHandler{permissionService: permissionService} } +// CreatePermission 创建权限 +// @Summary 创建权限 +// @Description 创建新的权限定义(仅管理员) +// @Tags 权限管理 +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param request body service.CreatePermissionRequest true "权限信息" +// @Success 201 {object} Response{data=domain.Permission} "创建成功" +// @Failure 400 {object} Response "请求参数错误" +// @Failure 403 {object} Response "无权限" +// @Router /api/v1/permissions [post] func (h *PermissionHandler) CreatePermission(c *gin.Context) { var req service.CreatePermissionRequest if err := c.ShouldBindJSON(&req); err != nil { @@ -40,6 +52,14 @@ func (h *PermissionHandler) CreatePermission(c *gin.Context) { }) } +// ListPermissions 获取权限列表 +// @Summary 获取权限列表 +// @Description 获取系统权限列表 +// @Tags 权限管理 +// @Produce json +// @Security BearerAuth +// @Success 200 {object} Response{data=[]domain.Permission} "权限列表" +// @Router /api/v1/permissions [get] func (h *PermissionHandler) ListPermissions(c *gin.Context) { var req service.ListPermissionRequest if err := c.ShouldBindQuery(&req); err != nil { @@ -60,6 +80,16 @@ func (h *PermissionHandler) ListPermissions(c *gin.Context) { }) } +// GetPermission 获取权限详情 +// @Summary 获取权限详情 +// @Description 根据ID获取权限详细信息 +// @Tags 权限管理 +// @Produce json +// @Security BearerAuth +// @Param id path int true "权限ID" +// @Success 200 {object} Response{data=domain.Permission} "权限信息" +// @Failure 404 {object} Response "权限不存在" +// @Router /api/v1/permissions/{id} [get] func (h *PermissionHandler) GetPermission(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { @@ -80,6 +110,20 @@ func (h *PermissionHandler) GetPermission(c *gin.Context) { }) } +// UpdatePermission 更新权限 +// @Summary 更新权限 +// @Description 更新权限信息(仅管理员) +// @Tags 权限管理 +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "权限ID" +// @Param request body service.UpdatePermissionRequest true "更新信息" +// @Success 200 {object} Response{data=domain.Permission} "更新成功" +// @Failure 400 {object} Response "请求参数错误" +// @Failure 403 {object} Response "无权限" +// @Failure 404 {object} Response "权限不存在" +// @Router /api/v1/permissions/{id} [put] func (h *PermissionHandler) UpdatePermission(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { @@ -106,6 +150,17 @@ func (h *PermissionHandler) UpdatePermission(c *gin.Context) { }) } +// DeletePermission 删除权限 +// @Summary 删除权限 +// @Description 删除权限定义(仅管理员) +// @Tags 权限管理 +// @Produce json +// @Security BearerAuth +// @Param id path int true "权限ID" +// @Success 200 {object} Response "删除成功" +// @Failure 403 {object} Response "无权限" +// @Failure 404 {object} Response "权限不存在" +// @Router /api/v1/permissions/{id} [delete] func (h *PermissionHandler) DeletePermission(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { @@ -124,6 +179,20 @@ func (h *PermissionHandler) DeletePermission(c *gin.Context) { }) } +// UpdatePermissionStatus 更新权限状态 +// @Summary 更新权限状态 +// @Description 更新权限状态(enabled/disabled)(仅管理员) +// @Tags 权限管理 +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "权限ID" +// @Param request body UpdatePermissionStatusRequest true "状态信息" +// @Success 200 {object} Response "状态更新成功" +// @Failure 400 {object} Response "无效的状态值" +// @Failure 403 {object} Response "无权限" +// @Failure 404 {object} Response "权限不存在" +// @Router /api/v1/permissions/{id}/status [put] func (h *PermissionHandler) UpdatePermissionStatus(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { @@ -162,6 +231,14 @@ func (h *PermissionHandler) UpdatePermissionStatus(c *gin.Context) { }) } +// GetPermissionTree 获取权限树 +// @Summary 获取权限树 +// @Description 获取系统权限的树形结构 +// @Tags 权限管理 +// @Produce json +// @Security BearerAuth +// @Success 200 {object} Response{data=[]domain.Permission} "权限树" +// @Router /api/v1/permissions/tree [get] func (h *PermissionHandler) GetPermissionTree(c *gin.Context) { tree, err := h.permissionService.GetPermissionTree(c.Request.Context()) if err != nil { diff --git a/internal/api/handler/role_handler.go b/internal/api/handler/role_handler.go index 5024160..42dabbb 100644 --- a/internal/api/handler/role_handler.go +++ b/internal/api/handler/role_handler.go @@ -20,6 +20,18 @@ func NewRoleHandler(roleService *service.RoleService) *RoleHandler { return &RoleHandler{roleService: roleService} } +// CreateRole 创建角色 +// @Summary 创建角色 +// @Description 创建新角色(仅管理员) +// @Tags 角色管理 +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param request body service.CreateRoleRequest true "角色信息" +// @Success 201 {object} Response{data=domain.Role} "角色创建成功" +// @Failure 400 {object} Response "请求参数错误" +// @Failure 403 {object} Response "无权限" +// @Router /api/v1/roles [post] func (h *RoleHandler) CreateRole(c *gin.Context) { var req service.CreateRoleRequest if err := c.ShouldBindJSON(&req); err != nil { @@ -40,6 +52,14 @@ func (h *RoleHandler) CreateRole(c *gin.Context) { }) } +// ListRoles 获取角色列表 +// @Summary 获取角色列表 +// @Description 获取系统角色列表 +// @Tags 角色管理 +// @Produce json +// @Security BearerAuth +// @Success 200 {object} Response{data=RoleListResponse} "角色列表" +// @Router /api/v1/roles [get] func (h *RoleHandler) ListRoles(c *gin.Context) { var req service.ListRoleRequest if err := c.ShouldBindQuery(&req); err != nil { @@ -63,6 +83,16 @@ func (h *RoleHandler) ListRoles(c *gin.Context) { }) } +// GetRole 获取角色详情 +// @Summary 获取角色详情 +// @Description 根据ID获取角色详细信息 +// @Tags 角色管理 +// @Produce json +// @Security BearerAuth +// @Param id path int true "角色ID" +// @Success 200 {object} Response{data=domain.Role} "角色信息" +// @Failure 404 {object} Response "角色不存在" +// @Router /api/v1/roles/{id} [get] func (h *RoleHandler) GetRole(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { @@ -83,6 +113,20 @@ func (h *RoleHandler) GetRole(c *gin.Context) { }) } +// UpdateRole 更新角色 +// @Summary 更新角色 +// @Description 更新角色信息(仅管理员) +// @Tags 角色管理 +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "角色ID" +// @Param request body service.UpdateRoleRequest true "更新信息" +// @Success 200 {object} Response{data=domain.Role} "更新成功" +// @Failure 400 {object} Response "请求参数错误" +// @Failure 403 {object} Response "无权限" +// @Failure 404 {object} Response "角色不存在" +// @Router /api/v1/roles/{id} [put] func (h *RoleHandler) UpdateRole(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { @@ -109,6 +153,17 @@ func (h *RoleHandler) UpdateRole(c *gin.Context) { }) } +// DeleteRole 删除角色 +// @Summary 删除角色 +// @Description 删除角色(仅管理员) +// @Tags 角色管理 +// @Produce json +// @Security BearerAuth +// @Param id path int true "角色ID" +// @Success 200 {object} Response "删除成功" +// @Failure 403 {object} Response "无权限" +// @Failure 404 {object} Response "角色不存在" +// @Router /api/v1/roles/{id} [delete] func (h *RoleHandler) DeleteRole(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { @@ -127,6 +182,20 @@ func (h *RoleHandler) DeleteRole(c *gin.Context) { }) } +// UpdateRoleStatus 更新角色状态 +// @Summary 更新角色状态 +// @Description 更新角色状态(enabled/disabled)(仅管理员) +// @Tags 角色管理 +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "角色ID" +// @Param request body UpdateRoleStatusRequest true "状态信息" +// @Success 200 {object} Response "状态更新成功" +// @Failure 400 {object} Response "无效的状态值" +// @Failure 403 {object} Response "无权限" +// @Failure 404 {object} Response "角色不存在" +// @Router /api/v1/roles/{id}/status [put] func (h *RoleHandler) UpdateRoleStatus(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { @@ -166,6 +235,16 @@ func (h *RoleHandler) UpdateRoleStatus(c *gin.Context) { }) } +// GetRolePermissions 获取角色权限 +// @Summary 获取角色权限列表 +// @Description 获取角色的权限列表 +// @Tags 角色管理 +// @Produce json +// @Security BearerAuth +// @Param id path int true "角色ID" +// @Success 200 {object} Response{data=[]domain.Permission} "权限列表" +// @Failure 404 {object} Response "角色不存在" +// @Router /api/v1/roles/{id}/permissions [get] func (h *RoleHandler) GetRolePermissions(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { @@ -186,6 +265,20 @@ func (h *RoleHandler) GetRolePermissions(c *gin.Context) { }) } +// AssignPermissions 分配角色权限 +// @Summary 分配角色权限 +// @Description 为角色分配权限(替换现有权限)(仅管理员) +// @Tags 角色管理 +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "角色ID" +// @Param request body AssignPermissionsRequest true "权限ID列表" +// @Success 200 {object} Response "权限分配成功" +// @Failure 400 {object} Response "请求参数错误" +// @Failure 403 {object} Response "无权限" +// @Failure 404 {object} Response "角色不存在" +// @Router /api/v1/roles/{id}/permissions [post] func (h *RoleHandler) AssignPermissions(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { diff --git a/internal/api/handler/stats_handler.go b/internal/api/handler/stats_handler.go index 79a5c15..ff3b353 100644 --- a/internal/api/handler/stats_handler.go +++ b/internal/api/handler/stats_handler.go @@ -18,6 +18,16 @@ func NewStatsHandler(statsService *service.StatsService) *StatsHandler { return &StatsHandler{statsService: statsService} } +// GetDashboard 获取仪表盘统计 +// @Summary 获取仪表盘统计 +// @Description 获取系统仪表盘统计数据(仅管理员) +// @Tags 统计 +// @Produce json +// @Security BearerAuth +// @Success 200 {object} Response{data=service.DashboardStats} "仪表盘数据" +// @Failure 403 {object} Response "无权限" +// @Failure 500 {object} Response "服务器错误" +// @Router /api/v1/admin/stats/dashboard [get] func (h *StatsHandler) GetDashboard(c *gin.Context) { stats, err := h.statsService.GetDashboardStats(c.Request.Context()) if err != nil { @@ -27,6 +37,16 @@ func (h *StatsHandler) GetDashboard(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"code": 0, "message": "success", "data": stats}) } +// GetUserStats 获取用户统计 +// @Summary 获取用户统计 +// @Description 获取用户统计数据(仅管理员) +// @Tags 统计 +// @Produce json +// @Security BearerAuth +// @Success 200 {object} Response{data=service.UserStats} "用户统计数据" +// @Failure 403 {object} Response "无权限" +// @Failure 500 {object} Response "服务器错误" +// @Router /api/v1/admin/stats/users [get] func (h *StatsHandler) GetUserStats(c *gin.Context) { stats, err := h.statsService.GetUserStats(c.Request.Context()) if err != nil { diff --git a/internal/api/handler/totp_handler.go b/internal/api/handler/totp_handler.go index 4dffd73..2debe8d 100644 --- a/internal/api/handler/totp_handler.go +++ b/internal/api/handler/totp_handler.go @@ -22,6 +22,15 @@ func NewTOTPHandler(authService *service.AuthService, totpService *service.TOTPS } } +// GetTOTPStatus 获取TOTP状态 +// @Summary 获取TOTP状态 +// @Description 获取当前用户的TOTP两步验证状态 +// @Tags 两步验证 +// @Produce json +// @Security BearerAuth +// @Success 200 {object} Response{data=TOTPStatusResponse} "TOTP状态" +// @Failure 401 {object} Response "未认证" +// @Router /api/v1/auth/totp/status [get] func (h *TOTPHandler) GetTOTPStatus(c *gin.Context) { userID, ok := getUserIDFromContext(c) if !ok { diff --git a/internal/api/handler/user_handler.go b/internal/api/handler/user_handler.go index 2aca7d2..a2fbf1d 100644 --- a/internal/api/handler/user_handler.go +++ b/internal/api/handler/user_handler.go @@ -21,6 +21,19 @@ func NewUserHandler(userService *service.UserService) *UserHandler { return &UserHandler{userService: userService} } +// CreateUser 创建用户 +// @Summary 创建用户 +// @Description 创建新用户账号(仅管理员) +// @Tags 用户管理 +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param request body CreateUserRequest true "用户信息" +// @Success 201 {object} Response{data=UserResponse} "用户创建成功" +// @Failure 400 {object} Response "请求参数错误" +// @Failure 401 {object} Response "未认证" +// @Failure 403 {object} Response "无权限" +// @Router /api/v1/users [post] func (h *UserHandler) CreateUser(c *gin.Context) { var req struct { Username string `json:"username" binding:"required"` @@ -62,6 +75,18 @@ func (h *UserHandler) CreateUser(c *gin.Context) { }) } +// ListUsers 获取用户列表 +// @Summary 获取用户列表 +// @Description 获取用户列表,支持游标分页和偏移分页 +// @Tags 用户管理 +// @Produce json +// @Security BearerAuth +// @Param cursor query string false "游标分页游标" +// @Param size query int false "每页大小" +// @Param offset query int false "偏移分页偏移量" +// @Param limit query int false "每页大小" +// @Success 200 {object} Response{data=UserListResponse} "用户列表" +// @Router /api/v1/users [get] func (h *UserHandler) ListUsers(c *gin.Context) { cursor := c.Query("cursor") sizeStr := c.DefaultQuery("size", "") @@ -113,6 +138,16 @@ func (h *UserHandler) ListUsers(c *gin.Context) { }) } +// GetUser 获取用户详情 +// @Summary 获取用户详情 +// @Description 根据ID获取用户详细信息 +// @Tags 用户管理 +// @Produce json +// @Security BearerAuth +// @Param id path int true "用户ID" +// @Success 200 {object} Response{data=UserResponse} "用户信息" +// @Failure 404 {object} Response "用户不存在" +// @Router /api/v1/users/{id} [get] func (h *UserHandler) GetUser(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { @@ -129,6 +164,20 @@ func (h *UserHandler) GetUser(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"code": 0, "message": "success", "data": toUserResponse(user)}) } +// UpdateUser 更新用户 +// @Summary 更新用户信息 +// @Description 更新用户的基本信息(仅管理员或本人) +// @Tags 用户管理 +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "用户ID" +// @Param request body UpdateUserRequest true "更新信息" +// @Success 200 {object} Response{data=UserResponse} "更新成功" +// @Failure 400 {object} Response "请求参数错误" +// @Failure 403 {object} Response "无权限" +// @Failure 404 {object} Response "用户不存在" +// @Router /api/v1/users/{id} [put] func (h *UserHandler) UpdateUser(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { @@ -167,6 +216,17 @@ func (h *UserHandler) UpdateUser(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"code": 0, "message": "success", "data": toUserResponse(user)}) } +// DeleteUser 删除用户 +// @Summary 删除用户 +// @Description 删除用户账号(仅管理员) +// @Tags 用户管理 +// @Produce json +// @Security BearerAuth +// @Param id path int true "用户ID" +// @Success 200 {object} Response "删除成功" +// @Failure 403 {object} Response "无权限" +// @Failure 404 {object} Response "用户不存在" +// @Router /api/v1/users/{id} [delete] func (h *UserHandler) DeleteUser(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { @@ -182,6 +242,20 @@ func (h *UserHandler) DeleteUser(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"code": 0, "message": "success"}) } +// UpdatePassword 修改密码 +// @Summary 修改用户密码 +// @Description 修改用户密码(仅管理员或本人) +// @Tags 用户管理 +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "用户ID" +// @Param request body UpdatePasswordRequest true "密码信息" +// @Success 200 {object} Response "密码修改成功" +// @Failure 400 {object} Response "请求参数错误" +// @Failure 403 {object} Response "无权限" +// @Failure 404 {object} Response "用户不存在" +// @Router /api/v1/users/{id}/password [put] func (h *UserHandler) UpdatePassword(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { @@ -207,6 +281,20 @@ func (h *UserHandler) UpdatePassword(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"code": 0, "message": "密码修改成功"}) } +// UpdateUserStatus 更新用户状态 +// @Summary 更新用户状态 +// @Description 更新用户账号状态(active/inactive/locked/disabled)(仅管理员) +// @Tags 用户管理 +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "用户ID" +// @Param request body UpdateStatusRequest true "状态信息" +// @Success 200 {object} Response "状态更新成功" +// @Failure 400 {object} Response "无效的状态值" +// @Failure 403 {object} Response "无权限" +// @Failure 404 {object} Response "用户不存在" +// @Router /api/v1/users/{id}/status [put] func (h *UserHandler) UpdateUserStatus(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { @@ -246,6 +334,17 @@ func (h *UserHandler) UpdateUserStatus(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"code": 0, "message": "success"}) } +// GetUserRoles 获取用户角色 +// @Summary 获取用户角色列表 +// @Description 获取指定用户的角色列表(仅本人或管理员) +// @Tags 用户管理 +// @Produce json +// @Security BearerAuth +// @Param id path int true "用户ID" +// @Success 200 {object} Response{data=[]domain.Role} "角色列表" +// @Failure 403 {object} Response "无权限" +// @Failure 404 {object} Response "用户不存在" +// @Router /api/v1/users/{id}/roles [get] func (h *UserHandler) GetUserRoles(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { @@ -282,6 +381,20 @@ func (h *UserHandler) GetUserRoles(c *gin.Context) { }) } +// AssignRoles 分配用户角色 +// @Summary 分配用户角色 +// @Description 为用户分配角色(替换现有角色)(仅管理员) +// @Tags 用户管理 +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "用户ID" +// @Param request body AssignRolesRequest true "角色ID列表" +// @Success 200 {object} Response "角色分配成功" +// @Failure 400 {object} Response "请求参数错误" +// @Failure 403 {object} Response "无权限" +// @Failure 404 {object} Response "用户不存在" +// @Router /api/v1/users/{id}/roles [post] func (h *UserHandler) AssignRoles(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { @@ -306,6 +419,18 @@ func (h *UserHandler) AssignRoles(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"code": 0, "message": "角色分配成功"}) } +// BatchUpdateStatus 批量更新用户状态 +// @Summary 批量更新用户状态 +// @Description 批量更新多个用户的状态(仅管理员) +// @Tags 用户管理 +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param request body service.BatchUpdateStatusRequest true "批量更新请求" +// @Success 200 {object} Response "批量更新成功" +// @Failure 400 {object} Response "请求参数错误" +// @Failure 403 {object} Response "无权限" +// @Router /api/v1/users/batch/status [put] func (h *UserHandler) BatchUpdateStatus(c *gin.Context) { var req service.BatchUpdateStatusRequest if err := c.ShouldBindJSON(&req); err != nil { @@ -322,6 +447,18 @@ func (h *UserHandler) BatchUpdateStatus(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"code": 0, "message": "更新成功", "data": gin.H{"count": count}}) } +// BatchDelete 批量删除用户 +// @Summary 批量删除用户 +// @Description 批量删除多个用户(仅管理员) +// @Tags 用户管理 +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param request body service.BatchDeleteRequest true "批量删除请求" +// @Success 200 {object} Response "批量删除成功" +// @Failure 400 {object} Response "请求参数错误" +// @Failure 403 {object} Response "无权限" +// @Router /api/v1/users/batch [delete] func (h *UserHandler) BatchDelete(c *gin.Context) { var req service.BatchDeleteRequest if err := c.ShouldBindJSON(&req); err != nil { @@ -338,6 +475,15 @@ func (h *UserHandler) BatchDelete(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"code": 0, "message": "删除成功", "data": gin.H{"count": count}}) } +// ListAdmins 获取管理员列表 +// @Summary 获取管理员列表 +// @Description 获取所有管理员用户列表(仅管理员) +// @Tags 用户管理 +// @Produce json +// @Security BearerAuth +// @Success 200 {object} Response{data=[]UserResponse} "管理员列表" +// @Failure 403 {object} Response "无权限" +// @Router /api/v1/users/admins [get] func (h *UserHandler) ListAdmins(c *gin.Context) { admins, err := h.userService.ListAdmins(c.Request.Context()) if err != nil { @@ -353,6 +499,18 @@ func (h *UserHandler) ListAdmins(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"code": 0, "message": "success", "data": adminResponses}) } +// CreateAdmin 创建管理员 +// @Summary 创建管理员 +// @Description 创建新管理员账号(仅管理员) +// @Tags 用户管理 +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param request body CreateAdminRequest true "管理员信息" +// @Success 201 {object} Response{data=UserResponse} "管理员创建成功" +// @Failure 400 {object} Response "请求参数错误" +// @Failure 403 {object} Response "无权限" +// @Router /api/v1/users/admins [post] func (h *UserHandler) CreateAdmin(c *gin.Context) { var req struct { Username string `json:"username" binding:"required"` @@ -382,6 +540,18 @@ func (h *UserHandler) CreateAdmin(c *gin.Context) { c.JSON(http.StatusCreated, gin.H{"code": 0, "message": "管理员创建成功", "data": toUserResponse(admin)}) } +// DeleteAdmin 删除管理员 +// @Summary 删除管理员 +// @Description 删除管理员角色(最后管理员保护、自删保护)(仅管理员) +// @Tags 用户管理 +// @Produce json +// @Security BearerAuth +// @Param id path int true "用户ID" +// @Success 200 {object} Response "管理员已移除" +// @Failure 400 {object} Response "无效的用户ID" +// @Failure 403 {object} Response "无权限" +// @Failure 409 {object} Response "无法删除(最后管理员或自删)" +// @Router /api/v1/users/admins/{id} [delete] func (h *UserHandler) DeleteAdmin(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil {