refactor: 拆分超标控制器文件至行数≤120行

- auth_controller.go(238→54): 拆出auth_api_login.go(107)+auth_api_register.go(96)
- settings_controller.go(289→101): 拆出settings_api_profile(116)+account(59)+error_handlers(41)
- message_controller.go(129→85): 拆出message_page_controller.go(55)
- 所有拆分后文件均≤120行,符合code-style.md瘦控制器规范
This commit is contained in:
2026-05-27 13:57:08 +08:00
parent 0cc903b44a
commit a19b1fcb51
9 changed files with 475 additions and 417 deletions

View File

@ -0,0 +1,107 @@
package controller
import (
"net/http"
"metazone.cc/metalab/internal/common"
"metazone.cc/metalab/internal/model"
"github.com/gin-gonic/gin"
)
// clientIP 获取客户端真实 IP考虑反向代理
func clientIP(c *gin.Context) string {
if fwd := c.GetHeader("X-Forwarded-For"); fwd != "" {
return fwd
}
if real := c.GetHeader("X-Real-IP"); real != "" {
return real
}
return c.ClientIP()
}
// Login 登录 API含双维度限流
func (ac *AuthController) Login(c *gin.Context) {
var req model.LoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
common.Error(c, http.StatusBadRequest, "请检查输入")
return
}
email := req.Email
ip := clientIP(c)
// --- 限流:账户维度 ---
acctResult, recordAccount := ac.rateLimiter.AllowAccount(email)
if acctResult.Blocked {
common.Error(c, http.StatusTooManyRequests, acctResult.Message)
return
}
// --- 限流IP 维度 ---
ipResult, recordIP := ac.rateLimiter.AllowIP(ip)
if ipResult.Blocked {
common.Error(c, http.StatusTooManyRequests, ipResult.Message)
return
}
accessToken, refreshToken, user, err := ac.authService.Login(req, ip)
if err != nil {
// 记录失败 → 两个维度各 +1
if recordAccount != nil {
recordAccount()
}
if recordIP != nil {
recordIP()
}
switch err {
case common.ErrInvalidCred:
common.Error(c, http.StatusUnauthorized, "邮箱或密码错误")
case common.ErrUserBanned:
common.Error(c, http.StatusForbidden, "账号已被封禁")
case common.ErrUserLocked:
common.Error(c, http.StatusUnauthorized, "邮箱或密码错误")
case common.ErrNeedsConfirmRestore:
// 注销账号登录 → 需要二次确认恢复
c.JSON(http.StatusOK, gin.H{
"success": true,
"action": "confirm_restore",
"message": "你的账号正在注销中,登录将中止注销流程",
})
default:
common.Error(c, http.StatusInternalServerError, "登录失败,请稍后重试")
}
return
}
// 登录成功 → 清除失败计数
ac.rateLimiter.Clear(email, ip)
common.SetAuthCookies(c, accessToken, refreshToken, req.RememberMe, ac.cfg)
common.OkWithMessage(c, user, "登录成功")
}
// ConfirmRestore 二次确认恢复已注销账号
func (ac *AuthController) ConfirmRestore(c *gin.Context) {
var req model.LoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
common.Error(c, http.StatusBadRequest, "请检查输入")
return
}
accessToken, refreshToken, user, err := ac.authService.ConfirmRestore(req, clientIP(c))
if err != nil {
switch err {
case common.ErrInvalidCred:
common.Error(c, http.StatusUnauthorized, "邮箱或密码错误")
default:
common.Error(c, http.StatusInternalServerError, "操作失败,请稍后重试")
}
return
}
common.SetAuthCookies(c, accessToken, refreshToken, req.RememberMe, ac.cfg)
common.OkWithMessage(c, user, "账号已恢复,欢迎回来")
}

View File

