package cn.ey.biz.module.system.service.permission;

import cn.ey.biz.common.enums.CommonStatusEnum;
import cn.ey.biz.common.util.collection.CollectionUtils;
import cn.ey.biz.common.util.collection.MapUtils;
import cn.ey.biz.common.util.collection.SetUtils;
import cn.ey.biz.common.util.i18n.I18nContextHolder;
import cn.ey.biz.common.util.json.JsonUtils;
import cn.ey.biz.framework.datapermission.core.annotation.DataPermission;
import cn.ey.biz.framework.security.config.SecurityProperties;
import cn.ey.biz.framework.tenant.core.aop.TenantIgnore;
import cn.ey.biz.module.system.api.permission.dto.*;
import cn.ey.biz.module.system.api.user.dto.AdminUserRespDTO;
import cn.ey.biz.module.system.controller.admin.auth.vo.AuthMenuRespVO;
import cn.ey.biz.module.system.convert.auth.AuthConvert;
import cn.ey.biz.module.system.convert.codeblock.CodeBlockConvert;
import cn.ey.biz.module.system.convert.permission.DataRoleDTOConvert;
import cn.ey.biz.module.system.convert.user.UserConvert;
import cn.ey.biz.module.system.dal.dataobject.codeblock.CodeBlockDO;
import cn.ey.biz.module.system.dal.dataobject.dept.DeptDO;
import cn.ey.biz.module.system.dal.dataobject.permission.*;
import cn.ey.biz.module.system.dal.dataobject.user.AdminUserDO;
import cn.ey.biz.module.system.dal.mapper.codeblock.CodeBlockMapper;
import cn.ey.biz.module.system.dal.mapper.permission.*;
import cn.ey.biz.module.system.enums.category.CategoryAppEnum;
import cn.ey.biz.module.system.enums.permission.DataScopeEnum;
import cn.ey.biz.module.system.enums.permission.MenuTypeEnum;
import cn.ey.biz.module.system.mq.producer.permission.PermissionProducer;
import cn.ey.biz.module.system.service.abac.AbacService;
import cn.ey.biz.module.system.service.dept.DeptService;
import cn.ey.biz.module.system.service.i18n.I18nService;
import cn.ey.biz.module.system.service.user.AdminUserService;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static cn.ey.biz.common.util.collection.CollectionUtils.convertSet;
import static cn.ey.biz.common.util.collection.CollectionUtils.getMaxValue;
import static cn.ey.biz.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static java.util.Collections.singleton;

/**
 * 权限 Service 实现类
 */
@Service
@Slf4j
public class PermissionServiceImpl implements PermissionService {

    /**
     * 定时执行 {@link #schedulePeriodicRefresh()} 的周期
     * 因为已经通过 Redis Pub/Sub 机制，所以频率不需要高
     */
    private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;

    /**
     * 角色编号与菜单编号的缓存映射
     * key：角色编号
     * value：菜单编号的数组
     * <p>
     * 这里声明 volatile 修饰的原因是，每次刷新时，直接修改指向
     */
    @Getter
    @Setter // 单元测试需要
    private volatile Multimap<Long, Long> roleMenuCache;
    /**
     * 菜单编号与角色编号的缓存映射
     * key：菜单编号
     * value：角色编号的数组
     * <p>
     * 这里声明 volatile 修饰的原因是，每次刷新时，直接修改指向
     */
    @Getter
    @Setter // 单元测试需要
    private volatile Multimap<Long, Long> menuRoleCache;
    /**
     * 缓存 RoleMenu 的最大更新时间，用于后续的增量轮询，判断是否有更新
     */
    @Getter
    private volatile Date roleMenuMaxUpdateTime;

    /**
     * 用户编号与角色编号的缓存映射
     * key：用户编号
     * value：角色编号的数组
     * <p>
     * 这里声明 volatile 修饰的原因是，每次刷新时，直接修改指向
     */
    @Getter
    @Setter // 单元测试需要
    private volatile Map<Long, Set<Long>> userRoleCache;

    @Getter
    @Setter // 单元测试需要
    private volatile Map<Long, Set<Long>> userDataRoleCache;

    /**
     * 缓存 UserRole 的最大更新时间，用于后续的增量轮询，判断是否有更新
     */
    @Getter
    private volatile Date userRoleMaxUpdateTime;

