Commit c74ab3c0bbcc3593670a1afaaae8e5e45eee7b48
1 parent
9c6dcd52
feat(usr): login service + dto/vo REQ-USR-004
Showing
6 changed files
with
268 additions
and
2 deletions
backend/src/main/java/com/xly/erp/module/usr/dto/LoginDTO.java
0 → 100644
| 1 | +package com.xly.erp.module.usr.dto; | ||
| 2 | + | ||
| 3 | +import com.fasterxml.jackson.annotation.JsonProperty; | ||
| 4 | +import jakarta.validation.constraints.NotBlank; | ||
| 5 | + | ||
| 6 | +public class LoginDTO { | ||
| 7 | + | ||
| 8 | + @JsonProperty("sUserName") | ||
| 9 | + @NotBlank | ||
| 10 | + private String sUserName; | ||
| 11 | + | ||
| 12 | + @JsonProperty("password") | ||
| 13 | + @NotBlank | ||
| 14 | + private String password; | ||
| 15 | + | ||
| 16 | + @JsonProperty("version") | ||
| 17 | + @NotBlank | ||
| 18 | + private String version; | ||
| 19 | + | ||
| 20 | + public String getSUserName() { return sUserName; } | ||
| 21 | + public void setSUserName(String sUserName) { this.sUserName = sUserName; } | ||
| 22 | + public String getPassword() { return password; } | ||
| 23 | + public void setPassword(String password) { this.password = password; } | ||
| 24 | + public String getVersion() { return version; } | ||
| 25 | + public void setVersion(String version) { this.version = version; } | ||
| 26 | +} |
backend/src/main/java/com/xly/erp/module/usr/service/UserService.java
| 1 | package com.xly.erp.module.usr.service; | 1 | package com.xly.erp.module.usr.service; |
| 2 | 2 | ||
| 3 | import com.xly.erp.module.usr.dto.CreateUserDTO; | 3 | import com.xly.erp.module.usr.dto.CreateUserDTO; |
| 4 | +import com.xly.erp.module.usr.dto.LoginDTO; | ||
| 4 | import com.xly.erp.module.usr.dto.UpdateUserDTO; | 5 | import com.xly.erp.module.usr.dto.UpdateUserDTO; |
| 6 | +import com.xly.erp.module.usr.vo.LoginVO; | ||
| 5 | 7 | ||
| 6 | import java.util.Map; | 8 | import java.util.Map; |
| 7 | 9 | ||
| @@ -11,4 +13,6 @@ public interface UserService { | @@ -11,4 +13,6 @@ public interface UserService { | ||
| 11 | Integer update(Integer id, UpdateUserDTO dto); | 13 | Integer update(Integer id, UpdateUserDTO dto); |
| 12 | 14 | ||
| 13 | Map<String, Object> list(String field, String match, String value, Integer pageNum, Integer pageSize); | 15 | Map<String, Object> list(String field, String match, String value, Integer pageNum, Integer pageSize); |
| 16 | + | ||
| 17 | + LoginVO login(LoginDTO dto); | ||
| 14 | } | 18 | } |
backend/src/main/java/com/xly/erp/module/usr/service/impl/UserServiceImpl.java
| @@ -3,10 +3,15 @@ package com.xly.erp.module.usr.service.impl; | @@ -3,10 +3,15 @@ package com.xly.erp.module.usr.service.impl; | ||
| 3 | import com.xly.erp.common.config.StubSecurityProperties; | 3 | import com.xly.erp.common.config.StubSecurityProperties; |
| 4 | import com.xly.erp.common.config.TenantProperties; | 4 | import com.xly.erp.common.config.TenantProperties; |
| 5 | import com.xly.erp.common.exception.BizException; | 5 | import com.xly.erp.common.exception.BizException; |
| 6 | +import com.xly.erp.common.security.JwtUtil; | ||
| 6 | import com.xly.erp.common.security.SecurityContextHelper; | 7 | import com.xly.erp.common.security.SecurityContextHelper; |
| 7 | import com.xly.erp.module.usr.dto.CreateUserDTO; | 8 | import com.xly.erp.module.usr.dto.CreateUserDTO; |
| 9 | +import com.xly.erp.module.usr.dto.LoginDTO; | ||
| 8 | import com.xly.erp.module.usr.dto.UpdateUserDTO; | 10 | import com.xly.erp.module.usr.dto.UpdateUserDTO; |
| 9 | import com.xly.erp.module.usr.entity.User; | 11 | import com.xly.erp.module.usr.entity.User; |
| 12 | +import com.xly.erp.module.usr.security.LoginAttemptStore; | ||
| 13 | +import com.xly.erp.module.usr.vo.LoginVO; | ||
| 14 | +import com.xly.erp.module.usr.vo.UserBriefVO; | ||
| 10 | import com.xly.erp.module.usr.vo.UserListVO; | 15 | import com.xly.erp.module.usr.vo.UserListVO; |
| 11 | import com.xly.erp.module.usr.entity.UserPermission; | 16 | import com.xly.erp.module.usr.entity.UserPermission; |
| 12 | import com.xly.erp.module.usr.mapper.PermissionCategoryMapper; | 17 | import com.xly.erp.module.usr.mapper.PermissionCategoryMapper; |
| @@ -66,6 +71,8 @@ public class UserServiceImpl implements UserService { | @@ -66,6 +71,8 @@ public class UserServiceImpl implements UserService { | ||
| 66 | private final TenantProperties tenant; | 71 | private final TenantProperties tenant; |
| 67 | private final StubSecurityProperties stub; | 72 | private final StubSecurityProperties stub; |
| 68 | private final BCryptPasswordEncoder passwordEncoder; | 73 | private final BCryptPasswordEncoder passwordEncoder; |
| 74 | + private final JwtUtil jwtUtil; | ||
| 75 | + private final LoginAttemptStore loginAttemptStore; | ||
| 69 | 76 | ||
| 70 | public UserServiceImpl(UserMapper userMapper, | 77 | public UserServiceImpl(UserMapper userMapper, |
| 71 | UserPermissionMapper userPermissionMapper, | 78 | UserPermissionMapper userPermissionMapper, |
| @@ -73,7 +80,9 @@ public class UserServiceImpl implements UserService { | @@ -73,7 +80,9 @@ public class UserServiceImpl implements UserService { | ||
| 73 | PermissionCategoryMapper permissionCategoryMapper, | 80 | PermissionCategoryMapper permissionCategoryMapper, |
| 74 | TenantProperties tenant, | 81 | TenantProperties tenant, |
| 75 | StubSecurityProperties stub, | 82 | StubSecurityProperties stub, |
| 76 | - BCryptPasswordEncoder passwordEncoder) { | 83 | + BCryptPasswordEncoder passwordEncoder, |
| 84 | + JwtUtil jwtUtil, | ||
| 85 | + LoginAttemptStore loginAttemptStore) { | ||
| 77 | this.userMapper = userMapper; | 86 | this.userMapper = userMapper; |
| 78 | this.userPermissionMapper = userPermissionMapper; | 87 | this.userPermissionMapper = userPermissionMapper; |
| 79 | this.staffMapper = staffMapper; | 88 | this.staffMapper = staffMapper; |
| @@ -81,6 +90,8 @@ public class UserServiceImpl implements UserService { | @@ -81,6 +90,8 @@ public class UserServiceImpl implements UserService { | ||
| 81 | this.tenant = tenant; | 90 | this.tenant = tenant; |
| 82 | this.stub = stub; | 91 | this.stub = stub; |
| 83 | this.passwordEncoder = passwordEncoder; | 92 | this.passwordEncoder = passwordEncoder; |
| 93 | + this.jwtUtil = jwtUtil; | ||
| 94 | + this.loginAttemptStore = loginAttemptStore; | ||
| 84 | } | 95 | } |
| 85 | 96 | ||
| 86 | @Override | 97 | @Override |
| @@ -257,4 +268,43 @@ public class UserServiceImpl implements UserService { | @@ -257,4 +268,43 @@ public class UserServiceImpl implements UserService { | ||
| 257 | default -> -1; | 268 | default -> -1; |
| 258 | }; | 269 | }; |
| 259 | } | 270 | } |
| 271 | + | ||
| 272 | + @Override | ||
| 273 | + public LoginVO login(LoginDTO dto) { | ||
| 274 | + String userName = dto.getSUserName(); | ||
| 275 | + loginAttemptStore.isLocked(userName).ifPresent(seconds -> { | ||
| 276 | + throw new BizException(42301, "账号临时锁定,剩余 " + seconds + " 秒"); | ||
| 277 | + }); | ||
| 278 | + User user = userMapper.selectByUserName(userName); | ||
| 279 | + if (user == null) { | ||
| 280 | + throw new BizException(40101, "用户名或密码错误"); | ||
| 281 | + } | ||
| 282 | + if (Boolean.TRUE.equals(user.getBDeleted())) { | ||
| 283 | + throw new BizException(40102, "账号已禁用"); | ||
| 284 | + } | ||
| 285 | + if (!passwordEncoder.matches(dto.getPassword(), user.getSPasswordHash())) { | ||
| 286 | + int newCount = loginAttemptStore.recordFailure(userName); | ||
| 287 | + if (newCount >= LoginAttemptStore.MAX_ATTEMPTS) { | ||
| 288 | + long remaining = loginAttemptStore.isLocked(userName).orElse(0L); | ||
| 289 | + throw new BizException(42301, "账号临时锁定,剩余 " + remaining + " 秒"); | ||
| 290 | + } | ||
| 291 | + throw new BizException(40101, "用户名或密码错误"); | ||
| 292 | + } | ||
| 293 | + loginAttemptStore.clearFailures(userName); | ||
| 294 | + userMapper.updateLastLoginDate(user.getIIncrement(), LocalDateTime.now()); | ||
| 295 | + | ||
| 296 | + UserBriefVO brief = new UserBriefVO(); | ||
| 297 | + brief.setIIncrement(user.getIIncrement()); | ||
| 298 | + brief.setSUserNo(user.getSUserNo()); | ||
| 299 | + brief.setSUserName(user.getSUserName()); | ||
| 300 | + brief.setSUserType(user.getSUserType()); | ||
| 301 | + brief.setSLanguage(user.getSLanguage()); | ||
| 302 | + | ||
| 303 | + LoginVO vo = new LoginVO(); | ||
| 304 | + vo.setAccessToken(jwtUtil.sign(user.getSUserNo())); | ||
| 305 | + vo.setRefreshToken(jwtUtil.signRefresh(user.getSUserNo())); | ||
| 306 | + vo.setExpiresIn(JwtUtil.ACCESS_TTL.toSeconds()); | ||
| 307 | + vo.setUser(brief); | ||
| 308 | + return vo; | ||
| 309 | + } | ||
| 260 | } | 310 | } |
backend/src/main/java/com/xly/erp/module/usr/vo/LoginVO.java
0 → 100644
| 1 | +package com.xly.erp.module.usr.vo; | ||
| 2 | + | ||
| 3 | +import com.fasterxml.jackson.annotation.JsonProperty; | ||
| 4 | + | ||
| 5 | +public class LoginVO { | ||
| 6 | + | ||
| 7 | + @JsonProperty("accessToken") | ||
| 8 | + private String accessToken; | ||
| 9 | + | ||
| 10 | + @JsonProperty("refreshToken") | ||
| 11 | + private String refreshToken; | ||
| 12 | + | ||
| 13 | + @JsonProperty("expiresIn") | ||
| 14 | + private long expiresIn; | ||
| 15 | + | ||
| 16 | + @JsonProperty("user") | ||
| 17 | + private UserBriefVO user; | ||
| 18 | + | ||
| 19 | + public String getAccessToken() { return accessToken; } | ||
| 20 | + public void setAccessToken(String accessToken) { this.accessToken = accessToken; } | ||
| 21 | + public String getRefreshToken() { return refreshToken; } | ||
| 22 | + public void setRefreshToken(String refreshToken) { this.refreshToken = refreshToken; } | ||
| 23 | + public long getExpiresIn() { return expiresIn; } | ||
| 24 | + public void setExpiresIn(long expiresIn) { this.expiresIn = expiresIn; } | ||
| 25 | + public UserBriefVO getUser() { return user; } | ||
| 26 | + public void setUser(UserBriefVO user) { this.user = user; } | ||
| 27 | +} |
backend/src/main/java/com/xly/erp/module/usr/vo/UserBriefVO.java
0 → 100644
| 1 | +package com.xly.erp.module.usr.vo; | ||
| 2 | + | ||
| 3 | +import com.fasterxml.jackson.annotation.JsonProperty; | ||
| 4 | + | ||
| 5 | +public class UserBriefVO { | ||
| 6 | + | ||
| 7 | + @JsonProperty("iIncrement") | ||
| 8 | + private Integer iIncrement; | ||
| 9 | + | ||
| 10 | + @JsonProperty("sUserNo") | ||
| 11 | + private String sUserNo; | ||
| 12 | + | ||
| 13 | + @JsonProperty("sUserName") | ||
| 14 | + private String sUserName; | ||
| 15 | + | ||
| 16 | + @JsonProperty("sUserType") | ||
| 17 | + private String sUserType; | ||
| 18 | + | ||
| 19 | + @JsonProperty("sLanguage") | ||
| 20 | + private String sLanguage; | ||
| 21 | + | ||
| 22 | + public Integer getIIncrement() { return iIncrement; } | ||
| 23 | + public void setIIncrement(Integer iIncrement) { this.iIncrement = iIncrement; } | ||
| 24 | + public String getSUserNo() { return sUserNo; } | ||
| 25 | + public void setSUserNo(String sUserNo) { this.sUserNo = sUserNo; } | ||
| 26 | + public String getSUserName() { return sUserName; } | ||
| 27 | + public void setSUserName(String sUserName) { this.sUserName = sUserName; } | ||
| 28 | + public String getSUserType() { return sUserType; } | ||
| 29 | + public void setSUserType(String sUserType) { this.sUserType = sUserType; } | ||
| 30 | + public String getSLanguage() { return sLanguage; } | ||
| 31 | + public void setSLanguage(String sLanguage) { this.sLanguage = sLanguage; } | ||
| 32 | +} |
backend/src/test/java/com/xly/erp/module/usr/service/UserServiceImplTest.java
| @@ -3,9 +3,13 @@ package com.xly.erp.module.usr.service; | @@ -3,9 +3,13 @@ package com.xly.erp.module.usr.service; | ||
| 3 | import com.xly.erp.common.config.StubSecurityProperties; | 3 | import com.xly.erp.common.config.StubSecurityProperties; |
| 4 | import com.xly.erp.common.config.TenantProperties; | 4 | import com.xly.erp.common.config.TenantProperties; |
| 5 | import com.xly.erp.common.exception.BizException; | 5 | import com.xly.erp.common.exception.BizException; |
| 6 | +import com.xly.erp.common.security.JwtUtil; | ||
| 6 | import com.xly.erp.module.usr.dto.CreateUserDTO; | 7 | import com.xly.erp.module.usr.dto.CreateUserDTO; |
| 8 | +import com.xly.erp.module.usr.dto.LoginDTO; | ||
| 7 | import com.xly.erp.module.usr.dto.UpdateUserDTO; | 9 | import com.xly.erp.module.usr.dto.UpdateUserDTO; |
| 8 | import com.xly.erp.module.usr.entity.User; | 10 | import com.xly.erp.module.usr.entity.User; |
| 11 | +import com.xly.erp.module.usr.security.LoginAttemptStore; | ||
| 12 | +import com.xly.erp.module.usr.vo.LoginVO; | ||
| 9 | import com.xly.erp.module.usr.entity.UserPermission; | 13 | import com.xly.erp.module.usr.entity.UserPermission; |
| 10 | import com.xly.erp.module.usr.mapper.PermissionCategoryMapper; | 14 | import com.xly.erp.module.usr.mapper.PermissionCategoryMapper; |
| 11 | import com.xly.erp.module.usr.mapper.StaffMapper; | 15 | import com.xly.erp.module.usr.mapper.StaffMapper; |
| @@ -45,6 +49,8 @@ class UserServiceImplTest { | @@ -45,6 +49,8 @@ class UserServiceImplTest { | ||
| 45 | private StaffMapper staffMapper; | 49 | private StaffMapper staffMapper; |
| 46 | private PermissionCategoryMapper permissionCategoryMapper; | 50 | private PermissionCategoryMapper permissionCategoryMapper; |
| 47 | private BCryptPasswordEncoder encoder; | 51 | private BCryptPasswordEncoder encoder; |
| 52 | + private JwtUtil jwtUtil; | ||
| 53 | + private LoginAttemptStore loginAttemptStore; | ||
| 48 | private UserServiceImpl service; | 54 | private UserServiceImpl service; |
| 49 | 55 | ||
| 50 | @BeforeEach | 56 | @BeforeEach |
| @@ -54,6 +60,8 @@ class UserServiceImplTest { | @@ -54,6 +60,8 @@ class UserServiceImplTest { | ||
| 54 | staffMapper = mock(StaffMapper.class); | 60 | staffMapper = mock(StaffMapper.class); |
| 55 | permissionCategoryMapper = mock(PermissionCategoryMapper.class); | 61 | permissionCategoryMapper = mock(PermissionCategoryMapper.class); |
| 56 | encoder = new BCryptPasswordEncoder(); | 62 | encoder = new BCryptPasswordEncoder(); |
| 63 | + jwtUtil = new JwtUtil("f8d4be76bff13bf32fa33ca0b14a4b152ad01ca5719f57df18ec4ecf2370b235"); | ||
| 64 | + loginAttemptStore = new LoginAttemptStore(); | ||
| 57 | TenantProperties tenant = new TenantProperties(); | 65 | TenantProperties tenant = new TenantProperties(); |
| 58 | tenant.setBrandsId("XLY"); | 66 | tenant.setBrandsId("XLY"); |
| 59 | tenant.setSubsidiaryId("XLY"); | 67 | tenant.setSubsidiaryId("XLY"); |
| @@ -61,7 +69,7 @@ class UserServiceImplTest { | @@ -61,7 +69,7 @@ class UserServiceImplTest { | ||
| 61 | stub.setStubUserNo("STUB_ADMIN"); | 69 | stub.setStubUserNo("STUB_ADMIN"); |
| 62 | 70 | ||
| 63 | service = new UserServiceImpl(userMapper, userPermissionMapper, staffMapper, | 71 | service = new UserServiceImpl(userMapper, userPermissionMapper, staffMapper, |
| 64 | - permissionCategoryMapper, tenant, stub, encoder); | 72 | + permissionCategoryMapper, tenant, stub, encoder, jwtUtil, loginAttemptStore); |
| 65 | 73 | ||
| 66 | lenient().when(userMapper.insert(any(User.class))).thenAnswer(inv -> { | 74 | lenient().when(userMapper.insert(any(User.class))).thenAnswer(inv -> { |
| 67 | User u = inv.getArgument(0); | 75 | User u = inv.getArgument(0); |
| @@ -407,6 +415,125 @@ class UserServiceImplTest { | @@ -407,6 +415,125 @@ class UserServiceImplTest { | ||
| 407 | verify(userMapper).pageWithFilter(eq("u.bDeleted"), eq("equals"), eq(1), eq(0), eq(20)); | 415 | verify(userMapper).pageWithFilter(eq("u.bDeleted"), eq("equals"), eq(1), eq(0), eq(20)); |
| 408 | } | 416 | } |
| 409 | 417 | ||
| 418 | + @Test | ||
| 419 | + void loginWithValidCredentials_returnsTokens_andUpdatesLastLoginDate() { | ||
| 420 | + User u = new User(); | ||
| 421 | + u.setIIncrement(100); | ||
| 422 | + u.setSUserNo("u100"); | ||
| 423 | + u.setSUserName("login_ok"); | ||
| 424 | + u.setSUserType("普通用户"); | ||
| 425 | + u.setSLanguage("zh"); | ||
| 426 | + u.setSPasswordHash(encoder.encode("666666")); | ||
| 427 | + u.setBDeleted(false); | ||
| 428 | + when(userMapper.selectByUserName("login_ok")).thenReturn(u); | ||
| 429 | + | ||
| 430 | + LoginDTO dto = new LoginDTO(); | ||
| 431 | + dto.setSUserName("login_ok"); | ||
| 432 | + dto.setPassword("666666"); | ||
| 433 | + dto.setVersion("标准版"); | ||
| 434 | + | ||
| 435 | + LoginVO vo = service.login(dto); | ||
| 436 | + | ||
| 437 | + assertThat(vo.getAccessToken()).isNotBlank(); | ||
| 438 | + assertThat(vo.getRefreshToken()).isNotBlank(); | ||
| 439 | + assertThat(vo.getRefreshToken()).isNotEqualTo(vo.getAccessToken()); | ||
| 440 | + assertThat(vo.getExpiresIn()).isEqualTo(28800); | ||
| 441 | + assertThat(vo.getUser().getIIncrement()).isEqualTo(100); | ||
| 442 | + assertThat(vo.getUser().getSUserNo()).isEqualTo("u100"); | ||
| 443 | + verify(userMapper).updateLastLoginDate(eq(100), any()); | ||
| 444 | + } | ||
| 445 | + | ||
| 446 | + @Test | ||
| 447 | + void loginWithUserNotFound_throws40101() { | ||
| 448 | + when(userMapper.selectByUserName("ghost")).thenReturn(null); | ||
| 449 | + LoginDTO dto = loginDto("ghost", "x"); | ||
| 450 | + assertThatThrownBy(() -> service.login(dto)) | ||
| 451 | + .isInstanceOf(BizException.class) | ||
| 452 | + .hasFieldOrPropertyWithValue("code", 40101); | ||
| 453 | + } | ||
| 454 | + | ||
| 455 | + @Test | ||
| 456 | + void loginWithDeletedUser_throws40102() { | ||
| 457 | + User u = stubLoginUser("dead"); | ||
| 458 | + u.setBDeleted(true); | ||
| 459 | + when(userMapper.selectByUserName("dead")).thenReturn(u); | ||
| 460 | + LoginDTO dto = loginDto("dead", "666666"); | ||
| 461 | + assertThatThrownBy(() -> service.login(dto)) | ||
| 462 | + .isInstanceOf(BizException.class) | ||
| 463 | + .hasFieldOrPropertyWithValue("code", 40102); | ||
| 464 | + } | ||
| 465 | + | ||
| 466 | + @Test | ||
| 467 | + void loginWithWrongPassword_incrementsCounter_throws40101() { | ||
| 468 | + when(userMapper.selectByUserName("u_wrong")).thenReturn(stubLoginUser("u_wrong")); | ||
| 469 | + LoginDTO dto = loginDto("u_wrong", "wrong"); | ||
| 470 | + assertThatThrownBy(() -> service.login(dto)) | ||
| 471 | + .isInstanceOf(BizException.class) | ||
| 472 | + .hasFieldOrPropertyWithValue("code", 40101); | ||
| 473 | + } | ||
| 474 | + | ||
| 475 | + @Test | ||
| 476 | + void loginAfterMaxAttemptsReached_throws42301() { | ||
| 477 | + when(userMapper.selectByUserName("u_lock")).thenReturn(stubLoginUser("u_lock")); | ||
| 478 | + LoginDTO dto = loginDto("u_lock", "wrong"); | ||
| 479 | + for (int i = 0; i < LoginAttemptStore.MAX_ATTEMPTS - 1; i++) { | ||
| 480 | + try { service.login(dto); } catch (BizException ignored) {} | ||
| 481 | + } | ||
| 482 | + // 第 5 次失败应触发锁定 → 42301 | ||
| 483 | + assertThatThrownBy(() -> service.login(dto)) | ||
| 484 | + .isInstanceOf(BizException.class) | ||
| 485 | + .hasFieldOrPropertyWithValue("code", 42301); | ||
| 486 | + } | ||
| 487 | + | ||
| 488 | + @Test | ||
| 489 | + void loginWhileLocked_throws42301() { | ||
| 490 | + when(userMapper.selectByUserName("u_locked2")).thenReturn(stubLoginUser("u_locked2")); | ||
| 491 | + LoginDTO bad = loginDto("u_locked2", "wrong"); | ||
| 492 | + for (int i = 0; i < LoginAttemptStore.MAX_ATTEMPTS; i++) { | ||
| 493 | + try { service.login(bad); } catch (BizException ignored) {} | ||
| 494 | + } | ||
| 495 | + // 锁定后即使密码正确也走 42301 | ||
| 496 | + LoginDTO good = loginDto("u_locked2", "666666"); | ||
| 497 | + assertThatThrownBy(() -> service.login(good)) | ||
| 498 | + .isInstanceOf(BizException.class) | ||
| 499 | + .hasFieldOrPropertyWithValue("code", 42301); | ||
| 500 | + } | ||
| 501 | + | ||
| 502 | + @Test | ||
| 503 | + void loginSuccess_clearsFailureCounter() { | ||
| 504 | + when(userMapper.selectByUserName("u_clear")).thenReturn(stubLoginUser("u_clear")); | ||
| 505 | + try { service.login(loginDto("u_clear", "wrong")); } catch (BizException ignored) {} | ||
| 506 | + try { service.login(loginDto("u_clear", "wrong")); } catch (BizException ignored) {} | ||
| 507 | + // 现在 2 次失败;正确登录后清空 | ||
| 508 | + service.login(loginDto("u_clear", "666666")); | ||
| 509 | + // 之后再 4 次错误应不锁(计数已重置) | ||
| 510 | + for (int i = 0; i < LoginAttemptStore.MAX_ATTEMPTS - 1; i++) { | ||
| 511 | + try { service.login(loginDto("u_clear", "wrong")); } catch (BizException ignored) {} | ||
| 512 | + } | ||
| 513 | + // MAX-1 次错误后仍未锁 | ||
| 514 | + assertThat(loginAttemptStore.isLocked("u_clear")).isEmpty(); | ||
| 515 | + } | ||
| 516 | + | ||
| 517 | + private LoginDTO loginDto(String userName, String password) { | ||
| 518 | + LoginDTO dto = new LoginDTO(); | ||
| 519 | + dto.setSUserName(userName); | ||
| 520 | + dto.setPassword(password); | ||
| 521 | + dto.setVersion("标准版"); | ||
| 522 | + return dto; | ||
| 523 | + } | ||
| 524 | + | ||
| 525 | + private User stubLoginUser(String userName) { | ||
| 526 | + User u = new User(); | ||
| 527 | + u.setIIncrement(200); | ||
| 528 | + u.setSUserNo("u200"); | ||
| 529 | + u.setSUserName(userName); | ||
| 530 | + u.setSUserType("普通用户"); | ||
| 531 | + u.setSLanguage("zh"); | ||
| 532 | + u.setSPasswordHash(encoder.encode("666666")); | ||
| 533 | + u.setBDeleted(false); | ||
| 534 | + return u; | ||
| 535 | + } | ||
| 536 | + | ||
| 410 | private UpdateUserDTO baseUpdateDto() { | 537 | private UpdateUserDTO baseUpdateDto() { |
| 411 | UpdateUserDTO dto = new UpdateUserDTO(); | 538 | UpdateUserDTO dto = new UpdateUserDTO(); |
| 412 | dto.setSUserNo("u_new"); | 539 | dto.setSUserNo("u_new"); |