4985c2f2b6
- 为管理员登录接口添加完整的Swagger文档注释 - 修复支付和订单相关接口的参数类型定义 - 更新订单查询接口使用订单号作为路径参数 - 添加支付记录导出功能的API文档 - 创建详细的Apifox接口测试指南文档 - 修正金额参数的数据格式为float64 - 更新接口描述以反映分表后的查询逻辑
773 lines
25 KiB
Go
773 lines
25 KiB
Go
package admin
|
||
|
||
import (
|
||
"encoding/json"
|
||
"strconv"
|
||
"time"
|
||
|
||
"github.com/goravel/framework/contracts/http"
|
||
"github.com/goravel/framework/facades"
|
||
"github.com/goravel/framework/support/str"
|
||
|
||
apperrors "goravel/app/errors"
|
||
"goravel/app/http/helpers"
|
||
"goravel/app/http/requests/admin"
|
||
"goravel/app/http/response"
|
||
"goravel/app/models"
|
||
"goravel/app/services"
|
||
"goravel/app/utils"
|
||
)
|
||
|
||
type AuthController struct {
|
||
authService services.AuthService
|
||
captchaService services.CaptchaService
|
||
googleAuthenticatorService services.GoogleAuthenticatorService
|
||
}
|
||
|
||
func NewAuthController() *AuthController {
|
||
adminService := services.NewAdminServiceImpl()
|
||
tokenService := services.NewTokenServiceImpl()
|
||
authService := services.NewAuthServiceImpl(adminService, tokenService)
|
||
return &AuthController{
|
||
authService: authService,
|
||
captchaService: services.NewCaptchaServiceImpl(),
|
||
googleAuthenticatorService: services.NewGoogleAuthenticatorServiceImpl(),
|
||
}
|
||
}
|
||
|
||
// getLoginRequestData 获取登录请求数据(排除敏感信息)
|
||
func (r *AuthController) getLoginRequestData(ctx http.Context) string {
|
||
inputs := make(map[string]any)
|
||
allInputs := ctx.Request().All()
|
||
for key, value := range allInputs {
|
||
// 使用工具函数检查是否是敏感字段
|
||
if utils.IsSensitiveField(key) {
|
||
inputs[key] = "***"
|
||
} else {
|
||
inputs[key] = value
|
||
}
|
||
}
|
||
if data, err := json.Marshal(inputs); err == nil {
|
||
return string(data)
|
||
}
|
||
return ""
|
||
}
|
||
|
||
// Login 管理员登录
|
||
// @Summary 管理员登录
|
||
// @Description 管理员登录接口,支持用户名密码登录。如果管理员绑定了谷歌验证码,需要提供 google_code;如果未绑定但启用了图形验证码,需要提供 captcha_id 和 captcha_answer。登录成功后返回 JWT Token,Token 会同时出现在响应体的 data.token 和响应头 Authorization 中。
|
||
// @Tags 认证管理
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Param username body string true "用户名"
|
||
// @Param password body string true "密码(最少6位)"
|
||
// @Param captcha_id body string false "图形验证码ID(如果启用了图形验证码且未绑定谷歌验证码)"
|
||
// @Param captcha_answer body string false "图形验证码答案(如果启用了图形验证码且未绑定谷歌验证码)"
|
||
// @Param google_code body string false "谷歌验证码(如果绑定了谷歌验证码)"
|
||
// @Success 200 {object} map[string]any "登录成功,返回 Token 和管理员信息"
|
||
// @Failure 400 {object} map[string]any "参数错误、验证码错误或谷歌验证码错误"
|
||
// @Failure 401 {object} map[string]any "用户名或密码错误"
|
||
// @Failure 403 {object} map[string]any "账号已禁用"
|
||
// @Failure 500 {object} map[string]any "服务器错误"
|
||
// @Router /api/admin/login [post]
|
||
func (r *AuthController) Login(ctx http.Context) http.Response {
|
||
var loginRequest admin.Login
|
||
errors, err := ctx.Request().ValidateRequest(&loginRequest)
|
||
if err != nil {
|
||
// 记录验证失败日志
|
||
requestData := r.getLoginRequestData(ctx)
|
||
r.authService.RecordLoginLog(ctx, 0, loginRequest.Username, 0, "validation_failed", requestData)
|
||
return response.Error(ctx, http.StatusBadRequest, err.Error())
|
||
}
|
||
if errors != nil {
|
||
// 记录验证失败日志
|
||
requestData := r.getLoginRequestData(ctx)
|
||
r.authService.RecordLoginLog(ctx, 0, loginRequest.Username, 0, "validation_failed", requestData)
|
||
return response.ValidationError(ctx, http.StatusBadRequest, "validation_failed", errors.All())
|
||
}
|
||
|
||
// 获取请求数据用于日志记录
|
||
requestData := r.getLoginRequestData(ctx)
|
||
|
||
// 先验证用户名是否存在
|
||
exists, err := facades.Orm().Query().Model(&models.Admin{}).Where("username", loginRequest.Username).Exists()
|
||
if err != nil {
|
||
return response.ErrorWithLog(ctx, "auth", err, map[string]any{
|
||
"username": loginRequest.Username,
|
||
})
|
||
}
|
||
if !exists {
|
||
// 记录用户名不存在日志
|
||
r.authService.RecordLoginLog(ctx, 0, loginRequest.Username, 0, "username_not_found", requestData)
|
||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrUsernameOrPasswordErr.Code)
|
||
}
|
||
|
||
// 获取管理员信息
|
||
var admin models.Admin
|
||
if err := facades.Orm().Query().Where("username", loginRequest.Username).FirstOrFail(&admin); err != nil {
|
||
return response.ErrorWithLog(ctx, "auth", err, map[string]any{
|
||
"username": loginRequest.Username,
|
||
})
|
||
}
|
||
|
||
if admin.Status == 0 {
|
||
// 记录账号禁用日志
|
||
r.authService.RecordLoginLog(ctx, admin.ID, loginRequest.Username, 0, "account_disabled", requestData)
|
||
return response.Error(ctx, http.StatusForbidden, apperrors.ErrAccountDisabled.Code)
|
||
}
|
||
|
||
// 验证密码
|
||
if !facades.Hash().Check(loginRequest.Password, admin.Password) {
|
||
// 记录登录失败日志
|
||
r.authService.RecordLoginLog(ctx, admin.ID, loginRequest.Username, 0, "password_error", requestData)
|
||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrUsernameOrPasswordErr.Code)
|
||
}
|
||
|
||
// 检查是否绑定了谷歌验证码
|
||
isBound, err := r.googleAuthenticatorService.IsBound(admin.ID)
|
||
if err != nil {
|
||
return response.ErrorWithLog(ctx, "auth", err, map[string]any{
|
||
"admin_id": admin.ID,
|
||
})
|
||
}
|
||
|
||
// 如果绑定了谷歌验证码,验证谷歌验证码
|
||
if isBound {
|
||
googleCode := loginRequest.GoogleCode
|
||
if googleCode == "" {
|
||
// 记录谷歌验证码缺失日志
|
||
r.authService.RecordLoginLog(ctx, admin.ID, loginRequest.Username, 0, "google_code_required", requestData)
|
||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrGoogleCodeRequired.Code)
|
||
}
|
||
|
||
// 获取管理员的密钥
|
||
secret, err := r.googleAuthenticatorService.GetSecret(admin.ID)
|
||
if err != nil {
|
||
return response.ErrorWithLog(ctx, "auth", err, map[string]any{
|
||
"admin_id": admin.ID,
|
||
})
|
||
}
|
||
|
||
// 验证谷歌验证码
|
||
if !r.googleAuthenticatorService.Verify(secret, googleCode) {
|
||
// 记录登录失败日志
|
||
r.authService.RecordLoginLog(ctx, admin.ID, loginRequest.Username, 0, "google_code_error", requestData)
|
||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrGoogleCodeInvalid.Code)
|
||
}
|
||
} else {
|
||
// 如果没有绑定谷歌验证码,验证图形验证码(如果启用了)
|
||
if r.captchaService.Enabled() {
|
||
captchaID := ctx.Request().Input("captcha_id")
|
||
captchaAnswer := ctx.Request().Input("captcha_answer")
|
||
if ok, messageKey := r.captchaService.Verify(captchaID, captchaAnswer); !ok {
|
||
if messageKey == "" {
|
||
messageKey = "captcha_invalid"
|
||
}
|
||
// 记录验证码错误日志
|
||
r.authService.RecordLoginLog(ctx, admin.ID, loginRequest.Username, 0, messageKey, requestData)
|
||
return response.Error(ctx, http.StatusBadRequest, messageKey)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 验证通过,生成token并完成登录
|
||
// 获取浏览器和操作系统信息
|
||
browser, os := helpers.GetBrowserAndOS(ctx)
|
||
// 获取真实IP地址
|
||
ip := helpers.GetRealIP(ctx)
|
||
|
||
// 生成token
|
||
var expiresAt *time.Time
|
||
ttl := facades.Config().GetInt("jwt.ttl", 60) // 默认60分钟
|
||
if ttl > 0 {
|
||
exp := time.Now().Add(time.Duration(ttl) * time.Minute)
|
||
expiresAt = &exp
|
||
}
|
||
|
||
tokenService := services.NewTokenServiceImpl()
|
||
plainToken, _, err := tokenService.CreateToken("admin", admin.ID, "admin-token", expiresAt, browser, ip, os, "")
|
||
if err != nil {
|
||
return response.ErrorWithLog(ctx, "auth", err, map[string]any{
|
||
"admin_id": admin.ID,
|
||
})
|
||
}
|
||
token := plainToken
|
||
|
||
// 记录登录成功日志
|
||
r.authService.RecordLoginLog(ctx, admin.ID, loginRequest.Username, 1, "login_success", requestData)
|
||
|
||
// 更新最后登录时间(ORM会自动更新UpdatedAt)
|
||
facades.Orm().Query().Save(&admin)
|
||
|
||
return response.SuccessWithHeader(ctx, "login_success", "Authorization", "Bearer "+token, http.Json{
|
||
"token": token,
|
||
"admin": http.Json{
|
||
"id": admin.ID,
|
||
"username": admin.Username,
|
||
"nickname": admin.Nickname,
|
||
"avatar": admin.Avatar,
|
||
},
|
||
})
|
||
}
|
||
|
||
// Captcha 获取登录验证码
|
||
func (r *AuthController) Captcha(ctx http.Context) http.Response {
|
||
enabled := r.captchaService.Enabled()
|
||
captchaData := http.Json{
|
||
"enabled": enabled,
|
||
}
|
||
|
||
if enabled {
|
||
captchaID, image, err := r.captchaService.Generate()
|
||
if err != nil {
|
||
return response.ErrorWithLog(ctx, "captcha", err)
|
||
}
|
||
captchaData["captcha_id"] = captchaID
|
||
captchaData["captcha_image"] = image
|
||
// captchaData["captcha_image"] = "data:image/png;base64," + image
|
||
}
|
||
|
||
return response.Success(ctx, http.Json{
|
||
"captcha": captchaData,
|
||
})
|
||
}
|
||
|
||
// Info 获取当前登录管理员信息
|
||
func (r *AuthController) Info(ctx http.Context) http.Response {
|
||
admin, permissions, menus, err := r.authService.GetAdminInfo(ctx)
|
||
if err != nil {
|
||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||
}
|
||
|
||
// 获取配置:是否显示无权限的按钮
|
||
showButtonsWithoutPermission := facades.Config().GetBool("admin.show_buttons_without_permission", false)
|
||
|
||
// 检查是否是超级管理员
|
||
const SuperAdminRoleSlug = "super-admin"
|
||
isSuperAdmin := false
|
||
for _, role := range admin.Roles {
|
||
if role.Slug == SuperAdminRoleSlug && role.Status == 1 {
|
||
isSuperAdmin = true
|
||
break
|
||
}
|
||
}
|
||
|
||
return response.Success(ctx, http.Json{
|
||
"admin": http.Json{
|
||
"id": admin.ID,
|
||
"username": admin.Username,
|
||
"nickname": admin.Nickname,
|
||
"avatar": admin.Avatar,
|
||
"email": admin.Email,
|
||
"phone": admin.Phone,
|
||
"department_id": admin.DepartmentID,
|
||
"department": admin.Department,
|
||
"roles": admin.Roles,
|
||
"permissions": permissions,
|
||
"menus": menus,
|
||
"is_super_admin": isSuperAdmin,
|
||
},
|
||
"config": http.Json{
|
||
"show_buttons_without_permission": showButtonsWithoutPermission,
|
||
"monitor_hidden": facades.Config().GetString("admin.monitor_hidden", ""),
|
||
},
|
||
})
|
||
}
|
||
|
||
// UpdateProfile 更新个人信息
|
||
func (r *AuthController) UpdateProfile(ctx http.Context) http.Response {
|
||
// 从context中获取admin信息(由JWT中间件设置)
|
||
adminValue := ctx.Value("admin")
|
||
if adminValue == nil {
|
||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||
}
|
||
|
||
var admin models.Admin
|
||
// 尝试值类型
|
||
if adminVal, ok := adminValue.(models.Admin); ok {
|
||
admin = adminVal
|
||
} else if adminPtr, ok := adminValue.(*models.Admin); ok {
|
||
// 尝试指针类型
|
||
if adminPtr == nil {
|
||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||
}
|
||
admin = *adminPtr
|
||
} else {
|
||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||
}
|
||
|
||
// 重新查询admin以确保获取最新数据
|
||
if err := facades.Orm().Query().Where("id", admin.ID).FirstOrFail(&admin); err != nil {
|
||
return response.Error(ctx, http.StatusNotFound, apperrors.ErrAdminNotFound.Code)
|
||
}
|
||
|
||
nickname := ctx.Request().Input("nickname")
|
||
email := ctx.Request().Input("email")
|
||
phone := ctx.Request().Input("phone")
|
||
avatar := ctx.Request().Input("avatar")
|
||
|
||
if nickname != "" {
|
||
admin.Nickname = nickname
|
||
}
|
||
if email != "" {
|
||
admin.Email = email
|
||
}
|
||
if phone != "" {
|
||
admin.Phone = phone
|
||
}
|
||
if avatar != "" {
|
||
admin.Avatar = avatar
|
||
}
|
||
|
||
if err := facades.Orm().Query().Save(&admin); err != nil {
|
||
return response.ErrorWithLog(ctx, "auth", err, map[string]any{
|
||
"admin_id": admin.ID,
|
||
})
|
||
}
|
||
|
||
// 重新加载关联数据(确保部门和角色被正确加载)
|
||
var adminWithRelations models.Admin
|
||
if err := facades.Orm().Query().With("Department").With("Roles").Where("id", admin.ID).FirstOrFail(&adminWithRelations); err != nil {
|
||
return response.ErrorWithLog(ctx, "auth", err, map[string]any{
|
||
"admin_id": admin.ID,
|
||
})
|
||
}
|
||
admin = adminWithRelations
|
||
|
||
return response.Success(ctx, http.Json{
|
||
"admin": http.Json{
|
||
"id": admin.ID,
|
||
"username": admin.Username,
|
||
"nickname": admin.Nickname,
|
||
"avatar": admin.Avatar,
|
||
"email": admin.Email,
|
||
"phone": admin.Phone,
|
||
"department_id": admin.DepartmentID,
|
||
"department": admin.Department,
|
||
"roles": admin.Roles,
|
||
},
|
||
})
|
||
}
|
||
|
||
// Refresh 刷新Token
|
||
// 注意:此接口需要在JWT中间件之前调用,或者使用特殊的中间件处理
|
||
// 因为Refresh方法需要token过期但仍在刷新窗口内才能工作
|
||
func (r *AuthController) Refresh(ctx http.Context) http.Response {
|
||
// 从请求头获取token
|
||
token := ctx.Request().Header("Authorization", "")
|
||
if token == "" {
|
||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrUnauthorized.Code)
|
||
}
|
||
|
||
// 移除Bearer前缀
|
||
token = str.Of(token).ChopStart("Bearer ").Trim().String()
|
||
|
||
// 先尝试解析token,如果token有效,直接重新生成(滑动过期)
|
||
if _, err := facades.Auth(ctx).Guard("admin").Parse(token); err == nil {
|
||
// Token有效,重新生成新token(延长过期时间)
|
||
if userID, err := facades.Auth(ctx).Guard("admin").ID(); err == nil {
|
||
if newToken, err := facades.Auth(ctx).Guard("admin").LoginUsingID(userID); err == nil {
|
||
return response.SuccessWithHeader(ctx, "token_refresh_success", "Authorization", "Bearer "+newToken, http.Json{
|
||
"token": newToken,
|
||
})
|
||
}
|
||
}
|
||
}
|
||
|
||
// 如果token已过期,尝试刷新(需要在刷新窗口内)
|
||
newToken, err := facades.Auth(ctx).Guard("admin").Refresh()
|
||
if err != nil {
|
||
// 刷新失败,返回错误
|
||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrTokenRefreshFailed.Code)
|
||
}
|
||
|
||
// 刷新成功,返回新token
|
||
return response.SuccessWithHeader(ctx, "token_refresh_success", "Authorization", "Bearer "+newToken, http.Json{
|
||
"token": newToken,
|
||
})
|
||
}
|
||
|
||
// Heartbeat 心跳接口,用于更新用户的最后活跃时间
|
||
// JWT中间件会自动更新 last_used_at,这个接口只是确保用户在线状态
|
||
func (r *AuthController) Heartbeat(ctx http.Context) http.Response {
|
||
// JWT中间件已经更新了 last_used_at,这里只需要返回成功即可
|
||
return response.Success(ctx, "heartbeat_success")
|
||
}
|
||
|
||
// Logout 退出登录
|
||
func (r *AuthController) Logout(ctx http.Context) http.Response {
|
||
// 从context中获取admin信息(由JWT中间件设置)
|
||
adminValue := ctx.Value("admin")
|
||
if adminValue != nil {
|
||
var admin models.Admin
|
||
if adminVal, ok := adminValue.(models.Admin); ok {
|
||
admin = adminVal
|
||
} else if adminPtr, ok := adminValue.(*models.Admin); ok {
|
||
if adminPtr != nil {
|
||
admin = *adminPtr
|
||
}
|
||
}
|
||
|
||
if admin.ID > 0 {
|
||
// 获取token
|
||
token := ctx.Request().Header("Authorization", "")
|
||
token = str.Of(token).ChopStart("Bearer ").Trim().String()
|
||
|
||
if token != "" {
|
||
// 删除token
|
||
tokenService := services.NewTokenServiceImpl()
|
||
_ = tokenService.DeleteToken(token)
|
||
}
|
||
|
||
// 记录退出日志
|
||
logoutRequestData := r.getLoginRequestData(ctx)
|
||
r.authService.RecordLoginLog(ctx, admin.ID, admin.Username, 1, "logout_success", logoutRequestData)
|
||
}
|
||
}
|
||
|
||
return response.Success(ctx, "logout_success")
|
||
}
|
||
|
||
// Tokens 获取当前用户的所有token列表
|
||
func (r *AuthController) Tokens(ctx http.Context) http.Response {
|
||
// 从context中获取admin信息(由JWT中间件设置)
|
||
adminValue := ctx.Value("admin")
|
||
if adminValue == nil {
|
||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||
}
|
||
|
||
admin, ok := adminValue.(models.Admin)
|
||
if !ok {
|
||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||
}
|
||
|
||
// 获取用户的所有token
|
||
tokenService := services.NewTokenServiceImpl()
|
||
tokens, err := tokenService.GetTokensByUser("admin", admin.ID)
|
||
if err != nil {
|
||
return response.ErrorWithLog(ctx, "auth", err, map[string]any{
|
||
"admin_id": admin.ID,
|
||
})
|
||
}
|
||
|
||
// 获取当前使用的token
|
||
currentTokenValue := ctx.Value("token")
|
||
var currentTokenID uint
|
||
if currentTokenValue != nil {
|
||
if currentToken, ok := currentTokenValue.(models.PersonalAccessToken); ok {
|
||
currentTokenID = currentToken.ID
|
||
} else if currentTokenPtr, ok := currentTokenValue.(*models.PersonalAccessToken); ok {
|
||
if currentTokenPtr != nil {
|
||
currentTokenID = currentTokenPtr.ID
|
||
}
|
||
}
|
||
}
|
||
|
||
// 格式化token列表
|
||
var tokenList []http.Json
|
||
for _, token := range tokens {
|
||
tokenData := http.Json{
|
||
"id": token.ID,
|
||
"name": token.Name,
|
||
"last_used_at": token.LastUsedAt,
|
||
"expires_at": token.ExpiresAt,
|
||
"created_at": token.CreatedAt,
|
||
"is_current": token.ID == currentTokenID,
|
||
}
|
||
tokenList = append(tokenList, tokenData)
|
||
}
|
||
|
||
return response.Success(ctx, http.Json{
|
||
"tokens": tokenList,
|
||
})
|
||
}
|
||
|
||
// RevokeToken 删除指定的token(踢出指定设备)
|
||
func (r *AuthController) RevokeToken(ctx http.Context) http.Response {
|
||
// 从context中获取admin信息(由JWT中间件设置)
|
||
adminValue := ctx.Value("admin")
|
||
if adminValue == nil {
|
||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||
}
|
||
|
||
admin, ok := adminValue.(models.Admin)
|
||
if !ok {
|
||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||
}
|
||
|
||
// 获取要删除的token ID
|
||
tokenIDStr := ctx.Request().Route("id")
|
||
if tokenIDStr == "" {
|
||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrTokenIDRequired.Code)
|
||
}
|
||
|
||
tokenID, err := strconv.ParseUint(tokenIDStr, 10, 32)
|
||
if err != nil {
|
||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrInvalidTokenID.Code)
|
||
}
|
||
|
||
// 查询token是否存在且属于当前用户
|
||
var token models.PersonalAccessToken
|
||
if err := facades.Orm().Query().
|
||
Where("id", tokenID).
|
||
Where("tokenable_type", "admin").
|
||
Where("tokenable_id", admin.ID).
|
||
First(&token); err != nil {
|
||
return response.Error(ctx, http.StatusNotFound, apperrors.ErrTokenNotFound.Code)
|
||
}
|
||
|
||
// 删除token(直接通过ID删除,因为数据库中存储的是hash值,无法获取原始token)
|
||
_, err = facades.Orm().Query().Delete(&token)
|
||
if err != nil {
|
||
return response.ErrorWithLog(ctx, "auth", err, map[string]any{
|
||
"token_id": token.ID,
|
||
"admin_id": admin.ID,
|
||
})
|
||
}
|
||
|
||
return response.Success(ctx, "revoke_success")
|
||
}
|
||
|
||
// RevokeAllTokens 删除当前用户的所有token(踢出所有设备)
|
||
func (r *AuthController) RevokeAllTokens(ctx http.Context) http.Response {
|
||
// 从context中获取admin信息(由JWT中间件设置)
|
||
adminValue := ctx.Value("admin")
|
||
if adminValue == nil {
|
||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||
}
|
||
|
||
admin, ok := adminValue.(models.Admin)
|
||
if !ok {
|
||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||
}
|
||
|
||
// 删除用户的所有token
|
||
tokenService := services.NewTokenServiceImpl()
|
||
if err := tokenService.DeleteTokensByUser("admin", admin.ID); err != nil {
|
||
return response.ErrorWithLog(ctx, "auth", err, map[string]any{
|
||
"admin_id": admin.ID,
|
||
})
|
||
}
|
||
|
||
return response.Success(ctx, "revoke_all_success")
|
||
}
|
||
|
||
// KickOutUser 踢出指定用户的所有token(管理员操作)
|
||
func (r *AuthController) KickOutUser(ctx http.Context) http.Response {
|
||
// 从context中获取admin信息(由JWT中间件设置)
|
||
adminValue := ctx.Value("admin")
|
||
if adminValue == nil {
|
||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||
}
|
||
|
||
var admin models.Admin
|
||
if adminVal, ok := adminValue.(models.Admin); ok {
|
||
admin = adminVal
|
||
} else if adminPtr, ok := adminValue.(*models.Admin); ok {
|
||
admin = *adminPtr
|
||
} else {
|
||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||
}
|
||
|
||
// 获取要踢出的用户ID
|
||
userIDStr := ctx.Request().Route("id")
|
||
if userIDStr == "" {
|
||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrUserIDRequired.Code)
|
||
}
|
||
|
||
userID, err := strconv.ParseUint(userIDStr, 10, 32)
|
||
if err != nil {
|
||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrInvalidUserID.Code)
|
||
}
|
||
|
||
// 查询用户是否存在
|
||
var targetAdmin models.Admin
|
||
if err := facades.Orm().Query().Where("id", userID).FirstOrFail(&targetAdmin); err != nil {
|
||
return response.Error(ctx, http.StatusNotFound, apperrors.ErrUserNotFound.Code)
|
||
}
|
||
|
||
// 删除用户的所有token
|
||
tokenService := services.NewTokenServiceImpl()
|
||
if err := tokenService.DeleteTokensByUser("admin", targetAdmin.ID); err != nil {
|
||
return response.ErrorWithLog(ctx, "auth", err, map[string]any{
|
||
"target_user_id": targetAdmin.ID,
|
||
"operator_id": admin.ID,
|
||
})
|
||
}
|
||
|
||
return response.Success(ctx, "kick_out_success")
|
||
}
|
||
|
||
// GetGoogleAuthenticatorQRCode 获取谷歌验证码二维码(用于绑定)
|
||
func (r *AuthController) GetGoogleAuthenticatorQRCode(ctx http.Context) http.Response {
|
||
// 从context中获取admin信息(由JWT中间件设置)
|
||
adminValue := ctx.Value("admin")
|
||
if adminValue == nil {
|
||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||
}
|
||
|
||
var admin models.Admin
|
||
if adminVal, ok := adminValue.(models.Admin); ok {
|
||
admin = adminVal
|
||
} else if adminPtr, ok := adminValue.(*models.Admin); ok {
|
||
admin = *adminPtr
|
||
} else {
|
||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||
}
|
||
|
||
// 检查是否已经绑定
|
||
isBound, err := r.googleAuthenticatorService.IsBound(admin.ID)
|
||
if err != nil {
|
||
return response.ErrorWithLog(ctx, "auth", err, map[string]any{
|
||
"admin_id": admin.ID,
|
||
})
|
||
}
|
||
|
||
if isBound {
|
||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrGoogleAuthenticatorAlreadyBound.Code)
|
||
}
|
||
|
||
// 生成密钥和二维码
|
||
accountName := admin.Username
|
||
if admin.Email != "" {
|
||
accountName = admin.Email
|
||
}
|
||
secret, qrCodeURL, err := r.googleAuthenticatorService.GenerateSecret(accountName)
|
||
if err != nil {
|
||
return response.ErrorWithLog(ctx, "auth", err, map[string]any{
|
||
"admin_id": admin.ID,
|
||
})
|
||
}
|
||
|
||
// 生成二维码图片
|
||
qrCodeImage, err := r.googleAuthenticatorService.GenerateQRCodeImage(accountName, secret)
|
||
if err != nil {
|
||
return response.ErrorWithLog(ctx, "auth", err, map[string]any{
|
||
"admin_id": admin.ID,
|
||
})
|
||
}
|
||
|
||
return response.Success(ctx, http.Json{
|
||
"secret": secret,
|
||
"qr_code_url": qrCodeURL,
|
||
"qr_code_image": qrCodeImage,
|
||
})
|
||
}
|
||
|
||
// BindGoogleAuthenticator 绑定谷歌验证码
|
||
func (r *AuthController) BindGoogleAuthenticator(ctx http.Context) http.Response {
|
||
// 从context中获取admin信息(由JWT中间件设置)
|
||
adminValue := ctx.Value("admin")
|
||
if adminValue == nil {
|
||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||
}
|
||
|
||
var admin models.Admin
|
||
if adminVal, ok := adminValue.(models.Admin); ok {
|
||
admin = adminVal
|
||
} else if adminPtr, ok := adminValue.(*models.Admin); ok {
|
||
admin = *adminPtr
|
||
} else {
|
||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||
}
|
||
|
||
secret := ctx.Request().Input("secret")
|
||
code := ctx.Request().Input("code")
|
||
|
||
if secret == "" || code == "" {
|
||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrSecretAndCodeRequired.Code)
|
||
}
|
||
|
||
// 绑定谷歌验证码
|
||
if err := r.googleAuthenticatorService.Bind(admin.ID, secret, code); err != nil {
|
||
if err.Error() == "invalid_code" {
|
||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrGoogleCodeInvalid.Code)
|
||
}
|
||
return response.ErrorWithLog(ctx, "auth", err, map[string]any{
|
||
"admin_id": admin.ID,
|
||
})
|
||
}
|
||
|
||
return response.Success(ctx, "bind_success")
|
||
}
|
||
|
||
// UnbindGoogleAuthenticator 解绑谷歌验证码
|
||
func (r *AuthController) UnbindGoogleAuthenticator(ctx http.Context) http.Response {
|
||
// 从context中获取admin信息(由JWT中间件设置)
|
||
adminValue := ctx.Value("admin")
|
||
if adminValue == nil {
|
||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||
}
|
||
|
||
var admin models.Admin
|
||
if adminVal, ok := adminValue.(models.Admin); ok {
|
||
admin = adminVal
|
||
} else if adminPtr, ok := adminValue.(*models.Admin); ok {
|
||
admin = *adminPtr
|
||
} else {
|
||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||
}
|
||
|
||
// 需要验证码确认
|
||
code := ctx.Request().Input("code")
|
||
if code == "" {
|
||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrCodeRequired.Code)
|
||
}
|
||
|
||
// 获取管理员的密钥
|
||
secret, err := r.googleAuthenticatorService.GetSecret(admin.ID)
|
||
if err != nil {
|
||
return response.ErrorWithLog(ctx, "auth", err, map[string]any{
|
||
"admin_id": admin.ID,
|
||
})
|
||
}
|
||
|
||
if secret == "" {
|
||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrGoogleAuthenticatorNotBound.Code)
|
||
}
|
||
|
||
// 验证验证码
|
||
if !r.googleAuthenticatorService.Verify(secret, code) {
|
||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrGoogleCodeInvalid.Code)
|
||
}
|
||
|
||
// 解绑谷歌验证码
|
||
if err := r.googleAuthenticatorService.Unbind(admin.ID); err != nil {
|
||
return response.ErrorWithLog(ctx, "auth", err, map[string]any{
|
||
"admin_id": admin.ID,
|
||
})
|
||
}
|
||
|
||
return response.Success(ctx, "unbind_success")
|
||
}
|
||
|
||
// GetGoogleAuthenticatorStatus 获取谷歌验证码绑定状态
|
||
func (r *AuthController) GetGoogleAuthenticatorStatus(ctx http.Context) http.Response {
|
||
// 从context中获取admin信息(由JWT中间件设置)
|
||
adminValue := ctx.Value("admin")
|
||
if adminValue == nil {
|
||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||
}
|
||
|
||
var admin models.Admin
|
||
if adminVal, ok := adminValue.(models.Admin); ok {
|
||
admin = adminVal
|
||
} else if adminPtr, ok := adminValue.(*models.Admin); ok {
|
||
admin = *adminPtr
|
||
} else {
|
||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||
}
|
||
|
||
// 检查是否绑定
|
||
isBound, err := r.googleAuthenticatorService.IsBound(admin.ID)
|
||
if err != nil {
|
||
return response.ErrorWithLog(ctx, "auth", err, map[string]any{
|
||
"admin_id": admin.ID,
|
||
})
|
||
}
|
||
|
||
return response.Success(ctx, http.Json{
|
||
"is_bound": isBound,
|
||
})
|
||
}
|