From 405d6b94d7ec8302e9f03b60e92c91681e4e4cbb Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Tue, 1 Sep 2026 19:27:46 +0800 Subject: [PATCH 1/2] fix(auth): reject placeholder mobile numbers --- .../auth/dto/AccountManagementDtos.java | 6 +-- .../auth/dto/MerchantRegisterRequest.java | 2 +- .../com/baiyue/auth/service/AuthService.java | 2 +- .../resources/db/auth-login-identifiers.sql | 4 +- .../LoginIdentifierContractTest.java | 38 +++++++++++++++++++ .../auth/service/AuthServiceSmallJwtTest.java | 12 ++++++ .../java/com/baiyue/iam/dto/MemberDtos.java | 4 +- .../iam/dto/PlatformInternalUserDtos.java | 4 +- .../iam/dto/PlatformUserManagementDtos.java | 4 +- .../java/com/baiyue/iam/dto/ProfileDtos.java | 2 +- iam/src/main/resources/openapi/iam-v1.yaml | 16 ++++---- 11 files changed, 72 insertions(+), 22 deletions(-) create mode 100644 auth/src/test/java/com/baiyue/auth/repository/LoginIdentifierContractTest.java diff --git a/auth/src/main/java/com/baiyue/auth/dto/AccountManagementDtos.java b/auth/src/main/java/com/baiyue/auth/dto/AccountManagementDtos.java index 786deac67..41093a628 100644 --- a/auth/src/main/java/com/baiyue/auth/dto/AccountManagementDtos.java +++ b/auth/src/main/java/com/baiyue/auth/dto/AccountManagementDtos.java @@ -22,7 +22,7 @@ public final class AccountManagementDtos { String newPassword) { } public record CreateAccountRequest(@NotNull Long accountId, @NotBlank @Pattern(regexp = "^[A-Za-z][A-Za-z0-9_.@-]{3,63}$") String username, - @Pattern(regexp = "^1\\d{10}$") String phone, + @Pattern(regexp = "^1[3-9]\\d{9}$") String phone, @NotBlank @Size(min = 8, max = 64) @Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d).+$", message = "密码必须同时包含字母和数字") String password, @NotBlank String displayName, @NotNull Long tenantId, @@ -42,10 +42,10 @@ public final class AccountManagementDtos { } public record ChangeLoginIdentifiersRequest( @NotBlank @Pattern(regexp = "^[A-Za-z][A-Za-z0-9_.@-]{3,63}$") String username, - @NotBlank @Pattern(regexp = "^1\\d{10}$") String phone, + @NotBlank @Pattern(regexp = "^1[3-9]\\d{9}$") String phone, @NotBlank String currentPassword) { } public record UpdateAccountPhoneRequest( - @NotBlank @Pattern(regexp = "^1\\d{10}$") String phone) { } + @NotBlank @Pattern(regexp = "^1[3-9]\\d{9}$") String phone) { } public record UpdateAccountProfileRequest(@NotBlank @Size(max = 64) String displayName) { } public record UpdateAccountStatusRequest(@NotBlank String status) { } public record RevokeAccountSessionsRequest( diff --git a/auth/src/main/java/com/baiyue/auth/dto/MerchantRegisterRequest.java b/auth/src/main/java/com/baiyue/auth/dto/MerchantRegisterRequest.java index 570d4a6a9..13287dedd 100644 --- a/auth/src/main/java/com/baiyue/auth/dto/MerchantRegisterRequest.java +++ b/auth/src/main/java/com/baiyue/auth/dto/MerchantRegisterRequest.java @@ -13,7 +13,7 @@ public record MerchantRegisterRequest( @NotBlank @Size(max = 64) String displayName, @NotBlank @Size(max = 128) String companyName, @NotBlank @Size(max = 64) String legalPerson, - @NotBlank @Pattern(regexp = "^1\\d{10}$") String legalPhone, + @NotBlank @Pattern(regexp = "^1[3-9]\\d{9}$") String legalPhone, @Email @Size(max = 128) String contactEmail, @Size(min = 2, max = 2) String countryCode, @Size(max = 255) String address diff --git a/auth/src/main/java/com/baiyue/auth/service/AuthService.java b/auth/src/main/java/com/baiyue/auth/service/AuthService.java index 51b84a88c..80ebbdfeb 100644 --- a/auth/src/main/java/com/baiyue/auth/service/AuthService.java +++ b/auth/src/main/java/com/baiyue/auth/service/AuthService.java @@ -61,7 +61,7 @@ public class AuthService { description = "User login / 用户登录", captureAfter = false) public TokenResponse login(LoginRequest request) { String loginId = request.loginId().trim(); - AuthAccount account = loginId.matches("^1\\d{10}$") + AuthAccount account = loginId.matches("^1[3-9]\\d{9}$") ? accountRepository.findByPhone(loginId) : accountRepository.findByUsername(loginId); if (account == null) throw invalidCredentials(); diff --git a/auth/src/main/resources/db/auth-login-identifiers.sql b/auth/src/main/resources/db/auth-login-identifiers.sql index e328e69b4..5f36ab421 100644 --- a/auth/src/main/resources/db/auth-login-identifiers.sql +++ b/auth/src/main/resources/db/auth-login-identifiers.sql @@ -23,14 +23,14 @@ BEGIN IF EXISTS ( SELECT user.phone FROM baiyue_iam.iam_user user - WHERE user.deleted = 0 AND user.phone REGEXP '^1[0-9]{10}$' + WHERE user.deleted = 0 AND user.phone REGEXP '^1[3-9][0-9]{9}$' GROUP BY user.phone HAVING COUNT(DISTINCT user.auth_user_id) > 1) THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'IAM 存在重复手机号,禁止建立 Auth 唯一登录索引'; END IF; UPDATE auth_account account JOIN baiyue_iam.iam_user user ON user.auth_user_id = account.id AND user.deleted = 0 SET account.phone = user.phone - WHERE account.phone IS NULL AND user.phone REGEXP '^1[0-9]{10}$'; + WHERE account.phone IS NULL AND user.phone REGEXP '^1[3-9][0-9]{9}$'; IF NOT EXISTS ( SELECT 1 FROM information_schema.statistics WHERE table_schema = 'baiyue_auth' AND table_name = 'auth_account' diff --git a/auth/src/test/java/com/baiyue/auth/repository/LoginIdentifierContractTest.java b/auth/src/test/java/com/baiyue/auth/repository/LoginIdentifierContractTest.java new file mode 100644 index 000000000..0b471f746 --- /dev/null +++ b/auth/src/test/java/com/baiyue/auth/repository/LoginIdentifierContractTest.java @@ -0,0 +1,38 @@ +package com.baiyue.auth.repository; + +import com.baiyue.auth.dto.AccountManagementDtos.ChangeLoginIdentifiersRequest; +import com.baiyue.auth.dto.MerchantRegisterRequest; +import jakarta.validation.Validation; +import org.junit.jupiter.api.Test; +import org.springframework.core.io.ClassPathResource; + +import java.nio.charset.StandardCharsets; + +import static org.assertj.core.api.Assertions.assertThat; + +class LoginIdentifierContractTest { + + @Test + void mainlandMobileRuleRejectsPlaceholderNumbersAcrossJavaAndSql() throws Exception { + try (var factory = Validation.buildDefaultValidatorFactory()) { + var validator = factory.getValidator(); + var invalidRegistration = new MerchantRegisterRequest( + "merchant.user", "Password1", "测试用户", "测试企业", "张三", + "11111111111", null, "CN", null); + var validChange = new ChangeLoginIdentifiersRequest( + "merchant.user", "13800138000", "Password1"); + var invalidChange = new ChangeLoginIdentifiersRequest( + "merchant.user", "11111111111", "Password1"); + + assertThat(validator.validate(invalidRegistration)).isNotEmpty(); + assertThat(validator.validate(validChange)).isEmpty(); + assertThat(validator.validate(invalidChange)).isNotEmpty(); + } + + String sql = new ClassPathResource("db/auth-login-identifiers.sql") + .getContentAsString(StandardCharsets.UTF_8); + assertThat(sql) + .contains("REGEXP '^1[3-9][0-9]{9}$'") + .doesNotContain("REGEXP '^1[0-9]{10}$'"); + } +} diff --git a/auth/src/test/java/com/baiyue/auth/service/AuthServiceSmallJwtTest.java b/auth/src/test/java/com/baiyue/auth/service/AuthServiceSmallJwtTest.java index 00040bbe3..31d936945 100644 --- a/auth/src/test/java/com/baiyue/auth/service/AuthServiceSmallJwtTest.java +++ b/auth/src/test/java/com/baiyue/auth/service/AuthServiceSmallJwtTest.java @@ -128,6 +128,18 @@ class AuthServiceSmallJwtTest { verify(repository, never()).findByUsername("13800138000"); } + @Test + void placeholderNumberIsNeverResolvedAsAMobileLoginIdentifier() { + when(repository.findByUsername("11111111111")).thenReturn(null); + + assertThatThrownBy(() -> service.login(new LoginRequest("11111111111", "secret"))) + .isInstanceOf(com.baiyue.common.core.exception.BizException.class) + .hasMessageContaining("登录标识或密码错误"); + + verify(repository).findByUsername("11111111111"); + verify(repository, never()).findByPhone("11111111111"); + } + @Test void refreshRotatesDomesticAndOverseasTokensFromOneAuthorizationSnapshot() { AuthAccount account = new AuthAccount(); diff --git a/iam/src/main/java/com/baiyue/iam/dto/MemberDtos.java b/iam/src/main/java/com/baiyue/iam/dto/MemberDtos.java index 8981f2e44..e8cb9ce4b 100644 --- a/iam/src/main/java/com/baiyue/iam/dto/MemberDtos.java +++ b/iam/src/main/java/com/baiyue/iam/dto/MemberDtos.java @@ -18,7 +18,7 @@ public final class MemberDtos { @NotBlank @Size(min = 8, max = 64) @Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d).+$", message = "密码必须同时包含字母和数字") String password, @NotBlank @Size(max = 64) String displayName, - @Pattern(regexp = "^1\\d{10}$") String phone, + @Pattern(regexp = "^1[3-9]\\d{9}$") String phone, @Email @Size(max = 128) String email, @NotBlank String memberType, @Size(max = 64) String memberNo, @@ -30,7 +30,7 @@ public final class MemberDtos { public record UpdateMemberRequest( @NotBlank @Size(max = 64) String displayName, - @Pattern(regexp = "^1\\d{10}$") String phone, + @Pattern(regexp = "^1[3-9]\\d{9}$") String phone, @Email @Size(max = 128) String email, @NotBlank String memberType, @Size(max = 64) String memberNo, diff --git a/iam/src/main/java/com/baiyue/iam/dto/PlatformInternalUserDtos.java b/iam/src/main/java/com/baiyue/iam/dto/PlatformInternalUserDtos.java index 5ccc4e106..ffae0e240 100644 --- a/iam/src/main/java/com/baiyue/iam/dto/PlatformInternalUserDtos.java +++ b/iam/src/main/java/com/baiyue/iam/dto/PlatformInternalUserDtos.java @@ -20,7 +20,7 @@ public final class PlatformInternalUserDtos { Long authUserId, String userType, @NotBlank @Size(max = 64) String displayName, - @Pattern(regexp = "^1\\d{10}$") String phone, + @Pattern(regexp = "^1[3-9]\\d{9}$") String phone, @Email @Size(max = 128) String email, @Pattern(regexp = "^[A-Za-z][A-Za-z0-9_.@-]{3,63}$") String username, @Size(min = 8, max = 64) @@ -31,7 +31,7 @@ public final class PlatformInternalUserDtos { public record UpdateInternalUserRequest( @NotBlank @Size(max = 64) String displayName, - @Pattern(regexp = "^1\\d{10}$") String phone, + @Pattern(regexp = "^1[3-9]\\d{9}$") String phone, @Email @Size(max = 128) String email, @NotBlank @Size(max = 64) String memberNo, @NotNull List roleIds) { } diff --git a/iam/src/main/java/com/baiyue/iam/dto/PlatformUserManagementDtos.java b/iam/src/main/java/com/baiyue/iam/dto/PlatformUserManagementDtos.java index 67ae5ea6b..1056a6519 100644 --- a/iam/src/main/java/com/baiyue/iam/dto/PlatformUserManagementDtos.java +++ b/iam/src/main/java/com/baiyue/iam/dto/PlatformUserManagementDtos.java @@ -16,7 +16,7 @@ public final class PlatformUserManagementDtos { public record CreateUserRequest( @NotBlank @Pattern(regexp = "^[A-Za-z][A-Za-z0-9_.@-]{3,63}$") String username, @NotBlank @Size(max = 64) String displayName, - @NotBlank @Pattern(regexp = "^1\\d{10}$", message = "手机号格式不正确") String phone, + @NotBlank @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") String phone, @Email @Size(max = 128) String email, @NotNull Long tenantId, @NotNull List roleIds, @@ -32,7 +32,7 @@ public final class PlatformUserManagementDtos { public record UpdateUserRequest( @NotBlank @Size(max = 64) String displayName, - @NotBlank @Pattern(regexp = "^1\\d{10}$", message = "手机号格式不正确") String phone, + @NotBlank @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") String phone, @Email @Size(max = 128) String email, @NotNull Long tenantId, @NotNull List roleIds, diff --git a/iam/src/main/java/com/baiyue/iam/dto/ProfileDtos.java b/iam/src/main/java/com/baiyue/iam/dto/ProfileDtos.java index 471247703..ac9a88edf 100644 --- a/iam/src/main/java/com/baiyue/iam/dto/ProfileDtos.java +++ b/iam/src/main/java/com/baiyue/iam/dto/ProfileDtos.java @@ -17,7 +17,7 @@ public final class ProfileDtos { public record ChangeLoginIdentifiersRequest( @NotBlank @Pattern(regexp = "^[A-Za-z][A-Za-z0-9_.@-]{3,63}$") String username, - @NotBlank @Pattern(regexp = "^1\\d{10}$") String phone, + @NotBlank @Pattern(regexp = "^1[3-9]\\d{9}$") String phone, @NotBlank String currentPassword) { } public record AddressRequest(@NotBlank String addressType, String countryCode, String province, diff --git a/iam/src/main/resources/openapi/iam-v1.yaml b/iam/src/main/resources/openapi/iam-v1.yaml index cba1b2b57..a186189b4 100644 --- a/iam/src/main/resources/openapi/iam-v1.yaml +++ b/iam/src/main/resources/openapi/iam-v1.yaml @@ -982,7 +982,7 @@ components: authUserId: {type: string, pattern: '^\d+$'} username: {type: string, pattern: '^[A-Za-z][A-Za-z0-9_.@-]{3,63}$'} displayName: {type: string, maxLength: 64} - phone: {type: string, pattern: '^1\d{10}$'} + phone: {type: string, pattern: '^1[3-9]\d{9}$'} email: {type: string, format: email, nullable: true} UpdatePersonalProfileRequest: type: object @@ -995,7 +995,7 @@ components: required: [username, phone, currentPassword] properties: username: {type: string, pattern: '^[A-Za-z][A-Za-z0-9_.@-]{3,63}$'} - phone: {type: string, pattern: '^1\d{10}$'} + phone: {type: string, pattern: '^1[3-9]\d{9}$'} currentPassword: {type: string, format: password, writeOnly: true} PersonalProfileApiResponse: allOf: @@ -1398,7 +1398,7 @@ components: properties: username: {type: string, pattern: '^[A-Za-z][A-Za-z0-9_.@-]{3,63}$'} displayName: {type: string, maxLength: 64} - phone: {type: string, pattern: '^1\\d{10}$'} + phone: {type: string, pattern: '^1[3-9]\\d{9}$'} email: {type: string, format: email, maxLength: 128} tenantId: {type: integer, format: int64, enum: [0], description: 保留的兼容字段,只允许平台技术作用域 0} roleIds: {type: array, minItems: 1, items: {type: integer, format: int64}} @@ -1410,7 +1410,7 @@ components: required: [displayName, phone, tenantId, roleIds] properties: displayName: {type: string, maxLength: 64} - phone: {type: string, pattern: '^1\\d{10}$'} + phone: {type: string, pattern: '^1[3-9]\\d{9}$'} email: {type: string, format: email, maxLength: 128} tenantId: {type: integer, format: int64, enum: [0], description: 保留的兼容字段,只允许平台技术作用域 0} roleIds: {type: array, minItems: 1, items: {type: integer, format: int64}} @@ -1424,7 +1424,7 @@ components: username: {type: string, pattern: '^[A-Za-z][A-Za-z0-9_.@-]{3,63}$'} password: {type: string, format: password, minLength: 8, maxLength: 64, writeOnly: true} displayName: {type: string, maxLength: 64} - phone: {type: string, pattern: '^1\d{10}$'} + phone: {type: string, pattern: '^1[3-9]\d{9}$'} email: {type: string, format: email, maxLength: 128} memberNo: {type: string, maxLength: 64} roleIds: {type: array, items: {type: integer, format: int64}} @@ -1433,7 +1433,7 @@ components: required: [displayName, memberNo, roleIds] properties: displayName: {type: string, maxLength: 64} - phone: {type: string, pattern: '^1\d{10}$'} + phone: {type: string, pattern: '^1[3-9]\d{9}$'} email: {type: string, format: email, maxLength: 128} memberNo: {type: string, maxLength: 64} roleIds: {type: array, items: {type: integer, format: int64}} @@ -1495,7 +1495,7 @@ components: username: {type: string, pattern: '^[A-Za-z][A-Za-z0-9_.@-]{3,63}$'} password: {type: string, format: password, minLength: 8, maxLength: 64} displayName: {type: string, maxLength: 64} - phone: {type: string, pattern: '^1\d{10}$'} + phone: {type: string, pattern: '^1[3-9]\d{9}$'} email: {type: string, format: email, maxLength: 128} memberType: {type: string, enum: [EMPLOYEE]} memberNo: {type: string} @@ -1508,7 +1508,7 @@ components: required: [displayName, memberType] properties: displayName: {type: string, maxLength: 64} - phone: {type: string, pattern: '^1\d{10}$'} + phone: {type: string, pattern: '^1[3-9]\d{9}$'} email: {type: string, format: email, maxLength: 128} memberType: {type: string, enum: [EMPLOYEE]} memberNo: {type: string, maxLength: 64} From 2ec8fcb8861b1725a0757dcaa98a3cac810a00f9 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Tue, 1 Sep 2026 19:32:01 +0800 Subject: [PATCH 2/2] fix(order): route customer addresses through gateway --- .../order/controller/MerchantCustomerAddressController.java | 2 +- order/src/main/resources/openapi/order-v1.yaml | 4 ++-- .../OrderCustomerFoundationOpenApiContractTest.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/order/src/main/java/com/baiyue/order/controller/MerchantCustomerAddressController.java b/order/src/main/java/com/baiyue/order/controller/MerchantCustomerAddressController.java index 53654f06e..8c4b0089d 100644 --- a/order/src/main/java/com/baiyue/order/controller/MerchantCustomerAddressController.java +++ b/order/src/main/java/com/baiyue/order/controller/MerchantCustomerAddressController.java @@ -23,7 +23,7 @@ import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController -@RequestMapping("/api/merchant/v1/order-addresses") +@RequestMapping("/api/merchant/v1/orders/first-leg/addresses") public class MerchantCustomerAddressController { @Resource private CustomerAddressService service; diff --git a/order/src/main/resources/openapi/order-v1.yaml b/order/src/main/resources/openapi/order-v1.yaml index 09a39a094..c278f3f0f 100644 --- a/order/src/main/resources/openapi/order-v1.yaml +++ b/order/src/main/resources/openapi/order-v1.yaml @@ -6,7 +6,7 @@ info: security: - bearerAuth: [] paths: - /api/merchant/v1/order-addresses: + /api/merchant/v1/orders/first-leg/addresses: get: summary: 查询当前客户下单地址簿 parameters: @@ -27,7 +27,7 @@ paths: responses: '200': {description: 返回地址内部 ID,Long 按字符串序列化} '403': {description: 需要 merchant:order:create 权限} - /api/merchant/v1/order-addresses/{id}: + /api/merchant/v1/orders/first-leg/addresses/{id}: put: summary: 修改当前客户下单地址 description: 已创建订单保存独立快照,本操作不会修改历史订单。 diff --git a/order/src/test/java/com/baiyue/order/controller/OrderCustomerFoundationOpenApiContractTest.java b/order/src/test/java/com/baiyue/order/controller/OrderCustomerFoundationOpenApiContractTest.java index 76236fc27..63b261157 100644 --- a/order/src/test/java/com/baiyue/order/controller/OrderCustomerFoundationOpenApiContractTest.java +++ b/order/src/test/java/com/baiyue/order/controller/OrderCustomerFoundationOpenApiContractTest.java @@ -13,7 +13,7 @@ class OrderCustomerFoundationOpenApiContractTest { assertThat(input).isNotNull(); String yaml = new String(input.readAllBytes(), StandardCharsets.UTF_8); assertThat(yaml) - .contains("/api/merchant/v1/order-addresses:") + .contains("/api/merchant/v1/orders/first-leg/addresses:") .contains("/api/merchant/v1/orders/first-leg/batch-imports:") .contains("senderAddressId:") .contains("privateDestinationAddressId:")