diff --git a/pom.xml b/pom.xml index 949978260..532f7e200 100644 --- a/pom.xml +++ b/pom.xml @@ -46,7 +46,12 @@ 2.2.7 2.2 1.0.5 + 7.2.6.RELEASE + 0.1.16 + 4.5.18 + 2.1.0 + 1.2.7 @@ -213,14 +218,14 @@ com.github.fppt jedis-mock - 0.1.16 + ${jedis-mock.version} test uk.co.jemos.podam podam - 7.2.6.RELEASE + ${podam.version} test @@ -278,18 +283,18 @@ com.yunpian.sdk yunpian-java-sdk - 1.2.7 + ${yunpian-java-sdk.version} com.aliyun aliyun-java-sdk-core - 4.5.18 + ${aliyun-java-sdk-core.version} com.aliyun aliyun-java-sdk-dysmsapi - 2.1.0 + ${aliyun-java-sdk-dysmsapi.version} diff --git a/ruoyi-ui/src/views/system/user/index.vue b/ruoyi-ui/src/views/system/user/index.vue index 8c836c28f..a9d418a7f 100644 --- a/ruoyi-ui/src/views/system/user/index.vue +++ b/ruoyi-ui/src/views/system/user/index.vue @@ -362,7 +362,7 @@ export default { created() { this.getList(); this.getTreeselect(); - this.getConfigKey("sys.user.initPassword").then(response => { + this.getConfigKey("sys.user.init-password").then(response => { this.initPassword = response.msg; }); }, diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/controller/user/SysUserController.java b/src/main/java/cn/iocoder/dashboard/modules/system/controller/user/SysUserController.java index 17dcff200..dcfce4d5b 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/controller/user/SysUserController.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/controller/user/SysUserController.java @@ -149,9 +149,9 @@ public class SysUserController { // 手动创建导出 demo List list = Arrays.asList( SysUserImportExcelVO.builder().username("yudao").deptId(1L).email("yudao@iocoder.cn").mobile("15601691300") - .nickname("芋道").status(CommonStatusEnum.ENABLE.getStatus()).sex(SysSexEnum.MALE.getSEX()).build(), + .nickname("芋道").status(CommonStatusEnum.ENABLE.getStatus()).sex(SysSexEnum.MALE.getSex()).build(), SysUserImportExcelVO.builder().username("yuanma").deptId(2L).email("yuanma@iocoder.cn").mobile("15601701300") - .nickname("源码").status(CommonStatusEnum.DISABLE.getStatus()).sex(SysSexEnum.FEMALE.getSEX()).build() + .nickname("源码").status(CommonStatusEnum.DISABLE.getStatus()).sex(SysSexEnum.FEMALE.getSex()).build() ); // 输出 diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/controller/user/vo/profile/SysUserProfileUpdateReqVO.java b/src/main/java/cn/iocoder/dashboard/modules/system/controller/user/vo/profile/SysUserProfileUpdateReqVO.java index 1f02e553f..95051f0a1 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/controller/user/vo/profile/SysUserProfileUpdateReqVO.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/controller/user/vo/profile/SysUserProfileUpdateReqVO.java @@ -3,9 +3,9 @@ package cn.iocoder.dashboard.modules.system.controller.user.vo.profile; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; +import org.hibernate.validator.constraints.Length; import javax.validation.constraints.Email; -import javax.validation.constraints.Pattern; import javax.validation.constraints.Size; @ApiModel("用户个人信息更新 Request VO") @@ -22,7 +22,7 @@ public class SysUserProfileUpdateReqVO { private String email; @ApiModelProperty(value = "手机号码", example = "15601691300") - @Pattern(regexp = "^1(3|4|5|7|8)\\d{9}$",message = "手机号码格式错误") + @Length(min = 11, max = 11, message = "手机号长度必须 11 位") private String mobile; @ApiModelProperty(value = "用户性别", example = "1", notes = "参见 SysSexEnum 枚举类") diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/controller/user/vo/user/SysUserBaseVO.java b/src/main/java/cn/iocoder/dashboard/modules/system/controller/user/vo/user/SysUserBaseVO.java index 7361bb342..4fe0f9b15 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/controller/user/vo/user/SysUserBaseVO.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/controller/user/vo/user/SysUserBaseVO.java @@ -2,10 +2,10 @@ package cn.iocoder.dashboard.modules.system.controller.user.vo.user; import io.swagger.annotations.ApiModelProperty; import lombok.Data; +import org.hibernate.validator.constraints.Length; import javax.validation.constraints.Email; import javax.validation.constraints.NotBlank; -import javax.validation.constraints.Pattern; import javax.validation.constraints.Size; import java.util.Set; @@ -40,7 +40,7 @@ public class SysUserBaseVO { private String email; @ApiModelProperty(value = "手机号码", example = "15601691300") - @Pattern(regexp = "^1(3|4|5|7|8)\\d{9}$",message = "手机号码格式错误") + @Length(min = 11, max = 11, message = "手机号长度必须 11 位") private String mobile; @ApiModelProperty(value = "用户性别", example = "1", notes = "参见 SysSexEnum 枚举类") diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/enums/common/SysSexEnum.java b/src/main/java/cn/iocoder/dashboard/modules/system/enums/common/SysSexEnum.java index fb9bfb588..63dec921c 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/enums/common/SysSexEnum.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/enums/common/SysSexEnum.java @@ -3,6 +3,11 @@ package cn.iocoder.dashboard.modules.system.enums.common; import lombok.AllArgsConstructor; import lombok.Getter; +/** + * 性别的枚举值 + * + * @author 芋道源码 + */ @Getter @AllArgsConstructor public enum SysSexEnum { @@ -10,6 +15,9 @@ public enum SysSexEnum { MALE(1), // 男 FEMALE(2); // 女 - private final Integer SEX; + /** + * 性别 + */ + private final Integer sex; } diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/user/SysUserServiceImpl.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/user/SysUserServiceImpl.java index 6fefbbdb0..4e882794a 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/service/user/SysUserServiceImpl.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/service/user/SysUserServiceImpl.java @@ -6,17 +6,11 @@ import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.dashboard.common.enums.CommonStatusEnum; import cn.iocoder.dashboard.common.exception.ServiceException; -import cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil; import cn.iocoder.dashboard.common.pojo.PageResult; import cn.iocoder.dashboard.modules.infra.service.file.InfFileService; import cn.iocoder.dashboard.modules.system.controller.user.vo.profile.SysUserProfileUpdatePasswordReqVO; import cn.iocoder.dashboard.modules.system.controller.user.vo.profile.SysUserProfileUpdateReqVO; -import cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserCreateReqVO; -import cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserExportReqVO; -import cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserImportExcelVO; -import cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserImportRespVO; -import cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserPageReqVO; -import cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserUpdateReqVO; +import cn.iocoder.dashboard.modules.system.controller.user.vo.user.*; import cn.iocoder.dashboard.modules.system.convert.user.SysUserConvert; import cn.iocoder.dashboard.modules.system.dal.dataobject.dept.SysDeptDO; import cn.iocoder.dashboard.modules.system.dal.dataobject.dept.SysPostDO; @@ -26,21 +20,18 @@ import cn.iocoder.dashboard.modules.system.service.dept.SysDeptService; import cn.iocoder.dashboard.modules.system.service.dept.SysPostService; import cn.iocoder.dashboard.modules.system.service.permission.SysPermissionService; import cn.iocoder.dashboard.util.collection.CollectionUtils; +import com.google.common.annotations.VisibleForTesting; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.io.InputStream; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; +import static cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*; @@ -53,6 +44,9 @@ import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*; @Slf4j public class SysUserServiceImpl implements SysUserService { + @Value("${sys.user.init-password:yudaoyuanma}") + private String userInitPassword; + @Resource private SysUserMapper userMapper; @@ -142,8 +136,6 @@ public class SysUserServiceImpl implements SysUserService { updateObj.setId(id); updateObj.setStatus(status); userMapper.updateById(updateObj); - // 删除用户关联数据 - permissionService.processUserDeleted(id); } @Override @@ -152,6 +144,8 @@ public class SysUserServiceImpl implements SysUserService { this.checkUserExists(id); // 删除用户 userMapper.deleteById(id); + // 删除用户关联数据 + permissionService.processUserDeleted(id); } @Override @@ -221,17 +215,19 @@ public class SysUserServiceImpl implements SysUserService { this.checkPostEnable(postIds); } - private void checkUserExists(Long id) { + @VisibleForTesting + void checkUserExists(Long id) { if (id == null) { return; } SysUserDO user = userMapper.selectById(id); if (user == null) { - throw ServiceExceptionUtil.exception(USER_NOT_EXISTS); + throw exception(USER_NOT_EXISTS); } } - private void checkUsernameUnique(Long id, String username) { + @VisibleForTesting + void checkUsernameUnique(Long id, String username) { if (StrUtil.isBlank(username)) { return; } @@ -241,14 +237,15 @@ public class SysUserServiceImpl implements SysUserService { } // 如果 id 为空,说明不用比较是否为相同 id 的用户 if (id == null) { - throw ServiceExceptionUtil.exception(USER_USERNAME_EXISTS); + throw exception(USER_USERNAME_EXISTS); } if (!user.getId().equals(id)) { - throw ServiceExceptionUtil.exception(USER_USERNAME_EXISTS); + throw exception(USER_USERNAME_EXISTS); } } - private void checkEmailUnique(Long id, String email) { + @VisibleForTesting + void checkEmailUnique(Long id, String email) { if (StrUtil.isBlank(email)) { return; } @@ -258,14 +255,15 @@ public class SysUserServiceImpl implements SysUserService { } // 如果 id 为空,说明不用比较是否为相同 id 的用户 if (id == null) { - throw ServiceExceptionUtil.exception(USER_EMAIL_EXISTS); + throw exception(USER_EMAIL_EXISTS); } if (!user.getId().equals(id)) { - throw ServiceExceptionUtil.exception(USER_EMAIL_EXISTS); + throw exception(USER_EMAIL_EXISTS); } } - private void checkMobileUnique(Long id, String mobile) { + @VisibleForTesting + void checkMobileUnique(Long id, String mobile) { if (StrUtil.isBlank(mobile)) { return; } @@ -275,42 +273,44 @@ public class SysUserServiceImpl implements SysUserService { } // 如果 id 为空,说明不用比较是否为相同 id 的用户 if (id == null) { - throw ServiceExceptionUtil.exception(USER_MOBILE_EXISTS); + throw exception(USER_MOBILE_EXISTS); } if (!user.getId().equals(id)) { - throw ServiceExceptionUtil.exception(USER_MOBILE_EXISTS); + throw exception(USER_MOBILE_EXISTS); } } - private void checkDeptEnable(Long deptId) { + @VisibleForTesting + void checkDeptEnable(Long deptId) { if (deptId == null) { // 允许不选择 return; } SysDeptDO dept = deptService.getDept(deptId); if (dept == null) { - throw ServiceExceptionUtil.exception(DEPT_NOT_FOUND); + throw exception(DEPT_NOT_FOUND); } if (!CommonStatusEnum.ENABLE.getStatus().equals(dept.getStatus())) { - throw ServiceExceptionUtil.exception(DEPT_NOT_ENABLE); + throw exception(DEPT_NOT_ENABLE); } } - private void checkPostEnable(Set postIds) { + @VisibleForTesting + void checkPostEnable(Set postIds) { if (CollUtil.isEmpty(postIds)) { // 允许不选择 return; } List posts = postService.getPosts(postIds, null); if (CollUtil.isEmpty(posts)) { - throw ServiceExceptionUtil.exception(POST_NOT_FOUND); + throw exception(POST_NOT_FOUND); } Map postMap = CollectionUtils.convertMap(posts, SysPostDO::getId); postIds.forEach(postId -> { SysPostDO post = postMap.get(postId); if (post == null) { - throw ServiceExceptionUtil.exception(POST_NOT_FOUND); + throw exception(POST_NOT_FOUND); } if (!CommonStatusEnum.ENABLE.getStatus().equals(post.getStatus())) { - throw ServiceExceptionUtil.exception(POST_NOT_ENABLE, post.getName()); + throw exception(POST_NOT_ENABLE, post.getName()); } }); } @@ -321,13 +321,14 @@ public class SysUserServiceImpl implements SysUserService { * @param id 用户 id * @param oldPassword 旧密码 */ - private void checkOldPassword(Long id, String oldPassword) { + @VisibleForTesting + void checkOldPassword(Long id, String oldPassword) { SysUserDO user = userMapper.selectById(id); if (user == null) { - throw ServiceExceptionUtil.exception(USER_NOT_EXISTS); + throw exception(USER_NOT_EXISTS); } if (!passwordEncoder.matches(oldPassword, user.getPassword())) { - throw ServiceExceptionUtil.exception(USER_PASSWORD_FAILED); + throw exception(USER_PASSWORD_FAILED); } } @@ -335,7 +336,7 @@ public class SysUserServiceImpl implements SysUserService { @Transactional(rollbackFor = Exception.class) // 添加事务,异常则回滚所有导入 public SysUserImportRespVO importUsers(List importUsers, boolean isUpdateSupport) { if (CollUtil.isEmpty(importUsers)) { - throw ServiceExceptionUtil.exception(USER_IMPORT_LIST_IS_EMPTY); + throw exception(USER_IMPORT_LIST_IS_EMPTY); } SysUserImportRespVO respVO = SysUserImportRespVO.builder().createUsernames(new ArrayList<>()) .updateUsernames(new ArrayList<>()).failureUsernames(new LinkedHashMap<>()).build(); @@ -351,8 +352,8 @@ public class SysUserServiceImpl implements SysUserService { // 判断如果不存在,在进行插入 SysUserDO existUser = userMapper.selectByUsername(importUser.getUsername()); if (existUser == null) { - // TODO 芋艿:初始密码 - userMapper.insert(SysUserConvert.INSTANCE.convert(importUser)); + userMapper.insert(SysUserConvert.INSTANCE.convert(importUser) + .setPassword(passwordEncoder.encode(userInitPassword))); // 设置默认密码 respVO.getCreateUsernames().add(importUser.getUsername()); return; } diff --git a/src/test/java/cn/iocoder/dashboard/modules/system/service/auth/SysUserSessionServiceImplTest.java b/src/test/java/cn/iocoder/dashboard/modules/system/service/auth/SysUserSessionServiceImplTest.java index 92cc9c32f..740519430 100644 --- a/src/test/java/cn/iocoder/dashboard/modules/system/service/auth/SysUserSessionServiceImplTest.java +++ b/src/test/java/cn/iocoder/dashboard/modules/system/service/auth/SysUserSessionServiceImplTest.java @@ -1,7 +1,6 @@ package cn.iocoder.dashboard.modules.system.service.auth; import static cn.hutool.core.util.RandomUtil.randomEle; -import static cn.iocoder.dashboard.modules.system.dal.redis.SysRedisKeyConstants.LOGIN_USER; import static cn.iocoder.dashboard.util.AssertUtils.assertPojoEquals; import static cn.iocoder.dashboard.util.RandomUtils.randomDate; import static cn.iocoder.dashboard.util.RandomUtils.randomLongId; @@ -25,7 +24,6 @@ import javax.annotation.Resource; import org.junit.jupiter.api.Test; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; -import org.springframework.data.redis.core.StringRedisTemplate; import cn.hutool.core.date.DateUtil; import cn.iocoder.dashboard.BaseDbAndRedisUnitTest; @@ -33,10 +31,6 @@ import cn.iocoder.dashboard.common.enums.CommonStatusEnum; import cn.iocoder.dashboard.common.pojo.PageResult; import cn.iocoder.dashboard.framework.security.config.SecurityProperties; import cn.iocoder.dashboard.framework.security.core.LoginUser; -import cn.iocoder.dashboard.modules.infra.controller.job.vo.job.InfJobPageReqVO; -import cn.iocoder.dashboard.modules.infra.dal.dataobject.job.InfJobDO; -import cn.iocoder.dashboard.modules.infra.enums.config.InfConfigTypeEnum; -import cn.iocoder.dashboard.modules.infra.enums.job.InfJobStatusEnum; import cn.iocoder.dashboard.modules.system.controller.auth.vo.session.SysUserSessionPageReqVO; import cn.iocoder.dashboard.modules.system.dal.dataobject.auth.SysUserSessionDO; import cn.iocoder.dashboard.modules.system.dal.dataobject.user.SysUserDO; @@ -50,7 +44,6 @@ import cn.iocoder.dashboard.modules.system.service.logger.impl.SysLoginLogServic import cn.iocoder.dashboard.modules.system.service.user.SysUserServiceImpl; import cn.iocoder.dashboard.util.AssertUtils; import cn.iocoder.dashboard.util.RandomUtils; -import cn.iocoder.dashboard.util.json.JsonUtils; import cn.iocoder.dashboard.util.object.ObjectUtils; /** @@ -167,12 +160,12 @@ public class SysUserSessionServiceImplTest extends BaseDbAndRedisUnitTest { String userIp = randomString(); SysUserDO dbUser1 = randomPojo(SysUserDO.class, o -> { o.setUsername("testUsername1"); - o.setSex(randomEle(SysSexEnum.values()).getSEX()); + o.setSex(randomEle(SysSexEnum.values()).getSex()); o.setStatus(CommonStatusEnum.ENABLE.getStatus()); }); SysUserDO dbUser2 = randomPojo(SysUserDO.class, o -> { o.setUsername("testUsername2"); - o.setSex(randomEle(SysSexEnum.values()).getSEX()); + o.setSex(randomEle(SysSexEnum.values()).getSex()); o.setStatus(CommonStatusEnum.ENABLE.getStatus()); }); SysUserSessionDO dbSession = randomPojo(SysUserSessionDO.class, o -> { diff --git a/src/test/java/cn/iocoder/dashboard/modules/system/service/logger/SysOperateLogServiceImplTest.java b/src/test/java/cn/iocoder/dashboard/modules/system/service/logger/SysOperateLogServiceImplTest.java index f0a235ff4..67f99e35f 100644 --- a/src/test/java/cn/iocoder/dashboard/modules/system/service/logger/SysOperateLogServiceImplTest.java +++ b/src/test/java/cn/iocoder/dashboard/modules/system/service/logger/SysOperateLogServiceImplTest.java @@ -68,7 +68,7 @@ public class SysOperateLogServiceImplTest extends BaseDbUnitTest { // 先构造用户 SysUserDO user = RandomUtils.randomPojo(SysUserDO.class, o -> { o.setNickname("wangkai"); - o.setSex(SysSexEnum.MALE.getSEX()); + o.setSex(SysSexEnum.MALE.getSex()); o.setStatus(CommonStatusEnum.ENABLE.getStatus()); }); when(userService.getUsersByNickname("wangkai")).thenReturn(Collections.singletonList(user)); @@ -119,7 +119,7 @@ public class SysOperateLogServiceImplTest extends BaseDbUnitTest { // 先构造用户 SysUserDO user = RandomUtils.randomPojo(SysUserDO.class, o -> { o.setNickname("wangkai"); - o.setSex(SysSexEnum.MALE.getSEX()); + o.setSex(SysSexEnum.MALE.getSex()); o.setStatus(CommonStatusEnum.ENABLE.getStatus()); }); when(userService.getUsersByNickname("wangkai")).thenReturn(Collections.singletonList(user)); diff --git a/src/test/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsChannelServiceTest.java b/src/test/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsChannelServiceTest.java index a662b82aa..e1b7b145c 100644 --- a/src/test/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsChannelServiceTest.java +++ b/src/test/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsChannelServiceTest.java @@ -56,7 +56,7 @@ public class SysSmsChannelServiceTest extends BaseDbUnitTest { @Test public void testInitLocalCache_success() { - // mock 数据s + // mock 数据 SysSmsChannelDO smsChannelDO01 = randomSmsChannelDO(); smsChannelMapper.insert(smsChannelDO01); SysSmsChannelDO smsChannelDO02 = randomSmsChannelDO(); diff --git a/src/test/java/cn/iocoder/dashboard/modules/system/service/user/SysUserServiceImplTest.java b/src/test/java/cn/iocoder/dashboard/modules/system/service/user/SysUserServiceImplTest.java new file mode 100644 index 000000000..bdb663f82 --- /dev/null +++ b/src/test/java/cn/iocoder/dashboard/modules/system/service/user/SysUserServiceImplTest.java @@ -0,0 +1,590 @@ +package cn.iocoder.dashboard.modules.system.service.user; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.RandomUtil; +import cn.iocoder.dashboard.BaseDbUnitTest; +import cn.iocoder.dashboard.common.enums.CommonStatusEnum; +import cn.iocoder.dashboard.common.pojo.PageResult; +import cn.iocoder.dashboard.modules.infra.service.file.InfFileService; +import cn.iocoder.dashboard.modules.system.controller.user.vo.profile.SysUserProfileUpdatePasswordReqVO; +import cn.iocoder.dashboard.modules.system.controller.user.vo.profile.SysUserProfileUpdateReqVO; +import cn.iocoder.dashboard.modules.system.controller.user.vo.user.*; +import cn.iocoder.dashboard.modules.system.dal.dataobject.dept.SysDeptDO; +import cn.iocoder.dashboard.modules.system.dal.dataobject.dept.SysPostDO; +import cn.iocoder.dashboard.modules.system.dal.dataobject.user.SysUserDO; +import cn.iocoder.dashboard.modules.system.dal.mysql.user.SysUserMapper; +import cn.iocoder.dashboard.modules.system.enums.common.SysSexEnum; +import cn.iocoder.dashboard.modules.system.service.dept.SysDeptService; +import cn.iocoder.dashboard.modules.system.service.dept.SysPostService; +import cn.iocoder.dashboard.modules.system.service.permission.SysPermissionService; +import cn.iocoder.dashboard.util.collection.ArrayUtils; +import cn.iocoder.dashboard.util.collection.CollectionUtils; +import cn.iocoder.dashboard.util.object.ObjectUtils; +import org.junit.jupiter.api.Test; +import org.mockito.stubbing.Answer; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.security.crypto.password.PasswordEncoder; + +import javax.annotation.Resource; +import java.io.ByteArrayInputStream; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; + +import static cn.hutool.core.util.RandomUtil.randomBytes; +import static cn.hutool.core.util.RandomUtil.randomEle; +import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*; +import static cn.iocoder.dashboard.util.AssertUtils.assertPojoEquals; +import static cn.iocoder.dashboard.util.AssertUtils.assertServiceException; +import static cn.iocoder.dashboard.util.RandomUtils.*; +import static cn.iocoder.dashboard.util.date.DateUtils.buildTime; +import static org.assertj.core.util.Lists.newArrayList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +/** + * {@link SysUserService} 的单元测试类 + * + * @author zxl + */ +@Import(SysUserServiceImpl.class) +public class SysUserServiceImplTest extends BaseDbUnitTest { + + @Resource + private SysUserServiceImpl userService; + + @Resource + private SysUserMapper userMapper; + + @MockBean + private SysDeptService deptService; + @MockBean + private SysPostService postService; + @MockBean + private SysPermissionService permissionService; + @MockBean + private PasswordEncoder passwordEncoder; + @MockBean + private InfFileService fileService; + + @Test + public void testCreatUser_success() { + // 准备参数 + SysUserCreateReqVO reqVO = randomPojo(SysUserCreateReqVO.class, o -> { + o.setSex(RandomUtil.randomEle(SysSexEnum.values()).getSex()); + o.setMobile(randomString()); + }); + // mock deptService 的方法 + SysDeptDO dept = randomPojo(SysDeptDO.class, o -> { + o.setId(reqVO.getDeptId()); + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + }); + when(deptService.getDept(eq(dept.getId()))).thenReturn(dept); + // mock postService 的方法 + List posts = CollectionUtils.convertList(reqVO.getPostIds(), postId -> + randomPojo(SysPostDO.class, o -> { + o.setId(postId); + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + })); + when(postService.getPosts(eq(reqVO.getPostIds()), isNull())).thenReturn(posts); + // mock passwordEncoder 的方法 + when(passwordEncoder.encode(eq(reqVO.getPassword()))).thenReturn("yudaoyuanma"); + + // 调用 + Long userId = userService.createUser(reqVO); + // 断言 + SysUserDO user = userMapper.selectById(userId); + assertPojoEquals(reqVO, user, "password"); + assertEquals("yudaoyuanma", user.getPassword()); + assertEquals(CommonStatusEnum.ENABLE.getStatus(), user.getStatus()); + } + + @Test + public void testUpdateUser_success() { + // mock 数据 + SysUserDO dbUser = randomSysUserDO(); + userMapper.insert(dbUser); + // 准备参数 + SysUserUpdateReqVO reqVO = randomPojo(SysUserUpdateReqVO.class, o -> { + o.setId(dbUser.getId()); + o.setSex(RandomUtil.randomEle(SysSexEnum.values()).getSex()); + o.setMobile(randomString()); + }); + // mock deptService 的方法 + SysDeptDO dept = randomPojo(SysDeptDO.class, o -> { + o.setId(reqVO.getDeptId()); + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + }); + when(deptService.getDept(eq(dept.getId()))).thenReturn(dept); + // mock postService 的方法 + List posts = CollectionUtils.convertList(reqVO.getPostIds(), postId -> + randomPojo(SysPostDO.class, o -> { + o.setId(postId); + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + })); + when(postService.getPosts(eq(reqVO.getPostIds()), isNull())).thenReturn(posts); + + // 调用 + userService.updateUser(reqVO); + // 断言 + SysUserDO user = userMapper.selectById(reqVO.getId()); + assertPojoEquals(reqVO, user); + } + + @Test + public void testUpdateUserProfile_success() { + // mock 数据 + SysUserDO dbUser = randomSysUserDO(); + userMapper.insert(dbUser); + // 准备参数 + Long userId = dbUser.getId(); + SysUserProfileUpdateReqVO reqVO = randomPojo(SysUserProfileUpdateReqVO.class, o -> { + o.setMobile(randomString()); + o.setSex(RandomUtil.randomEle(SysSexEnum.values()).getSex()); + }); + + // 调用 + userService.updateUserProfile(userId, reqVO); + // 断言 + SysUserDO user = userMapper.selectById(userId); + assertPojoEquals(reqVO, user); + } + + @Test + public void testUpdateUserPassword_success() { + // mock 数据 + SysUserDO dbUser = randomSysUserDO(o -> o.setPassword("encode:yudao")); + userMapper.insert(dbUser); + // 准备参数 + Long userId = dbUser.getId(); + SysUserProfileUpdatePasswordReqVO reqVO = randomPojo(SysUserProfileUpdatePasswordReqVO.class, o -> { + o.setOldPassword("yudao"); + o.setNewPassword("yuanma"); + }); + // mock 方法 + when(passwordEncoder.encode(anyString())).then( + (Answer) invocationOnMock -> "encode:" + invocationOnMock.getArgument(0)); + when(passwordEncoder.matches(eq(reqVO.getOldPassword()), eq(dbUser.getPassword()))).thenReturn(true); + + // 调用 + userService.updateUserPassword(userId, reqVO); + // 断言 + SysUserDO user = userMapper.selectById(userId); + assertEquals("encode:yuanma", user.getPassword()); + } + + @Test + public void testUpdateUserAvatar_success() { + // mock 数据 + SysUserDO dbUser = randomSysUserDO(); + userMapper.insert(dbUser); + // 准备参数 + Long userId = dbUser.getId(); + byte[] avatarFileBytes = randomBytes(10); + ByteArrayInputStream avatarFile = new ByteArrayInputStream(avatarFileBytes); + // mock 方法 + String avatar = randomString(); + when(fileService.createFile(anyString(), eq(avatarFileBytes))).thenReturn(avatar); + + // 调用 + userService.updateUserAvatar(userId, avatarFile); + // 断言 + SysUserDO user = userMapper.selectById(userId); + assertEquals(avatar, user.getAvatar()); + } + + @Test + public void testUpdateUserPassword02_success() { + // mock 数据 + SysUserDO dbUser = randomSysUserDO(); + userMapper.insert(dbUser); + // 准备参数 + Long userId = dbUser.getId(); + String password = "yudao"; + // mock 方法 + when(passwordEncoder.encode(anyString())).then( + (Answer) invocationOnMock -> "encode:" + invocationOnMock.getArgument(0)); + + // 调用 + userService.updateUserPassword(userId, password); + // 断言 + SysUserDO user = userMapper.selectById(userId); + assertEquals("encode:" + password, user.getPassword()); + } + + @Test + public void testUpdateUserStatus() { + // mock 数据 + SysUserDO dbUser = randomSysUserDO(); + userMapper.insert(dbUser); + // 准备参数 + Long userId = dbUser.getId(); + Integer status = randomCommonStatus(); + + // 调用 + userService.updateUserStatus(userId, status); + // 断言 + SysUserDO user = userMapper.selectById(userId); + assertEquals(status, user.getStatus()); + } + + @Test + public void testDeleteUser_success(){ + // mock 数据 + SysUserDO dbUser = randomSysUserDO(); + userMapper.insert(dbUser); + // 准备参数 + Long userId = dbUser.getId(); + + // 调用数据 + userService.deleteUser(userId); + // 校验结果 + assertNull(userService.getUser(userId)); + // 校验调用次数 + verify(permissionService, times(1)).processUserDeleted(eq(userId)); + } + + @Test + public void testGetUserPage() { + // mock 数据 + SysUserDO dbUser = initGetUserPageData(); + // 准备参数 + SysUserPageReqVO reqVO = new SysUserPageReqVO(); + reqVO.setUsername("yudao"); + reqVO.setMobile("1560"); + reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus()); + reqVO.setBeginTime(buildTime(2020, 12, 1)); + reqVO.setEndTime(buildTime(2020, 12, 24)); + reqVO.setDeptId(1L); // 其中,1L 是 2L 的父部门 + // mock 方法 + List deptList = newArrayList(randomPojo(SysDeptDO.class, o -> o.setId(2L))); + when(deptService.getDeptsByParentIdFromCache(eq(reqVO.getDeptId()), eq(true))).thenReturn(deptList); + + // 调用 + PageResult pageResult = userService.getUserPage(reqVO); + // 断言 + assertEquals(1, pageResult.getTotal()); + assertEquals(1, pageResult.getList().size()); + assertPojoEquals(dbUser, pageResult.getList().get(0)); + } + + @Test + public void testGetUsers() { + // mock 数据 + SysUserDO dbUser = initGetUserPageData(); + // 准备参数 + SysUserExportReqVO reqVO = new SysUserExportReqVO(); + reqVO.setUsername("yudao"); + reqVO.setMobile("1560"); + reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus()); + reqVO.setBeginTime(buildTime(2020, 12, 1)); + reqVO.setEndTime(buildTime(2020, 12, 24)); + reqVO.setDeptId(1L); // 其中,1L 是 2L 的父部门 + // mock 方法 + List deptList = newArrayList(randomPojo(SysDeptDO.class, o -> o.setId(2L))); + when(deptService.getDeptsByParentIdFromCache(eq(reqVO.getDeptId()), eq(true))).thenReturn(deptList); + + // 调用 + List list = userService.getUsers(reqVO); + // 断言 + assertEquals(1, list.size()); + assertPojoEquals(dbUser, list.get(0)); + } + + /** + * 初始化 getUserPage 方法的测试数据 + */ + private SysUserDO initGetUserPageData() { + // mock 数据 + SysUserDO dbUser = randomSysUserDO(o -> { // 等会查询到 + o.setUsername("yudaoyuanma"); + o.setMobile("15601691300"); + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + o.setCreateTime(buildTime(2020, 12, 12)); + o.setDeptId(2L); + }); + userMapper.insert(dbUser); + // 测试 username 不匹配 + userMapper.insert(ObjectUtils.clone(dbUser, o -> o.setUsername("yuanma"))); + // 测试 mobile 不匹配 + userMapper.insert(ObjectUtils.clone(dbUser, o -> o.setMobile("18818260888"))); + // 测试 status 不匹配 + userMapper.insert(ObjectUtils.clone(dbUser, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()))); + // 测试 createTime 不匹配 + userMapper.insert(ObjectUtils.clone(dbUser, o -> o.setCreateTime(buildTime(2020, 11, 11)))); + // 测试 dept 不匹配 + userMapper.insert(ObjectUtils.clone(dbUser, o -> o.setDeptId(0L))); + return dbUser; + } + + /** + * 情况一,校验不通过,导致插入失败 + */ + @Test + public void testImportUsers_01() { + // 准备参数 + SysUserImportExcelVO importUser = randomPojo(SysUserImportExcelVO.class); + // mock 方法 + + // 调用 + SysUserImportRespVO respVO = userService.importUsers(newArrayList(importUser), true); + // 断言 + assertEquals(0, respVO.getCreateUsernames().size()); + assertEquals(0, respVO.getUpdateUsernames().size()); + assertEquals(1, respVO.getFailureUsernames().size()); + assertEquals(DEPT_NOT_FOUND.getMsg(), respVO.getFailureUsernames().get(importUser.getUsername())); + } + + /** + * 情况二,不存在,进行插入 + */ + @Test + public void testImportUsers_02() { + // 准备参数 + SysUserImportExcelVO importUser = randomPojo(SysUserImportExcelVO.class, o -> { + o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围 + o.setSex(randomEle(SysSexEnum.values()).getSex()); // 保证 sex 的范围 + }); + // mock deptService 的方法 + SysDeptDO dept = randomPojo(SysDeptDO.class, o -> { + o.setId(importUser.getDeptId()); + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + }); + when(deptService.getDept(eq(dept.getId()))).thenReturn(dept); + // mock passwordEncoder 的方法 + when(passwordEncoder.encode(eq("yudaoyuanma"))).thenReturn("java"); + + // 调用 + SysUserImportRespVO respVO = userService.importUsers(newArrayList(importUser), true); + // 断言 + assertEquals(1, respVO.getCreateUsernames().size()); + SysUserDO user = userMapper.selectByUsername(respVO.getCreateUsernames().get(0)); + assertPojoEquals(importUser, user); + assertEquals("java", user.getPassword()); + assertEquals(0, respVO.getUpdateUsernames().size()); + assertEquals(0, respVO.getFailureUsernames().size()); + } + + /** + * 情况三,存在,但是不强制更新 + */ + @Test + public void testImportUsers_03() { + // mock 数据 + SysUserDO dbUser = randomSysUserDO(); + userMapper.insert(dbUser); + // 准备参数 + SysUserImportExcelVO importUser = randomPojo(SysUserImportExcelVO.class, o -> { + o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围 + o.setSex(randomEle(SysSexEnum.values()).getSex()); // 保证 sex 的范围 + o.setUsername(dbUser.getUsername()); + }); + // mock deptService 的方法 + SysDeptDO dept = randomPojo(SysDeptDO.class, o -> { + o.setId(importUser.getDeptId()); + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + }); + when(deptService.getDept(eq(dept.getId()))).thenReturn(dept); + + // 调用 + SysUserImportRespVO respVO = userService.importUsers(newArrayList(importUser), false); + // 断言 + assertEquals(0, respVO.getCreateUsernames().size()); + assertEquals(0, respVO.getUpdateUsernames().size()); + assertEquals(1, respVO.getFailureUsernames().size()); + assertEquals(USER_USERNAME_EXISTS.getMsg(), respVO.getFailureUsernames().get(importUser.getUsername())); + } + + /** + * 情况四,存在,强制更新 + */ + @Test + public void testImportUsers_04() { + // mock 数据 + SysUserDO dbUser = randomSysUserDO(); + userMapper.insert(dbUser); + // 准备参数 + SysUserImportExcelVO importUser = randomPojo(SysUserImportExcelVO.class, o -> { + o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围 + o.setSex(randomEle(SysSexEnum.values()).getSex()); // 保证 sex 的范围 + o.setUsername(dbUser.getUsername()); + }); + // mock deptService 的方法 + SysDeptDO dept = randomPojo(SysDeptDO.class, o -> { + o.setId(importUser.getDeptId()); + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + }); + when(deptService.getDept(eq(dept.getId()))).thenReturn(dept); + + // 调用 + SysUserImportRespVO respVO = userService.importUsers(newArrayList(importUser), true); + // 断言 + assertEquals(0, respVO.getCreateUsernames().size()); + assertEquals(1, respVO.getUpdateUsernames().size()); + SysUserDO user = userMapper.selectByUsername(respVO.getUpdateUsernames().get(0)); + assertPojoEquals(importUser, user); + assertEquals(0, respVO.getFailureUsernames().size()); + } + + @Test + public void testCheckUserExists_notExists() { + assertServiceException(() -> userService.checkUserExists(randomLongId()), USER_NOT_EXISTS); + } + + @Test + public void testCheckUsernameUnique_usernameExistsForCreate() { + // 准备参数 + String username = randomString(); + // mock 数据 + userMapper.insert(randomSysUserDO(o -> o.setUsername(username))); + + // 调用,校验异常 + assertServiceException(() -> userService.checkUsernameUnique(null, username), + USER_USERNAME_EXISTS); + } + + @Test + public void testCheckUsernameUnique_usernameExistsForUpdate() { + // 准备参数 + Long id = randomLongId(); + String username = randomString(); + // mock 数据 + userMapper.insert(randomSysUserDO(o -> o.setUsername(username))); + + // 调用,校验异常 + assertServiceException(() -> userService.checkUsernameUnique(id, username), + USER_USERNAME_EXISTS); + } + + @Test + public void testCheckEmailUnique_emailExistsForCreate() { + // 准备参数 + String email = randomString(); + // mock 数据 + userMapper.insert(randomSysUserDO(o -> o.setEmail(email))); + + // 调用,校验异常 + assertServiceException(() -> userService.checkEmailUnique(null, email), + USER_EMAIL_EXISTS); + } + + @Test + public void testCheckEmailUnique_emailExistsForUpdate() { + // 准备参数 + Long id = randomLongId(); + String email = randomString(); + // mock 数据 + userMapper.insert(randomSysUserDO(o -> o.setEmail(email))); + + // 调用,校验异常 + assertServiceException(() -> userService.checkEmailUnique(id, email), + USER_EMAIL_EXISTS); + } + + @Test + public void testCheckMobileUnique_mobileExistsForCreate() { + // 准备参数 + String mobile = randomString(); + // mock 数据 + userMapper.insert(randomSysUserDO(o -> o.setMobile(mobile))); + + // 调用,校验异常 + assertServiceException(() -> userService.checkMobileUnique(null, mobile), + USER_MOBILE_EXISTS); + } + + @Test + public void testCheckMobileUnique_mobileExistsForUpdate() { + // 准备参数 + Long id = randomLongId(); + String mobile = randomString(); + // mock 数据 + userMapper.insert(randomSysUserDO(o -> o.setMobile(mobile))); + + // 调用,校验异常 + assertServiceException(() -> userService.checkMobileUnique(id, mobile), + USER_MOBILE_EXISTS); + } + + @Test + public void testCheckDeptEnable_notFound() { + assertServiceException(() -> userService.checkDeptEnable(randomLongId()), + DEPT_NOT_FOUND); + } + + @Test + public void testCheckDeptEnable_notEnable() { + // 准备参数 + Long deptId = randomLongId(); + // mock deptService 的方法 + SysDeptDO dept = randomPojo(SysDeptDO.class, o -> { + o.setId(deptId); + o.setStatus(CommonStatusEnum.DISABLE.getStatus()); + }); + when(deptService.getDept(eq(dept.getId()))).thenReturn(dept); + + // 调用,校验异常 + assertServiceException(() -> userService.checkDeptEnable(deptId), + DEPT_NOT_ENABLE); + } + + @Test + public void testCheckPostEnable_notFound() { + assertServiceException(() -> userService.checkPostEnable(randomSet(Long.class)), + POST_NOT_FOUND); + } + + @Test + public void testCheckPostEnable_notEnable() { + // 准备参数 + Set postIds = randomSet(Long.class); + // mock postService 的方法 + List posts = CollectionUtils.convertList(postIds, postId -> + randomPojo(SysPostDO.class, o -> { + o.setId(postId); + o.setStatus(CommonStatusEnum.DISABLE.getStatus()); + })); + when(postService.getPosts(eq(postIds), isNull())).thenReturn(posts); + + // 调用,校验异常 + assertServiceException(() -> userService.checkPostEnable(postIds), + POST_NOT_ENABLE, CollUtil.getFirst(posts).getName()); + } + + @Test + public void testCheckOldPassword_notExists() { + assertServiceException(() -> userService.checkOldPassword(randomLongId(), randomString()), + USER_NOT_EXISTS); + } + + @Test + public void testCheckOldPassword_passwordFailed() { + // mock 数据 + SysUserDO user = randomSysUserDO(); + userMapper.insert(user); + // 准备参数 + Long id = user.getId(); + String oldPassword = user.getPassword(); + + // 调用,校验异常 + assertServiceException(() -> userService.checkOldPassword(id, oldPassword), + USER_PASSWORD_FAILED); + // 校验调用 + verify(passwordEncoder, times(1)).matches(eq(oldPassword), eq(user.getPassword())); + } + + // ========== 随机对象 ========== + + @SafeVarargs + private static SysUserDO randomSysUserDO(Consumer... consumers) { + Consumer consumer = (o) -> { + o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围 + o.setSex(randomEle(SysSexEnum.values()).getSex()); // 保证 sex 的范围 + }; + return randomPojo(SysUserDO.class, ArrayUtils.append(consumer, consumers)); + } + +}