Commit 1cd7a10c9fcf26e390a20637d3be3c3e14a2430c

Authored by zichun
1 parent 975afd36

feat(usr): 登录入参 LoginDTO 与公司下拉项 CompanyOptionVO REQ-USR-004

backend/src/main/java/com/xly/erp/modules/usr/dto/LoginDTO.java 0 → 100644
  1 +package com.xly.erp.modules.usr.dto;
  2 +
  3 +import com.fasterxml.jackson.annotation.JsonProperty;
  4 +import jakarta.validation.constraints.NotBlank;
  5 +import jakarta.validation.constraints.NotNull;
  6 +import jakarta.validation.constraints.Size;
  7 +
  8 +/**
  9 + * 登录入参(docs/05 契约 / spec § 2.1)。REQ-USR-004 T2。
  10 + *
  11 + * <p>三字段均必填,校验失败由全局处理器统一转 40001。{@code sUserName} 匈牙利前缀字段的
  12 + * getter({@code getSUserName})会被 Jackson 推断为属性名 {@code SUserName},与契约 JSON
  13 + * 键不符,故加 {@link JsonProperty} 锁定键名(与 CreateUserDTO/UserVO 同做法);
  14 + * {@code password}/{@code companyId} 为常规小驼峰,无需注解。</p>
  15 + *
  16 + * <p>{@code password} 为登录明文,仅供服务端 BCrypt 比对,绝不落日志 / 回显。</p>
  17 + */
  18 +public class LoginDTO {
  19 +
  20 + /** 登录用户名:必填,最长 50。 */
  21 + @JsonProperty("sUserName")
  22 + @NotBlank(message = "用户名不能为空")
  23 + @Size(max = 50, message = "用户名长度不能超过 50")
  24 + private String sUserName;
  25 +
  26 + /** 登录密码明文:必填,最长 100;服务端 BCrypt 比对,绝不落日志。 */
  27 + @NotBlank(message = "密码不能为空")
  28 + @Size(max = 100, message = "密码长度不能超过 100")
  29 + private String password;
  30 +
  31 + /** 版本下拉选中的 usr_company.iIncrement:必填,仅存在性校验,不参与认证绑定。 */
  32 + @NotNull(message = "版本不能为空")
  33 + private Integer companyId;
  34 +
  35 + public String getSUserName() {
  36 + return sUserName;
  37 + }
  38 +
  39 + public void setSUserName(String sUserName) {
  40 + this.sUserName = sUserName;
  41 + }
  42 +
  43 + public String getPassword() {
  44 + return password;
  45 + }
  46 +
  47 + public void setPassword(String password) {
  48 + this.password = password;
  49 + }
  50 +
  51 + public Integer getCompanyId() {
  52 + return companyId;
  53 + }
  54 +
  55 + public void setCompanyId(Integer companyId) {
  56 + this.companyId = companyId;
  57 + }
  58 +}
... ...
backend/src/main/java/com/xly/erp/modules/usr/vo/CompanyOptionVO.java 0 → 100644
  1 +package com.xly.erp.modules.usr.vo;
  2 +
  3 +import com.fasterxml.jackson.annotation.JsonProperty;
  4 +import java.io.Serializable;
  5 +
  6 +/**
  7 + * 公司下拉项输出(spec § 2.3)。REQ-USR-004 T2。
  8 + *
  9 + * <p>供登录页「版本」下拉展示。匈牙利前缀字段({@code sCompanyName}/{@code sVersion})的
  10 + * getter 会被 Jackson 推断为大驼峰键,与契约不符,故加 {@link JsonProperty} 锁键;
  11 + * {@code id} 为常规小驼峰,无需注解。{@code sVersion} 可为 null。</p>
  12 + */
  13 +public class CompanyOptionVO implements Serializable {
  14 +
  15 + private static final long serialVersionUID = 1L;
  16 +
  17 + /** 公司主键 iIncrement。 */
  18 + private Integer id;
  19 +
  20 + /** 公司名称(usr_company.sCompanyName)。 */
  21 + @JsonProperty("sCompanyName")
  22 + private String sCompanyName;
  23 +
  24 + /** 版本 / 账套标识(usr_company.sVersion,可 null)。 */
  25 + @JsonProperty("sVersion")
  26 + private String sVersion;
  27 +
  28 + public Integer getId() {
  29 + return id;
  30 + }
  31 +
  32 + public void setId(Integer id) {
  33 + this.id = id;
  34 + }
  35 +
  36 + public String getSCompanyName() {
  37 + return sCompanyName;
  38 + }
  39 +
  40 + public void setSCompanyName(String sCompanyName) {
  41 + this.sCompanyName = sCompanyName;
  42 + }
  43 +
  44 + public String getSVersion() {
  45 + return sVersion;
  46 + }
  47 +
  48 + public void setSVersion(String sVersion) {
  49 + this.sVersion = sVersion;
  50 + }
  51 +}