    @Resource
    private RoleMenuMapper roleMenuMapper;
    @Resource
    private RoleMenuBatchInsertMapper roleMenuBatchInsertMapper;
    @Resource
    private UserRoleMapper userRoleMapper;
    @Resource
    private RoleMapper roleMapper;
    @Resource
    private UserRoleBatchInsertMapper userRoleBatchInsertMapper;
    @Resource
    private RoleService roleService;
    @Resource
    private MenuService menuService;
    @Resource
    private DeptService deptService;
    @Resource
    private AdminUserService userService;
    @Resource
    private PermissionProducer permissionProducer;
    @Resource
    private SecurityProperties securityProperties;
    @Resource
    private DataRoleMapper dataRoleMapper;
    @Resource
    private UserDataRoleMapper userDataRoleMapper;
    @Resource
    private AbacService abacService;
    @Resource
    private RoleCodeblockMapper roleCodeblockMapper;
    @Resource
    private PermissionService permissionService;

    @Resource
    private CodeBlockMapper codeBlockMapper;


    @Resource
    @Lazy // 注入自己，所以延迟加载
    private PermissionService self;

    @Resource
    private I18nService i18nService;

    @Override
    @PostConstruct
    @TenantIgnore // 初始化缓存，无需租户过滤
    public void initLocalCache() {
        initUserRoleLocalCache();
        initUserDataRoleLocalCache();
        initRoleMenuLocalCache();
    }

    /**
     * 初始化 {@link #roleMenuCache} 和 {@link #menuRoleCache} 缓存
     */
    @VisibleForTesting
    void initRoleMenuLocalCache() {
        // 获取角色与菜单的关联列表，如果有更新
        List<RoleMenuDO> roleMenuList = loadRoleMenuIfUpdate(roleMenuMaxUpdateTime);
        if (CollUtil.isEmpty(roleMenuList)) {
            return;
        }

        // 初始化 roleMenuCache 和 menuRoleCache 缓存
        ImmutableMultimap.Builder<Long, Long> roleMenuCacheBuilder = ImmutableMultimap.builder();
        ImmutableMultimap.Builder<Long, Long> menuRoleCacheBuilder = ImmutableMultimap.builder();
        roleMenuList.forEach(roleMenuDO -> {
            roleMenuCacheBuilder.put(roleMenuDO.getRoleId(), roleMenuDO.getMenuId());
            menuRoleCacheBuilder.put(roleMenuDO.getMenuId(), roleMenuDO.getRoleId());
        });
        roleMenuCache = roleMenuCacheBuilder.build();
        menuRoleCache = menuRoleCacheBuilder.build();
        roleMenuMaxUpdateTime = getMaxValue(roleMenuList, RoleMenuDO::getUpdateTime);
        log.info("[initRoleMenuLocalCache][初始化角色与菜单的关联数量为 {}]", menuRoleCache.size());
    }

    /**
     * 初始化 {@link #userRoleCache} 缓存
     */
    @VisibleForTesting
    void initUserRoleLocalCache() {
        // 获取用户与角色的关联列表，如果有更新
        List<UserRoleDO> userRoleList = loadUserRoleIfUpdate(userRoleMaxUpdateTime);
        if (CollUtil.isEmpty(userRoleList)) {
            return;
        }

        // 初始化 userRoleCache 缓存
        ImmutableMultimap.Builder<Long, Long> userRoleCacheBuilder = ImmutableMultimap.builder();
        userRoleList.forEach(userRoleDO -> userRoleCacheBuilder.put(userRoleDO.getUserId(), userRoleDO.getRoleId()));
        userRoleCache = CollectionUtils.convertMultiMap2(userRoleList, UserRoleDO::getUserId, UserRoleDO::getRoleId);
        userRoleMaxUpdateTime = getMaxValue(userRoleList, UserRoleDO::getUpdateTime);
        log.info("[initUserRoleLocalCache]:{}", userRoleList.size());
    }

    void initUserDataRoleLocalCache() {
        // 获取用户与角色的关联列表，如果有更新
        List<UserDataRoleDO> userRoleList = loadUserDataRoleIfUpdate(userRoleMaxUpdateTime);
        if (CollUtil.isEmpty(userRoleList)) {
            return;
        }

        // 初始化 userDataRoleCache 缓存
        ImmutableMultimap.Builder<Long, Long> userRoleCacheBuilder = ImmutableMultimap.builder();
        userRoleList.forEach(userRoleDO -> userRoleCacheBuilder.put(userRoleDO.getUserId(), userRoleDO.getRoleId()));
        userDataRoleCache = CollectionUtils.convertMultiMap2(userRoleList, UserDataRoleDO::getUserId, UserDataRoleDO::getRoleId);
        userRoleMaxUpdateTime = getMaxValue(userRoleList, UserDataRoleDO::getUpdateTime);
        log.info("[initUserDataRoleLocalCache]:{}", userRoleList);
    }

