This commit is contained in:
2026-08-05 16:26:10 +08:00
parent 06f7f9d2f2
commit 5ba9716e12
155 changed files with 2883 additions and 551 deletions
@@ -30,7 +30,12 @@ public class CustomerListExportProviderController {
@PostConstruct
void initializeToken() {
this.internalToken = internalTokenValue.getBytes(StandardCharsets.UTF_8);
byte[] configured = internalTokenValue == null
? new byte[0] : internalTokenValue.getBytes(StandardCharsets.UTF_8);
if (configured.length < 32) {
throw new IllegalStateException("INTERNAL_SERVICE_TOKEN 至少需要 32 字节");
}
this.internalToken = configured;
}
@PostMapping("/batches")
@@ -40,7 +40,14 @@ public class InternalTenantController {
private byte[] internalToken;
@PostConstruct
void initializeToken() { internalToken = internalTokenValue.getBytes(StandardCharsets.UTF_8); }
void initializeToken() {
byte[] configured = internalTokenValue == null
? new byte[0] : internalTokenValue.getBytes(StandardCharsets.UTF_8);
if (configured.length < 32) {
throw new IllegalStateException("INTERNAL_SERVICE_TOKEN 至少需要 32 字节");
}
internalToken = configured;
}
@PostMapping("/merchant-registrations")
public ApiResponse<MerchantRegistrationResponse> register(
@@ -8,8 +8,11 @@ public final class TenantGuard {
public static long tenant(AuthenticatedUser user,String expectedApp){
if(user==null||user.tenantId()==null) throw new BizException(ResultCode.UNAUTHORIZED,"Authentication required / 请先登录");
if(!expectedApp.equals(user.appCode())) throw new BizException(ResultCode.FORBIDDEN,"Wrong application scope / 应用权限域不匹配");
if("PLATFORM".equals(expectedApp)) return 0L;
if(user.tenantId()<=0) throw new BizException(ResultCode.FORBIDDEN,"Merchant tenant required / 缺少商户租户");
if("PLATFORM".equals(expectedApp)) {
if(user.tenantId()!=0L) throw new BizException(ResultCode.FORBIDDEN,"Platform tenant must be zero / 平台作用域必须为 0");
return 0L;
}
if(!"MERCHANT".equals(expectedApp)||user.tenantId()<=0) throw new BizException(ResultCode.FORBIDDEN,"Merchant tenant required / 缺少商户租户");
return user.tenantId();
}
}
@@ -199,10 +199,7 @@ public class TenantService {
}
public TenantResponse profile(long tenantId) {
IamTenant tenant = repository.findTenant(tenantId);
if (tenant == null || !tenantIdEquals(tenant, tenantId)) {
throw new BizException(ResultCode.NOT_FOUND, "Tenant not found / 企业不存在");
}
IamTenant tenant = requireMerchant(tenantId);
return merchantVisibleResponse(tenant);
}
@@ -285,10 +282,7 @@ public class TenantService {
@BusinessAudit(event = "TENANT_PROFILE_UPDATE", resourceType = "TENANT",
resourceId = "#tenantId", description = "编辑企业资料")
public TenantResponse update(long tenantId, UpdateProfileRequest request) {
IamTenant tenant = repository.findTenant(tenantId);
if (tenant == null || !tenantIdEquals(tenant, tenantId)) {
throw new BizException(ResultCode.NOT_FOUND, "Tenant not found / 企业不存在");
}
IamTenant tenant = requireMerchant(tenantId);
tenant.setTenantName(request.tenantName());
tenant.setLegalName(request.legalName());
tenant.setLegalRepresentative(request.legalRepresentative());
+13 -13
View File
@@ -1,7 +1,7 @@
openapi: 3.0.3
info:
title: Baiyue IAM API / 百悦身份与权限 API
description: Tenant, member, role, menu, page, button and API permission management. / 租户、成员、角色、菜单、页面、按钮和 API 权限管理。
description: Customer master data, members, roles, menus, pages, buttons and API permissions for the current single-tenant product. Historical tenant-named paths, schemas and fields are retained for compatibility; tenantId > 0 currently means customer ID, while 0 is technical scope for platform identities, RBAC, and public warehouse resources. / 当前单租户产品的客户主档、成员、角色、菜单、页面、按钮和 API 权限管理。tenant 命名的路径、模型和字段仅为历史兼容;tenantId 大于 0 当前表示客户 ID,0 表示平台身份、RBAC 和公共仓储资源技术作用域。
version: 1.0.1
servers:
- url: /
@@ -9,9 +9,9 @@ security:
- bearerAuth: []
tags:
- name: Platform Tenant / 平台租户
description: Enterprise registration and review / 企业注册与审核
description: Historical tag for platform customer administration; it does not represent a real tenant layer. / 历史兼容标签,实际用于平台客户管理,不表示真实租户层。
- name: Merchant Tenant / 商户企业
description: Current tenant profile and members / 当前企业资料与成员
description: Current customer profile and members; tenant naming is retained for compatibility. / 当前客户企业资料与成员;tenant 命名仅为兼容。
- name: IAM Resources / IAM 资源
description: Internal users, addresses and qualifications / 内部用户、地址与资质
- name: RBAC
@@ -237,7 +237,7 @@ paths:
/api/platform/v1/iam/users/options:
get:
tags: [IAM Resources / IAM 资源]
summary: 查询平台租户和 PLATFORM 角色选项
summary: 查询平台技术作用域和 PLATFORM 角色选项
responses:
'200': {$ref: '#/components/responses/Success'}
'403': {$ref: '#/components/responses/Forbidden'}
@@ -363,7 +363,7 @@ paths:
put:
tags: [IAM 资源]
summary: 全量分配平台内部用户角色
description: 仅允许分配平台租户下已启用的平台端角色;空集合表示清空角色。
description: 仅允许分配平台技术作用域(tenant_id=0)下已启用的平台端角色;空集合表示清空角色。
requestBody:
required: true
content:
@@ -382,7 +382,7 @@ paths:
get:
tags: [Merchant Tenant / 商户企业]
summary: Get current tenant profile / 查询当前企业资料
description: Tenant ID comes only from the verified JWT. / 租户 ID 仅取自已验证 JWT。
description: The compatibility field tenant ID comes only from the verified JWT and currently identifies the customer. / 客户隔离兼容字段 tenant ID 仅取自已验证 JWT,当前表示登录客户 ID。
responses:
'200': {$ref: '#/components/responses/Success'}
'401': {$ref: '#/components/responses/Unauthorized'}
@@ -432,7 +432,7 @@ paths:
get:
tags: [Merchant Tenant / 商户企业]
summary: 查询成员详情及当前角色
description: 返回当前租户成员资料,roleIds 为服务端实时查询的当前角色集合;跨租户成员按不存在处理。
description: 返回当前客户成员资料,roleIds 为服务端实时查询的当前角色集合;跨客户成员按不存在处理。
responses:
'200': {$ref: '#/components/responses/Success'}
'403': {$ref: '#/components/responses/Forbidden'}
@@ -493,7 +493,7 @@ paths:
get:
tags: [RBAC]
summary: List roles / 查询角色
description: List roles in the selected application and tenant scope. / 查询指定应用及租户范围内的角色。
description: List roles in the selected application and current customer or platform technical scope. / 查询指定应用及当前客户范围或平台技术作用域内的角色。
responses:
'200': {$ref: '#/components/responses/Success'}
'403': {$ref: '#/components/responses/Forbidden'}
@@ -553,7 +553,7 @@ paths:
put:
tags: [RBAC]
summary: Grant permissions to role / 设置角色权限
description: Replace role permissions; cross-tenant and cross-application grants are rejected. / 替换角色权限,拒绝跨租户和跨应用授权。
description: Replace role permissions; cross-customer and cross-application grants are rejected. / 替换角色权限,拒绝跨客户和跨应用授权。
parameters:
- {$ref: '#/components/parameters/App'}
requestBody:
@@ -719,7 +719,7 @@ components:
name: tenantId
in: path
required: true
description: 企业客户租户 ID
description: 企业客户 ID;tenantId 为历史兼容参数名
schema: {type: string, pattern: '^\d+$'}
PermissionId:
name: permissionId
@@ -732,7 +732,7 @@ components:
TenantSuccess: {description: Enterprise tenant returned / 返回企业客户档案, content: {application/json: {schema: {$ref: '#/components/schemas/TenantApiResponse'}}}}
BadRequest: {description: Invalid request / 请求参数错误, content: {application/json: {schema: {$ref: '#/components/schemas/ApiResponse'}}}}
Unauthorized: {description: Authentication required / 需要登录, content: {application/json: {schema: {$ref: '#/components/schemas/ApiResponse'}}}}
Forbidden: {description: Permission or tenant scope rejected / 权限或租户范围不匹配, content: {application/json: {schema: {$ref: '#/components/schemas/ApiResponse'}}}}
Forbidden: {description: Permission, customer scope or platform technical scope rejected / 权限、客户范围或平台技术作用域不匹配, content: {application/json: {schema: {$ref: '#/components/schemas/ApiResponse'}}}}
NotFound: {description: Resource not found / 资源不存在, content: {application/json: {schema: {$ref: '#/components/schemas/ApiResponse'}}}}
Conflict: {description: Resource conflict / 资源冲突, content: {application/json: {schema: {$ref: '#/components/schemas/ApiResponse'}}}}
UnprocessableEntity: {description: 参数校验失败, content: {application/json: {schema: {$ref: '#/components/schemas/ApiResponse'}}}}
@@ -977,7 +977,7 @@ components:
displayName: {type: string, maxLength: 64}
phone: {type: string, pattern: '^1\\d{10}$'}
email: {type: string, format: email, maxLength: 128}
tenantId: {type: integer, format: int64, enum: [0], description: 保留的兼容字段,只允许平台租户 0}
tenantId: {type: integer, format: int64, enum: [0], description: 保留的兼容字段,只允许平台技术作用域 0}
roleIds: {type: array, minItems: 1, items: {type: integer, format: int64}}
password: {type: string, format: password, minLength: 8, maxLength: 64, writeOnly: true}
status: {type: string, enum: [ACTIVE, DISABLED]}
@@ -988,7 +988,7 @@ components:
displayName: {type: string, maxLength: 64}
phone: {type: string, pattern: '^1\\d{10}$'}
email: {type: string, format: email, maxLength: 128}
tenantId: {type: integer, format: int64, enum: [0], description: 保留的兼容字段,只允许平台租户 0}
tenantId: {type: integer, format: int64, enum: [0], description: 保留的兼容字段,只允许平台技术作用域 0}
roleIds: {type: array, minItems: 1, items: {type: integer, format: int64}}
CreateInternalUserRequest:
type: object