package com.cf.imes.module.system.service.permission;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.cf.imes.framework.common.enums.CommonStatusEnum;
import com.cf.imes.framework.common.exception.ServiceException;
import com.cf.imes.framework.common.exception.enums.GlobalErrorCodeConstants;
import com.cf.imes.framework.common.util.collection.CollectionUtils;
import com.cf.imes.framework.common.util.json.JsonUtils;
import com.cf.imes.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.cf.imes.framework.organ.core.aop.OrganIgnore;
import com.cf.imes.framework.organ.core.context.OrganContextHolder;
import com.cf.imes.framework.security.core.LoginUser;
import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils;
import com.cf.imes.module.system.api.permission.dto.DeptDataPermissionRespDTO;
import com.cf.imes.module.system.constants.permission.InternalRoleConstants;
import com.cf.imes.module.system.controller.admin.permission.vo.permission.PermissionAssignRoleUserReqVO;
import com.cf.imes.module.system.controller.admin.permission.vo.permission.PermissionAssignUserRoleReqVO;
import com.cf.imes.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
import com.cf.imes.module.system.dal.dataobject.organ.OrganizationDO;
import com.cf.imes.module.system.dal.dataobject.permission.MenuDO;
import com.cf.imes.module.system.dal.dataobject.permission.RoleDO;
import com.cf.imes.module.system.dal.dataobject.permission.RoleMenuDO;
import com.cf.imes.module.system.dal.dataobject.permission.UserRoleDO;
import com.cf.imes.module.system.dal.dataobject.user.AdminUserDO;
import com.cf.imes.module.system.dal.mysql.permission.RoleMenuMapper;
import com.cf.imes.module.system.dal.mysql.permission.UserRoleMapper;
import com.cf.imes.module.system.dal.redis.RedisKeyConstants;
import com.cf.imes.module.system.enums.ErrorCodeConstants;
import com.cf.imes.module.system.enums.permission.DataScopeEnum;
import com.cf.imes.module.system.service.dept.DeptService;
import com.cf.imes.module.system.service.organ.OrganService;
import com.cf.imes.module.system.service.organ.TenantPackageService;
import com.cf.imes.module.system.service.user.AdminUserService;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.cf.imes.module.system.util.organ.OrganUtils;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Suppliers;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import jakarta.annotation.Resource;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.cf.imes.framework.common.util.collection.CollectionUtils.convertSet;
import static com.cf.imes.framework.common.util.json.JsonUtils.toJsonString;
import static com.cf.imes.framework.security.core.util.SecurityFrameworkUtils.getLoginUser;
import static com.cf.imes.module.system.dal.redis.RedisKeyConstants.OAUTH2_ACCESS_TOKEN;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.ROLE_ME_ERROR;

/**
 * 权限 Service 实现类
 *
 * @author 晨丰科技
 */
@Service
@Slf4j
public class PermissionServiceImpl implements PermissionService {

    @Resource
    private RoleMenuMapper roleMenuMapper;
    @Resource
    private UserRoleMapper userRoleMapper;

    @Resource
    private RoleService roleService;
    @Resource
    private MenuService menuService;
    @Resource
    private DeptService deptService;
    @Resource
    private AdminUserService userService;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    @Lazy // 延迟，避免循环依赖报错
    private TenantPackageService tenantPackageService;

    @Resource
    @Lazy // 延迟，避免循环依赖报错
    private OrganService organService;

    @Override
    public boolean hasAnyPermissions(Long userId, String... permissions) {
        // 如果为空，说明已经有权限
        if (ArrayUtil.isEmpty(permissions)) {
            return true;
        }

        // 获得当前登录的角色。如果为空，说明没有权限
        List<RoleDO> roles = getEnableUserRoleListByUserIdFromCache(userId);
        if (CollUtil.isEmpty(roles)) {
            return false;
        }

        AdminUserDO user = userService.getUser(userId);
        if (ObjectUtil.isNull(user)) {
            throw new ServiceException(ErrorCodeConstants.USER_NOT_EXISTS);
        }
        OrganizationDO organizationDO = organService.validOrgan(user.getOrganId());

        // 情况一：遍历判断每个权限，如果有一满足，说明有权限
        for (String permission : permissions) {
            if (hasAnyPermission(roles, permission, organizationDO.getPackageId())) {
                return true;
            }
        }

        // 情况二：如果是超管，也说明有权限
        return roleService.hasAnySuperAdmin(convertSet(roles, RoleDO::getId));
    }