... ...
backend/src/test/java/com/xly/erp/modules/usr/dto/LoginDTOValidationTest.java 0 → 100644
  1 +package com.xly.erp.modules.usr.dto;
  2 +
  3 +import static org.assertj.core.api.Assertions.assertThat;
  4 +
  5 +import com.fasterxml.jackson.databind.ObjectMapper;
  6 +import jakarta.validation.ConstraintViolation;
  7 +import jakarta.validation.Validation;
  8 +import jakarta.validation.Validator;
  9 +import jakarta.validation.ValidatorFactory;
  10 +import java.util.Set;
  11 +import org.junit.jupiter.api.AfterAll;
  12 +import org.junit.jupiter.api.BeforeAll;
  13 +import org.junit.jupiter.api.Test;
  14 +
  15 +/**
  16 + * REQ-USR-004 T2:LoginDTO Bean Validation 与 JSON 反序列化键名。
  17 + *
  18 + * <p>校验 {@code sUserName}/{@code password} 必填、长度上限,{@code companyId} 必填;
  19 + * 并验证 {@code @JsonProperty("sUserName")} 锁定反序列化键名(与 CreateUserDTO/UserVO 同做法)。</p>
  20 + */
  21 +class LoginDTOValidationTest {
  22 +
  23 + private static ValidatorFactory factory;
  24 + private static Validator validator;
  25 + private final ObjectMapper objectMapper = new ObjectMapper();
  26 +
  27 + @BeforeAll
  28 + static void initValidator() {
  29 + factory = Validation.buildDefaultValidatorFactory();
  30 + validator = factory.getValidator();
  31 + }
  32 +
  33 + @AfterAll
  34 + static void closeValidator() {
  35 + factory.close();
  36 + }
  37 +
  38 + private LoginDTO valid() {
  39 + LoginDTO dto = new LoginDTO();
  40 + dto.setSUserName("admin");
  41 + dto.setPassword("666666");
  42 + dto.setCompanyId(1);
  43 + return dto;
  44 + }
  45 +
  46 + @Test
  47 + void acceptsValidLogin() {
  48 + Set<ConstraintViolation<LoginDTO>> violations = validator.validate(valid());
  49 + assertThat(violations).isEmpty();
  50 + }
  51 +
  52 + @Test
  53 + void rejectsBlankUserName() {
  54 + LoginDTO dto = valid();
  55 + dto.setSUserName("");
  56 + assertThat(validator.validate(dto)).isNotEmpty();
  57 + dto.setSUserName(null);
  58 + assertThat(validator.validate(dto)).isNotEmpty();
  59 + }
  60 +
  61 + @Test
  62 + void rejectsBlankPassword() {
  63 + LoginDTO dto = valid();
  64 + dto.setPassword("");
  65 + assertThat(validator.validate(dto)).isNotEmpty();
  66 + dto.setPassword(null);
  67 + assertThat(validator.validate(dto)).isNotEmpty();
  68 + }
  69 +
  70 + @Test
  71 + void rejectsNullCompanyId() {
  72 + LoginDTO dto = valid();
  73 + dto.setCompanyId(null);
  74 + assertThat(validator.validate(dto)).isNotEmpty();
  75 + }
  76 +
  77 + @Test
  78 + void rejectsTooLongUserName() {
  79 + LoginDTO dto = valid();
  80 + dto.setSUserName("a".repeat(51));
  81 + assertThat(validator.validate(dto)).isNotEmpty();
  82 + }
  83 +
  84 + @Test
  85 + void deserializesSUserNameJsonKey() throws Exception {
  86 + LoginDTO dto = objectMapper.readValue(
  87 + "{\"sUserName\":\"admin\",\"password\":\"x\",\"companyId\":1}", LoginDTO.class);
  88 + assertThat(dto.getSUserName()).isEqualTo("admin");
  89 + assertThat(dto.getPassword()).isEqualTo("x");
  90 + assertThat(dto.getCompanyId()).isEqualTo(1);
  91 + }
  92 +}
... ...
backend/src/test/java/com/xly/erp/modules/usr/vo/CompanyOptionVOJsonTest.java 0 → 100644
  1 +package com.xly.erp.modules.usr.vo;
  2 +
  3 +import static org.assertj.core.api.Assertions.assertThat;
  4 +
  5 +import com.fasterxml.jackson.databind.ObjectMapper;
  6 +import org.junit.jupiter.api.Test;
  7 +
  8 +/**
  9 + * REQ-USR-004 T2:CompanyOptionVO 序列化键名锁定。
  10 + *
  11 + * <p>验证匈牙利前缀字段({@code sCompanyName}/{@code sVersion})经 {@code @JsonProperty}
  12 + * 序列化为契约小驼峰键,而非 Jackson 推断的大驼峰 {@code SCompanyName}。</p>
  13 + */
  14 +class CompanyOptionVOJsonTest {
  15 +
  16 + private final ObjectMapper objectMapper = new ObjectMapper();
  17 +
  18 + @Test
  19 + void serializesContractKeys() throws Exception {
  20 + CompanyOptionVO vo = new CompanyOptionVO();
  21 + vo.setId(7);
  22 + vo.setSCompanyName("小羚羊总部");
  23 + vo.setSVersion("企业版");
  24 +
  25 + String json = objectMapper.writeValueAsString(vo);
  26 +
  27 + assertThat(json).contains("\"id\":7");
  28 + assertThat(json).contains("\"sCompanyName\":\"小羚羊总部\"");
  29 + assertThat(json).contains("\"sVersion\":\"企业版\"");
  30 + assertThat(json).doesNotContain("SCompanyName");
  31 + assertThat(json).doesNotContain("SVersion");
  32 + }
  33 +}
... ...