From 897f91af20568143967c709c8b714bc164c04f3e Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Fri, 7 Aug 2026 09:06:19 +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 --- .../controller/PlatformTenantController.java | 13 +++++++ .../iam/dto/PlatformCustomerMemberDtos.java | 13 +++++++ .../com/baiyue/iam/service/MemberService.java | 32 ++++++++++++++++ iam/src/main/resources/openapi/iam-v1.yaml | 38 +++++++++++++++++++ .../baiyue/iam/service/MemberServiceTest.java | 25 ++++++++++++ 5 files changed, 121 insertions(+) create mode 100644 iam/src/main/java/com/baiyue/iam/dto/PlatformCustomerMemberDtos.java 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 1999e1ad6..373035190 100644 --- a/iam/src/main/java/com/baiyue/iam/controller/PlatformTenantController.java +++ b/iam/src/main/java/com/baiyue/iam/controller/PlatformTenantController.java @@ -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> 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. / 仅创建待完善企业客户草稿,保存客户不会创建审核单。") diff --git a/iam/src/main/java/com/baiyue/iam/dto/PlatformCustomerMemberDtos.java b/iam/src/main/java/com/baiyue/iam/dto/PlatformCustomerMemberDtos.java new file mode 100644 index 000000000..44b7c67af --- /dev/null +++ b/iam/src/main/java/com/baiyue/iam/dto/PlatformCustomerMemberDtos.java @@ -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 roleIds, List roleNames) { } +} diff --git a/iam/src/main/java/com/baiyue/iam/service/MemberService.java b/iam/src/main/java/com/baiyue/iam/service/MemberService.java index 99e405f5a..c5a9f0396 100644 --- a/iam/src/main/java/com/baiyue/iam/service/MemberService.java +++ b/iam/src/main/java/com/baiyue/iam/service/MemberService.java @@ -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 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 roleIds = repository.findRoleIds(tenantId, membership.getId()); + List 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)); } diff --git a/iam/src/main/resources/openapi/iam-v1.yaml b/iam/src/main/resources/openapi/iam-v1.yaml index 7ab2688fc..cea557b7a 100644 --- a/iam/src/main/resources/openapi/iam-v1.yaml +++ b/iam/src/main/resources/openapi/iam-v1.yaml @@ -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'} diff --git a/iam/src/test/java/com/baiyue/iam/service/MemberServiceTest.java b/iam/src/test/java/com/baiyue/iam/service/MemberServiceTest.java index 179c23c79..c91a6f31e 100644 --- a/iam/src/test/java/com/baiyue/iam/service/MemberServiceTest.java +++ b/iam/src/test/java/com/baiyue/iam/service/MemberServiceTest.java @@ -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));