@ -0,0 +1,96 @@
package controller
import (
"net/http"
"metazone.cc/metalab/internal/common"
"metazone.cc/metalab/internal/model"
"github.com/gin-gonic/gin"
)
// CheckEmail 检查邮箱是否已注册
func (ac *AuthController) CheckEmail(c *gin.Context) {
var req model.CheckEmailRequest
if err := c.ShouldBindJSON(&req); err != nil {
common.Error(c, http.StatusBadRequest, "请提供有效的邮箱地址")
return
}
exists, err := ac.authService.CheckEmail(req.Email)
if err != nil {
common.Error(c, http.StatusInternalServerError, "检查失败")
return
}
common.Ok(c, gin.H{"exists": exists})
}
// Register 注册 API
func (ac *AuthController) Register(c *gin.Context) {
var req model.RegisterRequest
if err := c.ShouldBindJSON(&req); err != nil {
common.Error(c, http.StatusBadRequest, "请检查输入:"+err.Error())
return
}
// 注册 IP 限流1 分钟 5 次
regResult, recordReg := ac.rateLimiter.AllowIP(clientIP(c) + ":register")
if regResult.Blocked {
common.Error(c, http.StatusTooManyRequests, "注册请求过于频繁,请稍后重试")
return
}
accessToken, refreshToken, user, err := ac.authService.Register(req, clientIP(c))
if err != nil {
if recordReg != nil {
recordReg()
}
switch err {
case common.ErrEmailExists:
common.Error(c, http.StatusConflict, "该邮箱已注册")
case common.ErrWeakPassword:
common.Error(c, http.StatusBadRequest, err.Error())
default:
common.Error(c, http.StatusInternalServerError, "注册失败,请稍后重试")
}
return
}
common.SetAuthCookies(c, accessToken, refreshToken, req.RememberMe, ac.cfg)
common.OkWithMessage(c, user, "注册成功!欢迎加入 MetaLab")
}
// Logout 退出登录:清除所有认证 Cookie
func (ac *AuthController) Logout(c *gin.Context) {
common.ClearAuthCookies(c, ac.cfg)
common.OkMessage(c, "已退出登录")
}
// RefreshToken 用 refresh token 换取新的 access token
func (ac *AuthController) RefreshToken(c *gin.Context) {
refreshToken, err := c.Cookie(common.RefreshCookieName)
if err != nil {
common.Error(c, http.StatusUnauthorized, "请重新登录")
return
}
accessToken, _, err := ac.tokenService.RefreshAccessToken(refreshToken)
if err != nil {
common.ClearAuthCookies(c, ac.cfg)
switch err {
case common.ErrTokenExpired, common.ErrTokenRevoked:
common.Error(c, http.StatusUnauthorized, "登录凭证已失效,请重新登录")
case common.ErrUserBanned:
common.Error(c, http.StatusForbidden, "账号已被封禁")
default:
common.Error(c, http.StatusUnauthorized, "请重新登录")
}
return
}
common.SetAccessCookie(c, accessToken, ac.cfg)
common.Ok(c, nil)
}

View File

