From e83dff6ac7cb6088b9a02cc622a7597306c59290 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Wed, 2 Sep 2026 10:04:18 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/client/IamOnboardingClient.java | 2 +- .../auth/dto/MerchantRegisterRequest.java | 5 +-- .../service/MerchantRegistrationService.java | 6 ++-- .../controller/PlatformTenantController.java | 14 ++++---- .../com/baiyue/iam/dto/OnboardingDtos.java | 2 +- .../java/com/baiyue/iam/dto/TenantDtos.java | 12 ++++--- .../iam/repository/TenantRepository.java | 13 ------- .../service/MerchantOnboardingService.java | 4 +-- ...enantAdministratorProvisioningService.java | 30 +++++++--------- .../com/baiyue/iam/service/TenantService.java | 35 ++++++------------ iam/src/main/resources/db/iam.sql | 4 +-- iam/src/main/resources/openapi/iam-v1.yaml | 36 +++++++++---------- 12 files changed, 67 insertions(+), 96 deletions(-) diff --git a/auth/src/main/java/com/baiyue/auth/client/IamOnboardingClient.java b/auth/src/main/java/com/baiyue/auth/client/IamOnboardingClient.java index c0bb9d5ed..bb60517ca 100644 --- a/auth/src/main/java/com/baiyue/auth/client/IamOnboardingClient.java +++ b/auth/src/main/java/com/baiyue/auth/client/IamOnboardingClient.java @@ -22,7 +22,7 @@ public interface IamOnboardingClient { @PathVariable("authUserId") Long authUserId); record OnboardingRequest(Long authUserId, String displayName, String companyName, - String legalPerson, String legalPhone, String contactEmail, + String legalPerson, String administratorPhone, String contactEmail, String countryCode, String address) { } record OnboardingResponse(Long tenantId, Long membershipId, String tenantStatus, 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 13287dedd..bebd48f4d 100644 --- a/auth/src/main/java/com/baiyue/auth/dto/MerchantRegisterRequest.java +++ b/auth/src/main/java/com/baiyue/auth/dto/MerchantRegisterRequest.java @@ -1,10 +1,10 @@ package com.baiyue.auth.dto; -import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Size; +import io.swagger.v3.oas.annotations.media.Schema; @Schema(name = "MerchantRegisterRequest", description = "Phase-one merchant account registration") public record MerchantRegisterRequest( @@ -13,7 +13,8 @@ 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[3-9]\\d{9}$") String legalPhone, + @Schema(description = "首个企业管理员的登录手机号;注册时同时初始化企业联系电话,创建后两者独立维护") + @NotBlank @Pattern(regexp = "^1[3-9]\\d{9}$") String phone, @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/MerchantRegistrationService.java b/auth/src/main/java/com/baiyue/auth/service/MerchantRegistrationService.java index e0fc11be3..1ce404873 100644 --- a/auth/src/main/java/com/baiyue/auth/service/MerchantRegistrationService.java +++ b/auth/src/main/java/com/baiyue/auth/service/MerchantRegistrationService.java @@ -38,7 +38,7 @@ public class MerchantRegistrationService { if (accountRepository.existsByUsername(request.username())) { throw new BizException(ResultCode.CONFLICT, "用户名已存在"); } - if (accountRepository.existsByPhone(request.legalPhone())) { + if (accountRepository.existsByPhone(request.phone())) { throw new BizException(ResultCode.CONFLICT, "手机号已存在"); } long accountId = IdWorker.getId(); @@ -49,7 +49,7 @@ public class MerchantRegistrationService { try { var response = onboardingClient.register(internalToken, new IamOnboardingClient.OnboardingRequest(accountId, request.displayName(), - request.companyName(), request.legalPerson(), request.legalPhone(), + request.companyName(), request.legalPerson(), request.phone(), request.contactEmail(), request.countryCode(), request.address())); if (response == null) { throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "IAM registration failed"); @@ -84,7 +84,7 @@ public class MerchantRegistrationService { AuthAccount account = new AuthAccount(); account.setId(accountId); account.setUsername(request.username()); - account.setPhone(request.legalPhone()); + account.setPhone(request.phone()); account.setPasswordHash(passwordHash); account.setDisplayName(request.displayName()); account.setAccountStatus("ACTIVE"); diff --git a/iam/src/main/java/com/baiyue/iam/controller/PlatformTenantController.java b/iam/src/main/java/com/baiyue/iam/controller/PlatformTenantController.java index 9a5e0642c..004b32af5 100644 --- a/iam/src/main/java/com/baiyue/iam/controller/PlatformTenantController.java +++ b/iam/src/main/java/com/baiyue/iam/controller/PlatformTenantController.java @@ -96,15 +96,15 @@ public class PlatformTenantController { return ApiResponse.success(service.dimensionInitializationStatus(tenantId)); } - @GetMapping("/tenants/contact-phone-availability") - @PreAuthorize("hasAnyAuthority('platform:customer:create','platform:customer:edit')") - @Operation(summary = "校验客户联系电话唯一性") - public ApiResponse contactPhoneAvailability( - @RequestParam String contactPhone, - @RequestParam(required = false) Long excludeTenantId, + @GetMapping("/tenants/administrator-phone-availability") + @PreAuthorize("hasAuthority('platform:customer:create')") + @Operation(summary = "校验首个管理员登录手机号唯一性", + description = "仅用于创建客户前校验 Auth 登录手机号;企业联系电话本身不要求唯一。") + public ApiResponse administratorPhoneAvailability( + @RequestParam @Pattern(regexp = "^1[3-9]\\d{9}$") String phone, @AuthenticationPrincipal AuthenticatedUser user) { TenantGuard.tenant(user, "PLATFORM"); - return ApiResponse.success(service.contactPhoneAndUsernameAvailable(contactPhone, excludeTenantId)); + return ApiResponse.success(service.administratorPhoneAvailable(phone)); } @GetMapping("/tenants/{tenantId:\\d+}/members") diff --git a/iam/src/main/java/com/baiyue/iam/dto/OnboardingDtos.java b/iam/src/main/java/com/baiyue/iam/dto/OnboardingDtos.java index 08bbab0ad..0383c9105 100644 --- a/iam/src/main/java/com/baiyue/iam/dto/OnboardingDtos.java +++ b/iam/src/main/java/com/baiyue/iam/dto/OnboardingDtos.java @@ -14,7 +14,7 @@ public final class OnboardingDtos { @NotBlank String displayName, @NotBlank String companyName, @NotBlank String legalPerson, - @NotBlank String legalPhone, + @NotBlank @Pattern(regexp = "^1[3-9]\\d{9}$") String administratorPhone, String contactEmail, String countryCode, String address diff --git a/iam/src/main/java/com/baiyue/iam/dto/TenantDtos.java b/iam/src/main/java/com/baiyue/iam/dto/TenantDtos.java index 69a08ec1c..875d001a9 100644 --- a/iam/src/main/java/com/baiyue/iam/dto/TenantDtos.java +++ b/iam/src/main/java/com/baiyue/iam/dto/TenantDtos.java @@ -27,8 +27,8 @@ public final class TenantDtos { @Schema(description = "Legal entity name / 企业法定名称", example = "深圳虫虫出海科技有限公司") @NotBlank String legalName, @Schema(description = "Legal representative / 法定代表人", example = "张三") @NotBlank @Size(max = 64) String legalRepresentative, @Schema(description = "Primary contact / 联系人", example = "张三") @NotBlank String contactName, - @Schema(description = "Contact phone and administrator username / 联系电话及管理员用户名", example = "13800138000") - @NotBlank @Pattern(regexp = "[0-9-]{4,32}") String contactPhone, + @Schema(description = "Company contact phone; also initializes the first administrator login phone at creation, then maintained independently / 公司联系电话;创建时同时初始化首个管理员登录手机号,之后独立维护", example = "13800138000") + @NotBlank @Pattern(regexp = "^1[3-9]\\d{9}$") String contactPhone, @Schema(description = "Contact email / 联系邮箱", example = "ops@example.com") @Email String contactEmail, @NotBlank @Size(max = 64) String servicePackage, @Size(max = 64) String contactWechat, @@ -48,7 +48,8 @@ public final class TenantDtos { @Schema(description = "Legal entity name / 企业法定名称", example = "深圳虫虫出海科技有限公司") @NotBlank String legalName, @Schema(description = "Legal representative / 法定代表人", example = "张三") @NotBlank @Size(max = 64) String legalRepresentative, @Schema(description = "Primary contact / 联系人", example = "李四") @NotBlank String contactName, - @Schema(description = "Contact phone / 联系电话", example = "13900139000") @NotBlank String contactPhone, + @Schema(description = "Company contact phone, never used to resolve login identity / 公司联系电话,不作为登录标识", example = "0755-12345678") + @NotBlank @Pattern(regexp = "[0-9-]{4,32}") String contactPhone, @Schema(description = "Contact email / 联系邮箱", example = "service@example.com") @Email String contactEmail ) { } @@ -59,6 +60,7 @@ public final class TenantDtos { @NotBlank String legalName, @NotBlank @Size(max = 64) String legalRepresentative, @NotBlank String contactName, + @Schema(description = "Company contact phone, independent from every member login phone / 公司联系电话,与所有成员登录手机号独立") @NotBlank @Pattern(regexp = "[0-9-]{4,32}") String contactPhone, @Email String contactEmail, @NotBlank @Size(max = 64) String servicePackage, @@ -120,14 +122,14 @@ public final class TenantDtos { public record ApprovalResponse( @Schema(description = "Tenant ID / 企业租户 ID", example = "190000000000000001") Long tenantId, @Schema(description = "Whether this approval created the administrator / 本次审核是否新建管理员") boolean administratorCreated, - @Schema(description = "Contact phone used as login username; null when an owner already exists / 使用联系电话作为登录账号,已有所有者时为空", nullable = true) String administratorUsername, + @Schema(description = "Generated first-administrator username; independent from company contact phone / 自动生成的首个管理员用户名,与公司联系电话独立", nullable = true) String administratorUsername, @Schema(description = "One-time temporary password; only returned when created / 一次性初始密码,仅新建时返回", format = "password", nullable = true) String temporaryPassword ) { } @Schema(name = "TenantCreationResponse", description = "Created enterprise draft and one-time administrator credentials / 新建企业草稿及一次性管理员凭据") public record CreationResponse( @Schema(description = "Created enterprise draft / 新建企业草稿") TenantResponse tenant, - @Schema(description = "Contact phone used as login username / 使用联系电话作为登录账号") String administratorUsername, + @Schema(description = "Generated administrator username, independent from company contact phone / 自动生成的管理员用户名,与公司联系电话独立") String administratorUsername, @Schema(description = "One-time temporary password / 一次性初始密码", format = "password") String temporaryPassword ) { } diff --git a/iam/src/main/java/com/baiyue/iam/repository/TenantRepository.java b/iam/src/main/java/com/baiyue/iam/repository/TenantRepository.java index 04e432e32..c2fd87bae 100644 --- a/iam/src/main/java/com/baiyue/iam/repository/TenantRepository.java +++ b/iam/src/main/java/com/baiyue/iam/repository/TenantRepository.java @@ -35,19 +35,6 @@ public class TenantRepository { .ne(IamTenant::getId, tenantId)) > 0; } - public boolean existsByContactPhone(String contactPhone) { - return tenantMapper.selectCount(new LambdaQueryWrapper() - .eq(IamTenant::getTenantType, "MERCHANT") - .eq(IamTenant::getContactPhone, contactPhone)) > 0; - } - - public boolean existsByContactPhoneForOtherTenant(String contactPhone, long tenantId) { - return tenantMapper.selectCount(new LambdaQueryWrapper() - .eq(IamTenant::getTenantType, "MERCHANT") - .eq(IamTenant::getContactPhone, contactPhone) - .ne(IamTenant::getId, tenantId)) > 0; - } - public IamTenant findTenant(Long tenantId) { return tenantMapper.selectById(tenantId); } public List findMerchantsByIds(Collection tenantIds) { if (tenantIds == null || tenantIds.isEmpty()) return List.of(); diff --git a/iam/src/main/java/com/baiyue/iam/service/MerchantOnboardingService.java b/iam/src/main/java/com/baiyue/iam/service/MerchantOnboardingService.java index a393d5cac..a38f0b668 100644 --- a/iam/src/main/java/com/baiyue/iam/service/MerchantOnboardingService.java +++ b/iam/src/main/java/com/baiyue/iam/service/MerchantOnboardingService.java @@ -74,7 +74,7 @@ public class MerchantOnboardingService { tenant.setLegalName(request.companyName()); tenant.setLegalRepresentative(request.legalPerson()); tenant.setContactName(request.displayName()); - tenant.setContactPhone(request.legalPhone()); + tenant.setContactPhone(request.administratorPhone()); tenant.setContactEmail(request.contactEmail()); tenant.setDeleted(false); tenant.setVersion(0); @@ -86,7 +86,7 @@ public class MerchantOnboardingService { user.setAuthUserId(request.authUserId()); user.setUserType("ENTERPRISE"); user.setDisplayName(request.displayName()); - user.setPhone(request.legalPhone()); + user.setPhone(request.administratorPhone()); user.setEmail(request.contactEmail()); user.setStatus("ACTIVE"); userMapper.insert(user); diff --git a/iam/src/main/java/com/baiyue/iam/service/TenantAdministratorProvisioningService.java b/iam/src/main/java/com/baiyue/iam/service/TenantAdministratorProvisioningService.java index 83b298eed..811857965 100644 --- a/iam/src/main/java/com/baiyue/iam/service/TenantAdministratorProvisioningService.java +++ b/iam/src/main/java/com/baiyue/iam/service/TenantAdministratorProvisioningService.java @@ -88,7 +88,8 @@ public class TenantAdministratorProvisioningService implements TenantApprovalIni long userId = IdWorker.getId(); long membershipId = IdWorker.getId(); String password = temporaryPassword(); - String username = createAuthAccountWithUniqueUsername(authUserId, password, tenant, membershipId); + String username = createAuthAccount(authUserId, password, tenant, membershipId, + tenant.getContactPhone(), tenant.getContactName()); ProvisioningResult result = ProvisioningResult.created(authUserId, username, password); try { @@ -153,8 +154,8 @@ public class TenantAdministratorProvisioningService implements TenantApprovalIni AuthAccountClient.AccountSummary account = findAuthAccount(user.getAuthUserId()); if (account == null) { String password = temporaryPassword(); - String username = createAuthAccountWithUniqueUsername(user.getAuthUserId(), password, - tenant, owner.getId()); + String username = createAuthAccount(user.getAuthUserId(), password, tenant, owner.getId(), + user.getPhone(), user.getDisplayName()); publishInitialSecurityProjection(user.getAuthUserId(), tenant.getId()); return ProvisioningResult.created(user.getAuthUserId(), username, password); } @@ -204,19 +205,13 @@ public class TenantAdministratorProvisioningService implements TenantApprovalIni if (result.created()) compensate(result.authUserId()); } - private String createAuthAccountWithUniqueUsername(long authUserId, String password, - IamTenant tenant, long membershipId) { - String username = username(tenant.getContactPhone()); - createAuthAccount(authUserId, username, password, tenant, membershipId); - return username; - } - - private void createAuthAccount(long authUserId, String username, String password, - IamTenant tenant, long membershipId) { + private String createAuthAccount(long authUserId, String password, IamTenant tenant, long membershipId, + String loginPhone, String displayName) { + String username = generatedUsername(authUserId); try { ApiResponse response = authClient.create(internalToken, - new AuthAccountClient.CreateAccountRequest(authUserId, username, tenant.getContactPhone(), password, - tenant.getContactName(), tenant.getId(), membershipId, "MERCHANT", "ACTIVE")); + new AuthAccountClient.CreateAccountRequest(authUserId, username, loginPhone, password, + displayName, tenant.getId(), membershipId, "MERCHANT", "ACTIVE")); if (response == null) throw unavailable(); if (!response.successful()) { ResultCode code = ResultCode.of(response.code()); @@ -224,9 +219,10 @@ public class TenantAdministratorProvisioningService implements TenantApprovalIni ? code.message() : response.message()); } if (response.data() == null) throw unavailable(); + return username; } catch (FeignException exception) { if (exception.status() == 409) { - throw new BizException(ResultCode.CONFLICT, "联系电话已被其他账号使用,不能作为企业管理员登录用户名"); + throw new BizException(ResultCode.CONFLICT, "管理员用户名或登录手机号已被其他账号使用"); } throw unavailable(); } catch (BizException exception) { @@ -267,8 +263,8 @@ public class TenantAdministratorProvisioningService implements TenantApprovalIni return new String(value); } - private String username(String contactPhone) { - return contactPhone == null ? "" : contactPhone.trim(); + private String generatedUsername(long authUserId) { + return "merchant_" + authUserId; } private char pick(char[] source) { return source[random.nextInt(source.length)]; } diff --git a/iam/src/main/java/com/baiyue/iam/service/TenantService.java b/iam/src/main/java/com/baiyue/iam/service/TenantService.java index a8a7835d5..50221060e 100644 --- a/iam/src/main/java/com/baiyue/iam/service/TenantService.java +++ b/iam/src/main/java/com/baiyue/iam/service/TenantService.java @@ -162,7 +162,7 @@ public class TenantService { @Transactional public CreationResponse register(RegisterRequest request, long operatorId) { String contactPhone = normalizeContactPhone(request.contactPhone()); - ensureContactPhoneAvailable(contactPhone, null); + ensureAdministratorPhoneAvailable(contactPhone); IamTenant tenant = new IamTenant(); tenant.setId(IdWorker.getId()); tenant.setTenantCode("TEN" + tenant.getId()); @@ -364,24 +364,17 @@ public class TenantService { return dimensionRuleInitialization.status(tenantId); } - public boolean contactPhoneAvailable(String contactPhone, Long tenantId) { - String normalized = normalizeContactPhone(contactPhone); - return tenantId == null - ? !repository.existsByContactPhone(normalized) - : !repository.existsByContactPhoneForOtherTenant(normalized, tenantId); - } - - public boolean contactPhoneAndUsernameAvailable(String contactPhone, Long tenantId) { - String normalized = normalizeContactPhone(contactPhone); - if (!contactPhoneAvailable(normalized, tenantId)) return false; + /** Platform creation uses the same initial value for company contact and owner login phone. */ + public boolean administratorPhoneAvailable(String phone) { + String normalized = normalizeContactPhone(phone); ApiResponse response; try { response = authClient.availability(internalToken, normalized); } catch (FeignException exception) { - throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "认证账号服务暂时不可用,不能校验联系电话"); + throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "认证账号服务暂时不可用,不能校验管理员登录手机号"); } if (response == null || !response.successful() || response.data() == null) { - throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "认证账号服务暂时不可用,不能校验联系电话"); + throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "认证账号服务暂时不可用,不能校验管理员登录手机号"); } return response.data(); } @@ -393,13 +386,6 @@ public class TenantService { IamTenant tenant = requireMerchant(tenantId); ensureNotArchived(tenant); String contactPhone = normalizeContactPhone(request.contactPhone()); - String existingContactPhone = normalizeContactPhone(tenant.getContactPhone()); - if (!contactPhone.equals(existingContactPhone)) { - if ("ACTIVE".equals(tenant.getStatus()) || "DISABLED".equals(tenant.getStatus())) { - throw new BizException(ResultCode.CONFLICT, "客户已开户,联系电话作为登录用户名不能直接修改"); - } - ensureContactPhoneAvailable(contactPhone, tenantId); - } tenant.setTenantName(request.tenantName()); tenant.setLegalName(request.legalName()); tenant.setLegalRepresentative(request.legalRepresentative()); @@ -446,7 +432,6 @@ public class TenantService { if (repository.findPendingReviewByTenant(tenantId) != null) { throw new BizException(ResultCode.CONFLICT, "Enterprise information is already under review / 客户资料已在审核中"); } - ensureContactPhoneAvailable(normalizeContactPhone(tenant.getContactPhone()), tenantId); if (tenant.getUnifiedSocialCreditCode() == null || !tenant.getUnifiedSocialCreditCode().matches("[0-9A-Z]{18}")) { throw new BizException(ResultCode.BAD_REQUEST, @@ -477,7 +462,7 @@ public class TenantService { tenant.setLegalName(request.legalName()); tenant.setLegalRepresentative(request.legalRepresentative()); tenant.setContactName(request.contactName()); - tenant.setContactPhone(request.contactPhone()); + tenant.setContactPhone(normalizeContactPhone(request.contactPhone())); tenant.setContactEmail(request.contactEmail()); repository.saveTenant(tenant); return merchantVisibleResponse(tenant); @@ -497,9 +482,9 @@ public class TenantService { return contactPhone == null ? "" : contactPhone.trim(); } - private void ensureContactPhoneAvailable(String contactPhone, Long tenantId) { - if (!contactPhoneAvailable(contactPhone, tenantId)) { - throw new BizException(ResultCode.CONFLICT, "联系电话已被其他客户使用,不能作为登录用户名"); + private void ensureAdministratorPhoneAvailable(String phone) { + if (!administratorPhoneAvailable(phone)) { + throw new BizException(ResultCode.CONFLICT, "管理员登录手机号已被其他账号使用"); } } diff --git a/iam/src/main/resources/db/iam.sql b/iam/src/main/resources/db/iam.sql index bc250abe7..4b4dd4f59 100644 --- a/iam/src/main/resources/db/iam.sql +++ b/iam/src/main/resources/db/iam.sql @@ -16,7 +16,7 @@ CREATE TABLE IF NOT EXISTS iam_tenant ( legal_representative VARCHAR(64) NULL COMMENT '法定代表人', unified_social_credit_code VARCHAR(32) NULL COMMENT '统一社会信用代码', contact_name VARCHAR(64) NULL COMMENT '企业联系人', - contact_phone VARCHAR(32) NULL COMMENT '联系电话', + contact_phone VARCHAR(32) NULL COMMENT '公司联系电话,非登录标识且不要求跨企业唯一', contact_email VARCHAR(128) NULL COMMENT '联系邮箱', service_package VARCHAR(64) NOT NULL DEFAULT '基础版' COMMENT '客户服务套餐', contact_wechat VARCHAR(64) NULL COMMENT '联系微信', @@ -119,7 +119,7 @@ CREATE TABLE IF NOT EXISTS iam_user ( auth_user_id BIGINT NOT NULL COMMENT '认证服务账号逻辑ID', user_type VARCHAR(20) NOT NULL DEFAULT 'ENTERPRISE' COMMENT '用户类型:ENTERPRISE企业用户、PLATFORM平台用户', display_name VARCHAR(64) NOT NULL COMMENT '用户展示名称', - phone VARCHAR(32) NULL COMMENT '手机号', + phone VARCHAR(32) NULL COMMENT '用户登录手机号,与企业联系电话独立', email VARCHAR(128) NULL COMMENT '邮箱', status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE' COMMENT '用户资料状态:ACTIVE正常、DISABLED停用', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', diff --git a/iam/src/main/resources/openapi/iam-v1.yaml b/iam/src/main/resources/openapi/iam-v1.yaml index a186189b4..61f8575d7 100644 --- a/iam/src/main/resources/openapi/iam-v1.yaml +++ b/iam/src/main/resources/openapi/iam-v1.yaml @@ -67,16 +67,16 @@ paths: '401': {$ref: '#/components/responses/Unauthorized'} '403': {$ref: '#/components/responses/Forbidden'} '409': {$ref: '#/components/responses/Conflict'} - /api/platform/v1/iam/tenants/contact-phone-availability: + /api/platform/v1/iam/tenants/administrator-phone-availability: get: tags: [Platform Tenant / 平台租户] - summary: 校验客户联系电话唯一性 + summary: 校验首个管理员登录手机号唯一性 + description: 仅在创建客户前校验 Auth 登录手机号;公司联系电话不要求跨企业唯一。 parameters: - - {name: contactPhone, in: query, required: true, schema: {type: string}} - - {name: excludeTenantId, in: query, required: false, schema: {type: integer, format: int64}} + - {name: phone, in: query, required: true, schema: {type: string, pattern: '^1[3-9]\d{9}$'}} responses: '200': - description: 返回联系电话是否可作为登录用户名 + description: 返回手机号是否可作为首个管理员登录手机号 content: application/json: schema: @@ -211,7 +211,7 @@ paths: post: tags: [Platform Tenant / 平台租户] summary: 提交企业客户审核 - description: 校验营业执照和法人身份证已上传后创建审核单,并把客户状态改为 `PENDING`;保存资料和上传文件均不会自动调用本接口。 + description: 校验营业执照和统一社会信用代码后创建审核单,并把客户状态改为 `PENDING`;法人身份证选填,保存资料和上传文件均不会自动调用本接口。 responses: '200': {$ref: '#/components/responses/TenantSuccess'} '400': {$ref: '#/components/responses/BadRequest'} @@ -683,7 +683,7 @@ paths: post: tags: [IAM Resources / IAM 资源] summary: Submit enterprise qualifications for review / 提交企业资质审核 - description: Uploading or replacing a qualification keeps the tenant in draft state. This endpoint creates the review after the business license and legal-person ID have been provided; the freight-forwarder certificate and bank account license are optional. / 上传或替换资质不会自动送审;营业执照和法人身份证齐全后由此接口创建审核单,货代资质证书和银行开户许可证选填。 + description: Uploading or replacing a qualification keeps the tenant in draft state. This endpoint requires only the business license and unified social credit code; legal-person ID, freight-forwarder certificate and bank account license are optional. / 上传或替换资质不会自动送审;本接口只要求营业执照和统一社会信用代码,法人身份证、货代资质证书和银行开户许可证选填。 responses: '200': {$ref: '#/components/responses/TenantSuccess'} '400': {$ref: '#/components/responses/BadRequest'} @@ -813,8 +813,8 @@ paths: /internal/iam/required-qualification-packages: post: tags: [IAM Resources / IAM 资源] - summary: 商户必填资质包登记 - description: 仅供 File Service 调用;在一个 IAM 事务中登记营业执照、统一社会信用代码和法人身份证。 + summary: 商户营业执照资质包登记 + description: 仅供 File Service 调用;在一个 IAM 事务中登记必填营业执照与统一社会信用代码,法人身份证可选。 security: [] parameters: - {$ref: '#/components/parameters/InternalServiceToken'} @@ -833,8 +833,8 @@ paths: /internal/iam/platform-required-qualification-packages: post: tags: [IAM Resources / IAM 资源] - summary: 平台代客户必填资质包登记 - description: 仅供 File Service 调用;与商户入口使用相同的三项事务保存规则。 + summary: 平台代客户登记营业执照资质包 + description: 仅供 File Service 调用;与商户入口使用相同规则,营业执照与统一社会信用代码必填,法人身份证可选。 security: [] parameters: - {$ref: '#/components/parameters/InternalServiceToken'} @@ -1318,7 +1318,7 @@ components: legalName: {type: string, example: 深圳虫虫出海科技有限公司} legalRepresentative: {type: string, maxLength: 64, example: 张三} contactName: {type: string, example: 李四} - contactPhone: {type: string, example: '13800138000'} + contactPhone: {type: string, pattern: '^1[3-9]\d{9}$', example: '13800138000', description: 创建时同时初始化公司联系电话与首个管理员登录手机号;创建后二者独立维护} contactEmail: {type: string, format: email, example: contact@example.com} contactWechat: {type: string, maxLength: 64} servicePackage: {type: string, maxLength: 64, example: 基础版} @@ -1338,7 +1338,7 @@ components: legalName: {type: string} legalRepresentative: {type: string, maxLength: 64} contactName: {type: string} - contactPhone: {type: string} + contactPhone: {type: string, pattern: '^[0-9-]{4,32}$', description: 公司联系电话,不作为登录标识且不要求跨企业唯一} contactEmail: {type: string, format: email} UpdateTenantRequest: type: object @@ -1348,7 +1348,7 @@ components: legalName: {type: string} legalRepresentative: {type: string, maxLength: 64} contactName: {type: string} - contactPhone: {type: string} + contactPhone: {type: string, pattern: '^[0-9-]{4,32}$', description: 公司联系电话,与管理员及成员登录手机号独立且不要求跨企业唯一} contactEmail: {type: string, format: email} contactWechat: {type: string, maxLength: 64} servicePackage: {type: string, maxLength: 64, example: 基础版} @@ -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[3-9]\\d{9}$'} + phone: {type: string, pattern: '^1[3-9]\\d{9}$', description: 平台账号登录手机号;修改时同步 Auth、撤销该账号会话并审计} 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[3-9]\\d{9}$'} + phone: {type: string, pattern: '^1[3-9]\\d{9}$', description: 平台账号登录手机号;与任何企业联系电话无关} 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[3-9]\d{9}$'} + phone: {type: string, pattern: '^1[3-9]\d{9}$', description: 企业成员登录手机号;修改时同步 Auth、撤销该账号会话并审计} 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[3-9]\d{9}$'} + phone: {type: string, pattern: '^1[3-9]\d{9}$', description: 企业成员登录手机号;与公司联系电话无关} email: {type: string, format: email, maxLength: 128} memberNo: {type: string, maxLength: 64} roleIds: {type: array, items: {type: integer, format: int64}}