    /**
     * 判断指定角色，是否拥有该 permission 权限
     *
     * @param roles 指定角色数组
     * @param permission 权限标识
     * @return 是否拥有
     */
    private boolean hasAnyPermission(List<RoleDO> roles, String permission, Long packageId) {
        List<Long> menuIds = menuService.getMenuIdListByPermissionFromCache(permission);
        // 采用严格模式，如果权限找不到对应的 Menu 的话，也认为没有权限
        if (CollUtil.isEmpty(menuIds)) {
            return false;
        }

        // 组织套餐下的菜单
        Set<Long> tenantPackageMenuIds = tenantPackageService.getTenantPackageMenuIds(packageId);

        // 判断是否有权限
        Set<Long> roleIds = convertSet(roles, RoleDO::getId);
        for (Long menuId : menuIds) {
            // 获得拥有该菜单的角色编号集合
            //Set<Long> menuRoleIds = getSelf().getMenuRoleIdListByMenuIdFromCache(menuId);
            Set<Long> menuRoleIds = getSelf().getMenuRoleIdListByMenuIdFromCache1(menuId);
            // 如果有交集，说明有权限
            // 角色下菜单id和组织套餐内菜单id必须同时满足
            if (CollUtil.containsAny(menuRoleIds, roleIds) && tenantPackageMenuIds.contains(menuId)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean hasAnyRoles(Long userId, String... roles) {
        // 如果为空，说明已经有权限
        if (ArrayUtil.isEmpty(roles)) {
            return true;
        }

        // 获得当前登录的角色。如果为空，说明没有权限
        List<RoleDO> roleList = getEnableUserRoleListByUserIdFromCache(userId);
        if (CollUtil.isEmpty(roleList)) {
            return false;
        }

        // 判断是否有角色
        Set<String> userRoles = convertSet(roleList, RoleDO::getCode);
        return CollUtil.containsAny(userRoles, Sets.newHashSet(roles));
    }

    // ========== 角色-菜单的相关方法  ==========

    @Override
    @DSTransactional // 多数据源，使用 @DSTransactional 保证本地事务，以及数据源的切换
    @CacheEvict(value = RedisKeyConstants.MENU_ROLE_ID_LIST,
            allEntries = true) // allEntries 清空所有缓存，主要一次更新涉及到的 menuIds 较多，反倒批量会更快
    public void assignRoleMenu(Long roleId, Set<Long> menuIds, Long organId) {
        // 获得角色拥有菜单编号
        Set<Long> dbMenuIds;
        boolean isOrganRole = OrganUtils.isOrgRole(roleId);
        if (isOrganRole) {
            dbMenuIds = convertSet(roleMenuMapper.selectListByRoleIdWithOrganAdmin(List.of(roleId)), RoleMenuDO::getMenuId);
        }else {
            dbMenuIds = convertSet(roleMenuMapper.selectListByRoleId(roleId), RoleMenuDO::getMenuId);
        }
        // 查询角色获取角色的组织id，如果没有传入组织id就跟角色的组织统一
        RoleDO role = validateRole(roleId);
        // 前端没传或者查一次后切换了组织导致跟角色组织不一致的统一为角色的组织
        Long roleOrganId = role.getOrganId();
        if (Objects.isNull(organId) || ObjectUtil.notEqual(roleOrganId, organId)) {
            organId = roleOrganId;
        }
        final Long finalOrganId = organId;

        // 计算新增和删除的菜单编号
        Set<Long> menuIdList = CollUtil.emptyIfNull(menuIds);
        Collection<Long> createMenuIds = CollUtil.subtract(menuIdList, dbMenuIds);
        Collection<Long> deleteMenuIds = CollUtil.subtract(dbMenuIds, menuIdList);
        // 执行新增和删除。对于已经授权的菜单，不用做任何处理
        if (CollUtil.isNotEmpty(createMenuIds)) {
            Function<Long, RoleMenuDO> longRoleMenuDOFunction = menuId -> {
                RoleMenuDO entity = new RoleMenuDO();
                entity.setRoleId(roleId);
                entity.setMenuId(menuId);
                if (isOrganRole) {
                    entity.setOrganId(InternalRoleConstants.INTERNAL_ROLE_ORGAN_ID);
                }else {
                    entity.setOrganId(finalOrganId);
                }
                return entity;
            };
            roleMenuMapper.insertBatch(CollectionUtils.convertList(createMenuIds, longRoleMenuDOFunction));
        }
        if (CollUtil.isNotEmpty(deleteMenuIds)) {
            roleMenuMapper.deleteListByRoleIdAndMenuIds(roleId, deleteMenuIds);
        }

        // 角色菜单权限发生修改，通知刷新本地缓存
        flushRoleMenuCache(roleId, isOrganRole, createMenuIds, deleteMenuIds, finalOrganId);
    }

    /**
     * 跨机构清理角色菜单缓存 + 通知刷新本地缓存
     *
     * @param roleId        操作的角色id
     * @param isOrganRole   是否内置角色
     * @param createMenuIds 新增角色-菜单关联id
     * @param deleteMenuIds 删除角色-菜单关联id
     * @param organId       角色所属机构id
     */
    private void flushRoleMenuCache(Long roleId, boolean isOrganRole, Collection<Long> createMenuIds, Collection<Long> deleteMenuIds, Long organId) {
        Long currentOrganId = SecurityFrameworkUtils.getUserOrganId();
        // 角色菜单权限发生修改，通知刷新本地缓存
        if (CollUtil.isNotEmpty(createMenuIds) || CollUtil.isNotEmpty(deleteMenuIds)) {
            CompletableFuture.runAsync(() -> {
                        // 当前操作人和所操作的目标菜单机构不同时，清空MENU_ROLE_ID_LIST:*:菜单id的缓存
                        if (ObjectUtil.notEqual(organId, currentOrganId)) {
                            // 获取新增和删除菜单id的并集
                            Collection<Long> delMenuIds = CollUtil.union(createMenuIds, deleteMenuIds);
                            delMenuIds.forEach(delMenuId -> {
                                String patternKey = String.format(String.valueOf(new StringBuffer(RedisKeyConstants.MENU_ROLE_ID_LIST).append(":*:%s")), delMenuId);
                                Long batchDelNum = scanAndDelKeys(patternKey);
                                log.info("[assignRoleMenu] 批量删除redis[{}]数量:{}", patternKey, batchDelNum);
                            });
                        }
                        stringRedisTemplate.convertAndSend(com.cf.imes.framework.redis.constants.RedisKeyConstants.PERMISSION_REFRESH, "");
                    })
                    .thenRunAsync(() -> {
                        // 清空角色下用户的token缓存
                        List<Long> userIds;
                        if(isOrganRole) {
                            userIds = userRoleMapper.selectRoleUserIds(roleId).stream().map(UserRoleDO::getUserId).toList();
                        } else {
                            // 子线程补充上下文组织id
                            OrganContextHolder.setOrganId(organId);
                            userIds = userRoleMapper.selectOrgRoleUserIds(roleId).stream().map(UserRoleDO::getUserId).toList();
                        }
                        if (CollUtil.isNotEmpty(userIds)) {
                            scanAndCompareUserAndDelKeys(String.format(OAUTH2_ACCESS_TOKEN, "*"), userIds);
                        }
                    })
                    .exceptionally(e -> {
                        log.error("[assignRoleMenu][flushRoleMenuCache]失败, 异常:{}", e);
                        return null;
                    });
        }
    }

    /**
     * scan匹配oauth2_access_token:下缓存并删除缓存
     * @param keyPattern
     * @param userIds
     */
    private void scanAndCompareUserAndDelKeys(String keyPattern, List<Long> userIds) {
        // 根据keyPattern scan匹配的redis key
        List<String> matchKeys = new ArrayList<>();
        Cursor<String> cursor = stringRedisTemplate.scan(ScanOptions.scanOptions().match(keyPattern).count(200).build());
        while (cursor.hasNext()) {
            matchKeys.add(cursor.next());
        }
        cursor.close();
        if (CollUtil.isNotEmpty(matchKeys)) {
            for (String key : matchKeys) {
                // 获取key下的用户信息
                OAuth2AccessTokenDO oAuth2AccessTokenDO = JsonUtils.parseObject(stringRedisTemplate.opsForValue().get(key), OAuth2AccessTokenDO.class);
                if (CollUtil.contains(userIds, oAuth2AccessTokenDO.getUserId())) {
                    // 角色下的用户id匹配上了删除redis中的token缓存
                    stringRedisTemplate.delete(key);
                }
            }
        }
    }

    /**
     * scan所有匹配的redis key并删除
     *
     * @param keyPattern 模糊匹配的表达式
     */
    private Long scanAndDelKeys(String keyPattern) {
        // 根据keyPattern scan匹配的redis key
        List<String> delKeys = new ArrayList<>();
        Cursor<String> cursor = stringRedisTemplate.scan(ScanOptions.scanOptions().match(keyPattern).count(200).build());
        while (cursor.hasNext()) {
            delKeys.add(cursor.next());
        }
        cursor.close();
        // 删除匹配到的key
        if (CollUtil.isNotEmpty(delKeys)) {
            return stringRedisTemplate.delete(delKeys);
        }
        return 0L;
    }


    /**
     * 校验角色
     *
     * @param roleId
     */
    private RoleDO validateRole(Long roleId) {
        RoleDO role = roleService.getRole(roleId);
        if (Objects.isNull(role)) {
            throw new ServiceException(ErrorCodeConstants.ROLE_NOT_EXISTS);
        }
        return role;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @Caching(evict = {
            @CacheEvict(value = RedisKeyConstants.MENU_ROLE_ID_LIST,
                    allEntries = true), // allEntries 清空所有缓存，此处无法方便获得 roleId 对应的 menu 缓存们
            @CacheEvict(value = RedisKeyConstants.USER_ROLE_ID_LIST,
                    allEntries = true) // allEntries 清空所有缓存，此处无法方便获得 roleId 对应的 user 缓存们
    })
    public void processRoleDeleted(Long roleId) {
        // 标记删除 UserRole
        userRoleMapper.deleteListByRoleId(roleId);
        // 标记删除 RoleMenu
        roleMenuMapper.deleteListByRoleId(roleId);
    }

    @Override
    @CacheEvict(value = RedisKeyConstants.MENU_ROLE_ID_LIST, key = "#menuId")
    public void processMenuDeleted(Long menuId) {
        roleMenuMapper.deleteListByMenuId(menuId);
    }

    @Override
    public Set<Long> getRoleMenuListByRoleId(Collection<Long> roleIds) {
        if (CollUtil.isEmpty(roleIds)) {
            return Collections.emptySet();
        }

        // 如果是管理员的情况下，获取全部菜单编号
        if (roleService.hasAnySuperAdmin(roleIds)) {
            return convertSet(menuService.getMenuList(), MenuDO::getId);
        }
        // 如果是非管理员的情况下，获得拥有的菜单编号
        List<RoleMenuDO> from = null;
        if(roleIds.contains(InternalRoleConstants.ORGAN_ADMIN_ROLE_ID) || roleIds.contains(InternalRoleConstants.ORGAN_STAFF_ROLE_ID)) {
            from = roleMenuMapper.selectListByRoleIdWithOrganAdmin(roleIds);
        }else {
            from = roleMenuMapper.selectListByRoleId(roleIds);
        }
        return convertSet(from, RoleMenuDO::getMenuId);
    }


    @Override
    @OrganIgnore
    public Set<Long> getRoleMenuListByRoleId2(Set<Long> roleIds, Long organId) {
        if (CollUtil.isEmpty(roleIds)) {
            return Collections.emptySet();
        }

        // 如果是管理员的情况下，获取全部菜单编号
        if (roleService.hasAnySuperAdmin(roleIds)) {
            return convertSet(menuService.getMenuList(), MenuDO::getId);
        } else {
            List<Long> buildinRoleIdList = new ArrayList<>(){{
                add(InternalRoleConstants.ORGAN_ADMIN_ROLE_ID);
                add(InternalRoleConstants.ORGAN_STAFF_ROLE_ID);
            }};

            Set<Long> roleMenuIds;
            if (CollUtil.containsAny(roleIds, buildinRoleIdList)) {
                // 内置角色不限制组织id
                roleMenuIds = convertSet(roleMenuMapper.selectListByRoleId(roleIds), RoleMenuDO::getMenuId);
            } else {
                // 普通成员只能看组织id下的菜单
                roleMenuIds = convertSet(roleMenuMapper.selectListByRoleIdsAndOrganId(roleIds, OrganContextHolder.getOrganId()), RoleMenuDO::getMenuId);
            }

            // 校验、获取可用的组织
            OrganizationDO organ = organService.validOrgan(organId);

            Set<Long> tenantPackageMenuIds = tenantPackageService.getTenantPackageMenuIds(organ.getPackageId());

            return Sets.intersection(roleMenuIds, tenantPackageMenuIds);
        }
    }

    @Override
    @DSTransactional
    @Deprecated //todo 重写授权角色下的用户，后续计划删除
    public void bathAssignUserRole(List<PermissionAssignUserRoleReqVO> listReqVO) {
        Long contextOrganId = OrganContextHolder.getOrganId();
        listReqVO.forEach(e->{
            Long organId = e.getOrganId() ==null ? contextOrganId : e.getOrganId();
            assignUserRole(e.getUserId(), e.getRoleIds(), organId);
        });
    }

    @Override
    @DSTransactional // 多数据源，使用 @DSTransactional 保证本地事务，以及数据源的切换
    @CacheEvict(value = RedisKeyConstants.USER_ROLE_ID_LIST, allEntries = true)
    public void batchAssignRoleUsers(PermissionAssignRoleUserReqVO reqVO) {
        LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
        assert loginUser != null;
        Boolean isSupAdmin = loginUser.getIsSupAdmin();
        Set<Long> userIds = reqVO.getUserIds();
        // 组织id
        Long organId = reqVO.getOrganId();
        // 角色id
        Long roleId = reqVO.getRoleId();
        // 超管不传组织id则操作本机构角色
        // 非超管不传组织id不允许操作
        if (isSupAdmin) {
            organId = Optional.ofNullable(organId).orElseGet(loginUser::getOrganId);
        } else {
            if (organId == null) {
                throw new ServiceException(ErrorCodeConstants.ROLE_NOT_SUPERADMIN_NO_ORGAN_ID_OPER_ERROR);
            }
        }
        // 查询角色获取角色的组织id，如果没有传入组织id就跟角色的组织统一
        RoleDO role = validateRole(roleId);
        // 前端没传或者查一次后切换了组织导致跟角色组织不一致的统一为角色的组织
        Long roleOrganId = role.getOrganId();
        boolean isOrganRole = OrganUtils.isOrgRole(roleId);
        // 内置角色还是按照机构来存
        if (!isOrganRole && (Objects.isNull(organId) || ObjectUtil.notEqual(roleOrganId, organId))) {
            organId = roleOrganId;
        }
        final Long finalOrganId = organId;

        // 获得角色下的角色用户列表
        Set<Long> roleUserIds = convertSet(userRoleMapper.selectList(new LambdaQueryWrapperX<UserRoleDO>()
                .eqIfPresent(UserRoleDO::getOrganId, organId)
                .eq(UserRoleDO::getRoleId, roleId)
                .select(UserRoleDO::getUserId)), UserRoleDO::getUserId);

        // 筛选要新增的userRole：前端有，数据库没有
        Collection<Long> createRoleUserIds = CollUtil.subtract(userIds, roleUserIds);
        if (CollUtil.isNotEmpty(createRoleUserIds)) {
            userRoleMapper.insertBatch(CollectionUtils.convertList(createRoleUserIds, userId -> {
                UserRoleDO entity = new UserRoleDO();
                entity.setUserId(userId).setRoleId(roleId).setOrganId(finalOrganId);
                return entity;
            }));
        }

        // 筛选要删除的userRole：前端没有，数据库有
        Collection<Long> deleteRoleUserIds = CollUtil.subtract(roleUserIds, userIds);
        if (CollUtil.isNotEmpty(deleteRoleUserIds)) {
            // 当前用户不能移除自身的角色
            Long incloudSelf = deleteRoleUserIds.stream().filter(userId -> userId.equals(loginUser.getId())).findAny().orElse(null);
            if (incloudSelf != null) {
                throw new ServiceException(ROLE_ME_ERROR);
            }
            userRoleMapper.deleteListByRoleIdAndUserIds(roleId, deleteRoleUserIds);
        }
        // 角色菜单权限发生修改，通知刷新本地缓存
        flushRoleUserCache(createRoleUserIds, deleteRoleUserIds, finalOrganId);
    }

    /**
     * 跨机构清理角色用户缓存 + 通知刷新本地缓存
     *
     * @param createRoleUserIds 新增角色-用户关联id
     * @param deleteRoleUserIds 删除角色-用户关联id
     * @param organId           角色所属机构
     */
    private void flushRoleUserCache(Collection<Long> createRoleUserIds, Collection<Long> deleteRoleUserIds, Long organId) {
        Long currentOrganId = SecurityFrameworkUtils.getUserOrganId();
        // 角色菜单权限发生修改，通知刷新本地缓存
        if (CollUtil.isNotEmpty(createRoleUserIds) || CollUtil.isNotEmpty(deleteRoleUserIds)) {
            CompletableFuture.runAsync(() -> {
                // 当前操作人和所操作的目标菜单机构不同时，清空USER_ROLE_ID_LIST:*:用户id的缓存
                if (ObjectUtil.notEqual(organId, currentOrganId)) {
                    // 获取新增和删除用户id的并集
                    Collection<Long> delRoleUserIds = CollUtil.union(createRoleUserIds, deleteRoleUserIds);
                    delRoleUserIds.forEach(delRoleUserId -> {
                        String patternKey = String.format(String.valueOf(new StringBuffer(RedisKeyConstants.USER_ROLE_ID_LIST).append(":*:%s")), delRoleUserId);
                        Long batchDelNum = scanAndDelKeys(patternKey);
                        log.info("[batchAssignRoleUsers] 批量删除redis[{}]数量:{}", patternKey, batchDelNum);
                    });
                }
                stringRedisTemplate.convertAndSend(com.cf.imes.framework.redis.constants.RedisKeyConstants.PERMISSION_REFRESH, "");
            }).exceptionally(e -> {
                log.error("[batchAssignRoleUsers] redis 清空USER_ROLE_ID_LIST或message发送失败, topic:{}, 异常:{}", com.cf.imes.framework.redis.constants.RedisKeyConstants.PERMISSION_REFRESH, e);
                return null;
            });
        }
    }

    @Override
    public Set<Long> getListRoleUsers(Long roleId, Long organId) {
        // 超管查看时没有选择机构则查看本机构
        LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
        assert loginUser != null;
        if (loginUser.getIsSupAdmin() && organId == null) {
            organId = loginUser.getOrganId();
        }
        List<UserRoleDO> userRoleDOS = userRoleMapper.selectList(new LambdaQueryWrapperX<UserRoleDO>()
                .eqIfPresent(UserRoleDO::getOrganId, organId)
                .eq(UserRoleDO::getRoleId, roleId)
                .select(UserRoleDO::getUserId)
        );
        if(CollUtil.isEmpty(userRoleDOS)) {
            return new HashSet<>();
        }
        return userRoleDOS.stream().map(UserRoleDO::getUserId).collect(Collectors.toSet());

    }

    @Override
    @Cacheable(value = RedisKeyConstants.MENU_ROLE_ID_LIST, key = "#menuId")
    public Set<Long> getMenuRoleIdListByMenuIdFromCache(Long menuId) {
        return convertSet(roleMenuMapper.selectListByMenuId(menuId), RoleMenuDO::getRoleId);
    }

    @Override
    @Cacheable(value = RedisKeyConstants.MENU_ROLE_ID_LIST, key = "#menuId")
    @OrganIgnore
    public Set<Long> getMenuRoleIdListByMenuIdFromCache1(Long menuId) {
       //todo 这里应该过滤组织id为0的和自己的
        List<RoleMenuDO> roleMenuDOS = roleMenuMapper.selectList(new LambdaQueryWrapperX<RoleMenuDO>()
                //.in(RoleMenuDO::getOrganId, 0l, organId)
                .eq(RoleMenuDO::getMenuId, menuId)
        );
        return convertSet(roleMenuDOS, RoleMenuDO::getRoleId);
       // return convertSet(roleMenuMapper.selectListByMenuId(menuId), RoleMenuDO::getRoleId);
    }

    // ========== 用户-角色的相关方法  ==========

    @Override
    @DSTransactional // 多数据源，使用 @DSTransactional 保证本地事务，以及数据源的切换
    @CacheEvict(value = RedisKeyConstants.USER_ROLE_ID_LIST, allEntries = true)
    public void assignUserRole(Long userId, Set<Long> roleIds, Long organId) {
        if(Objects.equals(userId, SecurityFrameworkUtils.getLoginUserId())) {
            throw exception(ROLE_ME_ERROR);
        }
        // 获得用户拥有角色编号
        Set<Long> dbRoleIds = convertSet(userRoleMapper.selectListByUserId(userId),
                UserRoleDO::getRoleId);
        // 计算新增和删除的角色编号
        Set<Long> roleIdList = CollUtil.emptyIfNull(roleIds);
        Collection<Long> createRoleIds = CollUtil.subtract(roleIdList, dbRoleIds);
        Collection<Long> deleteMenuIds = CollUtil.subtract(dbRoleIds, roleIdList);
        // 执行新增和删除。对于已经授权的角色，不用做任何处理
        if (CollUtil.isNotEmpty(createRoleIds)) {
            userRoleMapper.insertBatch(CollectionUtils.convertList(createRoleIds, roleId -> {
                UserRoleDO entity = new UserRoleDO();
                entity.setUserId(userId);
                entity.setRoleId(roleId);
                entity.setOrganId(organId);
                return entity;
            }));
        }
        if (CollUtil.isNotEmpty(deleteMenuIds)) {
            userRoleMapper.deleteListByUserIdAndRoleIdIds(userId, deleteMenuIds);
        }

        // 角色菜单权限发生修改，通知刷新本地缓存
        if (CollUtil.isNotEmpty(createRoleIds) || CollUtil.isNotEmpty(deleteMenuIds)) {
            CompletableFuture.runAsync(() -> {
                // 移除redis中的缓存
                stringRedisTemplate.delete(String.format(String.valueOf(new StringBuffer(RedisKeyConstants.USER_ROLE_ID_LIST).append(":%s:%s")), organId, userId));
                stringRedisTemplate.convertAndSend(com.cf.imes.framework.redis.constants.RedisKeyConstants.PERMISSION_REFRESH, "");
            }).exceptionally(e -> {
                log.error("[assignUserRole] redis message发送失败, topic:{}, 异常:{}", com.cf.imes.framework.redis.constants.RedisKeyConstants.PERMISSION_REFRESH, e);
                return null;
            });
        }
    }

    @Override
    @CacheEvict(value = RedisKeyConstants.USER_ROLE_ID_LIST, key = "#userId")
    public void processUserDeleted(Long userId) {
        userRoleMapper.deleteListByUserId(userId);
    }

    @Override
    public Set<Long> getUserRoleIdListByUserId(Long userId) {
        List<UserRoleDO> userRoleDOS = userRoleMapper.selectListByUserId(userId);
        userRoleDOS.removeIf(ur -> {
            Long roleId = ur.getRoleId();
            RoleDO role = roleService.getRoleFromCache(roleId);
            return ObjectUtil.isNotNull(role) && CommonStatusEnum.DISABLE.getStatus().equals(role.getStatus());
        });
        return convertSet(userRoleDOS, UserRoleDO::getRoleId);
    }

    @Override
    @Cacheable(value = RedisKeyConstants.USER_ROLE_ID_LIST, key = "#userId")
    public Set<Long> getUserRoleIdListByUserIdFromCache(Long userId) {
        return getUserRoleIdListByUserId(userId);
    }

    @Override
    public Set<Long> getUserRoleIdListByRoleId(Collection<Long> roleIds) {
        return convertSet(userRoleMapper.selectListByRoleIds(roleIds), UserRoleDO::getUserId);
    }

    /**
     * 获得用户拥有的角色，并且这些角色是开启状态的
     *
     * @param userId 用户编号
     * @return 用户拥有的角色
     */
    @VisibleForTesting
    List<RoleDO> getEnableUserRoleListByUserIdFromCache(Long userId) {
        // 获得用户拥有的角色编号
        Set<Long> roleIds = getSelf().getUserRoleIdListByUserIdFromCache(userId);
        // 获得角色数组，并移除被禁用的
        List<RoleDO> roles = roleService.getRoleListFromCache(roleIds);
        roles.removeIf(role -> !CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus()));
        return roles;
    }

    // ========== 用户-部门的相关方法  ==========

    @Override
    public void assignRoleDataScope(Long roleId, Integer dataScope, Set<Long> dataScopeDeptIds) {
        roleService.updateRoleDataScope(roleId, dataScope, dataScopeDeptIds);
    }

    @Override
    public DeptDataPermissionRespDTO getDeptDataPermission(Long userId) {
        // 获得用户的角色
        List<RoleDO> roles = getEnableUserRoleListByUserIdFromCache(userId);

        // 如果角色为空，则只能查看自己
        DeptDataPermissionRespDTO result = new DeptDataPermissionRespDTO();
        if (CollUtil.isEmpty(roles)) {
            result.setSelf(true);
            return result;
        }

        // 获得用户的部门编号的缓存，通过 Guava 的 Suppliers 惰性求值，即有且仅有第一次发起 DB 的查询
        Supplier<Long> userDeptId = Suppliers.memoize(() -> userService.getUser(userId).getDeptId());
        // 遍历每个角色，计算
        for (RoleDO role : roles) {
            // 为空时，跳过
            if (role.getDataScope() == null) {
                continue;
            }
            // 情况一，ALL
            if (Objects.equals(role.getDataScope(), DataScopeEnum.ALL.getScope())) {
                result.setAll(true);
                continue;
            }
            // 情况二，DEPT_CUSTOM
            if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_CUSTOM.getScope())) {
                CollUtil.addAll(result.getDeptIds(), role.getDataScopeDeptIds());
                // 自定义可见部门时，保证可以看到自己所在的部门。否则，一些场景下可能会有问题。
                // 例如说，登录时，基于 t_user 的 username 查询会可能被 dept_id 过滤掉
                CollUtil.addAll(result.getDeptIds(), userDeptId.get());
                continue;
            }
            // 情况三，DEPT_ONLY
            if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_ONLY.getScope())) {
                CollectionUtils.addIfNotNull(result.getDeptIds(), userDeptId.get());
                continue;
            }
            // 情况四，DEPT_DEPT_AND_CHILD
            if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_AND_CHILD.getScope())) {
                CollUtil.addAll(result.getDeptIds(), deptService.getChildDeptIdListFromCache(userDeptId.get()));
                // 添加本身部门编号
                CollUtil.addAll(result.getDeptIds(), userDeptId.get());
                continue;
            }
            // 情况五，SELF
            if (Objects.equals(role.getDataScope(), DataScopeEnum.SELF.getScope())) {
                result.setSelf(true);
                continue;
            }
            // 未知情况，error log 即可
            log.error("[getDeptDataPermission][LoginUser({}) role({}) 无法处理]", userId, toJsonString(result));
        }
        return result;
    }


    /**
     * 获得自身的代理对象，解决 AOP 生效问题
     *
     * @return 自己
     */
    private PermissionServiceImpl getSelf() {
        return SpringUtil.getBean(getClass());
    }

    /**
     * 获取用户权限标识
     */
    @Override
    public Set<String> getUserPermissions(Long userId) {
        LoginUser loginUser = getLoginUser();
        if (ObjectUtil.isNull(loginUser)) {
            throw new ServiceException(GlobalErrorCodeConstants.UNAUTHORIZED);
        }
        // 1.2 获得角色列表
        Set<Long> roleIds = getUserRoleIdListByUserId(loginUser.getId());
        if (CollUtil.isEmpty(roleIds)) {
            return Collections.emptySet();
        }
        List<RoleDO> roles = roleService.getRoleList1(roleIds);
        roles.removeIf(role -> !CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus())); // 移除禁用的角色

        // 1.3 获得菜单列表
        Set<Long> menuIds = getRoleMenuListByRoleId2(convertSet(roles, RoleDO::getId), loginUser.getOrganId());
        List<MenuDO> menuList = menuService.getMenuList1(menuIds);
        menuList.removeIf(menu -> !CommonStatusEnum.ENABLE.getStatus().equals(menu.getStatus())); // 移除禁用的菜单

        return convertSet(menuList, MenuDO::getPermission);
    }

    @Override
    public Boolean hasPermission(Long userId, String permission) {
        Set<String> permissions = getUserPermissions(userId);
        return permissions.contains(permission);
    }

    @Override
    public void flushCacheWhenTenantPackageChange(List<Long> organIds) {
        if (CollUtil.isEmpty(organIds)) {
            return;
        }
        CompletableFuture.runAsync(() -> {
                    for (Long organId : organIds) {
                        // 清空机构下的用户的token缓存
                        List<AdminUserDO> organUsers = userService.getOrganUsers(organId);
                        if (CollUtil.isNotEmpty(organUsers)) {
                            List<Long> userIds = CollectionUtils.convertList(organUsers, AdminUserDO::getId);
                            scanAndCompareUserAndDelKeys(String.format(OAUTH2_ACCESS_TOKEN, "*"), userIds);
                        }
                    }
                })
                .thenRunAsync(() -> {
                    //通知刷新本地权限缓存
                    stringRedisTemplate.convertAndSend(com.cf.imes.framework.redis.constants.RedisKeyConstants.PERMISSION_REFRESH, "");
                })
                .exceptionally(e -> {
                    log.error("[PermissionService][flushCacheWhenTenantPackageChange]失败, 异常:{}", e);
                    return null;
                });
    }
}
