530 lines
15 KiB
Go
530 lines
15 KiB
Go
package services
|
|
|
|
import (
|
|
"slices"
|
|
|
|
"github.com/goravel/framework/contracts/database/orm"
|
|
"github.com/goravel/framework/facades"
|
|
"github.com/goravel/framework/support/str"
|
|
"github.com/samber/lo"
|
|
"github.com/spf13/cast"
|
|
|
|
apperrors "goravel/app/errors"
|
|
"goravel/app/http/helpers"
|
|
"goravel/app/models"
|
|
)
|
|
|
|
type AdminService interface {
|
|
// GetByID 根据ID获取管理员
|
|
GetByID(id uint, withDepartment bool, withRoles bool) (*models.Admin, error)
|
|
// GetList 获取管理员列表
|
|
GetList(filters AdminFilters, page, pageSize int) ([]models.Admin, int64, error)
|
|
// GetAllAdminsForExport 获取所有管理员用于导出(不分页)
|
|
GetAllAdminsForExport(filters AdminFilters) ([]models.Admin, error)
|
|
// LoadRelations 加载管理员的关联数据(部门、角色)
|
|
LoadRelations(admin *models.Admin) error
|
|
// LoadRelationsWithPermissions 加载管理员的关联数据(包括权限和菜单)
|
|
LoadRelationsWithPermissions(admin *models.Admin) error
|
|
// LoadRelationsForList 批量加载管理员的关联数据
|
|
LoadRelationsForList(admins []models.Admin) error
|
|
// SyncRoles 同步管理员角色关联
|
|
SyncRoles(admin *models.Admin, roleIDs []uint) error
|
|
// GetProtectedAdminIDs 获取所有受保护的管理员ID
|
|
GetProtectedAdminIDs() map[uint]bool
|
|
// GetDepartmentAndChildrenIDs 获取部门及其子部门ID
|
|
GetDepartmentAndChildrenIDs(departmentID uint) []uint
|
|
// Update 更新管理员
|
|
Update(admin *models.Admin) error
|
|
}
|
|
|
|
// AdminFilters 管理员查询过滤器
|
|
type AdminFilters struct {
|
|
Username string
|
|
Status string
|
|
RoleID string
|
|
DepartmentID string
|
|
Is2FABound string
|
|
StartTime string
|
|
EndTime string
|
|
OrderBy string
|
|
}
|
|
|
|
type AdminServiceImpl struct {
|
|
}
|
|
|
|
func NewAdminServiceImpl() *AdminServiceImpl {
|
|
return &AdminServiceImpl{}
|
|
}
|
|
|
|
// GetByID 根据ID获取管理员
|
|
func (s *AdminServiceImpl) GetByID(id uint, withDepartment bool, withRoles bool) (*models.Admin, error) {
|
|
var admin models.Admin
|
|
query := facades.Orm().Query().Where("id", id)
|
|
|
|
// 预加载关联
|
|
if withDepartment {
|
|
query = query.With("Department")
|
|
}
|
|
if withRoles {
|
|
query = query.With("Roles")
|
|
}
|
|
|
|
if err := query.First(&admin); err != nil {
|
|
return nil, apperrors.ErrAdminNotFound.WithError(err)
|
|
}
|
|
|
|
return &admin, nil
|
|
}
|
|
|
|
// buildQuery 构建查询(公共方法,用于列表和导出)
|
|
func (s *AdminServiceImpl) buildQuery(filters AdminFilters) orm.Query {
|
|
query := facades.Orm().Query().Model(&models.Admin{})
|
|
|
|
// 排除受保护的管理员
|
|
protectedIDs := s.GetProtectedAdminIDs()
|
|
if len(protectedIDs) > 0 {
|
|
var ids []uint
|
|
for id := range protectedIDs {
|
|
ids = append(ids, id)
|
|
}
|
|
if len(ids) > 0 {
|
|
query = query.Where("id NOT IN ?", ids)
|
|
}
|
|
}
|
|
|
|
// 应用筛选条件
|
|
if filters.Username != "" {
|
|
query = query.Where("username LIKE ?", "%"+filters.Username+"%")
|
|
}
|
|
if filters.Status != "" {
|
|
query = query.Where("status", filters.Status)
|
|
}
|
|
if filters.RoleID != "" {
|
|
roleIDUint := cast.ToUint(filters.RoleID)
|
|
if roleIDUint > 0 {
|
|
query = query.Where("id IN (SELECT admin_id FROM admin_role WHERE role_id = ?)", roleIDUint)
|
|
}
|
|
}
|
|
if filters.DepartmentID != "" {
|
|
departmentIDUint := cast.ToUint(filters.DepartmentID)
|
|
if departmentIDUint > 0 {
|
|
departmentIDs := s.GetDepartmentAndChildrenIDs(departmentIDUint)
|
|
if len(departmentIDs) > 0 {
|
|
idsAny := make([]any, len(departmentIDs))
|
|
for i, id := range departmentIDs {
|
|
idsAny[i] = id
|
|
}
|
|
query = query.WhereIn("department_id", idsAny)
|
|
} else {
|
|
// 如果部门不存在,返回空结果
|
|
query = query.Where("1 = 0")
|
|
}
|
|
}
|
|
}
|
|
if filters.Is2FABound != "" {
|
|
switch filters.Is2FABound {
|
|
case "1":
|
|
// 已绑定:google_secret IS NOT NULL AND google_secret != ''
|
|
query = query.Where("google_secret IS NOT NULL AND google_secret != ?", "")
|
|
case "0":
|
|
// 未绑定:google_secret IS NULL OR google_secret = ''
|
|
query = query.Where("(google_secret IS NULL OR google_secret = ?)", "")
|
|
}
|
|
}
|
|
if filters.StartTime != "" {
|
|
query = query.Where("created_at >= ?", filters.StartTime)
|
|
}
|
|
if filters.EndTime != "" {
|
|
query = query.Where("created_at <= ?", filters.EndTime)
|
|
}
|
|
|
|
return query
|
|
}
|
|
|
|
// GetList 获取管理员列表
|
|
func (s *AdminServiceImpl) GetList(filters AdminFilters, page, pageSize int) ([]models.Admin, int64, error) {
|
|
query := s.buildQuery(filters)
|
|
|
|
// 应用排序
|
|
orderBy := filters.OrderBy
|
|
if orderBy == "" {
|
|
orderBy = "created_at:desc"
|
|
}
|
|
query = helpers.ApplySort(query, orderBy, "created_at:desc")
|
|
|
|
// 分页查询
|
|
var admins []models.Admin
|
|
var total int64
|
|
if err := query.With("Department").With("Roles").Paginate(page, pageSize, &admins, &total); err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
return admins, total, nil
|
|
}
|
|
|
|
// GetAllAdminsForExport 获取所有管理员用于导出(不分页)
|
|
func (s *AdminServiceImpl) GetAllAdminsForExport(filters AdminFilters) ([]models.Admin, error) {
|
|
query := s.buildQuery(filters)
|
|
|
|
// 应用排序
|
|
orderBy := filters.OrderBy
|
|
if orderBy == "" {
|
|
orderBy = "created_at:desc"
|
|
}
|
|
query = helpers.ApplySort(query, orderBy, "created_at:desc")
|
|
|
|
// 不分页,获取所有数据
|
|
var admins []models.Admin
|
|
if err := query.With("Department").With("Roles").Find(&admins); err != nil {
|
|
return nil, apperrors.ErrQueryFailed.WithError(err)
|
|
}
|
|
|
|
return admins, nil
|
|
}
|
|
|
|
// Update 更新管理员
|
|
func (s *AdminServiceImpl) Update(admin *models.Admin) error {
|
|
if err := facades.Orm().Query().Save(admin); err != nil {
|
|
return apperrors.ErrUpdateFailed.WithError(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetProtectedAdminIDs 获取所有受保护的管理员ID
|
|
func (s *AdminServiceImpl) GetProtectedAdminIDs() map[uint]bool {
|
|
allProtectedIDs := make(map[uint]bool)
|
|
// 添加超级管理员ID(从配置读取,默认1)
|
|
superAdminID := cast.ToUint(facades.Config().GetInt("admin.super_admin_id", 1))
|
|
allProtectedIDs[superAdminID] = true
|
|
// 添加开发者管理员ID
|
|
developerIDsStr := facades.Config().GetString("admin.developer_ids", "2")
|
|
developerIDs := s.parseProtectedIDs(developerIDsStr)
|
|
for _, did := range developerIDs {
|
|
allProtectedIDs[did] = true
|
|
}
|
|
return allProtectedIDs
|
|
}
|
|
|
|
// parseProtectedIDs 解析受保护的管理员ID字符串(支持逗号分隔)
|
|
func (s *AdminServiceImpl) parseProtectedIDs(idsStr string) []uint {
|
|
var ids []uint
|
|
if idsStr == "" {
|
|
return ids
|
|
}
|
|
|
|
// 使用字符串分割
|
|
parts := str.Of(idsStr).Split(",")
|
|
for _, part := range parts {
|
|
part = str.Of(part).Trim().String()
|
|
if !str.Of(part).IsEmpty() {
|
|
if id := cast.ToUint(part); id > 0 {
|
|
ids = append(ids, id)
|
|
}
|
|
}
|
|
}
|
|
|
|
return ids
|
|
}
|
|
|
|
// GetDepartmentAndChildrenIDs 获取部门及其子部门ID
|
|
func (s *AdminServiceImpl) GetDepartmentAndChildrenIDs(departmentID uint) []uint {
|
|
var departmentIDs []uint
|
|
departmentIDs = append(departmentIDs, departmentID)
|
|
s.getChildrenDepartmentIDs(departmentID, &departmentIDs)
|
|
return departmentIDs
|
|
}
|
|
|
|
// getChildrenDepartmentIDs 递归获取子部门ID
|
|
func (s *AdminServiceImpl) getChildrenDepartmentIDs(parentID uint, departmentIDs *[]uint) {
|
|
var children []models.Department
|
|
if err := facades.Orm().Query().Where("parent_id", parentID).Get(&children); err == nil {
|
|
for _, child := range children {
|
|
*departmentIDs = append(*departmentIDs, child.ID)
|
|
s.getChildrenDepartmentIDs(child.ID, departmentIDs)
|
|
}
|
|
}
|
|
}
|
|
|
|
// LoadRelations 加载管理员的关联数据(部门、角色)
|
|
func (s *AdminServiceImpl) LoadRelations(admin *models.Admin) error {
|
|
if admin == nil {
|
|
return nil
|
|
}
|
|
|
|
// 加载部门
|
|
if admin.DepartmentID > 0 {
|
|
var department models.Department
|
|
if err := facades.Orm().Query().Where("id", admin.DepartmentID).First(&department); err == nil {
|
|
admin.Department = department
|
|
}
|
|
}
|
|
|
|
// 加载角色关联
|
|
type AdminRole struct {
|
|
AdminID uint `gorm:"column:admin_id"`
|
|
RoleID uint `gorm:"column:role_id"`
|
|
}
|
|
var adminRoles []AdminRole
|
|
if err := facades.Orm().Query().Table("admin_role").Where("admin_id", admin.ID).Find(&adminRoles); err != nil {
|
|
return err
|
|
}
|
|
|
|
var roleIDs []uint
|
|
for _, ar := range adminRoles {
|
|
if !contains(roleIDs, ar.RoleID) {
|
|
roleIDs = append(roleIDs, ar.RoleID)
|
|
}
|
|
}
|
|
|
|
admin.Roles = nil
|
|
if len(roleIDs) > 0 {
|
|
var roles []models.Role
|
|
if err := facades.Orm().Query().Where("id IN ?", roleIDs).Find(&roles); err != nil {
|
|
return err
|
|
}
|
|
admin.Roles = roles
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// LoadRelationsWithPermissions 加载管理员的关联数据(包括权限和菜单)
|
|
func (s *AdminServiceImpl) LoadRelationsWithPermissions(admin *models.Admin) error {
|
|
// 先加载基本关联
|
|
if err := s.LoadRelations(admin); err != nil {
|
|
return err
|
|
}
|
|
|
|
// 批量加载所有角色的权限和菜单,避免 N+1 查询
|
|
if len(admin.Roles) > 0 {
|
|
for i := range admin.Roles {
|
|
admin.Roles[i].Permissions = nil
|
|
admin.Roles[i].Menus = nil
|
|
}
|
|
|
|
var roleIDs []uint
|
|
for _, role := range admin.Roles {
|
|
roleIDs = append(roleIDs, role.ID)
|
|
}
|
|
|
|
// 批量加载权限
|
|
type RolePermission struct {
|
|
RoleID uint `gorm:"column:role_id"`
|
|
PermissionID uint `gorm:"column:permission_id"`
|
|
}
|
|
var rolePermissions []RolePermission
|
|
if err := facades.Orm().Query().Table("role_permission").Where("role_id IN ?", roleIDs).Find(&rolePermissions); err == nil {
|
|
var permissionIDs []uint
|
|
rolePermissionMap := make(map[uint][]uint)
|
|
for _, rp := range rolePermissions {
|
|
rolePermissionMap[rp.RoleID] = append(rolePermissionMap[rp.RoleID], rp.PermissionID)
|
|
permissionIDs = append(permissionIDs, rp.PermissionID)
|
|
}
|
|
if len(permissionIDs) > 0 {
|
|
var permissions []models.Permission
|
|
// 预加载菜单关联,用于检查菜单状态
|
|
if err := facades.Orm().Query().With("Menu").Where("id IN ?", permissionIDs).Find(&permissions); err == nil {
|
|
permissionMap := make(map[uint]models.Permission)
|
|
for _, perm := range permissions {
|
|
permissionMap[perm.ID] = perm
|
|
}
|
|
for i := range admin.Roles {
|
|
if permIDs, ok := rolePermissionMap[admin.Roles[i].ID]; ok {
|
|
for _, permID := range permIDs {
|
|
if perm, ok := permissionMap[permID]; ok {
|
|
admin.Roles[i].Permissions = append(admin.Roles[i].Permissions, perm)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 批量加载菜单
|
|
type RoleMenu struct {
|
|
RoleID uint `gorm:"column:role_id"`
|
|
MenuID uint `gorm:"column:menu_id"`
|
|
}
|
|
var roleMenus []RoleMenu
|
|
if err := facades.Orm().Query().Table("role_menu").Where("role_id IN ?", roleIDs).Find(&roleMenus); err == nil {
|
|
var menuIDs []uint
|
|
roleMenuMap := make(map[uint][]uint)
|
|
for _, rm := range roleMenus {
|
|
roleMenuMap[rm.RoleID] = append(roleMenuMap[rm.RoleID], rm.MenuID)
|
|
menuIDs = append(menuIDs, rm.MenuID)
|
|
}
|
|
if len(menuIDs) > 0 {
|
|
var menus []models.Menu
|
|
if err := facades.Orm().Query().Where("id IN ?", menuIDs).Find(&menus); err == nil {
|
|
menuMap := make(map[uint]models.Menu)
|
|
for _, menu := range menus {
|
|
menuMap[menu.ID] = menu
|
|
}
|
|
for i := range admin.Roles {
|
|
if mIDs, ok := roleMenuMap[admin.Roles[i].ID]; ok {
|
|
for _, menuID := range mIDs {
|
|
if menu, ok := menuMap[menuID]; ok {
|
|
admin.Roles[i].Menus = append(admin.Roles[i].Menus, menu)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 收集所有角色的权限和菜单(去重)
|
|
permissionMap := make(map[uint]models.Permission)
|
|
menuMap := make(map[uint]models.Menu)
|
|
|
|
for _, role := range admin.Roles {
|
|
for _, perm := range role.Permissions {
|
|
permissionMap[perm.ID] = perm
|
|
}
|
|
for _, menu := range role.Menus {
|
|
menuMap[menu.ID] = menu
|
|
}
|
|
}
|
|
|
|
// 将权限和菜单存储到 admin 的扩展字段(如果需要)
|
|
// 这里可以根据实际需求调整
|
|
|
|
return nil
|
|
}
|
|
|
|
// LoadRelationsForList 批量加载管理员的关联数据(优化版,避免 N+1 查询)
|
|
func (s *AdminServiceImpl) LoadRelationsForList(admins []models.Admin) error {
|
|
if len(admins) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// 收集所有需要查询的 ID
|
|
var departmentIDs []uint
|
|
var adminIDs []uint
|
|
for _, admin := range admins {
|
|
if admin.DepartmentID > 0 {
|
|
departmentIDs = append(departmentIDs, admin.DepartmentID)
|
|
}
|
|
adminIDs = append(adminIDs, admin.ID)
|
|
}
|
|
|
|
// 批量查询部门
|
|
departmentsMap := make(map[uint]models.Department)
|
|
if len(departmentIDs) > 0 {
|
|
var departments []models.Department
|
|
// 去重
|
|
uniqueDeptIDs := make(map[uint]bool)
|
|
var uniqueIDs []uint
|
|
for _, id := range departmentIDs {
|
|
if !uniqueDeptIDs[id] {
|
|
uniqueIDs = append(uniqueIDs, id)
|
|
uniqueDeptIDs[id] = true
|
|
}
|
|
}
|
|
if err := facades.Orm().Query().Where("id IN ?", uniqueIDs).Find(&departments); err != nil {
|
|
return err
|
|
}
|
|
for _, dept := range departments {
|
|
departmentsMap[dept.ID] = dept
|
|
}
|
|
}
|
|
|
|
// 批量查询所有管理员的角色关联
|
|
// 查询中间表获取 admin_id 和 role_id 的映射
|
|
type AdminRole struct {
|
|
AdminID uint `gorm:"column:admin_id"`
|
|
RoleID uint `gorm:"column:role_id"`
|
|
}
|
|
var adminRoles []AdminRole
|
|
if err := facades.Orm().Query().Table("admin_role").Where("admin_id IN ?", adminIDs).Find(&adminRoles); err != nil {
|
|
return err
|
|
}
|
|
|
|
// 按管理员ID分组角色关联
|
|
adminRoleMap := lo.GroupBy(adminRoles, func(ar AdminRole) uint {
|
|
return ar.AdminID
|
|
})
|
|
|
|
// 提取所有角色ID并去重
|
|
allRoleIDs := lo.Map(adminRoles, func(ar AdminRole, _ int) uint {
|
|
return ar.RoleID
|
|
})
|
|
roleIDs := lo.Uniq(allRoleIDs)
|
|
|
|
// 批量查询所有角色
|
|
rolesMap := make(map[uint]models.Role)
|
|
if len(roleIDs) > 0 {
|
|
var roles []models.Role
|
|
if err := facades.Orm().Query().Where("id IN ?", roleIDs).Find(&roles); err != nil {
|
|
return err
|
|
}
|
|
rolesMap = lo.SliceToMap(roles, func(role models.Role) (uint, models.Role) {
|
|
return role.ID, role
|
|
})
|
|
}
|
|
|
|
// 填充关联数据
|
|
for i := range admins {
|
|
// 填充部门
|
|
if admins[i].DepartmentID > 0 {
|
|
if dept, ok := departmentsMap[admins[i].DepartmentID]; ok {
|
|
admins[i].Department = dept
|
|
}
|
|
}
|
|
|
|
// 填充角色(去重)
|
|
if adminRoleList, ok := adminRoleMap[admins[i].ID]; ok {
|
|
roleIDs := lo.Uniq(lo.Map(adminRoleList, func(ar AdminRole, _ int) uint {
|
|
return ar.RoleID
|
|
}))
|
|
admins[i].Roles = lo.FilterMap(roleIDs, func(roleID uint, _ int) (models.Role, bool) {
|
|
role, ok := rolesMap[roleID]
|
|
return role, ok
|
|
})
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// contains 辅助函数,检查切片中是否包含某个值
|
|
func contains(slice []uint, val uint) bool {
|
|
return slices.Contains(slice, val)
|
|
}
|
|
|
|
// SyncRoles 同步管理员角色关联
|
|
func (s *AdminServiceImpl) SyncRoles(admin *models.Admin, roleIDs []uint) error {
|
|
// 去重角色ID,避免重复数据
|
|
deduplicatedRoleIDs := lo.Uniq(roleIDs)
|
|
|
|
// 先清空该管理员的所有角色关联(包括重复的),确保彻底清理重复数据
|
|
if err := facades.Orm().Query().Model(admin).Association("Roles").Clear(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// 如果有角色需要关联,则添加去重后的角色关联
|
|
if len(deduplicatedRoleIDs) > 0 {
|
|
var roles []models.Role
|
|
if err := facades.Orm().Query().Where("id IN ?", deduplicatedRoleIDs).Find(&roles); err != nil {
|
|
return err
|
|
}
|
|
|
|
// 对查询到的角色再次去重(双重保险)
|
|
roleMap := lo.SliceToMap(roles, func(role models.Role) (uint, models.Role) {
|
|
return role.ID, role
|
|
})
|
|
|
|
// 将去重后的角色转换为切片
|
|
deduplicatedRoles := lo.Values(roleMap)
|
|
|
|
// Replace 方法会先删除所有现有关联,然后添加新的关联
|
|
if err := facades.Orm().Query().Model(admin).Association("Roles").Replace(deduplicatedRoles); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|