@ -5,7 +5,6 @@ import (
"metazone.cc/metalab/internal/common" "metazone.cc/metalab/internal/common"
"metazone.cc/metalab/internal/config" "metazone.cc/metalab/internal/config"
"metazone.cc/metalab/internal/model"
"metazone.cc/metalab/internal/theme" "metazone.cc/metalab/internal/theme"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -53,186 +52,3 @@ func (ac *AuthController) LoginPage(c *gin.Context) {
"ExtraCSS": "/static/css/auth.css", "ExtraCSS": "/static/css/auth.css",
})) }))
} }
// clientIP 获取客户端真实 IP考虑反向代理
func clientIP(c *gin.Context) string {
if fwd := c.GetHeader("X-Forwarded-For"); fwd != "" {
return fwd
}
if real := c.GetHeader("X-Real-IP"); real != "" {
return real
}
return c.ClientIP()
}
// Login 登录 API含双维度限流
func (ac *AuthController) Login(c *gin.Context) {
var req model.LoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
common.Error(c, http.StatusBadRequest, "请检查输入")
return
}
email := req.Email
ip := clientIP(c)
// --- 限流:账户维度 ---
acctResult, recordAccount := ac.rateLimiter.AllowAccount(email)
if acctResult.Blocked {
common.Error(c, http.StatusTooManyRequests, acctResult.Message)
return
}
// --- 限流IP 维度 ---
ipResult, recordIP := ac.rateLimiter.AllowIP(ip)
if ipResult.Blocked {
common.Error(c, http.StatusTooManyRequests, ipResult.Message)
return
}
accessToken, refreshToken, user, err := ac.authService.Login(req, ip)
if err != nil {
// 记录失败 → 两个维度各 +1
if recordAccount != nil {
recordAccount()
}
if recordIP != nil {
recordIP()
}
switch err {
case common.ErrInvalidCred:
common.Error(c, http.StatusUnauthorized, "邮箱或密码错误")
case common.ErrUserBanned:
common.Error(c, http.StatusForbidden, "账号已被封禁")
case common.ErrUserLocked:
common.Error(c, http.StatusUnauthorized, "邮箱或密码错误")
case common.ErrNeedsConfirmRestore:
// 注销账号登录 → 需要二次确认恢复
c.JSON(http.StatusOK, gin.H{
"success": true,
"action": "confirm_restore",
"message": "你的账号正在注销中,登录将中止注销流程",
})
default:
common.Error(c, http.StatusInternalServerError, "登录失败,请稍后重试")
}
return
}
// 登录成功 → 清除失败计数
ac.rateLimiter.Clear(email, ip)
common.SetAuthCookies(c, accessToken, refreshToken, req.RememberMe, ac.cfg)
common.OkWithMessage(c, user, "登录成功")
}
// ConfirmRestore 二次确认恢复已注销账号
func (ac *AuthController) ConfirmRestore(c *gin.Context) {
var req model.LoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
common.Error(c, http.StatusBadRequest, "请检查输入")
return
}
accessToken, refreshToken, user, err := ac.authService.ConfirmRestore(req, clientIP(c))
if err != nil {
switch err {
case common.ErrInvalidCred:
common.Error(c, http.StatusUnauthorized, "邮箱或密码错误")
default:
common.Error(c, http.StatusInternalServerError, "操作失败,请稍后重试")
}
return
}
common.SetAuthCookies(c, accessToken, refreshToken, req.RememberMe, ac.cfg)
common.OkWithMessage(c, user, "账号已恢复,欢迎回来")
}
// CheckEmail 检查邮箱是否已注册
func (ac *AuthController) CheckEmail(c *gin.Context) {
var req model.CheckEmailRequest
if err := c.ShouldBindJSON(&req); err != nil {
common.Error(c, http.StatusBadRequest, "请提供有效的邮箱地址")
return
}
exists, err := ac.authService.CheckEmail(req.Email)
if err != nil {
common.Error(c, http.StatusInternalServerError, "检查失败")
return
}
common.Ok(c, gin.H{"exists": exists})
}
// Register 注册 API
func (ac *AuthController) Register(c *gin.Context) {
var req model.RegisterRequest
if err := c.ShouldBindJSON(&req); err != nil {
common.Error(c, http.StatusBadRequest, "请检查输入:"+err.Error())
return
}
// 注册 IP 限流1 分钟 5 次
regResult, recordReg := ac.rateLimiter.AllowIP(clientIP(c) + ":register")
if regResult.Blocked {
common.Error(c, http.StatusTooManyRequests, "注册请求过于频繁,请稍后重试")
return
}
accessToken, refreshToken, user, err := ac.authService.Register(req, clientIP(c))
if err != nil {
if recordReg != nil {
recordReg()
}
switch err {
case common.ErrEmailExists:
common.Error(c, http.StatusConflict, "该邮箱已注册")
case common.ErrWeakPassword:
common.Error(c, http.StatusBadRequest, err.Error())
default:
common.Error(c, http.StatusInternalServerError, "注册失败,请稍后重试")
}
return
}
common.SetAuthCookies(c, accessToken, refreshToken, req.RememberMe, ac.cfg)
common.OkWithMessage(c, user, "注册成功!欢迎加入 MetaLab")
}
// Logout 退出登录:清除所有认证 Cookie
func (ac *AuthController) Logout(c *gin.Context) {
common.ClearAuthCookies(c, ac.cfg)
common.OkMessage(c, "已退出登录")
}
// RefreshToken 用 refresh token 换取新的 access token
func (ac *AuthController) RefreshToken(c *gin.Context) {
refreshToken, err := c.Cookie(common.RefreshCookieName)
if err != nil {
common.Error(c, http.StatusUnauthorized, "请重新登录")
return
}
accessToken, _, err := ac.tokenService.RefreshAccessToken(refreshToken)
if err != nil {
common.ClearAuthCookies(c, ac.cfg)
switch err {
case common.ErrTokenExpired, common.ErrTokenRevoked:
common.Error(c, http.StatusUnauthorized, "登录凭证已失效,请重新登录")
case common.ErrUserBanned:
common.Error(c, http.StatusForbidden, "账号已被封禁")
default:
common.Error(c, http.StatusUnauthorized, "请重新登录")
}
return
}
common.SetAccessCookie(c, accessToken, ac.cfg)
common.Ok(c, nil)
}

View File

