diff --git a/backend/src/main/java/com/xly/erp/modules/usr/dto/LoginDTO.java b/backend/src/main/java/com/xly/erp/modules/usr/dto/LoginDTO.java new file mode 100644 index 0000000..8384d0b --- /dev/null +++ b/backend/src/main/java/com/xly/erp/modules/usr/dto/LoginDTO.java @@ -0,0 +1,58 @@ +package com.xly.erp.modules.usr.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +/** + * 登录入参(docs/05 契约 / spec § 2.1)。REQ-USR-004 T2。 + * + *
三字段均必填,校验失败由全局处理器统一转 40001。{@code sUserName} 匈牙利前缀字段的 + * getter({@code getSUserName})会被 Jackson 推断为属性名 {@code SUserName},与契约 JSON + * 键不符,故加 {@link JsonProperty} 锁定键名(与 CreateUserDTO/UserVO 同做法); + * {@code password}/{@code companyId} 为常规小驼峰,无需注解。
+ * + *{@code password} 为登录明文,仅供服务端 BCrypt 比对,绝不落日志 / 回显。
+ */ +public class LoginDTO { + + /** 登录用户名:必填,最长 50。 */ + @JsonProperty("sUserName") + @NotBlank(message = "用户名不能为空") + @Size(max = 50, message = "用户名长度不能超过 50") + private String sUserName; + + /** 登录密码明文:必填,最长 100;服务端 BCrypt 比对,绝不落日志。 */ + @NotBlank(message = "密码不能为空") + @Size(max = 100, message = "密码长度不能超过 100") + private String password; + + /** 版本下拉选中的 usr_company.iIncrement:必填,仅存在性校验,不参与认证绑定。 */ + @NotNull(message = "版本不能为空") + private Integer companyId; + + public String getSUserName() { + return sUserName; + } + + public void setSUserName(String sUserName) { + this.sUserName = sUserName; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Integer getCompanyId() { + return companyId; + } + + public void setCompanyId(Integer companyId) { + this.companyId = companyId; + } +} diff --git a/backend/src/main/java/com/xly/erp/modules/usr/vo/CompanyOptionVO.java b/backend/src/main/java/com/xly/erp/modules/usr/vo/CompanyOptionVO.java new file mode 100644 index 0000000..d3cc70c --- /dev/null +++ b/backend/src/main/java/com/xly/erp/modules/usr/vo/CompanyOptionVO.java @@ -0,0 +1,51 @@ +package com.xly.erp.modules.usr.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; + +/** + * 公司下拉项输出(spec § 2.3)。REQ-USR-004 T2。 + * + *供登录页「版本」下拉展示。匈牙利前缀字段({@code sCompanyName}/{@code sVersion})的 + * getter 会被 Jackson 推断为大驼峰键,与契约不符,故加 {@link JsonProperty} 锁键; + * {@code id} 为常规小驼峰,无需注解。{@code sVersion} 可为 null。
+ */ +public class CompanyOptionVO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** 公司主键 iIncrement。 */ + private Integer id; + + /** 公司名称(usr_company.sCompanyName)。 */ + @JsonProperty("sCompanyName") + private String sCompanyName; + + /** 版本 / 账套标识(usr_company.sVersion,可 null)。 */ + @JsonProperty("sVersion") + private String sVersion; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getSCompanyName() { + return sCompanyName; + } + + public void setSCompanyName(String sCompanyName) { + this.sCompanyName = sCompanyName; + } + + public String getSVersion() { + return sVersion; + } + + public void setSVersion(String sVersion) { + this.sVersion = sVersion; + } +} diff --git a/backend/src/test/java/com/xly/erp/modules/usr/dto/LoginDTOValidationTest.java b/backend/src/test/java/com/xly/erp/modules/usr/dto/LoginDTOValidationTest.java new file mode 100644 index 0000000..2fd40fc --- /dev/null +++ b/backend/src/test/java/com/xly/erp/modules/usr/dto/LoginDTOValidationTest.java @@ -0,0 +1,92 @@ +package com.xly.erp.modules.usr.dto; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import jakarta.validation.ValidatorFactory; +import java.util.Set; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** + * REQ-USR-004 T2:LoginDTO Bean Validation 与 JSON 反序列化键名。 + * + *校验 {@code sUserName}/{@code password} 必填、长度上限,{@code companyId} 必填; + * 并验证 {@code @JsonProperty("sUserName")} 锁定反序列化键名(与 CreateUserDTO/UserVO 同做法)。
+ */ +class LoginDTOValidationTest { + + private static ValidatorFactory factory; + private static Validator validator; + private final ObjectMapper objectMapper = new ObjectMapper(); + + @BeforeAll + static void initValidator() { + factory = Validation.buildDefaultValidatorFactory(); + validator = factory.getValidator(); + } + + @AfterAll + static void closeValidator() { + factory.close(); + } + + private LoginDTO valid() { + LoginDTO dto = new LoginDTO(); + dto.setSUserName("admin"); + dto.setPassword("666666"); + dto.setCompanyId(1); + return dto; + } + + @Test + void acceptsValidLogin() { + Set验证匈牙利前缀字段({@code sCompanyName}/{@code sVersion})经 {@code @JsonProperty} + * 序列化为契约小驼峰键,而非 Jackson 推断的大驼峰 {@code SCompanyName}。
+ */ +class CompanyOptionVOJsonTest { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + void serializesContractKeys() throws Exception { + CompanyOptionVO vo = new CompanyOptionVO(); + vo.setId(7); + vo.setSCompanyName("小羚羊总部"); + vo.setSVersion("企业版"); + + String json = objectMapper.writeValueAsString(vo); + + assertThat(json).contains("\"id\":7"); + assertThat(json).contains("\"sCompanyName\":\"小羚羊总部\""); + assertThat(json).contains("\"sVersion\":\"企业版\""); + assertThat(json).doesNotContain("SCompanyName"); + assertThat(json).doesNotContain("SVersion"); + } +}