init
This commit is contained in:
@@ -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")
|
||||
}
|
||||
Reference in New Issue
Block a user