SysRoleService.java 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. package com.jtzx.crm.module.sys.service;
  2. import cc.uncarbon.framework.core.constant.HelioConstant;
  3. import cc.uncarbon.framework.core.context.UserContextHolder;
  4. import cc.uncarbon.framework.core.exception.BusinessException;
  5. import cc.uncarbon.framework.core.function.StreamFunction;
  6. import cc.uncarbon.framework.core.page.PageParam;
  7. import cc.uncarbon.framework.core.page.PageResult;
  8. import cn.hutool.core.bean.BeanUtil;
  9. import cn.hutool.core.collection.CollUtil;
  10. import cn.hutool.core.text.CharSequenceUtil;
  11. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  12. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  13. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  14. import com.jtzx.crm.module.sys.constant.SysConstant;
  15. import com.jtzx.crm.module.sys.entity.SysRoleEntity;
  16. import com.jtzx.crm.module.sys.entity.SysUserRoleRelationEntity;
  17. import com.jtzx.crm.module.sys.enums.SysErrorEnum;
  18. import com.jtzx.crm.module.sys.mapper.SysRoleMapper;
  19. import com.jtzx.crm.module.sys.mapper.SysUserRoleRelationMapper;
  20. import com.jtzx.crm.module.sys.model.interior.UserRoleContainer;
  21. import com.jtzx.crm.module.sys.model.request.AdminBindRoleMenuRelationDTO;
  22. import com.jtzx.crm.module.sys.model.request.AdminInsertOrUpdateSysRoleDTO;
  23. import com.jtzx.crm.module.sys.model.request.AdminListSysRoleDTO;
  24. import com.jtzx.crm.module.sys.model.response.SysRoleBO;
  25. import lombok.RequiredArgsConstructor;
  26. import lombok.extern.slf4j.Slf4j;
  27. import org.springframework.stereotype.Service;
  28. import org.springframework.transaction.annotation.Transactional;
  29. import java.util.*;
  30. import java.util.stream.Collectors;
  31. /**
  32. * 后台角色
  33. */
  34. @RequiredArgsConstructor
  35. @Service
  36. @Slf4j
  37. public class SysRoleService {
  38. private final SysRoleMapper sysRoleMapper;
  39. private final SysUserRoleRelationMapper sysUserRoleRelationMapper;
  40. private final SysRoleMenuRelationService sysRoleMenuRelationService;
  41. private final SysMenuService sysMenuService;
  42. /**
  43. * 后台管理-分页列表
  44. */
  45. public PageResult<SysRoleBO> adminList(PageParam pageParam, AdminListSysRoleDTO dto) {
  46. Page<SysRoleEntity> entityPage = sysRoleMapper.selectPage(
  47. new Page<>(pageParam.getPageNum(), pageParam.getPageSize()),
  48. new QueryWrapper<SysRoleEntity>()
  49. .lambda()
  50. // 名称
  51. .like(CharSequenceUtil.isNotBlank(dto.getTitle()), SysRoleEntity::getTitle, CharSequenceUtil.cleanBlank(dto.getTitle()))
  52. // 值
  53. .like(CharSequenceUtil.isNotBlank(dto.getValue()), SysRoleEntity::getValue, CharSequenceUtil.cleanBlank(dto.getValue()))
  54. // 排序
  55. .orderByDesc(SysRoleEntity::getCreatedAt)
  56. );
  57. return this.entityPage2BOPage(entityPage);
  58. }
  59. /**
  60. * 根据 ID 取详情
  61. *
  62. * @param id 主键ID
  63. * @return null or BO
  64. */
  65. public SysRoleBO getOneById(Long id) {
  66. return this.getOneById(id, false);
  67. }
  68. /**
  69. * 根据 ID 取详情
  70. *
  71. * @param id 主键ID
  72. * @param throwIfInvalidId 是否在 ID 无效时抛出异常
  73. * @return null or BO
  74. */
  75. public SysRoleBO getOneById(Long id, boolean throwIfInvalidId) throws BusinessException {
  76. SysRoleEntity entity = sysRoleMapper.selectById(id);
  77. if (throwIfInvalidId) {
  78. SysErrorEnum.INVALID_ID.assertNotNull(entity);
  79. }
  80. return this.entity2BO(entity, true);
  81. }
  82. /**
  83. * 后台管理-新增
  84. */
  85. @Transactional(rollbackFor = Exception.class)
  86. public void adminInsert(AdminInsertOrUpdateSysRoleDTO dto) {
  87. log.info("[后台管理-新增后台角色] >> 入参={}", dto);
  88. preInsertOrUpdateCheck(dto);
  89. this.checkExistence(dto);
  90. dto.setId(null);
  91. SysRoleEntity entity = new SysRoleEntity();
  92. BeanUtil.copyProperties(dto, entity);
  93. sysRoleMapper.insert(entity);
  94. }
  95. /**
  96. * 后台管理-编辑
  97. */
  98. @Transactional(rollbackFor = Exception.class)
  99. public void adminUpdate(AdminInsertOrUpdateSysRoleDTO dto) {
  100. log.info("[后台管理-编辑后台角色] >> 入参={}", dto);
  101. preInsertOrUpdateCheck(dto);
  102. this.checkExistence(dto);
  103. // 暂不检查该角色是否为当前用户关联的角色
  104. SysRoleEntity entity = new SysRoleEntity();
  105. BeanUtil.copyProperties(dto, entity);
  106. sysRoleMapper.updateById(entity);
  107. }
  108. /**
  109. * 后台管理-删除
  110. */
  111. @Transactional(rollbackFor = Exception.class)
  112. public void adminDelete(Collection<Long> ids) {
  113. log.info("[后台管理-删除后台角色] >> 入参={}", ids);
  114. preDeleteCheck(ids);
  115. sysRoleMapper.deleteByIds(ids);
  116. }
  117. /**
  118. * 后台管理-绑定角色与菜单关联关系
  119. *
  120. * @return 新菜单ID集合对应的权限名
  121. */
  122. @Transactional(rollbackFor = Exception.class)
  123. public Set<String> adminBindMenus(AdminBindRoleMenuRelationDTO dto) {
  124. preBindRoleMenuRelationCheck(dto);
  125. Set<String> newPermissions = sysMenuService.listPermissionsByMenuIds(dto.getMenuIds());
  126. sysRoleMenuRelationService.cleanAndBind(dto.getRoleId(), dto.getMenuIds());
  127. return newPermissions;
  128. }
  129. /**
  130. * 后台管理-下拉框数据
  131. */
  132. public List<SysRoleBO> adminSelectOptions() {
  133. List<SysRoleEntity> entityList = sysRoleMapper.selectList(
  134. new QueryWrapper<SysRoleEntity>()
  135. .lambda()
  136. // 只取特定字段
  137. .select(SysRoleEntity::getId, SysRoleEntity::getTitle)
  138. // 排序
  139. .orderByAsc(SysRoleEntity::getId)
  140. );
  141. // 无需填充菜单IDs
  142. return entityList2BOs(entityList, false);
  143. }
  144. /**
  145. * 后台管理-删除指定租户的特定角色
  146. * @param tenantIds 租户IDs,非主键ID,不能为空
  147. * @param roleValues 角色值集合,可以为空
  148. */
  149. @Transactional(rollbackFor = Exception.class)
  150. public void adminDeleteTenantRoles(Collection<Long> tenantIds, Collection<String> roleValues) {
  151. if (CollUtil.isEmpty(tenantIds)) {
  152. return;
  153. }
  154. sysRoleMapper.delete(
  155. new QueryWrapper<SysRoleEntity>()
  156. .lambda()
  157. // 租户ID
  158. .in(SysRoleEntity::getTenantId, tenantIds)
  159. // 值相符
  160. .in(CollUtil.isNotEmpty(roleValues), SysRoleEntity::getValue, roleValues)
  161. );
  162. }
  163. /**
  164. * 取用户ID拥有角色对应的 角色ID-角色名 map
  165. *
  166. * @param userId 用户ID
  167. * @return 失败返回空 map
  168. */
  169. public Map<Long, String> getRoleMapByUserId(Long userId) {
  170. List<SysUserRoleRelationEntity> relationEntityList =
  171. sysUserRoleRelationMapper.selectList(
  172. new LambdaQueryWrapper<>(SysUserRoleRelationEntity.class)
  173. .select(
  174. SysUserRoleRelationEntity::getRoleId,
  175. SysUserRoleRelationEntity::getRoleValue
  176. )
  177. .eq(SysUserRoleRelationEntity::getUserId, userId)
  178. );
  179. if (CollUtil.isEmpty(relationEntityList)) {
  180. return Collections.emptyMap();
  181. }
  182. // 根据角色Ids取 map
  183. return relationEntityList
  184. .stream()
  185. .collect(Collectors.toMap(SysUserRoleRelationEntity::getRoleId, SysUserRoleRelationEntity::getRoleValue, StreamFunction.ignoredThrowingMerger()));
  186. }
  187. /**
  188. * 取当前用户关联角色信息
  189. * 仅内部使用
  190. */
  191. protected UserRoleContainer getCurrentUserRoleContainer() {
  192. return getSpecifiedUserRoleContainer(UserContextHolder.getUserId());
  193. }
  194. /**
  195. * 取指定用户关联角色信息
  196. * 仅内部使用
  197. */
  198. protected UserRoleContainer getSpecifiedUserRoleContainer(Long specifiedUserId) {
  199. if (specifiedUserId == null) {
  200. throw new IllegalArgumentException("userId不能为空");
  201. }
  202. Set<Long> userRoleIds = sysUserRoleRelationMapper.selectList(
  203. new QueryWrapper<SysUserRoleRelationEntity>()
  204. .lambda()
  205. .select(SysUserRoleRelationEntity::getRoleId)
  206. .eq(SysUserRoleRelationEntity::getUserId, specifiedUserId)
  207. ).stream().map(SysUserRoleRelationEntity::getRoleId).collect(Collectors.toSet());
  208. List<SysRoleEntity> userRoles = null;
  209. if (CollUtil.isNotEmpty(userRoleIds)) {
  210. userRoles = sysRoleMapper.selectByIds(userRoleIds);
  211. }
  212. if (CollUtil.isEmpty(userRoles)) {
  213. userRoles = Collections.emptyList();
  214. }
  215. return new UserRoleContainer(userRoleIds, userRoles);
  216. }
  217. /*
  218. ----------------------------------------------------------------
  219. 私有方法 private methods
  220. ----------------------------------------------------------------
  221. */
  222. /**
  223. * 实体转 BO
  224. *
  225. * @param entity 实体
  226. * @param fillMenuIds 是否根据实体ID,查询关联菜单IDs并填充到BO
  227. * @return BO
  228. */
  229. private SysRoleBO entity2BO(SysRoleEntity entity, boolean fillMenuIds) {
  230. if (entity == null) {
  231. return null;
  232. }
  233. SysRoleBO bo = new SysRoleBO();
  234. BeanUtil.copyProperties(entity, bo);
  235. // 可以在此处为BO填充字段
  236. if (fillMenuIds) {
  237. bo.setMenuIds(sysRoleMenuRelationService.listMenuIdsByRoleIds(Collections.singleton(bo.getId())));
  238. }
  239. return bo;
  240. }
  241. /**
  242. * 实体 List 转 BO List
  243. *
  244. * @param entityList 实体 List
  245. * @param fillMenuIds 是否根据实体ID,查询关联菜单IDs并填充到BO
  246. * @return BO List
  247. */
  248. private List<SysRoleBO> entityList2BOs(List<SysRoleEntity> entityList, boolean fillMenuIds) {
  249. // 深拷贝
  250. List<SysRoleBO> ret = new ArrayList<>(entityList.size());
  251. entityList.forEach(
  252. entity -> ret.add(this.entity2BO(entity, fillMenuIds))
  253. );
  254. return ret;
  255. }
  256. /**
  257. * 实体分页转 BO 分页
  258. *
  259. * @param entityPage 实体分页
  260. * @return BO 分页
  261. */
  262. private PageResult<SysRoleBO> entityPage2BOPage(Page<SysRoleEntity> entityPage) {
  263. return new PageResult<SysRoleBO>()
  264. .setCurrent(entityPage.getCurrent())
  265. .setSize(entityPage.getSize())
  266. .setTotal(entityPage.getTotal())
  267. // 需填充菜单IDs
  268. .setRecords(this.entityList2BOs(entityPage.getRecords(), true));
  269. }
  270. /**
  271. * 检查是否已存在相同数据
  272. *
  273. * @param dto DTO
  274. */
  275. private void checkExistence(AdminInsertOrUpdateSysRoleDTO dto) {
  276. SysRoleEntity existingEntity = sysRoleMapper.selectOne(
  277. new QueryWrapper<SysRoleEntity>()
  278. .lambda()
  279. // 仅取主键ID
  280. .select(SysRoleEntity::getId)
  281. // 名称相同
  282. .eq(SysRoleEntity::getTitle, dto.getTitle())
  283. .last(HelioConstant.CRUD.SQL_LIMIT_1)
  284. );
  285. if (existingEntity != null && !existingEntity.getId().equals(dto.getId())) {
  286. throw new BusinessException(400, "已存在相同后台角色,请重新输入");
  287. }
  288. }
  289. /**
  290. * 新增/编辑后台角色信息前检查
  291. */
  292. private void preInsertOrUpdateCheck(AdminInsertOrUpdateSysRoleDTO dto) {
  293. if (SysConstant.SUPER_ADMIN_ROLE_VALUE.equalsIgnoreCase(dto.getValue())) {
  294. // 角色编码不能为SuperAdmin
  295. throw new BusinessException(SysErrorEnum.ROLE_VALUE_CANNOT_BE, SysConstant.SUPER_ADMIN_ROLE_VALUE);
  296. }
  297. boolean isUpdating = Objects.nonNull(dto.getId());
  298. if (isUpdating) {
  299. SysRoleEntity existingRole = sysRoleMapper.selectById(dto.getId());
  300. SysErrorEnum.INVALID_ID.assertNotNull(existingRole);
  301. if (existingRole.isAdmin()) {
  302. // 原来角色编码为SuperAdmin或Admin的,不能被改变
  303. throw new BusinessException(SysErrorEnum.ROLE_VALUE_CANNOT_BE, existingRole.getValue());
  304. }
  305. }
  306. }
  307. /**
  308. * 删除后台角色前检查
  309. */
  310. private void preDeleteCheck(Collection<Long> ids) {
  311. if (CollUtil.contains(ids, SysConstant.SUPER_ADMIN_ROLE_ID)) {
  312. throw new BusinessException(SysErrorEnum.CANNOT_DELETE_SUPER_ADMIN_ROLE);
  313. }
  314. List<SysRoleEntity> existingEntityList = sysRoleMapper.selectByIds(ids);
  315. for (SysRoleEntity item : existingEntityList) {
  316. if (item.isAdmin()) {
  317. throw new BusinessException(SysErrorEnum.CANNOT_OPERATE_THIS_USER);
  318. }
  319. }
  320. UserRoleContainer currentUser = getCurrentUserRoleContainer();
  321. if (CollUtil.containsAny(currentUser.getRelatedRoleIds(), ids)) {
  322. throw new BusinessException(SysErrorEnum.CANNOT_DELETE_SELF_ROLE);
  323. }
  324. }
  325. /**
  326. * 绑定后台角色与菜单关联关系前检查
  327. * 防止越权访问漏洞
  328. */
  329. private void preBindRoleMenuRelationCheck(AdminBindRoleMenuRelationDTO dto) {
  330. UserRoleContainer currentUser = getCurrentUserRoleContainer();
  331. if (SysConstant.SUPER_ADMIN_ROLE_ID.equals(dto.getRoleId())) {
  332. throw new BusinessException(SysErrorEnum.CANNOT_BIND_MENUS_FOR_SUPER_ADMIN_ROLE);
  333. }
  334. if (CollUtil.contains(currentUser.getRelatedRoleIds(), dto.getRoleId())) {
  335. // 不能动自身角色
  336. throw new BusinessException(SysErrorEnum.CANNOT_BIND_MENUS_FOR_SELF);
  337. }
  338. if (CollUtil.isNotEmpty(dto.getMenuIds()) && !currentUser.isAdmin()) {
  339. // 超级管理员之外的角色,都需要校验自身菜单范围是否满足输入值
  340. Set<Long> visibleMenuIds = sysRoleMenuRelationService.listMenuIdsByRoleIds(currentUser.getRelatedRoleIds());
  341. if (!CollUtil.containsAll(visibleMenuIds, dto.getMenuIds())) {
  342. // 可能存在超自身权限赋权
  343. throw new BusinessException(SysErrorEnum.BEYOND_AUTHORITY_BIND_MENUS);
  344. }
  345. }
  346. }
  347. }