Files
server/app/http/controllers/admin/admin_controller.go
T
2026-01-16 15:49:34 +08:00

731 lines
28 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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")
}