This commit is contained in:
Joe
2026-01-16 15:49:34 +08:00
commit 550d3e1f42
380 changed files with 62024 additions and 0 deletions
@@ -0,0 +1,730 @@
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")
}