Commit 0f608dff7b9d4d7f94b4bbb2a46a5ebb338e410d
1 parent
71b613d6
feat(usr): createUser 唯一性预检 (40002/40003) REQ-USR-001
Showing
2 changed files
with
50 additions
and
1 deletions
backend/src/main/java/com/xly/test4/module/usr/service/impl/UserServiceImpl.java
| 1 | package com.xly.test4.module.usr.service.impl; | 1 | package com.xly.test4.module.usr.service.impl; |
| 2 | 2 | ||
| 3 | +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | ||
| 4 | +import com.xly.test4.common.exception.BusinessException; | ||
| 5 | +import com.xly.test4.common.response.ResultCode; | ||
| 3 | import com.xly.test4.common.security.CurrentUser; | 6 | import com.xly.test4.common.security.CurrentUser; |
| 4 | import com.xly.test4.common.security.CurrentUserContext; | 7 | import com.xly.test4.common.security.CurrentUserContext; |
| 5 | import com.xly.test4.module.usr.converter.UserConverter; | 8 | import com.xly.test4.module.usr.converter.UserConverter; |
| @@ -36,6 +39,15 @@ public class UserServiceImpl implements UserService { | @@ -36,6 +39,15 @@ public class UserServiceImpl implements UserService { | ||
| 36 | public UserCreateVO createUser(UserCreateDTO dto) { | 39 | public UserCreateVO createUser(UserCreateDTO dto) { |
| 37 | CurrentUser current = CurrentUserContext.current(); | 40 | CurrentUser current = CurrentUserContext.current(); |
| 38 | 41 | ||
| 42 | + if (userMapper.selectCount(new LambdaQueryWrapper<User>() | ||
| 43 | + .eq(User::getSUserName, dto.getUserName())) > 0) { | ||
| 44 | + throw new BusinessException(ResultCode.USER_NAME_DUPLICATE, "用户名已存在"); | ||
| 45 | + } | ||
| 46 | + if (userMapper.selectCount(new LambdaQueryWrapper<User>() | ||
| 47 | + .eq(User::getSUserCode, dto.getUserCode())) > 0) { | ||
| 48 | + throw new BusinessException(ResultCode.USER_CODE_DUPLICATE, "用户号已存在"); | ||
| 49 | + } | ||
| 50 | + | ||
| 39 | User user = userConverter.toEntity(dto); | 51 | User user = userConverter.toEntity(dto); |
| 40 | user.setSBrandsId(current.getBrandsId()); | 52 | user.setSBrandsId(current.getBrandsId()); |
| 41 | user.setSSubsidiaryId(current.getSubsidiaryId()); | 53 | user.setSSubsidiaryId(current.getSubsidiaryId()); |
backend/src/test/java/com/xly/test4/module/usr/service/impl/UserServiceImplTest.java
| 1 | package com.xly.test4.module.usr.service.impl; | 1 | package com.xly.test4.module.usr.service.impl; |
| 2 | 2 | ||
| 3 | +import com.baomidou.mybatisplus.core.conditions.Wrapper; | ||
| 4 | +import com.xly.test4.common.exception.BusinessException; | ||
| 3 | import com.xly.test4.common.security.CurrentUser; | 5 | import com.xly.test4.common.security.CurrentUser; |
| 4 | import com.xly.test4.common.security.CurrentUserContext; | 6 | import com.xly.test4.common.security.CurrentUserContext; |
| 5 | import com.xly.test4.module.usr.converter.UserConverter; | 7 | import com.xly.test4.module.usr.converter.UserConverter; |
| @@ -18,9 +20,12 @@ import org.springframework.security.crypto.password.PasswordEncoder; | @@ -18,9 +20,12 @@ import org.springframework.security.crypto.password.PasswordEncoder; | ||
| 18 | import java.util.List; | 20 | import java.util.List; |
| 19 | 21 | ||
| 20 | import static org.assertj.core.api.Assertions.assertThat; | 22 | import static org.assertj.core.api.Assertions.assertThat; |
| 23 | +import static org.assertj.core.api.Assertions.assertThatThrownBy; | ||
| 21 | import static org.mockito.ArgumentMatchers.any; | 24 | import static org.mockito.ArgumentMatchers.any; |
| 22 | import static org.mockito.Mockito.mock; | 25 | import static org.mockito.Mockito.mock; |
| 23 | import static org.mockito.Mockito.mockStatic; | 26 | import static org.mockito.Mockito.mockStatic; |
| 27 | +import static org.mockito.Mockito.never; | ||
| 28 | +import static org.mockito.Mockito.verify; | ||
| 24 | import static org.mockito.Mockito.when; | 29 | import static org.mockito.Mockito.when; |
| 25 | 30 | ||
| 26 | class UserServiceImplTest { | 31 | class UserServiceImplTest { |
| @@ -64,6 +69,7 @@ class UserServiceImplTest { | @@ -64,6 +69,7 @@ class UserServiceImplTest { | ||
| 64 | return dto; | 69 | return dto; |
| 65 | } | 70 | } |
| 66 | 71 | ||
| 72 | + @SuppressWarnings("unchecked") | ||
| 67 | private void stubConverterReturnsEmptyUser() { | 73 | private void stubConverterReturnsEmptyUser() { |
| 68 | when(userConverter.toEntity(any(UserCreateDTO.class))).thenAnswer(inv -> new User()); | 74 | when(userConverter.toEntity(any(UserCreateDTO.class))).thenAnswer(inv -> new User()); |
| 69 | when(userConverter.toVO(any(User.class))).thenAnswer(inv -> { | 75 | when(userConverter.toVO(any(User.class))).thenAnswer(inv -> { |
| @@ -75,6 +81,7 @@ class UserServiceImplTest { | @@ -75,6 +81,7 @@ class UserServiceImplTest { | ||
| 75 | u.setIIncrement(42); | 81 | u.setIIncrement(42); |
| 76 | return 1; | 82 | return 1; |
| 77 | }); | 83 | }); |
| 84 | + when(userMapper.selectCount(any(Wrapper.class))).thenReturn(0L); | ||
| 78 | } | 85 | } |
| 79 | 86 | ||
| 80 | @Test | 87 | @Test |
| @@ -110,7 +117,37 @@ class UserServiceImplTest { | @@ -110,7 +117,37 @@ class UserServiceImplTest { | ||
| 110 | service.createUser(dto); | 117 | service.createUser(dto); |
| 111 | 118 | ||
| 112 | ArgumentCaptor<User> captor = ArgumentCaptor.forClass(User.class); | 119 | ArgumentCaptor<User> captor = ArgumentCaptor.forClass(User.class); |
| 113 | - org.mockito.Mockito.verify(userMapper).insert(captor.capture()); | 120 | + verify(userMapper).insert(captor.capture()); |
| 114 | assertThat(passwordEncoder.matches("666666", captor.getValue().getSPasswordHash())).isTrue(); | 121 | assertThat(passwordEncoder.matches("666666", captor.getValue().getSPasswordHash())).isTrue(); |
| 115 | } | 122 | } |
| 123 | + | ||
| 124 | + @Test | ||
| 125 | + @SuppressWarnings("unchecked") | ||
| 126 | + void createUser_duplicateUserName_throws40002() { | ||
| 127 | + stubConverterReturnsEmptyUser(); | ||
| 128 | + // 第一次 selectCount(对 sUserName) 返回 > 0 → 触发 40002 | ||
| 129 | + when(userMapper.selectCount(any(Wrapper.class))).thenReturn(1L); | ||
| 130 | + | ||
| 131 | + assertThatThrownBy(() -> service.createUser(baseDTO())) | ||
| 132 | + .isInstanceOf(BusinessException.class) | ||
| 133 | + .matches(e -> ((BusinessException) e).getCode() == 40002); | ||
| 134 | + | ||
| 135 | + verify(userMapper, never()).insert(any(User.class)); | ||
| 136 | + } | ||
| 137 | + | ||
| 138 | + @Test | ||
| 139 | + @SuppressWarnings("unchecked") | ||
| 140 | + void createUser_duplicateUserCode_throws40003() { | ||
| 141 | + stubConverterReturnsEmptyUser(); | ||
| 142 | + // 第一次 selectCount(userName)=0,第二次(userCode)=1 | ||
| 143 | + when(userMapper.selectCount(any(Wrapper.class))) | ||
| 144 | + .thenReturn(0L) | ||
| 145 | + .thenReturn(1L); | ||
| 146 | + | ||
| 147 | + assertThatThrownBy(() -> service.createUser(baseDTO())) | ||
| 148 | + .isInstanceOf(BusinessException.class) | ||
| 149 | + .matches(e -> ((BusinessException) e).getCode() == 40003); | ||
| 150 | + | ||
| 151 | + verify(userMapper, never()).insert(any(User.class)); | ||
| 152 | + } | ||
| 116 | } | 153 | } |