@ -28,50 +28,6 @@ func NewMessageController(notifService notifProvider) *MessageController {
return &MessageController{notifService: notifService} return &MessageController{notifService: notifService}
} }
// MessagesPage 消息中心页面需登录noindex
func (mc *MessageController) MessagesPage(c *gin.Context) {
uidVal, exists := c.Get("uid")
if !exists {
c.Redirect(http.StatusFound, "/auth/login")
c.Abort()
return
}
uid := uidVal.(uint)
// 从 query 取分页参数
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
if page < 1 {
page = 1
}
pageSize := 20
result, err := mc.notifService.List(uid, page, pageSize)
if err != nil {
result = &model.NotificationListResult{Items: []model.Notification{}, Total: 0, Page: 1}
}
totalPages := result.TotalPages
if totalPages == 0 && result.Total > 0 {
totalPages = int((result.Total + int64(pageSize) - 1) / int64(pageSize))
}
c.HTML(http.StatusOK, "messages/index.html", common.BuildPageData(c, gin.H{
"Title": "消息中心",
"ExtraCSS": "/static/css/messages.css",
"Messages": result.Items,
"Total": result.Total,
"Page": result.Page,
"TotalPages": totalPages,
"PrevPage": result.Page - 1,
"NextPage": result.Page + 1,
"HasPrev": result.Page > 1,
"HasNext": result.Page < totalPages,
"UnreadCount": result.Unread,
"NotifyTypeNames": model.NotifyTypeNames,
"AuditTypeNames": model.AuditTypeNames,
}))
}
// UnreadCount 获取未读消息数API供导航栏轮询 // UnreadCount 获取未读消息数API供导航栏轮询
func (mc *MessageController) UnreadCount(c *gin.Context) { func (mc *MessageController) UnreadCount(c *gin.Context) {
uidVal, exists := c.Get("uid") uidVal, exists := c.Get("uid")

View File

@ -0,0 +1,55 @@
package controller
import (
"net/http"
"strconv"
"metazone.cc/metalab/internal/common"
"metazone.cc/metalab/internal/model"
"github.com/gin-gonic/gin"
)
// MessagesPage 消息中心页面需登录noindex
func (mc *MessageController) MessagesPage(c *gin.Context) {
uidVal, exists := c.Get("uid")
if !exists {
c.Redirect(http.StatusFound, "/auth/login")
c.Abort()
return
}
uid := uidVal.(uint)
// 从 query 取分页参数
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
if page < 1 {
page = 1
}
pageSize := 20
result, err := mc.notifService.List(uid, page, pageSize)
if err != nil {
result = &model.NotificationListResult{Items: []model.Notification{}, Total: 0, Page: 1}
}
totalPages := result.TotalPages
if totalPages == 0 && result.Total > 0 {
totalPages = int((result.Total + int64(pageSize) - 1) / int64(pageSize))
}
c.HTML(http.StatusOK, "messages/index.html", common.BuildPageData(c, gin.H{
"Title": "消息中心",
"ExtraCSS": "/static/css/messages.css",
"Messages": result.Items,
"Total": result.Total,
"Page": result.Page,
"TotalPages": totalPages,
"PrevPage": result.Page - 1,
"NextPage": result.Page + 1,
"HasPrev": result.Page > 1,
"HasNext": result.Page < totalPages,
"UnreadCount": result.Unread,
"NotifyTypeNames": model.NotifyTypeNames,
"AuditTypeNames": model.AuditTypeNames,
}))
}

View File

@ -0,0 +1,59 @@
package controller
import (
"net/http"
"metazone.cc/metalab/internal/common"
"github.com/gin-gonic/gin"
)
// ChangePassword 修改密码(需登录)
func (sc *SettingsController) ChangePassword(c *gin.Context) {
uid, exists := c.Get("uid")
if !exists {
common.Error(c, http.StatusUnauthorized, "请先登录")
return
}
var req struct {
CurrentPassword string `json:"current_password" binding:"required"`
NewPassword string `json:"new_password" binding:"required,min=8"`
}
if err := c.ShouldBindJSON(&req); err != nil {
common.Error(c, http.StatusBadRequest, "请检查输入")
return
}
if err := sc.authService.ChangePassword(uid.(uint), req.CurrentPassword, req.NewPassword); err != nil {
handleSettingsError(c, err)
return
}
common.OkMessage(c, "密码已修改,请重新登录")
}
// DeleteAccount 自助注销账号(需登录,验证密码 + 注销原因)
func (sc *SettingsController) DeleteAccount(c *gin.Context) {
uid, exists := c.Get("uid")
if !exists {
common.Error(c, http.StatusUnauthorized, "请先登录")
return
}
var req struct {
Password string `json:"password" binding:"required"`
Reason string `json:"reason"`
}
if err := c.ShouldBindJSON(&req); err != nil {
common.Error(c, http.StatusBadRequest, "请检查输入")
return
}
if err := sc.authService.DeleteAccount(uid.(uint), req.Password, req.Reason); err != nil {
handleSettingsError(c, err)
return
}
common.OkMessage(c, "账号已注销7 天内重新登录即可恢复")
}

View File

@ -0,0 +1,116 @@
package controller
import (
"net/http"
"strconv"
"metazone.cc/metalab/internal/common"
"metazone.cc/metalab/internal/model"
"github.com/gin-gonic/gin"
)
// UpdateProfile 修改个人资料(用户名 + 个性签名,需登录)
// 普通用户走审核流程,管理员及以上直接落库
func (sc *SettingsController) UpdateProfile(c *gin.Context) {
uid, exists := c.Get("uid")
if !exists {
common.Error(c, http.StatusUnauthorized, "请先登录")
return
}
var req struct {
Username string `json:"username"`
Bio string `json:"bio"`
}
if err := c.ShouldBindJSON(&req); err != nil {
common.Error(c, http.StatusBadRequest, "参数错误")
return
}
userID := uid.(uint)
// 判断是否需要审核
shouldAudit, err := sc.auditService.ShouldAudit(userID)
if err != nil {
common.Error(c, http.StatusInternalServerError, "操作失败")
return
}
if shouldAudit {
user, err := sc.authService.GetProfile(userID)
if err != nil {
common.Error(c, http.StatusInternalServerError, "操作失败")
return
}
if err := sc.auditService.SubmitProfileChanges(userID, user, req.Username, req.Bio); err != nil {
handleAuditSubmitError(c, err)
return
}
common.OkMessage(c, "修改已提交审核,通过前当前信息保持不变")
return
}
// 管理员及以上直接更新
if err := sc.authService.UpdateProfile(userID, req.Username, req.Bio); err != nil {
handleSettingsError(c, err)
return
}
common.OkMessage(c, "个人资料已更新")
}
// UploadAvatar 上传头像需登录multipart/form-data
// 普通用户走审核流程,管理员及以上直接更新
func (sc *SettingsController) UploadAvatar(c *gin.Context) {
uid, exists := c.Get("uid")
if !exists {
common.Error(c, http.StatusUnauthorized, "请先登录")
return
}
file, header, err := c.Request.FormFile("avatar")
if err != nil {
common.Error(c, http.StatusBadRequest, "请选择文件")
return
}
defer file.Close()
// 裁切参数(可选,来自前端裁切弹窗)
cropX, _ := strconv.Atoi(c.PostForm("crop_x"))
cropY, _ := strconv.Atoi(c.PostForm("crop_y"))
cropSize, _ := strconv.Atoi(c.PostForm("crop_size"))
userID := uid.(uint)
// 判断是否需要审核
shouldAudit, err := sc.auditService.ShouldAudit(userID)
if err != nil {
common.Error(c, http.StatusInternalServerError, "操作失败")
return
}
if shouldAudit {
// 仅处理图片,不更新用户
avatarURL, err := sc.avatarService.ProcessImage(userID, file, header.Header.Get("Content-Type"), cropX, cropY, cropSize)
if err != nil {
common.Error(c, http.StatusBadRequest, err.Error())
return
}
if err := sc.auditService.Submit(userID, model.AuditTypeAvatar, avatarURL); err != nil {
handleAuditSubmitError(c, err)
return
}
common.OkMessage(c, "头像已提交审核,通过前当前头像保持不变")
return
}
// 管理员及以上直接更新
url, err := sc.avatarService.ProcessAvatar(userID, file, header.Header.Get("Content-Type"), cropX, cropY, cropSize)
if err != nil {
common.Error(c, http.StatusBadRequest, err.Error())
return
}
common.Ok(c, gin.H{"url": url})
}

View File

@ -3,7 +3,6 @@ package controller
import ( import (
"io" "io"
"net/http" "net/http"
"strconv"
"metazone.cc/metalab/internal/common" "metazone.cc/metalab/internal/common"
"metazone.cc/metalab/internal/model" "metazone.cc/metalab/internal/model"
@ -25,7 +24,7 @@ type avatarProvider interface {
ProcessImage(userID uint, file io.Reader, contentType string, cropX, cropY, cropSize int) (string, error) ProcessImage(userID uint, file io.Reader, contentType string, cropX, cropY, cropSize int) (string, error)
} }
// auditSubmittable 个人设置对审核服务的依赖ISP3 个方法) // auditSubmittable 个人设置对审核服务的依赖ISP4 个方法)
type auditSubmittable interface { type auditSubmittable interface {
ShouldAudit(userID uint) (bool, error) ShouldAudit(userID uint) (bool, error)
SubmitProfileChanges(userID uint, currentUser *model.User, newUsername, newBio string) error SubmitProfileChanges(userID uint, currentUser *model.User, newUsername, newBio string) error
@ -82,193 +81,6 @@ func (sc *SettingsController) SettingsPage(c *gin.Context) {
})) }))
} }
// UpdateProfile 修改个人资料(用户名 + 个性签名,需登录)
// 普通用户走审核流程,管理员及以上直接落库
func (sc *SettingsController) UpdateProfile(c *gin.Context) {
uid, exists := c.Get("uid")
if !exists {
common.Error(c, http.StatusUnauthorized, "请先登录")
return
}
var req struct {
Username string `json:"username"`
Bio string `json:"bio"`
}
if err := c.ShouldBindJSON(&req); err != nil {
common.Error(c, http.StatusBadRequest, "参数错误")
return
}
userID := uid.(uint)
// 判断是否需要审核
shouldAudit, err := sc.auditService.ShouldAudit(userID)
if err != nil {
common.Error(c, http.StatusInternalServerError, "操作失败")
return
}
if shouldAudit {
user, err := sc.authService.GetProfile(userID)
if err != nil {
common.Error(c, http.StatusInternalServerError, "操作失败")
return
}
if err := sc.auditService.SubmitProfileChanges(userID, user, req.Username, req.Bio); err != nil {
handleAuditSubmitError(c, err)
return
}
common.OkMessage(c, "修改已提交审核,通过前当前信息保持不变")
return
}
// 管理员及以上直接更新
if err := sc.authService.UpdateProfile(userID, req.Username, req.Bio); err != nil {
handleSettingsError(c, err)
return
}
common.OkMessage(c, "个人资料已更新")
}
// UploadAvatar 上传头像需登录multipart/form-data
// 普通用户走审核流程,管理员及以上直接更新
func (sc *SettingsController) UploadAvatar(c *gin.Context) {
uid, exists := c.Get("uid")
if !exists {
common.Error(c, http.StatusUnauthorized, "请先登录")
return
}
file, header, err := c.Request.FormFile("avatar")
if err != nil {
common.Error(c, http.StatusBadRequest, "请选择文件")
return
}
defer file.Close()
// 裁切参数(可选,来自前端裁切弹窗)
cropX, _ := strconv.Atoi(c.PostForm("crop_x"))
cropY, _ := strconv.Atoi(c.PostForm("crop_y"))
cropSize, _ := strconv.Atoi(c.PostForm("crop_size"))
userID := uid.(uint)
// 判断是否需要审核
shouldAudit, err := sc.auditService.ShouldAudit(userID)
if err != nil {
common.Error(c, http.StatusInternalServerError, "操作失败")
return
}
if shouldAudit {
// 仅处理图片,不更新用户
avatarURL, err := sc.avatarService.ProcessImage(userID, file, header.Header.Get("Content-Type"), cropX, cropY, cropSize)
if err != nil {
common.Error(c, http.StatusBadRequest, err.Error())
return
}
if err := sc.auditService.Submit(userID, model.AuditTypeAvatar, avatarURL); err != nil {
handleAuditSubmitError(c, err)
return
}
common.OkMessage(c, "头像已提交审核,通过前当前头像保持不变")
return
}
// 管理员及以上直接更新
url, err := sc.avatarService.ProcessAvatar(userID, file, header.Header.Get("Content-Type"), cropX, cropY, cropSize)
if err != nil {
common.Error(c, http.StatusBadRequest, err.Error())
return
}
common.Ok(c, gin.H{"url": url})
}
// ChangePassword 修改密码(需登录)
func (sc *SettingsController) ChangePassword(c *gin.Context) {
uid, exists := c.Get("uid")
if !exists {
common.Error(c, http.StatusUnauthorized, "请先登录")
return
}
var req struct {
CurrentPassword string `json:"current_password" binding:"required"`
NewPassword string `json:"new_password" binding:"required,min=8"`
}
if err := c.ShouldBindJSON(&req); err != nil {
common.Error(c, http.StatusBadRequest, "请检查输入")
return
}
if err := sc.authService.ChangePassword(uid.(uint), req.CurrentPassword, req.NewPassword); err != nil {
handleSettingsError(c, err)
return
}
common.OkMessage(c, "密码已修改,请重新登录")
}
// DeleteAccount 自助注销账号(需登录,验证密码 + 注销原因)
func (sc *SettingsController) DeleteAccount(c *gin.Context) {
uid, exists := c.Get("uid")
if !exists {
common.Error(c, http.StatusUnauthorized, "请先登录")
return
}
var req struct {
Password string `json:"password" binding:"required"`
Reason string `json:"reason"`
}
if err := c.ShouldBindJSON(&req); err != nil {
common.Error(c, http.StatusBadRequest, "请检查输入")
return
}
if err := sc.authService.DeleteAccount(uid.(uint), req.Password, req.Reason); err != nil {
handleSettingsError(c, err)
return
}
common.OkMessage(c, "账号已注销7 天内重新登录即可恢复")
}
// handleSettingsError 统一处理 settings 接口的 service 层错误
func handleSettingsError(c *gin.Context, err error) {
switch err {
case common.ErrUsernameTaken:
common.Error(c, http.StatusConflict, err.Error())
case common.ErrUsernameInvalid:
common.Error(c, http.StatusBadRequest, err.Error())
case common.ErrBioTooLong:
common.Error(c, http.StatusBadRequest, err.Error())
case common.ErrIncorrectPassword:
common.Error(c, http.StatusForbidden, err.Error())
case common.ErrOwnerCannotDelete:
common.Error(c, http.StatusForbidden, err.Error())
default:
common.Error(c, http.StatusInternalServerError, "操作失败")
}
}
// handleAuditSubmitError 统一处理审核提交的错误
func handleAuditSubmitError(c *gin.Context, err error) {
switch err {
case common.ErrAuditDisabled:
common.Error(c, http.StatusForbidden, "审核功能未开启")
case common.ErrAuditTypeOff:
common.Error(c, http.StatusForbidden, "该类型审核未开启,请联系管理员")
case common.ErrUsernameTaken:
common.Error(c, http.StatusConflict, err.Error())
default:
common.Error(c, http.StatusInternalServerError, "提交审核失败")
}
}
// AuditStatus 查询当前用户的待审核类型(需登录) // AuditStatus 查询当前用户的待审核类型(需登录)
func (sc *SettingsController) AuditStatus(c *gin.Context) { func (sc *SettingsController) AuditStatus(c *gin.Context) {
uid, exists := c.Get("uid") uid, exists := c.Get("uid")

View File

@ -0,0 +1,41 @@
package controller
import (
"net/http"
"metazone.cc/metalab/internal/common"
"github.com/gin-gonic/gin"
)
// handleSettingsError 统一处理 settings 接口的 service 层错误
func handleSettingsError(c *gin.Context, err error) {
switch err {
case common.ErrUsernameTaken:
common.Error(c, http.StatusConflict, err.Error())
case common.ErrUsernameInvalid:
common.Error(c, http.StatusBadRequest, err.Error())
case common.ErrBioTooLong:
common.Error(c, http.StatusBadRequest, err.Error())
case common.ErrIncorrectPassword:
common.Error(c, http.StatusForbidden, err.Error())
case common.ErrOwnerCannotDelete:
common.Error(c, http.StatusForbidden, err.Error())
default:
common.Error(c, http.StatusInternalServerError, "操作失败")
}
}
// handleAuditSubmitError 统一处理审核提交的错误
func handleAuditSubmitError(c *gin.Context, err error) {
switch err {
case common.ErrAuditDisabled:
common.Error(c, http.StatusForbidden, "审核功能未开启")
case common.ErrAuditTypeOff:
common.Error(c, http.StatusForbidden, "该类型审核未开启,请联系管理员")
case common.ErrUsernameTaken:
common.Error(c, http.StatusConflict, err.Error())
default:
common.Error(c, http.StatusInternalServerError, "提交审核失败")
}
}