diff --git a/backend/pom.xml b/backend/pom.xml index ffbee2a..f20f97e 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -82,6 +82,25 @@ ${hutool.version} + + + io.jsonwebtoken + jjwt-api + 0.12.6 + + + io.jsonwebtoken + jjwt-impl + 0.12.6 + runtime + + + io.jsonwebtoken + jjwt-jackson + 0.12.6 + runtime + + org.springframework.boot spring-boot-starter-test diff --git a/backend/src/main/java/com/xly/erp/common/response/ErrorCode.java b/backend/src/main/java/com/xly/erp/common/response/ErrorCode.java index 5ef5226..5e01a3f 100644 --- a/backend/src/main/java/com/xly/erp/common/response/ErrorCode.java +++ b/backend/src/main/java/com/xly/erp/common/response/ErrorCode.java @@ -6,6 +6,8 @@ import lombok.Getter; public enum ErrorCode { SUCCESS(200, "操作成功"), PARAM_INVALID(40010, "参数错误"), + LOGIN_INVALID_CREDENTIALS(40101, "用户名或密码错误"), + LOGIN_ACCOUNT_LOCKED(40301, "账号已临时锁定"), MOD_PARENT_NOT_FOUND(40411, "父模块不存在或已删除"), MOD_NOT_FOUND(40421, "模块不存在或已删除"), STAFF_NOT_FOUND(40421, "职员不存在或已删除"), diff --git a/backend/src/main/java/com/xly/erp/module/usr/dto/LoginDTO.java b/backend/src/main/java/com/xly/erp/module/usr/dto/LoginDTO.java new file mode 100644 index 0000000..468202c --- /dev/null +++ b/backend/src/main/java/com/xly/erp/module/usr/dto/LoginDTO.java @@ -0,0 +1,23 @@ +package com.xly.erp.module.usr.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import lombok.Data; + +/** REQ-USR-004 用户登录入参 */ +@Data +public class LoginDTO { + + @NotBlank + @Size(max = 50) + private String sUserName; + + @NotBlank + @Size(max = 100) + private String sPassword; + + @NotBlank + @Pattern(regexp = "^standard$", message = "sVersion 仅支持 standard") + private String sVersion; +} diff --git a/backend/src/main/java/com/xly/erp/module/usr/vo/LoginResultVO.java b/backend/src/main/java/com/xly/erp/module/usr/vo/LoginResultVO.java new file mode 100644 index 0000000..ba65a76 --- /dev/null +++ b/backend/src/main/java/com/xly/erp/module/usr/vo/LoginResultVO.java @@ -0,0 +1,26 @@ +package com.xly.erp.module.usr.vo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** REQ-USR-004 登录结果 VO(含 JWT + 用户基本信息) */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class LoginResultVO { + private String accessToken; + private long expiresIn; + private LoginUserInfo user; + + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class LoginUserInfo { + private Integer iIncrement; + private String sUserNo; + private String sUserName; + private String sUserType; + private String sLanguage; + } +} diff --git a/backend/src/test/java/com/xly/erp/common/response/ApiResponseTest.java b/backend/src/test/java/com/xly/erp/common/response/ApiResponseTest.java index f2fa1c3..5239297 100644 --- a/backend/src/test/java/com/xly/erp/common/response/ApiResponseTest.java +++ b/backend/src/test/java/com/xly/erp/common/response/ApiResponseTest.java @@ -55,5 +55,7 @@ class ApiResponseTest { assertThat(ErrorCode.PERM_CATEGORY_NOT_FOUND.getCode()).isEqualTo(40422); assertThat(ErrorCode.USR_USER_NAME_OR_NO_DUP.getCode()).isEqualTo(40921); assertThat(ErrorCode.USR_NOT_FOUND.getCode()).isEqualTo(40431); + assertThat(ErrorCode.LOGIN_INVALID_CREDENTIALS.getCode()).isEqualTo(40101); + assertThat(ErrorCode.LOGIN_ACCOUNT_LOCKED.getCode()).isEqualTo(40301); } } diff --git a/backend/src/test/java/com/xly/erp/module/usr/dto/LoginDTOValidationTest.java b/backend/src/test/java/com/xly/erp/module/usr/dto/LoginDTOValidationTest.java new file mode 100644 index 0000000..fb98d41 --- /dev/null +++ b/backend/src/test/java/com/xly/erp/module/usr/dto/LoginDTOValidationTest.java @@ -0,0 +1,57 @@ +package com.xly.erp.module.usr.dto; + +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import jakarta.validation.ValidatorFactory; +import org.junit.jupiter.api.Test; + +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; + +class LoginDTOValidationTest { + + private static final ValidatorFactory FACTORY = Validation.buildDefaultValidatorFactory(); + private final Validator validator = FACTORY.getValidator(); + + private LoginDTO valid() { + LoginDTO d = new LoginDTO(); + d.setSUserName("alice"); + d.setSPassword("666666"); + d.setSVersion("standard"); + return d; + } + + @Test + void allValid_yieldsNoViolations() { + Set> v = validator.validate(valid()); + assertThat(v).isEmpty(); + } + + @Test + void blankRequiredFields_yieldsViolations() { + LoginDTO d = new LoginDTO(); + Set> v = validator.validate(d); + assertThat(v).extracting(cv -> cv.getPropertyPath().toString()) + .contains("sUserName", "sPassword", "sVersion"); + } + + @Test + void invalidVersion_yieldsViolation() { + LoginDTO d = valid(); + d.setSVersion("experimental"); + Set> v = validator.validate(d); + assertThat(v).extracting(cv -> cv.getPropertyPath().toString()).contains("sVersion"); + } + + @Test + void overSized_yieldsViolation() { + LoginDTO d = valid(); + d.setSUserName("a".repeat(51)); + d.setSPassword("p".repeat(101)); + Set> v = validator.validate(d); + assertThat(v).extracting(cv -> cv.getPropertyPath().toString()) + .contains("sUserName", "sPassword"); + } +}