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