    @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
    public void schedulePeriodicRefresh() {
        self.initLocalCache();
    }

    /**
     * 如果角色与菜单的关联发生变化，从数据库中获取最新的全量角色与菜单的关联。
     * 如果未发生变化，则返回空
     *
     * @param maxUpdateTime 当前角色与菜单的关联的最大更新时间
     * @return 角色与菜单的关联列表
     */
    protected List<RoleMenuDO> loadRoleMenuIfUpdate(Date maxUpdateTime) {
        // 第一步，判断是否要更新。
        if (maxUpdateTime == null) { // 如果更新时间为空，说明 DB 一定有新数据
            log.info("[loadRoleMenuIfUpdate][首次加载全量角色与菜单的关联]");
        } else { // 判断数据库中是否有更新的角色与菜单的关联
            if (roleMenuMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
                return null;
            }
            log.info("[loadRoleMenuIfUpdate][增量加载全量角色与菜单的关联]");
        }
        // 第二步，如果有更新，则从数据库加载所有角色与菜单的关联
        return roleMenuMapper.selectList();
    }

    /**
     * 如果用户与角色的关联发生变化，从数据库中获取最新的全量用户与角色的关联。
     * 如果未发生变化，则返回空
     *
     * @param maxUpdateTime 当前角色与菜单的关联的最大更新时间
     * @return 角色与菜单的关联列表
     */
    protected List<UserRoleDO> loadUserRoleIfUpdate(Date maxUpdateTime) {
        // 第一步，判断是否要更新。
        if (maxUpdateTime == null) { // 如果更新时间为空，说明 DB 一定有新数据
            log.info("[loadUserRoleIfUpdate][首次加载全量用户与角色的关联]");
        } else { // 判断数据库中是否有更新的用户与角色的关联
            if (userRoleMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
                return null;
            }
            log.info("[loadUserRoleIfUpdate][增量加载全量用户与角色的关联]");
        }
        // 第二步，如果有更新，则从数据库加载所有用户与角色的关联
        return userRoleMapper.selectList();
    }

    /**
     * 如果用户与角色的关联发生变化，从数据库中获取最新的全量用户与角色的关联。
     * 如果未发生变化，则返回空
     *
     * @param maxUpdateTime 当前角色与菜单的关联的最大更新时间
     * @return 角色与菜单的关联列表
     */
    protected List<UserDataRoleDO> loadUserDataRoleIfUpdate(Date maxUpdateTime) {
        // 第一步，判断是否要更新。
        if (maxUpdateTime == null) { // 如果更新时间为空，说明 DB 一定有新数据
            log.info("[loadUserRoleIfUpdate][首次加载全量用户与角色的关联]");
        } else { // 判断数据库中是否有更新的用户与角色的关联
            if (userDataRoleMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
                return null;
            }
            log.info("[loadUserRoleIfUpdate][增量加载全量用户与角色的关联]");
        }
        // 第二步，如果有更新，则从数据库加载所有用户与角色的关联
        return userDataRoleMapper.selectList();
    }

    @Override
    public List<MenuDO> getRoleMenuListFromCache(Collection<Long> roleIds, Collection<Integer> menuTypes,
                                                 Collection<Integer> menusStatuses, Set<Long> abacMenuIds) {

        List<RoleDO> roleList = roleService.getRolesFromCache(roleIds);

        List<MenuDO> appMenus = new ArrayList<>();
        if (roleService.hasAnySuperAdmin(roleList)) {
            // 超管只对后管系统起效
            appMenus = menuService.getMenuListFromCache(menuTypes, menusStatuses).stream().filter(menuDO ->
                    menuDO.getApp().equals(CategoryAppEnum.ADMIN.getApp())).collect(Collectors.toList());
        }

        // 获得角色拥有的菜单关联
        List<Long> menuIds = MapUtils.getList(roleMenuCache, roleIds);

        // ABAC
        menuIds.addAll(abacMenuIds);
        List<MenuDO> ret = menuService.getMenuListFromCache(menuIds, menuTypes, menusStatuses);
        ret.addAll(appMenus);
        return ret;
    }

