731 lines
28 KiB
Go
731 lines
28 KiB
Go
package admin
|
||
|
||
import (
|
||
"fmt"
|
||
"time"
|
||
|
||
"github.com/goravel/framework/contracts/http"
|
||
"github.com/goravel/framework/facades"
|
||
"github.com/goravel/framework/support/carbon"
|
||
"github.com/spf13/cast"
|
||
|
||
apperrors "goravel/app/errors"
|
||
"goravel/app/http/helpers"
|
||
adminrequests "goravel/app/http/requests/admin"
|
||
"goravel/app/http/response"
|
||
"goravel/app/http/trans"
|
||
"goravel/app/models"
|
||
"goravel/app/services"
|
||
)
|
||
|
||
type AdminController struct {
|
||
adminService services.AdminService
|
||
googleAuthenticatorService services.GoogleAuthenticatorService
|
||
}
|
||
|
||
// AdminExportRequest 导出管理员请求参数
|
||
type AdminExportRequest struct {
|
||
Username string `json:"username" form:"username" example:"admin"` // 用户名(模糊搜索)
|
||
Status string `json:"status" form:"status" example:"1"` // 状态:1-启用,0-禁用
|
||
RoleID string `json:"role_id" form:"role_id" example:"1"` // 角色ID
|
||
DepartmentID string `json:"department_id" form:"department_id" example:"1"` // 部门ID
|
||
Is2FABound string `json:"is_2fa_bound" form:"is_2fa_bound" example:"1"` // 是否绑定2FA:1-已绑定,0-未绑定
|
||
StartTime string `json:"start_time" form:"start_time" example:"2024-01-01 00:00:00"` // 开始时间
|
||
EndTime string `json:"end_time" form:"end_time" example:"2024-12-31 23:59:59"` // 结束时间
|
||
OrderBy string `json:"order_by" form:"order_by" example:"created_at:desc"` // 排序
|
||
}
|
||
|
||
// AdminResponse 管理员响应数据
|
||
type AdminResponse struct {
|
||
ID uint `json:"id" example:"1"` // 管理员ID
|
||
Username string `json:"username" example:"admin"` // 用户名
|
||
Nickname string `json:"nickname" example:"管理员"` // 昵称
|
||
Avatar string `json:"avatar" example:""` // 头像
|
||
Email string `json:"email" example:"admin@example.com"` // 邮箱
|
||
Phone string `json:"phone" example:"13800138000"` // 手机号
|
||
Status uint8 `json:"status" example:"1"` // 状态:1-启用,0-禁用
|
||
Is2FABound bool `json:"is_2fa_bound" example:"true"` // 是否绑定2FA
|
||
DepartmentID uint `json:"department_id" example:"1"` // 部门ID
|
||
Department map[string]any `json:"department"` // 部门信息
|
||
Roles []map[string]any `json:"roles"` // 角色列表
|
||
CreatedAt string `json:"created_at" example:"2024-01-01 00:00:00"` // 创建时间
|
||
UpdatedAt string `json:"updated_at" example:"2024-01-01 00:00:00"` // 更新时间
|
||
}
|
||
|
||
// PaginatedAdminResponse 分页管理员响应
|
||
type PaginatedAdminResponse struct {
|
||
Code int `json:"code" example:"200"` // 状态码
|
||
Message string `json:"message" example:"获取成功"` // 消息
|
||
Data []AdminResponse `json:"data"` // 数据列表
|
||
Total int64 `json:"total" example:"100"` // 总数
|
||
Page int `json:"page" example:"1"` // 当前页码
|
||
PageSize int `json:"page_size" example:"10"` // 每页数量
|
||
TraceID string `json:"trace_id,omitempty" example:"abc123"` // 追踪ID
|
||
}
|
||
|
||
// AdminDetailResponse 管理员详情响应
|
||
type AdminDetailResponse struct {
|
||
Code int `json:"code" example:"200"` // 状态码
|
||
Message string `json:"message" example:"获取成功"` // 消息
|
||
Data AdminResponse `json:"data"` // 管理员数据
|
||
TraceID string `json:"trace_id,omitempty" example:"abc123"` // 追踪ID
|
||
}
|
||
|
||
func NewAdminController() *AdminController {
|
||
return &AdminController{
|
||
adminService: services.NewAdminServiceImpl(),
|
||
googleAuthenticatorService: services.NewGoogleAuthenticatorServiceImpl(),
|
||
}
|
||
}
|
||
|
||
// findAdminByID 根据ID查找管理员,如果不存在则返回错误响应
|
||
// withDepartment 为 true 时会预加载 Department 关联
|
||
// withRoles 为 true 时会预加载 Roles 关联
|
||
func (r *AdminController) findAdminByID(ctx http.Context, id uint, withDepartment bool, withRoles bool) (*models.Admin, http.Response) {
|
||
admin, err := r.adminService.GetByID(id, withDepartment, withRoles)
|
||
if err != nil {
|
||
return nil, response.Error(ctx, http.StatusNotFound, apperrors.ErrAdminNotFound.Code)
|
||
}
|
||
return admin, nil
|
||
}
|
||
|
||
// buildFilters 构建查询过滤器(列表和导出共用)
|
||
// 同时支持查询参数(GET)和请求体参数(POST)
|
||
func (r *AdminController) buildFilters(ctx http.Context) services.AdminFilters {
|
||
// 优先从请求体读取,如果没有则从查询参数读取(兼容 GET 和 POST)
|
||
username := ctx.Request().Input("username", ctx.Request().Query("username", ""))
|
||
status := ctx.Request().Input("status", ctx.Request().Query("status", ""))
|
||
roleID := ctx.Request().Input("role_id", ctx.Request().Query("role_id", ""))
|
||
departmentID := ctx.Request().Input("department_id", ctx.Request().Query("department_id", ""))
|
||
is2FABound := ctx.Request().Input("is_2fa_bound", ctx.Request().Query("is_2fa_bound", ""))
|
||
orderBy := ctx.Request().Input("order_by", ctx.Request().Query("order_by", ""))
|
||
// 时间参数同时支持从请求体和查询参数读取,并转换为 UTC
|
||
startTimeStr := ctx.Request().Input("start_time", ctx.Request().Query("start_time", ""))
|
||
endTimeStr := ctx.Request().Input("end_time", ctx.Request().Query("end_time", ""))
|
||
startTime := ""
|
||
endTime := ""
|
||
if startTimeStr != "" {
|
||
startTime = helpers.ConvertTimeToUTC(ctx, startTimeStr)
|
||
}
|
||
if endTimeStr != "" {
|
||
endTime = helpers.ConvertTimeToUTC(ctx, endTimeStr)
|
||
}
|
||
|
||
return services.AdminFilters{
|
||
Username: username,
|
||
Status: status,
|
||
RoleID: roleID,
|
||
DepartmentID: departmentID,
|
||
Is2FABound: is2FABound,
|
||
StartTime: startTime,
|
||
EndTime: endTime,
|
||
OrderBy: orderBy,
|
||
}
|
||
}
|
||
|
||
// Index 管理员列表
|
||
// @Summary 获取管理员列表
|
||
// @Description 分页获取管理员列表,支持按用户名、状态、角色、部门等条件筛选
|
||
// @Tags 管理员管理
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Param page query int false "页码" default(1)
|
||
// @Param page_size query int false "每页数量" default(10)
|
||
// @Param username query string false "用户名(模糊搜索)"
|
||
// @Param status query string false "状态:1-启用,0-禁用"
|
||
// @Param role_id query string false "角色ID"
|
||
// @Param department_id query string false "部门ID"
|
||
// @Param start_time query string false "开始时间(格式:YYYY-MM-DD HH:mm:ss)"
|
||
// @Param end_time query string false "结束时间(格式:YYYY-MM-DD HH:mm:ss)"
|
||
// @Param order_by query string false "排序(格式:字段:asc/desc,如:created_at:desc)"
|
||
// @Success 200 {object} PaginatedAdminResponse
|
||
// @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/admins [get]
|
||
// @Security BearerAuth
|
||
func (r *AdminController) Index(ctx http.Context) http.Response {
|
||
page := helpers.GetIntQuery(ctx, "page", 1)
|
||
pageSize := helpers.GetIntQuery(ctx, "page_size", 10)
|
||
|
||
filters := r.buildFilters(ctx)
|
||
|
||
admins, total, err := r.adminService.GetList(filters, page, pageSize)
|
||
if err != nil {
|
||
return response.Error(ctx, http.StatusInternalServerError, err.Error())
|
||
}
|
||
|
||
// 获取超级管理员ID
|
||
superAdminID := cast.ToUint(facades.Config().GetInt("admin.super_admin_id", 1))
|
||
|
||
// 转换数据格式
|
||
adminList := make([]http.Json, len(admins))
|
||
for i, admin := range admins {
|
||
isBound := admin.GoogleSecret != ""
|
||
adminList[i] = http.Json{
|
||
"id": admin.ID,
|
||
"username": admin.Username,
|
||
"nickname": admin.Nickname,
|
||
"avatar": admin.Avatar,
|
||
"email": admin.Email,
|
||
"phone": admin.Phone,
|
||
"status": admin.Status,
|
||
"is_2fa_bound": isBound,
|
||
"is_super_admin": admin.ID == superAdminID,
|
||
"department_id": admin.DepartmentID,
|
||
"department": admin.Department,
|
||
"roles": admin.Roles,
|
||
"created_at": admin.CreatedAt,
|
||
"updated_at": admin.UpdatedAt,
|
||
}
|
||
}
|
||
|
||
return response.Success(ctx, http.Json{
|
||
"list": adminList,
|
||
"total": total,
|
||
"page": page,
|
||
"page_size": pageSize,
|
||
})
|
||
}
|
||
|
||
// Show 管理员详情
|
||
// @Summary 获取管理员详情
|
||
// @Description 根据ID获取管理员详细信息,包括部门、角色等关联信息
|
||
// @Tags 管理员管理
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Param id path int true "管理员ID"
|
||
// @Success 200 {object} AdminDetailResponse
|
||
// @Failure 400 {object} map[string]any "参数错误"
|
||
// @Failure 401 {object} map[string]any "未登录"
|
||
// @Failure 403 {object} map[string]any "无权限"
|
||
// @Failure 404 {object} map[string]any "管理员不存在"
|
||
// @Failure 500 {object} map[string]any "服务器错误"
|
||
// @Router /api/admin/admins/{id} [get]
|
||
// @Security BearerAuth
|
||
func (r *AdminController) Show(ctx http.Context) http.Response {
|
||
id := helpers.GetUintRoute(ctx, "id")
|
||
admin, resp := r.findAdminByID(ctx, id, true, true) // 预加载 Department 和 Roles 关联
|
||
if resp != nil {
|
||
return resp
|
||
}
|
||
|
||
// 获取超级管理员ID
|
||
superAdminID := cast.ToUint(facades.Config().GetInt("admin.super_admin_id", 1))
|
||
|
||
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,
|
||
"status": admin.Status,
|
||
"is_super_admin": admin.ID == superAdminID, // 标识是否是超级管理员
|
||
"department_id": admin.DepartmentID,
|
||
"department": admin.Department,
|
||
"roles": admin.Roles,
|
||
"created_at": admin.CreatedAt,
|
||
"updated_at": admin.UpdatedAt,
|
||
},
|
||
})
|
||
}
|
||
|
||
// Store 创建管理员
|
||
// @Summary 创建管理员
|
||
// @Description 创建新的管理员账号,支持设置部门、角色等信息
|
||
// @Tags 管理员管理
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Param username body string true "用户名(必填)" example(admin)
|
||
// @Param password body string true "密码(必填)" example(123456)
|
||
// @Param nickname body string false "昵称" example(管理员)
|
||
// @Param email body string false "邮箱" example(admin@example.com)
|
||
// @Param phone body string false "手机号" example(13800138000)
|
||
// @Param department_id body int false "部门ID" example(1)
|
||
// @Param status body int false "状态:1-启用,0-禁用" example(1)
|
||
// @Param role_ids body []int false "角色ID列表" example([1,2])
|
||
// @Success 200 {object} AdminDetailResponse
|
||
// @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/admins [post]
|
||
// @Security BearerAuth
|
||
func (r *AdminController) Store(ctx http.Context) http.Response {
|
||
// 使用请求验证
|
||
var adminCreate adminrequests.AdminCreate
|
||
errors, err := ctx.Request().ValidateRequest(&adminCreate)
|
||
if err != nil {
|
||
return response.Error(ctx, http.StatusBadRequest, err.Error())
|
||
}
|
||
if errors != nil {
|
||
return response.ValidationError(ctx, http.StatusBadRequest, "validation_failed", errors.All())
|
||
}
|
||
|
||
// 检查用户名是否已存在
|
||
exists, err := facades.Orm().Query().Model(&models.Admin{}).Where("username", adminCreate.Username).Exists()
|
||
if err != nil {
|
||
return response.Error(ctx, http.StatusInternalServerError, apperrors.ErrCreateFailed.Code)
|
||
}
|
||
if exists {
|
||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrUsernameExists.Code)
|
||
}
|
||
|
||
// 加密密码
|
||
hashedPassword, err := facades.Hash().Make(adminCreate.Password)
|
||
if err != nil {
|
||
return response.Error(ctx, http.StatusInternalServerError, apperrors.ErrPasswordEncryptFailed.Code)
|
||
}
|
||
|
||
now := carbon.Now()
|
||
adminData := map[string]any{
|
||
"username": adminCreate.Username,
|
||
"password": hashedPassword,
|
||
"nickname": adminCreate.Nickname,
|
||
"avatar": "",
|
||
"email": adminCreate.Email,
|
||
"phone": adminCreate.Phone,
|
||
"department_id": adminCreate.DepartmentID,
|
||
"status": adminCreate.Status,
|
||
"created_at": now,
|
||
"updated_at": now,
|
||
}
|
||
|
||
if err := facades.Orm().Query().Table("admins").Create(adminData); err != nil {
|
||
return response.ErrorWithLog(ctx, "admin", err, map[string]any{
|
||
"username": adminCreate.Username,
|
||
})
|
||
}
|
||
|
||
var admin models.Admin
|
||
if err := facades.Orm().Query().Where("username", adminCreate.Username).FirstOrFail(&admin); err != nil {
|
||
return response.ErrorWithLog(ctx, "admin", err, map[string]any{
|
||
"username": adminCreate.Username,
|
||
})
|
||
}
|
||
|
||
if len(adminCreate.RoleIDs) > 0 {
|
||
if err := r.adminService.SyncRoles(&admin, adminCreate.RoleIDs); err != nil {
|
||
return response.ErrorWithLog(ctx, "admin", err, map[string]any{
|
||
"admin_id": admin.ID,
|
||
"role_ids": adminCreate.RoleIDs,
|
||
})
|
||
}
|
||
}
|
||
|
||
return response.Success(ctx, http.Json{
|
||
"admin": admin,
|
||
})
|
||
}
|
||
|
||
// Update 更新管理员
|
||
// @Summary 更新管理员信息
|
||
// @Description 更新管理员的基本信息,包括昵称、邮箱、手机号、部门、状态、角色等。受保护的管理员不能禁用。
|
||
// @Tags 管理员管理
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Param id path int true "管理员ID" example(1)
|
||
// @Param nickname body string false "昵称" example(管理员)
|
||
// @Param email body string false "邮箱" example(admin@example.com)
|
||
// @Param phone body string false "手机号" example(13800138000)
|
||
// @Param department_id body int false "部门ID" example(1)
|
||
// @Param status body string false "状态:1-启用,0-禁用" example(1)
|
||
// @Param password body string false "密码(可选,不传则不更新)" example(123456)
|
||
// @Param role_ids body []int false "角色ID列表" example([1,2])
|
||
// @Success 200 {object} AdminDetailResponse
|
||
// @Failure 400 {object} map[string]any "参数错误"
|
||
// @Failure 401 {object} map[string]any "未登录"
|
||
// @Failure 403 {object} map[string]any "无权限或受保护管理员不能禁用"
|
||
// @Failure 404 {object} map[string]any "管理员不存在"
|
||
// @Failure 500 {object} map[string]any "服务器错误"
|
||
// @Router /api/admin/admins/{id} [put]
|
||
// @Security BearerAuth
|
||
func (r *AdminController) Update(ctx http.Context) http.Response {
|
||
id := helpers.GetUintRoute(ctx, "id")
|
||
// 加载管理员的当前角色,用于后续比较角色是否改变
|
||
admin, resp := r.findAdminByID(ctx, id, false, true) // 预加载 Roles 关联
|
||
if resp != nil {
|
||
return resp
|
||
}
|
||
|
||
allProtectedIDs := r.getAllProtectedAdminIDs()
|
||
isProtected := allProtectedIDs[id]
|
||
|
||
// 使用请求验证
|
||
var adminUpdate adminrequests.AdminUpdate
|
||
errors, err := ctx.Request().ValidateRequest(&adminUpdate)
|
||
if err != nil {
|
||
return response.Error(ctx, http.StatusBadRequest, err.Error())
|
||
}
|
||
if errors != nil {
|
||
return response.ValidationError(ctx, http.StatusBadRequest, "validation_failed", errors.All())
|
||
}
|
||
|
||
// 使用 All() 方法检查字段是否存在
|
||
allInputs := ctx.Request().All()
|
||
|
||
if _, exists := allInputs["nickname"]; exists {
|
||
admin.Nickname = adminUpdate.Nickname
|
||
}
|
||
if _, exists := allInputs["email"]; exists {
|
||
admin.Email = adminUpdate.Email
|
||
}
|
||
if _, exists := allInputs["phone"]; exists {
|
||
admin.Phone = adminUpdate.Phone
|
||
}
|
||
if _, exists := allInputs["department_id"]; exists {
|
||
admin.DepartmentID = adminUpdate.DepartmentID
|
||
}
|
||
if _, exists := allInputs["status"]; exists {
|
||
// 请求中提供了 status 字段,使用验证后的值
|
||
// 检查是否是超级管理员或受保护的管理员
|
||
superAdminID := cast.ToUint(facades.Config().GetInt("admin.super_admin_id", 1))
|
||
isSuperAdmin := admin.ID == superAdminID
|
||
if (isProtected || isSuperAdmin) && adminUpdate.Status == 0 {
|
||
return response.Error(ctx, http.StatusForbidden, apperrors.ErrAdminProtectedCannotDisable.Code)
|
||
}
|
||
admin.Status = adminUpdate.Status
|
||
}
|
||
|
||
if adminUpdate.Password != "" {
|
||
hashedPassword, err := facades.Hash().Make(adminUpdate.Password)
|
||
if err != nil {
|
||
return response.Error(ctx, http.StatusInternalServerError, apperrors.ErrPasswordEncryptFailed.Code)
|
||
}
|
||
admin.Password = hashedPassword
|
||
}
|
||
|
||
if err := r.adminService.Update(admin); err != nil {
|
||
return response.ErrorWithLog(ctx, "admin", err, map[string]any{
|
||
"admin_id": admin.ID,
|
||
})
|
||
}
|
||
|
||
// 检查是否尝试修改 admin 用户的角色
|
||
if _, exists := allInputs["role_ids"]; exists {
|
||
// 获取当前管理员的角色ID列表(去重)
|
||
currentRoleIDSet := make(map[uint]bool)
|
||
var currentRoleIDs []uint
|
||
for _, role := range admin.Roles {
|
||
if !currentRoleIDSet[role.ID] {
|
||
currentRoleIDSet[role.ID] = true
|
||
currentRoleIDs = append(currentRoleIDs, role.ID)
|
||
}
|
||
}
|
||
|
||
// 对传入的角色ID进行去重
|
||
newRoleIDSet := make(map[uint]bool)
|
||
var deduplicatedRoleIDs []uint
|
||
for _, roleID := range adminUpdate.RoleIDs {
|
||
if !newRoleIDSet[roleID] {
|
||
newRoleIDSet[roleID] = true
|
||
deduplicatedRoleIDs = append(deduplicatedRoleIDs, roleID)
|
||
}
|
||
}
|
||
|
||
// 比较新的角色ID列表和当前的角色ID列表
|
||
// 只有当角色ID真正改变时才阻止修改
|
||
roleIDsChanged := false
|
||
|
||
// 如果长度不同,肯定改变了
|
||
if len(deduplicatedRoleIDs) != len(currentRoleIDs) {
|
||
roleIDsChanged = true
|
||
} else {
|
||
// 长度相同,需要检查内容是否完全一致(忽略顺序)
|
||
// 检查新的角色ID是否都在当前角色ID中
|
||
for _, newRoleID := range deduplicatedRoleIDs {
|
||
if !currentRoleIDSet[newRoleID] {
|
||
roleIDsChanged = true
|
||
break
|
||
}
|
||
}
|
||
// 如果所有新角色ID都在当前角色ID中,且长度相同,说明没有改变
|
||
}
|
||
|
||
// 检查是否是超级管理员(通过配置的ID判断,不依赖用户名)
|
||
superAdminID := cast.ToUint(facades.Config().GetInt("admin.super_admin_id", 1))
|
||
isSuperAdmin := admin.ID == superAdminID
|
||
|
||
// 只有当角色ID真正改变时才阻止修改
|
||
// 如果角色ID没有改变,允许调用 SyncRoles 来清理重复数据
|
||
if roleIDsChanged && isSuperAdmin {
|
||
return response.Error(ctx, http.StatusForbidden, apperrors.ErrAdminCannotModifyRoles.Code)
|
||
}
|
||
|
||
// 即使角色ID没有改变,也调用 SyncRoles 来清理重复数据
|
||
// 使用去重后的角色ID列表
|
||
if err := r.adminService.SyncRoles(admin, deduplicatedRoleIDs); err != nil {
|
||
return response.ErrorWithLog(ctx, "admin", err, map[string]any{
|
||
"admin_id": admin.ID,
|
||
"role_ids": deduplicatedRoleIDs,
|
||
})
|
||
}
|
||
}
|
||
|
||
return response.Success(ctx, http.Json{
|
||
"admin": *admin,
|
||
})
|
||
}
|
||
|
||
// Destroy 删除管理员
|
||
// @Summary 删除管理员
|
||
// @Description 删除指定的管理员账号。受保护的管理员和当前登录的管理员不能删除。
|
||
// @Tags 管理员管理
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Param id path int true "管理员ID"
|
||
// @Success 200 {object} map[string]any "删除成功"
|
||
// @Failure 400 {object} map[string]any "参数错误"
|
||
// @Failure 401 {object} map[string]any "未登录"
|
||
// @Failure 403 {object} map[string]any "无权限、受保护管理员不能删除或不能删除自己"
|
||
// @Failure 404 {object} map[string]any "管理员不存在"
|
||
// @Failure 500 {object} map[string]any "服务器错误"
|
||
// @Router /api/admin/admins/{id} [delete]
|
||
// @Security BearerAuth
|
||
func (r *AdminController) Destroy(ctx http.Context) http.Response {
|
||
id := helpers.GetUintRoute(ctx, "id")
|
||
|
||
allProtectedIDs := r.getAllProtectedAdminIDs()
|
||
if allProtectedIDs[id] {
|
||
return response.Error(ctx, http.StatusForbidden, apperrors.ErrAdminProtectedCannotDelete.Code)
|
||
}
|
||
|
||
adminValue := ctx.Value("admin")
|
||
if adminValue != nil {
|
||
var currentAdmin models.Admin
|
||
if admin, ok := adminValue.(models.Admin); ok {
|
||
currentAdmin = admin
|
||
} else if adminPtr, ok := adminValue.(*models.Admin); ok {
|
||
currentAdmin = *adminPtr
|
||
}
|
||
|
||
if currentAdmin.ID > 0 && currentAdmin.ID == id {
|
||
return response.Error(ctx, http.StatusForbidden, apperrors.ErrAdminCannotDeleteSelf.Code)
|
||
}
|
||
}
|
||
|
||
admin, resp := r.findAdminByID(ctx, id, false, false)
|
||
if resp != nil {
|
||
return resp
|
||
}
|
||
|
||
if _, err := facades.Orm().Query().Delete(admin); err != nil {
|
||
return response.ErrorWithLog(ctx, "admin", err, map[string]any{
|
||
"admin_id": admin.ID,
|
||
})
|
||
}
|
||
|
||
return response.Success(ctx)
|
||
}
|
||
|
||
// UnbindGoogleAuthenticator 管理员解绑其他管理员的谷歌验证码
|
||
// @Summary 解绑管理员的谷歌验证码
|
||
// @Description 管理员可以解绑其他管理员的谷歌验证码,需要当前管理员已绑定谷歌验证码并输入验证码确认
|
||
// @Tags 管理员管理
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Param id path int true "要解绑的管理员ID"
|
||
// @Param code body string true "当前管理员的谷歌验证码"
|
||
// @Success 200 {object} map[string]any "解绑成功"
|
||
// @Failure 400 {object} map[string]any "参数错误或验证码错误"
|
||
// @Failure 401 {object} map[string]any "未登录"
|
||
// @Failure 403 {object} map[string]any "无权限或当前管理员未绑定谷歌验证码"
|
||
// @Failure 404 {object} map[string]any "管理员不存在"
|
||
// @Failure 500 {object} map[string]any "服务器错误"
|
||
// @Router /api/admin/admins/{id}/unbind-google-auth [post]
|
||
// @Security BearerAuth
|
||
func (r *AdminController) UnbindGoogleAuthenticator(ctx http.Context) http.Response {
|
||
// 获取要解绑的管理员ID
|
||
targetAdminID := helpers.GetUintRoute(ctx, "id")
|
||
if targetAdminID == 0 {
|
||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrIDRequired.Code)
|
||
}
|
||
|
||
// 检查目标管理员是否存在
|
||
var targetAdmin models.Admin
|
||
if err := facades.Orm().Query().Where("id", targetAdminID).FirstOrFail(&targetAdmin); err != nil {
|
||
return response.Error(ctx, http.StatusNotFound, apperrors.ErrAdminNotFound.Code)
|
||
}
|
||
|
||
// 从context中获取当前管理员信息(由JWT中间件设置)
|
||
adminValue := ctx.Value("admin")
|
||
if adminValue == nil {
|
||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||
}
|
||
|
||
var currentAdmin models.Admin
|
||
if adminVal, ok := adminValue.(models.Admin); ok {
|
||
currentAdmin = adminVal
|
||
} else if adminPtr, ok := adminValue.(*models.Admin); ok {
|
||
currentAdmin = *adminPtr
|
||
} else {
|
||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||
}
|
||
|
||
// 检查当前管理员是否已绑定谷歌验证码
|
||
isBound, err := r.googleAuthenticatorService.IsBound(currentAdmin.ID)
|
||
if err != nil {
|
||
return response.ErrorWithLog(ctx, "admin", err, map[string]any{
|
||
"admin_id": currentAdmin.ID,
|
||
})
|
||
}
|
||
|
||
if !isBound {
|
||
return response.Error(ctx, http.StatusForbidden, apperrors.ErrGoogleAuthenticatorNotBound.Code)
|
||
}
|
||
|
||
// 需要验证码确认
|
||
code := ctx.Request().Input("code")
|
||
if code == "" {
|
||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrCodeRequired.Code)
|
||
}
|
||
|
||
// 获取当前管理员的密钥
|
||
secret, err := r.googleAuthenticatorService.GetSecret(currentAdmin.ID)
|
||
if err != nil {
|
||
return response.ErrorWithLog(ctx, "admin", err, map[string]any{
|
||
"admin_id": currentAdmin.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)
|
||
}
|
||
|
||
// 检查目标管理员是否已绑定
|
||
targetIsBound, err := r.googleAuthenticatorService.IsBound(targetAdminID)
|
||
if err != nil {
|
||
return response.ErrorWithLog(ctx, "admin", err, map[string]any{
|
||
"target_admin_id": targetAdminID,
|
||
})
|
||
}
|
||
|
||
if !targetIsBound {
|
||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrGoogleAuthenticatorNotBound.Code)
|
||
}
|
||
|
||
// 解绑目标管理员的谷歌验证码
|
||
if err := r.googleAuthenticatorService.Unbind(targetAdminID); err != nil {
|
||
return response.ErrorWithLog(ctx, "admin", err, map[string]any{
|
||
"target_admin_id": targetAdminID,
|
||
"current_admin_id": currentAdmin.ID,
|
||
})
|
||
}
|
||
|
||
return response.Success(ctx, "unbind_success")
|
||
}
|
||
|
||
// getAllProtectedAdminIDs 获取所有受保护的管理员ID(用于删除等操作)
|
||
func (r *AdminController) getAllProtectedAdminIDs() map[uint]bool {
|
||
return r.adminService.GetProtectedAdminIDs()
|
||
}
|
||
|
||
// Export 导出管理员列表
|
||
// @Summary 导出管理员列表
|
||
// @Description 根据筛选条件导出管理员列表为CSV文件,支持与列表查询相同的筛选条件
|
||
// @Tags 管理员管理
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Param request body AdminExportRequest false "导出筛选条件(可选)"
|
||
// @Success 200 {object} map[string]any "导出成功,返回文件下载信息"
|
||
// @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/admins/export [post]
|
||
// @Security BearerAuth
|
||
func (r *AdminController) Export(ctx http.Context) http.Response {
|
||
adminID, err := helpers.GetAdminIDFromContext(ctx)
|
||
if err != nil {
|
||
return response.Error(ctx, http.StatusUnauthorized, "unauthorized")
|
||
}
|
||
|
||
// 防重复点击:使用框架自带的原子锁(锁会在10秒后自动过期,防止短时间内重复请求)
|
||
lockKey := fmt.Sprintf("export:admins:lock:%d", adminID)
|
||
lock := facades.Cache().Lock(lockKey, 10*time.Second)
|
||
|
||
// 尝试获取锁,如果获取失败则返回错误
|
||
if !lock.Get() {
|
||
return response.Error(ctx, http.StatusTooManyRequests, "export_in_progress")
|
||
}
|
||
// 同步导出:锁会在 Redis 中自动过期(10秒),不需要手动释放
|
||
|
||
filters := r.buildFilters(ctx)
|
||
|
||
// 导出时获取所有数据,不分页
|
||
admins, err := r.adminService.GetAllAdminsForExport(filters)
|
||
if err != nil {
|
||
return response.Error(ctx, http.StatusInternalServerError, err.Error())
|
||
}
|
||
|
||
headers := []string{
|
||
"export_header_id",
|
||
"export_header_username",
|
||
"export_header_nickname",
|
||
"export_header_email",
|
||
"export_header_phone",
|
||
"export_header_status",
|
||
"export_header_department",
|
||
"export_header_roles",
|
||
"export_header_created_at",
|
||
"export_header_updated_at",
|
||
}
|
||
|
||
timezone := helpers.GetCurrentTimezone(ctx)
|
||
var data [][]string
|
||
for _, admin := range admins {
|
||
statusText := trans.Get(ctx, "export_status_disabled")
|
||
if admin.Status == 1 {
|
||
statusText = trans.Get(ctx, "export_status_enabled")
|
||
}
|
||
|
||
// 部门名称
|
||
departmentName := ""
|
||
if admin.Department.ID > 0 {
|
||
departmentName = admin.Department.Name
|
||
}
|
||
|
||
// 角色名称(多个角色用逗号分隔)
|
||
roleNames := ""
|
||
if len(admin.Roles) > 0 {
|
||
for i, role := range admin.Roles {
|
||
if i > 0 {
|
||
roleNames += ", "
|
||
}
|
||
roleNames += role.Name
|
||
}
|
||
}
|
||
|
||
// 时间格式化
|
||
createdAt := helpers.FormatCarbonWithTimezone(admin.CreatedAt, timezone)
|
||
updatedAt := helpers.FormatCarbonWithTimezone(admin.UpdatedAt, timezone)
|
||
|
||
row := []string{
|
||
cast.ToString(admin.ID),
|
||
admin.Username,
|
||
admin.Nickname,
|
||
admin.Email,
|
||
admin.Phone,
|
||
statusText,
|
||
departmentName,
|
||
roleNames,
|
||
createdAt,
|
||
updatedAt,
|
||
}
|
||
data = append(data, row)
|
||
}
|
||
|
||
// 在 context 中设置导出类型,供 ExportService 使用
|
||
ctx.WithValue("export_type", models.ExportTypeAdmins)
|
||
|
||
return response.Export(ctx, "export_success", headers, data, "admins")
|
||
}
|