This commit is contained in:
2026-08-07 09:06:19 +08:00
parent a5dc0f78c3
commit 897f91af20
5 changed files with 121 additions and 0 deletions
@@ -12,6 +12,8 @@ import com.baiyue.iam.dto.TenantDtos.ApprovalResponse;
import com.baiyue.iam.entity.IamTenantReview;
import com.baiyue.iam.service.TenantGuard;
import com.baiyue.iam.service.TenantService;
import com.baiyue.iam.service.MemberService;
import com.baiyue.iam.dto.PlatformCustomerMemberDtos;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
@@ -38,6 +40,8 @@ import java.util.List;
public class PlatformTenantController {
@Resource
private TenantService service;
@Resource
private MemberService memberService;
@GetMapping("/tenants")
@PreAuthorize("hasAuthority('platform:customer:list')")
@@ -69,6 +73,15 @@ public class PlatformTenantController {
return ApiResponse.success(service.merchant(tenantId));
}
@GetMapping("/tenants/{tenantId:\\d+}/members")
@PreAuthorize("hasAuthority('platform:customer:view')")
@Operation(summary = "查询企业客户成员及账号", description = "平台只读查询指定企业的全部成员,包含 OWNER 企业管理员,并返回登录账号。")
public ApiResponse<List<PlatformCustomerMemberDtos.MemberResponse>> members(
@PathVariable long tenantId, @AuthenticationPrincipal AuthenticatedUser user) {
TenantGuard.tenant(user, "PLATFORM");
return ApiResponse.success(memberService.platformList(tenantId));
}
@PostMapping("/tenants")
@PreAuthorize("hasAuthority('platform:customer:create')")
@Operation(summary = "Register enterprise tenant / 创建企业客户草稿", description = "Create a PROFILE_REQUIRED merchant draft only. Saving the customer does not create a review ticket. / 仅创建待完善企业客户草稿,保存客户不会创建审核单。")
@@ -0,0 +1,13 @@
package com.baiyue.iam.dto;
import java.util.List;
/** 平台查看客户企业成员时使用的只读契约,补充认证账号信息。 */
public final class PlatformCustomerMemberDtos {
private PlatformCustomerMemberDtos() { }
public record MemberResponse(Long membershipId, Long userId, Long authUserId,
String username, String displayName, String phone, String email,
String memberType, String memberNo, String status,
List<Long> roleIds, List<String> roleNames) { }
}
@@ -11,6 +11,8 @@ import com.baiyue.iam.entity.IamRole;
import com.baiyue.iam.entity.IamUser;
import com.baiyue.iam.mapper.IamRoleMapper;
import com.baiyue.iam.repository.MemberRepository;
import com.baiyue.iam.repository.TenantRepository;
import com.baiyue.iam.dto.PlatformCustomerMemberDtos;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import feign.FeignException;
import jakarta.annotation.Resource;
@@ -33,6 +35,8 @@ public class MemberService {
@Resource
private MemberRepository repository;
@Resource
private TenantRepository tenantRepository;
@Resource
private IamRoleMapper roleMapper;
@Resource
private AuthAccountClient authClient;
@@ -43,6 +47,34 @@ public class MemberService {
return repository.findByTenant(tenantId).stream().map(m -> response(tenantId, m)).toList();
}
/** 平台只读查询客户成员;显式校验客户主档,并包含 OWNER 管理员。 */
public List<PlatformCustomerMemberDtos.MemberResponse> platformList(long tenantId) {
var tenant = tenantRepository.findTenant(tenantId);
if (tenant == null || !"MERCHANT".equals(tenant.getTenantType())) {
throw new BizException(ResultCode.NOT_FOUND, "企业客户不存在");
}
return repository.findByTenant(tenantId).stream().map(m -> platformResponse(tenantId, m)).toList();
}
private PlatformCustomerMemberDtos.MemberResponse platformResponse(long tenantId, IamMembership membership) {
IamUser user = requiredUser(membership.getUserId());
AuthAccountClient.AccountSummary account = accountSummary(user.getAuthUserId());
List<Long> roleIds = repository.findRoleIds(tenantId, membership.getId());
List<String> roleNames = roleIds.stream()
.map(roleMapper::selectById)
.filter(role -> role != null && Long.valueOf(tenantId).equals(role.getTenantId()))
.map(IamRole::getRoleName)
.toList();
return new PlatformCustomerMemberDtos.MemberResponse(membership.getId(), user.getId(),
user.getAuthUserId(), account.username(), user.getDisplayName(), user.getPhone(), user.getEmail(),
membership.getMemberType(), membership.getMemberNo(), membership.getStatus(),
roleIds, roleNames);
}
private AuthAccountClient.AccountSummary accountSummary(Long authUserId) {
return data(call(() -> authClient.summary(internalToken, authUserId)));
}
public MemberResponse detail(long tenantId, Long membershipId) {
return response(tenantId, required(tenantId, membershipId));
}
@@ -88,6 +88,22 @@ paths:
'400': {$ref: '#/components/responses/BadRequest'}
'403': {$ref: '#/components/responses/Forbidden'}
'404': {$ref: '#/components/responses/NotFound'}
/api/platform/v1/iam/tenants/{tenantId}/members:
parameters:
- {$ref: '#/components/parameters/TenantId'}
get:
tags: [Platform Tenant / 平台租户]
summary: 查询企业客户成员及账号
description: 平台只读查询指定客户的全部企业成员,包含 OWNER 企业管理员并返回登录账号;需要 `platform:customer:view` 权限。
responses:
'200':
description: 企业成员查询成功
content:
application/json:
schema:
$ref: '#/components/schemas/PlatformCustomerMembersApiResponse'
'403': {$ref: '#/components/responses/Forbidden'}
'404': {$ref: '#/components/responses/NotFound'}
/api/platform/v1/iam/tenants/{tenantId}/submit-review:
parameters:
- {$ref: '#/components/parameters/TenantId'}
@@ -802,6 +818,28 @@ components:
- type: object
properties:
data: {$ref: '#/components/schemas/InternalUserResponse'}
PlatformCustomerMemberResponse:
type: object
required: [membershipId, userId, authUserId, username, displayName, memberType, status, roleIds, roleNames]
properties:
membershipId: {type: string, pattern: '^\d+$'}
userId: {type: string, pattern: '^\d+$'}
authUserId: {type: string, pattern: '^\d+$'}
username: {type: string}
displayName: {type: string}
phone: {type: string}
email: {type: string, format: email}
memberType: {type: string, enum: [OWNER, EMPLOYEE]}
memberNo: {type: string}
status: {type: string, enum: [ACTIVE, DISABLED]}
roleIds: {type: array, items: {type: string, pattern: '^\d+$'}}
roleNames: {type: array, items: {type: string}}
PlatformCustomerMembersApiResponse:
allOf:
- {$ref: '#/components/schemas/ApiResponse'}
- type: object
properties:
data: {type: array, items: {$ref: '#/components/schemas/PlatformCustomerMemberResponse'}}
PlatformManagedUserPageApiResponse:
allOf:
- {$ref: '#/components/schemas/ApiResponse'}
@@ -7,9 +7,11 @@ import com.baiyue.iam.client.AuthAccountClient;
import com.baiyue.iam.dto.MemberDtos.*;
import com.baiyue.iam.entity.IamMembership;
import com.baiyue.iam.entity.IamRole;
import com.baiyue.iam.entity.IamTenant;
import com.baiyue.iam.entity.IamUser;
import com.baiyue.iam.mapper.IamRoleMapper;
import com.baiyue.iam.repository.MemberRepository;
import com.baiyue.iam.repository.TenantRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import jakarta.annotation.Resource;
@@ -28,6 +30,7 @@ import static org.mockito.Mockito.*;
@TestPropertySource(properties = "baiyue.security.internal-service-token=internal-token")
class MemberServiceTest {
@MockBean private MemberRepository repository;
@MockBean private TenantRepository tenantRepository;
@MockBean private IamRoleMapper roleMapper;
@MockBean private AuthAccountClient auth;
@Resource private MemberService service;
@@ -52,6 +55,28 @@ class MemberServiceTest {
verifyNoInteractions(auth);
}
@Test void shouldReturnOwnerAccountForPlatformCustomerDetail() {
IamTenant tenant = new IamTenant();
tenant.setId(100L); tenant.setTenantType("MERCHANT");
member.setMemberType("OWNER");
user.setPhone("13800138000"); user.setEmail("owner@example.com");
when(tenantRepository.findTenant(100L)).thenReturn(tenant);
when(repository.findByTenant(100L)).thenReturn(List.of(member));
IamRole ownerRole = new IamRole();
ownerRole.setId(50L); ownerRole.setTenantId(100L); ownerRole.setRoleName("企业管理员");
when(roleMapper.selectById(50L)).thenReturn(ownerRole);
when(auth.summary("internal-token", 40L)).thenReturn(ApiResponse.success(
new AuthAccountClient.AccountSummary(40L, "merchant_owner", "企业管理员", "ACTIVE")));
var result = service.platformList(100L);
assertEquals(1, result.size());
assertEquals("merchant_owner", result.get(0).username());
assertEquals("OWNER", result.get(0).memberType());
assertEquals("ACTIVE", result.get(0).status());
assertEquals(List.of("企业管理员"), result.get(0).roleNames());
}
@Test void shouldHandleCase02() {
when(repository.findByTenantAndId(200L, 20L)).thenReturn(null);
assertThrows(BizException.class, () -> service.detail(200L, 20L));