    @Override
    public Set<Long> getUserRoleIdsFromCache(Long userId, Collection<Integer> roleStatuses) {
        Set<Long> cacheRoleIds = userRoleCache.get(userId);
        // 创建用户的时候没有分配角色，会存在空指针异常
        // Collections.emptySet()无法再添加item
        if (CollUtil.isEmpty(cacheRoleIds)) {
            return new HashSet<>();
        }
        Set<Long> roleIds = new HashSet<>(cacheRoleIds);
        // 过滤角色状态
        if (CollectionUtil.isNotEmpty(roleStatuses)) {
            roleIds.removeIf(roleId -> {
                RoleDO role = roleService.getRoleFromCache(roleId);
                return role == null || !roleStatuses.contains(role.getStatus());
            });
        }
        return roleIds;
    }

    @Override
    public Set<Long> getRoleMenuIds(Long roleId) {
        // 超管只对后管系统起效
        if (roleService.hasAnySuperAdmin(Collections.singleton(roleId))) {
            return convertSet(menuService.getMenus().stream().filter(menuDO ->
                            menuDO.getApp().equals(CategoryAppEnum.ADMIN.getApp()))
                    .collect(Collectors.toList()), MenuDO::getId);
        }
        // 如果是非管理员的情况下，获得拥有的菜单编号
        return convertSet(roleMenuMapper.selectListByRoleId(roleId), RoleMenuDO::getMenuId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void assignRoleMenu(Long roleId, Set<Long> menuIds) {
        // 获得角色拥有菜单编号
        Set<Long> dbMenuIds = convertSet(roleMenuMapper.selectListByRoleId(roleId),
                RoleMenuDO::getMenuId);
        // 计算新增和删除的菜单编号
        Collection<Long> createMenuIds = CollUtil.subtract(menuIds, dbMenuIds);
        Collection<Long> deleteMenuIds = CollUtil.subtract(dbMenuIds, menuIds);
        // 执行新增和删除。对于已经授权的菜单，不用做任何处理
        if (!CollectionUtil.isEmpty(createMenuIds)) {
            roleMenuBatchInsertMapper.saveBatch(CollectionUtils.convertList(createMenuIds, menuId -> {
                RoleMenuDO entity = new RoleMenuDO();
                entity.setRoleId(roleId);
                entity.setMenuId(menuId);
                return entity;
            }));
        }
        if (!CollectionUtil.isEmpty(deleteMenuIds)) {
            roleMenuMapper.deleteListByRoleIdAndMenuIds(roleId, deleteMenuIds);
        }
        // 发送刷新消息. 注意，需要事务提交后，在进行发送刷新消息。不然 db 还未提交，结果缓存先刷新了
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {

            @Override
            public void afterCommit() {
                permissionProducer.sendRoleMenuRefreshMessage();
            }

        });
    }

    @Override
    public Map<String, Set<Long>> getUserRoleIdListByUserId(Long userId) {
        Map<String, Set<Long>> ret = new HashMap<>();
        ret.put("func", convertSet(userRoleMapper.selectListByUserId(userId),
                UserRoleDO::getRoleId));
        ret.put("data", convertSet(userDataRoleMapper.selectList(new LambdaQueryWrapper<UserDataRoleDO>()
                .eq(UserDataRoleDO::getUserId, userId)), UserDataRoleDO::getRoleId));
        return ret;
    }

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

    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<AdminUserRespDTO> getUsersByRole(Long roleId) {
        // 获得角色拥有角色编号
        Set<Long> userIds = convertSet(userRoleMapper.selectListByRoleId(roleId),
                UserRoleDO::getUserId);

        List<AdminUserDO> adminUserDOS = userService.getUsers(userIds);

        return UserConvert.INSTANCE.convertList4(adminUserDOS);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<AdminUserRespDTO> getUsersByRoleCode(String roleCode) {
        RoleDO roleDO = roleMapper.selectByCode(roleCode);

        if (roleDO != null) {
            // 获得角色拥有角色编号
            Set<Long> userIds = convertSet(userRoleMapper.selectListByRoleId(roleDO.getId()),
                    UserRoleDO::getUserId);

            List<AdminUserDO> adminUserDOS = userService.getUsers(userIds);

            return UserConvert.INSTANCE.convertList4(adminUserDOS);
        }

        return new ArrayList<>();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<AdminUserRespDTO> getUsersByRoleName(String name) {
        RoleDO role = roleMapper.selectByName(name);

        if (role != null) {
            // 获得角色拥有角色编号
            Set<Long> userIds = convertSet(userRoleMapper.selectListByRoleId(role.getId()),
                    UserRoleDO::getUserId);

            List<AdminUserDO> adminUserDOS = userService.getUsers(userIds);

            return UserConvert.INSTANCE.convertList4(adminUserDOS);
        }

        return new ArrayList<>();
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void assignUserRole(Long userId, Set<Long> roleIds, Set<Long> dataRoleIds) {

        // 功能角色
        Set<Long> dbRoleIds = convertSet(userRoleMapper.selectListByUserId(userId),
                UserRoleDO::getRoleId);
        // 计算新增和删除的角色编号
        Collection<Long> createRoleIds = CollUtil.subtract(roleIds, dbRoleIds);
        Collection<Long> deleteRoleIds = CollUtil.subtract(dbRoleIds, roleIds);
        // 执行新增和删除。对于已经授权的角色，不用做任何处理
        if (!CollectionUtil.isEmpty(createRoleIds)) {
            userRoleBatchInsertMapper.saveBatch(CollectionUtils.convertList(createRoleIds, roleId -> {
                UserRoleDO entity = new UserRoleDO();
                entity.setUserId(userId);
                entity.setRoleId(roleId);
                return entity;
            }));
        }
        if (!CollectionUtil.isEmpty(deleteRoleIds)) {
            userRoleMapper.deleteListByUserIdAndRoleIdIds(userId, deleteRoleIds);
        }

        // 数据角色
        Set<Long> dbDataRoleIds = convertSet(userDataRoleMapper.selectList(new LambdaQueryWrapper<UserDataRoleDO>()
                .eq(UserDataRoleDO::getUserId, userId)), UserDataRoleDO::getRoleId);
        // 计算新增和删除的角色编号
        Collection<Long> createDataRoleIds = CollUtil.subtract(dataRoleIds, dbDataRoleIds);
        Collection<Long> deleteDataRoleIds = CollUtil.subtract(dbDataRoleIds, dataRoleIds);

        // 执行新增和删除。对于已经授权的角色，不用做任何处理
        if (!CollectionUtil.isEmpty(createDataRoleIds)) {
            userDataRoleMapper.insertBatch(CollectionUtils.convertList(createDataRoleIds, roleId -> {
                UserDataRoleDO entity = new UserDataRoleDO();
                entity.setUserId(userId);
                entity.setRoleId(roleId);
                return entity;
            }));
        }
        if (!CollectionUtil.isEmpty(deleteDataRoleIds)) {
            userDataRoleMapper.delete(new LambdaQueryWrapper<UserDataRoleDO>()
                    .eq(UserDataRoleDO::getUserId, userId).in(UserDataRoleDO::getRoleId, deleteDataRoleIds));
        }


        // 发送刷新消息. 注意，需要事务提交后，在进行发送刷新消息。不然 db 还未提交，结果缓存先刷新了
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {

            @Override
            public void afterCommit() {
                permissionProducer.sendUserRoleRefreshMessage();
            }

        });
    }

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

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void processRoleDeleted(Long roleId) {
        // 标记删除 UserRole
        userRoleMapper.deleteListByRoleId(roleId);
        // 标记删除 RoleMenu
        roleMenuMapper.deleteListByRoleId(roleId);
        // 发送刷新消息. 注意，需要事务提交后，在进行发送刷新消息。不然 db 还未提交，结果缓存先刷新了
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {

            @Override
            public void afterCommit() {
                permissionProducer.sendRoleMenuRefreshMessage();
                permissionProducer.sendUserRoleRefreshMessage();
            }

        });
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void processMenuDeleted(Long menuId) {
        roleMenuMapper.deleteListByMenuId(menuId);
        // 发送刷新消息. 注意，需要事务提交后，在进行发送刷新消息。不然 db 还未提交，结果缓存先刷新了
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {

            @Override
            public void afterCommit() {
                permissionProducer.sendRoleMenuRefreshMessage();
            }

        });
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void processUserDeleted(Long userId) {
        userRoleMapper.deleteListByUserId(userId);
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {

            @Override
            public void afterCommit() {
                permissionProducer.sendUserRoleRefreshMessage();
            }

        });
    }

    @Override
    public boolean hasAnyPermissions(Long userId, String app, String... permissions) {
        // 获得用户信息
        AdminUserDO user = userService.getUser(getLoginUserId());
        if (user == null) {
            return false;
        }
        log.info("[hasAnyPermissions app] 用户 {} APP {}", user.getUsername(), app);
        Set<Long> menuIds = new HashSet<>();

        Set<Long> roleIds = permissionService.getUserRoleIdsFromCache(user.getId(), singleton(CommonStatusEnum.ENABLE.getStatus()));
        // 第一次调用 获取的新角色，作为第二次调用的 判断条件
        abacService.checkAbac(user, roleIds, menuIds, null, app);
        abacService.checkAbac(user, roleIds, menuIds, null, app);

        if (CollUtil.isEmpty(roleIds) && CollUtil.isEmpty(menuIds)) {
            return false;
        }

        log.info("[hasAnyPermissions app] 用户 {} 拥有角色 {} 拥有菜单 {}", user.getUsername(), roleIds, menuIds);

        // 判断是否是超管。如果是，当然符合条件
        if (roleService.hasAnySuperAdmin(roleIds)) {
            if (CategoryAppEnum.ADMIN.getApp().equals(app)) {
                log.info("[hasAnyPermissions app] 用户 {} 通过APP匹配成功", user.getUsername());
                return true;
            }
        }

        // 遍历权限，判断是否有一个满足
        return Arrays.stream(permissions).anyMatch(permission -> {
            List<MenuDO> menuList = menuService.getMenuListByPermissionFromCache(permission);
            // 采用严格模式，如果权限找不到对应的 Menu 的话，认为
            if (CollUtil.isEmpty(menuList)) {
                log.info("[hasAnyPermissions][未找到权限:{},对应的菜单]", permission);
                return false;
            }
            // 获得是否拥有该权限，任意一个
            return menuList.stream().anyMatch(menu -> {
                Collection<Long> needRoles = menuRoleCache.get(menu.getId());
                return CollUtil.containsAny(roleIds, needRoles)
                        || menuIds.contains(menu.getId());
            });
        });
    }

    @Override
    public boolean hasAnyPermissions(Long userId, String... permissions) {
        // 如果为空，说明已经有权限
        if (ArrayUtil.isEmpty(permissions)) {
            log.info("[hasAnyPermissions] 权限为空.");
            return true;
        }
        // 判断是否权限校验
        if (!securityProperties.getAuthEnable()) {
            log.info("[hasAnyPermissions] 权限校验未开启.");
            return true;
        }
        // 获得当前登录的角色。如果为空，说明没有权限
        Set<Long> roleIds = getUserRoleIdsFromCache(userId, singleton(CommonStatusEnum.ENABLE.getStatus()));
        if (CollUtil.isEmpty(roleIds)) {
            log.info("[hasAnyPermissions] 用户ID:{} 角色为空.", userId);
            return false;
        }
        // 超管只对后管系统起效
        if (roleService.hasAnySuperAdmin(roleIds)) {
            if (Arrays.stream(permissions).anyMatch(permission -> {
                List<MenuDO> menuList = menuService.getMenuListByPermissionFromCache(permission);
                // 采用严格模式，如果权限找不到对应的 Menu 的话，认为
                if (CollUtil.isEmpty(menuList)) {
                    log.info("[hasAnyPermissions][未找到权限:{},对应的菜单]", permission);
                    return false;
                }
                // 获得是否拥有该权限，任意一个
                return menuList.stream().anyMatch(menu -> menu.getApp().equals(CategoryAppEnum.ADMIN.getApp()));
            })) {
                log.info("[hasAnyPermissions] 用户ID:{} 为后台超管,拥有 {} 权限中的一个.", userId, permissions);
                return true;
            }
        }

        // 遍历权限，判断是否有一个满足
        return Arrays.stream(permissions).anyMatch(permission -> {
            List<MenuDO> menuList = menuService.getMenuListByPermissionFromCache(permission);
            // 采用严格模式，如果权限找不到对应的 Menu 的话，认为
            if (CollUtil.isEmpty(menuList)) {
                log.info("[hasAnyPermissions][未找到权限:{},对应的菜单]", permission);
                return false;
            }
            // 获得是否拥有该权限，任一一个
            return menuList.stream().anyMatch(menu -> CollUtil.containsAny(roleIds,
                    menuRoleCache.get(menu.getId())));
        });
    }

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

        // 获得当前登录的角色。如果为空，说明没有权限
        Set<Long> roleIds = getUserRoleIdsFromCache(userId, singleton(CommonStatusEnum.ENABLE.getStatus()));
        if (CollUtil.isEmpty(roleIds)) {
            return false;
        }

        List<RoleDO> roleDOS = roleService.getRolesFromCache(roleIds);
        // 超管只对后管系统起效
        if (roleService.hasAnySuperAdmin(roleIds)) {
            return roleService.getRolesFromCacheByCode(Arrays.asList(roles)).stream()
                    .anyMatch(roleDO -> roleDO.getApp().equals(CategoryAppEnum.ADMIN.getApp()));
        }

        Set<String> roleCodes = convertSet(roleDOS, RoleDO::getCode);
        return CollUtil.containsAny(roleCodes, Sets.newHashSet(roles));
    }


    @Override
    @DataPermission(enable = false) // 关闭数据权限，不然就会出现递归获取数据权限的问题
    @TenantIgnore // 忽略多租户的自动过滤。如果不忽略，会导致添加租户时，因为切换租户，导致获取不到 User。即使忽略，本身该方法不存在跨租户的操作，不会存在问题。
    public DeptDataPermissionRespDTO getDeptDataPermission(Long userId) {
        // 获得用户的角色
        Set<Long> roleIds = getUserRoleIdsFromCache(userId, singleton(CommonStatusEnum.ENABLE.getStatus()));
        // 如果角色为空，则只能查看自己
        DeptDataPermissionRespDTO result = new DeptDataPermissionRespDTO();
        if (CollUtil.isEmpty(roleIds)) {
            result.setSelf(true);
            return result;
        }
        List<RoleDO> roles = roleService.getRolesFromCache(roleIds);

        // 获得用户的部门编号的缓存，通过 Guava 的 Suppliers 惰性求值，即有且仅有第一次发起 DB 的查询
        Supplier<Long> userDeptIdCache = 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(), userDeptIdCache.get());
                continue;
            }
            // 情况三，DEPT_ONLY
            if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_ONLY.getScope())) {
                CollectionUtils.addIfNotNull(result.getDeptIds(), userDeptIdCache.get());
                continue;
            }
            // 情况四，DEPT_DEPT_AND_CHILD
            if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_AND_CHILD.getScope())) {
                List<DeptDO> depts = deptService.getDeptsByParentIdFromCache(userDeptIdCache.get(), true);
                CollUtil.addAll(result.getDeptIds(), CollectionUtils.convertList(depts, DeptDO::getId));
                // 添加本身部门编号
                CollUtil.addAll(result.getDeptIds(), userDeptIdCache.get());
                continue;
            }
            // 情况五，SELF
            if (Objects.equals(role.getDataScope(), DataScopeEnum.SELF.getScope())) {
                result.setSelf(true);
                continue;
            }
            // 未知情况，error log 即可
            log.error("[getDeptDataPermission][LoginUser({}) role({}) 无法处理]", userId, JsonUtils.toJsonString(result));
        }
        return result;
    }

    @Override
    public CodeBlockDataPermissionRespDTO getCodeBlockDataPermission(Long userId) {
        log.info("getCodeBlockDataPermission 实现方法 入参userId:{}.", userId);
        List<UserDataRoleDO> userDataRoleDOs = userDataRoleMapper.selectList(new LambdaQueryWrapper<UserDataRoleDO>()
                .eq(UserDataRoleDO::getUserId, userId));

        CodeBlockDataPermissionRespDTO dto = new CodeBlockDataPermissionRespDTO();
        if (CollectionUtil.isEmpty(userDataRoleDOs)) {
            log.info("用户没有关联数据角色,userId:{}", userId);
            dto.setSelf(true);
            log.info("userId:{}, 返回code block的数据权限: {}", userId, dto);
            return dto;
        }

        List<DataRoleDO> dataRoleDOs = dataRoleMapper.selectList(new LambdaQueryWrapper<DataRoleDO>()
                .in(DataRoleDO::getId, userDataRoleDOs.stream().map(UserDataRoleDO::getRoleId).collect(Collectors.toList())));

        List<CodeBlockConfigDataRespDTO> configList = new ArrayList<>();
        List<DataRoleWithCodeBlockDTO> dataRoleWithCodeBlock = new ArrayList<>();
        List<String> codeBlocks = new ArrayList<>();

        for (DataRoleDO dataRoleDO : dataRoleDOs) {
            if (StringUtils.isNotBlank(dataRoleDO.getConfig())) {
                CodeBlockDataRespDTO dataRespDTO = JsonUtils.parseObject(dataRoleDO.getConfig(), CodeBlockDataRespDTO.class);
                CodeBlockConfigDataRespDTO configDataRespDTO = DataRoleDTOConvert.INSTANCE.configConvert(dataRespDTO);
                configList.add(configDataRespDTO);
            }
            List<RoleCodeblockDO> roleCodeBlockDOs = roleCodeblockMapper
                    .selectList(new LambdaQueryWrapper<RoleCodeblockDO>()
                            .eq(RoleCodeblockDO::getRoleId, dataRoleDO.getId()));
            List<String> ids = roleCodeBlockDOs.stream().map(RoleCodeblockDO::getCodeBlock).distinct().collect(Collectors.toList());
            codeBlocks.addAll(ids);

            DataRoleWithCodeBlockDTO dataRoleWithCodeBlockDTO = new DataRoleWithCodeBlockDTO();
            dataRoleWithCodeBlockDTO.setCodeBlocks(new HashSet<>(ids));

            List<CodeBlockListDataRespDTO> codeBlockListDataRespDTO=getCodeBlockListDataRespDTO(dataRoleWithCodeBlockDTO.getCodeBlocks());

            dataRoleWithCodeBlockDTO.setDataRole(DataRoleDTOConvert.INSTANCE.convert(dataRoleDO));
            dataRoleWithCodeBlockDTO.setCodeBlockList(codeBlockListDataRespDTO);
            dataRoleWithCodeBlock.add(dataRoleWithCodeBlockDTO);
        }

        if (dataRoleDOs.isEmpty()) {
            dto.setSelf(true);
        }
        dto.setConfig(configList);
        Set<String> codeBlocksSet = new HashSet<>(codeBlocks);
        dto.setCodeBlocks(codeBlocksSet);
        dto.setDataRoleWithCodeBlock(dataRoleWithCodeBlock);
        log.info("userId:{}, 返回code block的数据权限: {}", userId, dto);
        return dto;
    }

    @Override
    public List<String> getAppsByUser() {
        Set<Long> roleIds = getUserRoleIdsFromCache(getLoginUserId(), singleton(CommonStatusEnum.ENABLE.getStatus()));

        // M: 管理员类型
        if (ArrayUtil.isEmpty(roleIds)) {
            return new ArrayList<>();
        } else {
            List<RoleDO> roles = roleService.getRolesFromCache(roleIds);
            return roles.stream().map(RoleDO::getApp).filter(Objects::nonNull)
                    .collect(Collectors.toList());
        }
    }

    @Override
    public List<AuthMenuRespVO> getAuthMenuRespVOList(String app, Set<Long> roleIds, Set<Long> menuIds) {
        // 获得用户拥有的菜单列表
        List<MenuDO> menuList = this.getRoleMenuListFromCache(roleIds,
                SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType()), // 只要目录和菜单类型
                singleton(CommonStatusEnum.ENABLE.getStatus()), menuIds); // 只要开启的
        // 多语言转化
        menuList.forEach(item -> item.setName(i18nService.getLabel(String.valueOf(item.getId()),
                I18nContextHolder.getLangId(),
                "menu")));
        // 转换成 Tree 结构返回
        List<MenuDO> appList = menuList.stream().filter(d -> app.equals(d.getApp())).collect(Collectors.toList());
        return AuthConvert.INSTANCE.buildMenuTree(appList);
    }

    @Override
    public List<AdminUserRespDTO> getUsersByRoleCodes(List<String> roleCodes) {
        LambdaQueryWrapper<RoleDO> wrappers = Wrappers.lambdaQuery(RoleDO.class)
                .in(RoleDO::getCode, roleCodes);
        List<RoleDO> roleDOList = roleMapper.selectList(wrappers);

        if (CollectionUtil.isNotEmpty(roleDOList)) {
            List<Long> roIds = roleDOList.stream().map(RoleDO::getId).collect(Collectors.toList());
            LambdaQueryWrapper<UserRoleDO> wrappersUs = Wrappers.lambdaQuery(UserRoleDO.class)
                    .in(UserRoleDO::getRoleId, roIds);
            List<UserRoleDO> userDataRoleDOLi = userRoleMapper.selectList(wrappersUs);

            Set<Long> userIds = convertSet(userDataRoleDOLi, UserRoleDO::getUserId);
            List<AdminUserDO> adminUserDOS = userService.getUsers(userIds);
            return UserConvert.INSTANCE.convertList4(adminUserDOS);
        }
        return new ArrayList<>();
    }

    public List<CodeBlockListDataRespDTO> getCodeBlockListDataRespDTO(Set<String> ids){
        List<CodeBlockDO>  codeBlockDOList=codeBlockMapper.selectList(new LambdaQueryWrapper<CodeBlockDO>()
                .in(CodeBlockDO::getCodeBlockCode,ids));
        List<CodeBlockListDataRespDTO>  codeBlockListDataRespDTO=CodeBlockConvert.INSTANCE.convertDTOList(codeBlockDOList);
        return codeBlockListDataRespDTO;
    }

}
