SysUserService.java 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. package cc.uncarbon.module.sys.service;
  2. import cc.uncarbon.framework.core.context.TenantContext;
  3. import cc.uncarbon.framework.core.context.TenantContextHolder;
  4. import cc.uncarbon.framework.core.context.UserContextHolder;
  5. import cc.uncarbon.framework.core.exception.BusinessException;
  6. import cc.uncarbon.framework.core.page.PageParam;
  7. import cc.uncarbon.framework.core.page.PageResult;
  8. import cc.uncarbon.framework.core.props.HelioProperties;
  9. import cc.uncarbon.framework.crud.service.impl.HelioBaseServiceImpl;
  10. import cc.uncarbon.module.sys.annotation.SysLog;
  11. import cc.uncarbon.module.sys.constant.SysConstant;
  12. import cc.uncarbon.module.sys.entity.SysTenantEntity;
  13. import cc.uncarbon.module.sys.entity.SysUserEntity;
  14. import cc.uncarbon.module.sys.enums.GenericStatusEnum;
  15. import cc.uncarbon.module.sys.enums.SysErrorEnum;
  16. import cc.uncarbon.module.sys.enums.SysUserStatusEnum;
  17. import cc.uncarbon.module.sys.mapper.SysUserMapper;
  18. import cc.uncarbon.module.sys.model.request.*;
  19. import cc.uncarbon.module.sys.model.response.SysDeptBO;
  20. import cc.uncarbon.module.sys.model.response.SysUserBO;
  21. import cc.uncarbon.module.sys.model.response.SysUserLoginBO;
  22. import cc.uncarbon.module.sys.model.response.VbenAdminUserInfoVO;
  23. import cc.uncarbon.module.sys.util.PwdUtil;
  24. import cn.hutool.core.bean.BeanUtil;
  25. import cn.hutool.core.collection.CollUtil;
  26. import cn.hutool.core.date.LocalDateTimeUtil;
  27. import cn.hutool.core.util.IdUtil;
  28. import cn.hutool.core.util.ObjectUtil;
  29. import cn.hutool.core.util.StrUtil;
  30. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  31. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  32. import lombok.RequiredArgsConstructor;
  33. import lombok.extern.slf4j.Slf4j;
  34. import org.springframework.stereotype.Service;
  35. import org.springframework.transaction.annotation.Transactional;
  36. import javax.annotation.PostConstruct;
  37. import java.time.LocalDateTime;
  38. import java.util.*;
  39. import java.util.stream.Collectors;
  40. /**
  41. * 后台用户
  42. *
  43. * @author Uncarbon
  44. */
  45. @Slf4j
  46. @Service
  47. @RequiredArgsConstructor
  48. public class SysUserService extends HelioBaseServiceImpl<SysUserMapper, SysUserEntity> {
  49. private final SysRoleService sysRoleService;
  50. private final SysDeptService sysDeptService;
  51. private final SysMenuService sysMenuService;
  52. private final SysTenantService sysTenantService;
  53. private final SysUserDeptRelationService sysUserDeptRelationService;
  54. private final SysUserRoleRelationService sysUserRoleRelationService;
  55. private final HelioProperties helioProperties;
  56. private boolean isTenantEnabled;
  57. @PostConstruct
  58. public void postConstruct() {
  59. this.isTenantEnabled = helioProperties.getTenant().getEnabled();
  60. }
  61. /**
  62. * 后台管理-分页列表
  63. */
  64. public PageResult<SysUserBO> adminList(PageParam pageParam, AdminListSysUserDTO dto) {
  65. Page<SysUserEntity> entityPage = this.page(
  66. new Page<>(pageParam.getPageNum(), pageParam.getPageSize()),
  67. new QueryWrapper<SysUserEntity>()
  68. .lambda()
  69. // 手机号
  70. .like(StrUtil.isNotBlank(dto.getPhoneNo()), SysUserEntity::getPhoneNo, StrUtil.cleanBlank(dto.getPhoneNo()))
  71. // 排序
  72. .orderByDesc(SysUserEntity::getCreatedAt)
  73. );
  74. return this.entityPage2BOPage(entityPage);
  75. }
  76. /**
  77. * 根据 ID 取详情
  78. *
  79. * @param id 主键ID
  80. * @return null or BO
  81. */
  82. public SysUserBO getOneById(Long id) {
  83. return this.getOneById(id, false);
  84. }
  85. /**
  86. * 根据 ID 取详情
  87. *
  88. * @param id 主键ID
  89. * @param throwIfInvalidId 是否在 ID 无效时抛出异常
  90. * @return null or BO
  91. */
  92. public SysUserBO getOneById(Long id, boolean throwIfInvalidId) throws BusinessException {
  93. SysUserEntity entity = this.getById(id);
  94. if (throwIfInvalidId) {
  95. SysErrorEnum.INVALID_ID.assertNotNull(entity);
  96. }
  97. return this.entity2BO(entity);
  98. }
  99. /**
  100. * 后台管理-新增
  101. *
  102. * @return 主键ID
  103. */
  104. @SysLog(value = "新增后台用户")
  105. @Transactional(rollbackFor = Exception.class)
  106. public Long adminInsert(AdminInsertOrUpdateSysUserDTO dto) {
  107. log.info("[后台管理-新增后台用户] >> 入参={}", dto);
  108. this.checkExistence(dto);
  109. dto.setId(null);
  110. SysUserEntity entity = new SysUserEntity();
  111. BeanUtil.copyProperties(dto, entity);
  112. String salt = IdUtil.randomUUID();
  113. entity
  114. .setSalt(salt)
  115. .setPin(dto.getUsername())
  116. .setPwd(PwdUtil.encrypt(dto.getPasswordOfNewUser(), salt))
  117. ;
  118. this.save(entity);
  119. sysUserDeptRelationService.cleanAndBind(entity.getId(), dto.getDeptId());
  120. return entity.getId();
  121. }
  122. /**
  123. * 后台管理-编辑
  124. */
  125. @SysLog(value = "编辑后台用户")
  126. @Transactional(rollbackFor = Exception.class)
  127. public void adminUpdate(AdminInsertOrUpdateSysUserDTO dto) {
  128. log.info("[后台管理-编辑后台用户] >> 入参={}", dto);
  129. this.checkExistence(dto);
  130. SysUserEntity updateEntity = new SysUserEntity();
  131. BeanUtil.copyProperties(dto, updateEntity);
  132. // 手动处理异名字段
  133. updateEntity.setPin(dto.getUsername());
  134. sysUserDeptRelationService.cleanAndBind(dto.getId(), dto.getDeptId());
  135. this.updateById(updateEntity);
  136. }
  137. /**
  138. * 后台管理-删除
  139. */
  140. @SysLog(value = "删除后台用户")
  141. @Transactional(rollbackFor = Exception.class)
  142. public void adminDelete(Collection<Long> ids) {
  143. log.info("[后台管理-删除后台用户] >> 入参={}", ids);
  144. this.removeByIds(ids);
  145. }
  146. /**
  147. * 后台管理-登录
  148. */
  149. @SysLog(value = SysConstant.SysLogOperation.SYS_USER_LOGIN)
  150. public SysUserLoginBO adminLogin(SysUserLoginDTO dto) {
  151. /*
  152. 如果启用了多租户功能,并且前端指定了租户ID,则先查库确认租户是否有效
  153. 注意:数据源级多租户,登录前【必须】主动指定租户ID,如: dto.setTenantId(101L);
  154. */
  155. // ConcurrentHashMap 的 value 不能为 null,还是 new 一个吧
  156. TenantContext tenantContext = new TenantContext();
  157. if (isTenantEnabled && ObjectUtil.isNotNull(dto.getTenantId())) {
  158. tenantContext = this.checkAndGetTenantContext(dto.getTenantId());
  159. // 验证通过,将所属租户写入租户上下文,使得 SQL 拦截器可以正确执行
  160. TenantContextHolder.setTenantContext(tenantContext);
  161. }
  162. // 不要直接提示“账号不存在”或“密码不正确”,避免撞库攻击
  163. SysUserEntity sysUserEntity = this.getUserByPin(dto.getUsername());
  164. if (sysUserEntity == null) {
  165. throw new BusinessException(SysErrorEnum.INCORRECT_PIN_OR_PWD);
  166. }
  167. if (!PwdUtil.encrypt(dto.getPassword(), sysUserEntity.getSalt()).equals(sysUserEntity.getPwd())) {
  168. throw new BusinessException(SysErrorEnum.INCORRECT_PIN_OR_PWD);
  169. }
  170. if (SysUserStatusEnum.BANNED == sysUserEntity.getStatus()) {
  171. throw new BusinessException(SysErrorEnum.BANNED_USER);
  172. }
  173. /*
  174. 以上为有效性校验, 进入实际业务逻辑
  175. ---------------------------------------------------
  176. */
  177. if (isTenantEnabled && ObjectUtil.isNotNull(sysUserEntity.getTenantId())) {
  178. // 二次赋值,以防万一(也许前端没有传租户ID,上方的 if 块并没有执行)
  179. tenantContext = this.checkAndGetTenantContext(sysUserEntity.getTenantId());
  180. TenantContextHolder.setTenantContext(tenantContext);
  181. }
  182. this.updateLastLoginAt(sysUserEntity.getId(), LocalDateTimeUtil.now());
  183. // 取账号完整信息
  184. SysUserBO sysUserBO = this.entity2BO(sysUserEntity);
  185. Map<Long, String> roleMap = sysRoleService.getRoleMapByUserId(sysUserBO.getId());
  186. Map<Long, Set<String>> roleIdPermissionMap = sysMenuService.getRoleIdPermissionMap(roleMap.keySet());
  187. // 包装返回体;有的字段类型不一致, 单独转换
  188. SysUserLoginBO ret = new SysUserLoginBO();
  189. BeanUtil.copyProperties(sysUserBO, ret);
  190. Set<String> permissions = roleIdPermissionMap.values().stream().flatMap(Collection::stream).collect(Collectors.toSet());
  191. ret
  192. .setRoleIds(new HashSet<>(roleMap.keySet()))
  193. .setRoles(new ArrayList<>(roleMap.values()))
  194. .setPermissions(permissions)
  195. .setRoleIdPermissionMap(roleIdPermissionMap)
  196. .setTenantContext(tenantContext)
  197. ;
  198. return ret;
  199. }
  200. /**
  201. * 后台管理-取当前用户信息
  202. */
  203. public VbenAdminUserInfoVO adminGetCurrentUserInfo() {
  204. SysUserBO sysUserBO = this.getOneById(UserContextHolder.getUserId(), true);
  205. return VbenAdminUserInfoVO.builder()
  206. .username(sysUserBO.getUsername())
  207. .nickname(sysUserBO.getNickname())
  208. .lastLoginAt(sysUserBO.getLastLoginAt())
  209. .build();
  210. }
  211. /**
  212. * 后台管理-重置某用户密码
  213. */
  214. @SysLog(value = "重置某用户密码")
  215. public void adminResetUserPassword(AdminResetSysUserPasswordDTO dto) {
  216. SysUserEntity sysUserEntity = this.getById(dto.getUserId());
  217. SysUserEntity templateEntity = new SysUserEntity();
  218. templateEntity
  219. .setPwd(PwdUtil.encrypt(dto.getRandomPassword(), sysUserEntity.getSalt()))
  220. .setId(dto.getUserId())
  221. ;
  222. this.updateById(templateEntity);
  223. }
  224. /**
  225. * 后台管理-修改当前用户密码
  226. */
  227. @SysLog(value = "修改当前用户密码")
  228. public void adminUpdateCurrentUserPassword(AdminUpdateCurrentSysUserPasswordDTO dto) {
  229. SysUserEntity sysUserEntity = this.getById(UserContextHolder.getUserId());
  230. if (sysUserEntity == null || !sysUserEntity.getPwd().equals(PwdUtil.encrypt(dto.getOldPassword(), sysUserEntity.getSalt()))) {
  231. throw new BusinessException(SysErrorEnum.INCORRECT_OLD_PASSWORD);
  232. }
  233. sysUserEntity
  234. .setPwd(PwdUtil.encrypt(dto.getConfirmNewPassword(), sysUserEntity.getSalt()))
  235. .setId(UserContextHolder.getUserId())
  236. ;
  237. this.updateById(sysUserEntity);
  238. }
  239. /**
  240. * 后台管理-绑定用户与角色关联关系
  241. */
  242. public void adminBindRoles(AdminBindUserRoleRelationDTO dto) {
  243. sysUserRoleRelationService.cleanAndBind(dto.getUserId(), dto.getRoleIds());
  244. }
  245. /**
  246. * 根据用户账号查询
  247. */
  248. public SysUserEntity getUserByPin(String pin) {
  249. return this.getBaseMapper().getUserByPin(pin);
  250. }
  251. /**
  252. * 后台管理 - 取指定用户关联角色ID
  253. * @param userId 用户ID
  254. * @return 角色Ids
  255. */
  256. public Set<Long> listRelatedRoleIds(Long userId) {
  257. if (ObjectUtil.isNull(userId)) {
  258. return Collections.emptySet();
  259. }
  260. return sysRoleService.getRoleMapByUserId(userId).keySet();
  261. }
  262. /*
  263. ----------------------------------------------------------------
  264. 私有方法 private methods
  265. ----------------------------------------------------------------
  266. */
  267. /**
  268. * 实体转 BO
  269. *
  270. * @param entity 实体
  271. * @return BO
  272. */
  273. private SysUserBO entity2BO(SysUserEntity entity) {
  274. if (entity == null) {
  275. return null;
  276. }
  277. SysUserBO bo = new SysUserBO();
  278. BeanUtil.copyProperties(entity, bo);
  279. // 可以在此处为BO填充字段
  280. bo.setUsername(entity.getPin());
  281. SysDeptBO dept = sysDeptService.getPlainDeptByUserId(bo.getId());
  282. if (dept != null) {
  283. bo
  284. .setDeptId(dept.getId())
  285. .setDeptTitle(dept.getTitle())
  286. ;
  287. }
  288. return bo;
  289. }
  290. /**
  291. * 实体 List 转 BO List
  292. *
  293. * @param entityList 实体 List
  294. * @return BO List
  295. */
  296. private List<SysUserBO> entityList2BOs(List<SysUserEntity> entityList) {
  297. if (CollUtil.isEmpty(entityList)) {
  298. return Collections.emptyList();
  299. }
  300. // 深拷贝
  301. List<SysUserBO> ret = new ArrayList<>(entityList.size());
  302. entityList.forEach(
  303. entity -> ret.add(this.entity2BO(entity))
  304. );
  305. return ret;
  306. }
  307. /**
  308. * 实体分页转 BO 分页
  309. *
  310. * @param entityPage 实体分页
  311. * @return BO 分页
  312. */
  313. private PageResult<SysUserBO> entityPage2BOPage(Page<SysUserEntity> entityPage) {
  314. return new PageResult<SysUserBO>()
  315. .setCurrent(entityPage.getCurrent())
  316. .setSize(entityPage.getSize())
  317. .setTotal(entityPage.getTotal())
  318. .setRecords(this.entityList2BOs(entityPage.getRecords()))
  319. ;
  320. }
  321. /**
  322. * 检查是否已存在相同数据
  323. *
  324. * @param dto DTO
  325. */
  326. private void checkExistence(AdminInsertOrUpdateSysUserDTO dto) {
  327. SysUserEntity existingEntity = this.getUserByPin(dto.getUsername());
  328. if (existingEntity != null && !existingEntity.getId().equals(dto.getId())) {
  329. throw new BusinessException(400, "已存在相同账号,请重新输入");
  330. }
  331. }
  332. /**
  333. * 检查并获取租户上下文 bean,无效或被禁用则直接抛出异常
  334. * @param tenantId 租户ID
  335. * @return TenantContext
  336. */
  337. private TenantContext checkAndGetTenantContext(Long tenantId) throws BusinessException {
  338. // 查询租户是否仍有效
  339. SysTenantEntity tenantEntity = sysTenantService.getTenantEntityByTenantId(tenantId);
  340. if (tenantEntity == null) {
  341. throw new BusinessException(SysErrorEnum.INVALID_TENANT);
  342. }
  343. if (GenericStatusEnum.DISABLED == tenantEntity.getStatus()) {
  344. throw new BusinessException(SysErrorEnum.DISABLED_TENANT);
  345. }
  346. return TenantContext.builder()
  347. .tenantId(tenantEntity.getTenantId())
  348. .tenantName(tenantEntity.getTenantName())
  349. .build()
  350. ;
  351. }
  352. private void updateLastLoginAt(Long userId, LocalDateTime lastLoginAt) {
  353. SysUserEntity entity = new SysUserEntity();
  354. entity
  355. .setLastLoginAt(lastLoginAt)
  356. .setId(userId)
  357. ;
  358. this.updateById(entity);
  359. }
  360. }