houzhongjian
2024-10-16 7da8f196dee8e3c526c009a4bc7f5983ece6bb97
提交 | 用户 | 时间
e7c126 1 package com.iailab.module.system.service.user;
H 2
3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.collection.CollectionUtil;
5 import cn.hutool.core.io.IoUtil;
6 import cn.hutool.core.util.StrUtil;
818a01 7 import com.baomidou.dynamic.datasource.annotation.DSTransactional;
e7c126 8 import com.iailab.framework.common.enums.CommonStatusEnum;
H 9 import com.iailab.framework.common.exception.ServiceException;
10 import com.iailab.framework.common.pojo.PageResult;
11 import com.iailab.framework.common.util.collection.CollectionUtils;
12 import com.iailab.framework.common.util.object.BeanUtils;
13 import com.iailab.framework.datapermission.core.util.DataPermissionUtils;
325d2f 14 import com.iailab.module.infra.api.config.ConfigApi;
e7c126 15 import com.iailab.module.infra.api.file.FileApi;
H 16 import com.iailab.module.system.controller.admin.user.vo.profile.UserProfileUpdatePasswordReqVO;
17 import com.iailab.module.system.controller.admin.user.vo.profile.UserProfileUpdateReqVO;
18 import com.iailab.module.system.controller.admin.user.vo.user.UserImportExcelVO;
19 import com.iailab.module.system.controller.admin.user.vo.user.UserImportRespVO;
20 import com.iailab.module.system.controller.admin.user.vo.user.UserPageReqVO;
21 import com.iailab.module.system.controller.admin.user.vo.user.UserSaveReqVO;
22 import com.iailab.module.system.dal.dataobject.dept.DeptDO;
23 import com.iailab.module.system.dal.dataobject.dept.UserPostDO;
24 import com.iailab.module.system.dal.dataobject.user.AdminUserDO;
25 import com.iailab.module.system.dal.mysql.dept.UserPostMapper;
26 import com.iailab.module.system.dal.mysql.user.AdminUserMapper;
27 import com.iailab.module.system.service.dept.DeptService;
28 import com.iailab.module.system.service.dept.PostService;
29 import com.iailab.module.system.service.permission.PermissionService;
30 import com.iailab.module.system.service.tenant.TenantService;
31 import com.google.common.annotations.VisibleForTesting;
32 import com.mzt.logapi.context.LogRecordContext;
33 import com.mzt.logapi.service.impl.DiffParseFunction;
34 import com.mzt.logapi.starter.annotation.LogRecord;
35 import lombok.extern.slf4j.Slf4j;
36 import org.springframework.beans.factory.annotation.Value;
37 import org.springframework.context.annotation.Lazy;
38 import org.springframework.security.crypto.password.PasswordEncoder;
39 import org.springframework.stereotype.Service;
40 import org.springframework.transaction.annotation.Transactional;
41
42 import javax.annotation.Resource;
43 import java.io.InputStream;
44 import java.time.LocalDateTime;
45 import java.util.*;
46
47 import static com.iailab.framework.common.exception.util.ServiceExceptionUtil.exception;
48 import static com.iailab.framework.common.util.collection.CollectionUtils.convertList;
49 import static com.iailab.framework.common.util.collection.CollectionUtils.convertSet;
50 import static com.iailab.module.system.enums.ErrorCodeConstants.*;
51 import static com.iailab.module.system.enums.LogRecordConstants.*;
52
53 /**
54  * 后台用户 Service 实现类
55  *
56  * @author iailab
57  */
58 @Service("adminUserService")
59 @Slf4j
60 public class AdminUserServiceImpl implements AdminUserService {
61
325d2f 62     static final String USER_INIT_PASSWORD_KEY = "system.user.init-password";
e7c126 63
H 64     @Resource
65     private AdminUserMapper userMapper;
66
67     @Resource
68     private DeptService deptService;
69     @Resource
70     private PostService postService;
71     @Resource
72     private PermissionService permissionService;
73     @Resource
74     private PasswordEncoder passwordEncoder;
75     @Resource
76     @Lazy // 延迟,避免循环依赖报错
77     private TenantService tenantService;
78
79     @Resource
80     private UserPostMapper userPostMapper;
81
82     @Resource
83     private FileApi fileApi;
325d2f 84
H 85     @Resource
86     private ConfigApi configApi;
e7c126 87
H 88     @Override
818a01 89     @DSTransactional
e7c126 90     @LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_CREATE_SUB_TYPE, bizNo = "{{#user.id}}",
H 91             success = SYSTEM_USER_CREATE_SUCCESS)
92     public Long createUser(UserSaveReqVO createReqVO) {
93         // 1.1 校验账户配合
94         tenantService.handleTenantInfo(tenant -> {
95             long count = userMapper.selectCount();
96             if (count >= tenant.getAccountCount()) {
97                 throw exception(USER_COUNT_MAX, tenant.getAccountCount());
98             }
99         });
100         // 1.2 校验正确性
101         validateUserForCreateOrUpdate(null, createReqVO.getUsername(),
102                 createReqVO.getMobile(), createReqVO.getEmail(), createReqVO.getDeptId(), createReqVO.getPostIds());
103         // 2.1 插入用户
104         AdminUserDO user = BeanUtils.toBean(createReqVO, AdminUserDO.class);
105         user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启
106         user.setPassword(encodePassword(createReqVO.getPassword())); // 加密密码
107         userMapper.insert(user);
108         // 2.2 插入关联岗位
109         if (CollectionUtil.isNotEmpty(user.getPostIds())) {
110             userPostMapper.insertBatch(convertList(user.getPostIds(),
111                     postId -> new UserPostDO().setUserId(user.getId()).setPostId(postId)));
112         }
113
114         // 3. 记录操作日志上下文
115         LogRecordContext.putVariable("user", user);
116         return user.getId();
117     }
118
119     @Override
818a01 120     @DSTransactional
e7c126 121     @LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}",
H 122             success = SYSTEM_USER_UPDATE_SUCCESS)
123     public void updateUser(UserSaveReqVO updateReqVO) {
124         updateReqVO.setPassword(null); // 特殊:此处不更新密码
125         // 1. 校验正确性
126         AdminUserDO oldUser = validateUserForCreateOrUpdate(updateReqVO.getId(), updateReqVO.getUsername(),
127                 updateReqVO.getMobile(), updateReqVO.getEmail(), updateReqVO.getDeptId(), updateReqVO.getPostIds());
128
129         // 2.1 更新用户
130         AdminUserDO updateObj = BeanUtils.toBean(updateReqVO, AdminUserDO.class);
131         userMapper.updateById(updateObj);
132         // 2.2 更新岗位
133         updateUserPost(updateReqVO, updateObj);
134
135         // 3. 记录操作日志上下文
136         LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldUser, UserSaveReqVO.class));
137         LogRecordContext.putVariable("user", oldUser);
138     }
139
140     private void updateUserPost(UserSaveReqVO reqVO, AdminUserDO updateObj) {
141         Long userId = reqVO.getId();
142         Set<Long> dbPostIds = convertSet(userPostMapper.selectListByUserId(userId), UserPostDO::getPostId);
143         // 计算新增和删除的岗位编号
144         Set<Long> postIds = CollUtil.emptyIfNull(updateObj.getPostIds());
145         Collection<Long> createPostIds = CollUtil.subtract(postIds, dbPostIds);
146         Collection<Long> deletePostIds = CollUtil.subtract(dbPostIds, postIds);
147         // 执行新增和删除。对于已经授权的岗位,不用做任何处理
148         if (!CollectionUtil.isEmpty(createPostIds)) {
149             userPostMapper.insertBatch(convertList(createPostIds,
150                     postId -> new UserPostDO().setUserId(userId).setPostId(postId)));
151         }
152         if (!CollectionUtil.isEmpty(deletePostIds)) {
153             userPostMapper.deleteByUserIdAndPostId(userId, deletePostIds);
154         }
155     }
156
157     @Override
158     public void updateUserLogin(Long id, String loginIp) {
159         userMapper.updateById(new AdminUserDO().setId(id).setLoginIp(loginIp).setLoginDate(LocalDateTime.now()));
160     }
161
162     @Override
163     public void updateUserProfile(Long id, UserProfileUpdateReqVO reqVO) {
164         // 校验正确性
165         validateUserExists(id);
166         validateEmailUnique(id, reqVO.getEmail());
167         validateMobileUnique(id, reqVO.getMobile());
168         // 执行更新
169         userMapper.updateById(BeanUtils.toBean(reqVO, AdminUserDO.class).setId(id));
170     }
171
172     @Override
173     public void updateUserPassword(Long id, UserProfileUpdatePasswordReqVO reqVO) {
174         // 校验旧密码密码
175         validateOldPassword(id, reqVO.getOldPassword());
176         // 执行更新
177         AdminUserDO updateObj = new AdminUserDO().setId(id);
178         updateObj.setPassword(encodePassword(reqVO.getNewPassword())); // 加密密码
179         userMapper.updateById(updateObj);
180     }
181
182     @Override
183     public String updateUserAvatar(Long id, InputStream avatarFile) {
184         validateUserExists(id);
185         // 存储文件
186         String avatar = fileApi.createFile(IoUtil.readBytes(avatarFile));
187         // 更新路径
188         AdminUserDO sysUserDO = new AdminUserDO();
189         sysUserDO.setId(id);
190         sysUserDO.setAvatar(avatar);
191         userMapper.updateById(sysUserDO);
192         return avatar;
193     }
194
195     @Override
196     @LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_UPDATE_PASSWORD_SUB_TYPE, bizNo = "{{#id}}",
197             success = SYSTEM_USER_UPDATE_PASSWORD_SUCCESS)
198     public void updateUserPassword(Long id, String password) {
199         // 1. 校验用户存在
200         AdminUserDO user = validateUserExists(id);
201
202         // 2. 更新密码
203         AdminUserDO updateObj = new AdminUserDO();
204         updateObj.setId(id);
205         updateObj.setPassword(encodePassword(password)); // 加密密码
206         userMapper.updateById(updateObj);
207
208         // 3. 记录操作日志上下文
209         LogRecordContext.putVariable("user", user);
210         LogRecordContext.putVariable("newPassword", updateObj.getPassword());
211     }
212
213     @Override
214     public void updateUserStatus(Long id, Integer status) {
215         // 校验用户存在
216         validateUserExists(id);
217         // 更新状态
218         AdminUserDO updateObj = new AdminUserDO();
219         updateObj.setId(id);
220         updateObj.setStatus(status);
221         userMapper.updateById(updateObj);
222     }
223
224     @Override
225     @Transactional(rollbackFor = Exception.class)
226     @LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_DELETE_SUB_TYPE, bizNo = "{{#id}}",
227             success = SYSTEM_USER_DELETE_SUCCESS)
228     public void deleteUser(Long id) {
229         // 1. 校验用户存在
230         AdminUserDO user = validateUserExists(id);
231
232         // 2.1 删除用户
233         userMapper.deleteById(id);
234         // 2.2 删除用户关联数据
235         permissionService.processUserDeleted(id);
236         // 2.2 删除用户岗位
237         userPostMapper.deleteByUserId(id);
238
239         // 3. 记录操作日志上下文
240         LogRecordContext.putVariable("user", user);
241     }
242
243     @Override
244     public AdminUserDO getUserByUsername(String username) {
245         return userMapper.selectByUsername(username);
246     }
247
248     @Override
249     public AdminUserDO getUserByMobile(String mobile) {
250         return userMapper.selectByMobile(mobile);
251     }
252
253     @Override
254     public PageResult<AdminUserDO> getUserPage(UserPageReqVO reqVO) {
255         return userMapper.selectPage(reqVO, getDeptCondition(reqVO.getDeptId()));
256     }
257
258     @Override
259     public AdminUserDO getUser(Long id) {
260         return userMapper.selectById(id);
261     }
262
263     @Override
264     public List<AdminUserDO> getUserListByDeptIds(Collection<Long> deptIds) {
265         if (CollUtil.isEmpty(deptIds)) {
266             return Collections.emptyList();
267         }
268         return userMapper.selectListByDeptIds(deptIds);
269     }
270
271     @Override
272     public List<AdminUserDO> getUserListByPostIds(Collection<Long> postIds) {
273         if (CollUtil.isEmpty(postIds)) {
274             return Collections.emptyList();
275         }
276         Set<Long> userIds = convertSet(userPostMapper.selectListByPostIds(postIds), UserPostDO::getUserId);
277         if (CollUtil.isEmpty(userIds)) {
278             return Collections.emptyList();
279         }
280         return userMapper.selectBatchIds(userIds);
281     }
282
283     @Override
284     public List<AdminUserDO> getUserList(Collection<Long> ids) {
285         if (CollUtil.isEmpty(ids)) {
286             return Collections.emptyList();
287         }
288         return userMapper.selectBatchIds(ids);
289     }
290
291     @Override
292     public void validateUserList(Collection<Long> ids) {
293         if (CollUtil.isEmpty(ids)) {
294             return;
295         }
296         // 获得岗位信息
297         List<AdminUserDO> users = userMapper.selectBatchIds(ids);
298         Map<Long, AdminUserDO> userMap = CollectionUtils.convertMap(users, AdminUserDO::getId);
299         // 校验
300         ids.forEach(id -> {
301             AdminUserDO user = userMap.get(id);
302             if (user == null) {
303                 throw exception(USER_NOT_EXISTS);
304             }
305             if (!CommonStatusEnum.ENABLE.getStatus().equals(user.getStatus())) {
306                 throw exception(USER_IS_DISABLE, user.getNickname());
307             }
308         });
309     }
310
311     @Override
312     public List<AdminUserDO> getUserListByNickname(String nickname) {
313         return userMapper.selectListByNickname(nickname);
314     }
315
316     /**
317      * 获得部门条件:查询指定部门的子部门编号们,包括自身
318      * @param deptId 部门编号
319      * @return 部门编号集合
320      */
321     private Set<Long> getDeptCondition(Long deptId) {
322         if (deptId == null) {
323             return Collections.emptySet();
324         }
325         Set<Long> deptIds = convertSet(deptService.getChildDeptList(deptId), DeptDO::getId);
326         deptIds.add(deptId); // 包括自身
327         return deptIds;
328     }
329
330     private AdminUserDO validateUserForCreateOrUpdate(Long id, String username, String mobile, String email,
331                                                Long deptId, Set<Long> postIds) {
332         // 关闭数据权限,避免因为没有数据权限,查询不到数据,进而导致唯一校验不正确
333         return DataPermissionUtils.executeIgnore(() -> {
334             // 校验用户存在
335             AdminUserDO user = validateUserExists(id);
336             // 校验用户名唯一
337             validateUsernameUnique(id, username);
338             // 校验手机号唯一
339             validateMobileUnique(id, mobile);
340             // 校验邮箱唯一
341             validateEmailUnique(id, email);
342             // 校验部门处于开启状态
343             deptService.validateDeptList(CollectionUtils.singleton(deptId));
344             // 校验岗位处于开启状态
345             postService.validatePostList(postIds);
346             return user;
347         });
348     }
349
350     @VisibleForTesting
351     AdminUserDO validateUserExists(Long id) {
352         if (id == null) {
353             return null;
354         }
355         AdminUserDO user = userMapper.selectById(id);
356         if (user == null) {
357             throw exception(USER_NOT_EXISTS);
358         }
359         return user;
360     }
361
362     @VisibleForTesting
363     void validateUsernameUnique(Long id, String username) {
364         if (StrUtil.isBlank(username)) {
365             return;
366         }
367         AdminUserDO user = userMapper.selectByUsername(username);
368         if (user == null) {
369             return;
370         }
371         // 如果 id 为空,说明不用比较是否为相同 id 的用户
372         if (id == null) {
373             throw exception(USER_USERNAME_EXISTS);
374         }
375         if (!user.getId().equals(id)) {
376             throw exception(USER_USERNAME_EXISTS);
377         }
378     }
379
380     @VisibleForTesting
381     void validateEmailUnique(Long id, String email) {
382         if (StrUtil.isBlank(email)) {
383             return;
384         }
385         AdminUserDO user = userMapper.selectByEmail(email);
386         if (user == null) {
387             return;
388         }
389         // 如果 id 为空,说明不用比较是否为相同 id 的用户
390         if (id == null) {
391             throw exception(USER_EMAIL_EXISTS);
392         }
393         if (!user.getId().equals(id)) {
394             throw exception(USER_EMAIL_EXISTS);
395         }
396     }
397
398     @VisibleForTesting
399     void validateMobileUnique(Long id, String mobile) {
400         if (StrUtil.isBlank(mobile)) {
401             return;
402         }
403         AdminUserDO user = userMapper.selectByMobile(mobile);
404         if (user == null) {
405             return;
406         }
407         // 如果 id 为空,说明不用比较是否为相同 id 的用户
408         if (id == null) {
409             throw exception(USER_MOBILE_EXISTS);
410         }
411         if (!user.getId().equals(id)) {
412             throw exception(USER_MOBILE_EXISTS);
413         }
414     }
415
416     /**
417      * 校验旧密码
418      * @param id          用户 id
419      * @param oldPassword 旧密码
420      */
421     @VisibleForTesting
422     void validateOldPassword(Long id, String oldPassword) {
423         AdminUserDO user = userMapper.selectById(id);
424         if (user == null) {
425             throw exception(USER_NOT_EXISTS);
426         }
427         if (!isPasswordMatch(oldPassword, user.getPassword())) {
428             throw exception(USER_PASSWORD_FAILED);
429         }
430     }
431
432     @Override
433     @Transactional(rollbackFor = Exception.class) // 添加事务,异常则回滚所有导入
434     public UserImportRespVO importUserList(List<UserImportExcelVO> importUsers, boolean isUpdateSupport) {
325d2f 435         // 1.1 参数校验
e7c126 436         if (CollUtil.isEmpty(importUsers)) {
H 437             throw exception(USER_IMPORT_LIST_IS_EMPTY);
438         }
325d2f 439         // 1.2 初始化密码不能为空
H 440         String initPassword = configApi.getConfigValueByKey(USER_INIT_PASSWORD_KEY).getCheckedData();
441         if (StrUtil.isEmpty(initPassword)) {
442             throw exception(USER_IMPORT_INIT_PASSWORD);
443         }
444
445         // 2. 遍历,逐个创建 or 更新
e7c126 446         UserImportRespVO respVO = UserImportRespVO.builder().createUsernames(new ArrayList<>())
H 447                 .updateUsernames(new ArrayList<>()).failureUsernames(new LinkedHashMap<>()).build();
448         importUsers.forEach(importUser -> {
449             // 校验,判断是否有不符合的原因
450             try {
451                 validateUserForCreateOrUpdate(null, null, importUser.getMobile(), importUser.getEmail(),
452                         importUser.getDeptId(), null);
453             } catch (ServiceException ex) {
454                 respVO.getFailureUsernames().put(importUser.getUsername(), ex.getMessage());
455                 return;
456             }
457             // 判断如果不存在,在进行插入
458             AdminUserDO existUser = userMapper.selectByUsername(importUser.getUsername());
459             if (existUser == null) {
460                 userMapper.insert(BeanUtils.toBean(importUser, AdminUserDO.class)
ec0bce 461                         .setPassword(encodePassword(initPassword)).setPostIds(new HashSet<>())); // 设置默认密码及空岗位编号数组
e7c126 462                 respVO.getCreateUsernames().add(importUser.getUsername());
H 463                 return;
464             }
465             // 如果存在,判断是否允许更新
466             if (!isUpdateSupport) {
467                 respVO.getFailureUsernames().put(importUser.getUsername(), USER_USERNAME_EXISTS.getMsg());
468                 return;
469             }
470             AdminUserDO updateUser = BeanUtils.toBean(importUser, AdminUserDO.class);
471             updateUser.setId(existUser.getId());
472             userMapper.updateById(updateUser);
473             respVO.getUpdateUsernames().add(importUser.getUsername());
474         });
475         return respVO;
476     }
477
478     @Override
479     public List<AdminUserDO> getUserListByStatus(Integer status) {
480         return userMapper.selectListByStatus(status);
481     }
482
483     @Override
484     public boolean isPasswordMatch(String rawPassword, String encodedPassword) {
485         return passwordEncoder.matches(rawPassword, encodedPassword);
486     }
487
488     /**
489      * 对密码进行加密
490      *
491      * @param password 密码
492      * @return 加密后的密码
493      */
494     private String encodePassword(String password) {
495         return passwordEncoder.encode(password);
496     }
497
498 }