This commit is contained in:
2026-09-02 10:04:18 +08:00
parent b85e021cbe
commit e83dff6ac7
12 changed files with 67 additions and 96 deletions
@@ -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,
@@ -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
@@ -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");
@@ -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<Boolean> 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<Boolean> 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")
@@ -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
@@ -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
) { }
@@ -35,19 +35,6 @@ public class TenantRepository {
.ne(IamTenant::getId, tenantId)) > 0;
}
public boolean existsByContactPhone(String contactPhone) {
return tenantMapper.selectCount(new LambdaQueryWrapper<IamTenant>()
.eq(IamTenant::getTenantType, "MERCHANT")
.eq(IamTenant::getContactPhone, contactPhone)) > 0;
}
public boolean existsByContactPhoneForOtherTenant(String contactPhone, long tenantId) {
return tenantMapper.selectCount(new LambdaQueryWrapper<IamTenant>()
.eq(IamTenant::getTenantType, "MERCHANT")
.eq(IamTenant::getContactPhone, contactPhone)
.ne(IamTenant::getId, tenantId)) > 0;
}
public IamTenant findTenant(Long tenantId) { return tenantMapper.selectById(tenantId); }
public List<IamTenant> findMerchantsByIds(Collection<Long> tenantIds) {
if (tenantIds == null || tenantIds.isEmpty()) return List.of();
@@ -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);
@@ -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<AuthAccountClient.AccountSummary> 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)]; }
@@ -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<Boolean> 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, "管理员登录手机号已被其他账号使用");
}
}
+2 -2
View File
@@ -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 '创建时间',
+18 -18
View File
@@ -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}}