1
This commit is contained in:
+8
-2
@@ -41,7 +41,13 @@ public class NotificationRecipientResolver {
|
||||
if (user != null && "ACTIVE".equals(user.getStatus())) unique.put(item.getId(), new ResolvedRecipient(
|
||||
item.getTenantId(), item.getId(), item.getUserId(), user.getDisplayName(), item.getTenantId() == 0 ? "PLATFORM" : "MERCHANT"));
|
||||
});
|
||||
if (unique.isEmpty()) fail("NOTIFICATION_RECIPIENT_EMPTY");
|
||||
if (unique.isEmpty()) {
|
||||
if (request.recipientType() == com.baiyue.iam.notification.dto.NotificationDtos.RecipientType.TENANT_ROLE
|
||||
|| request.recipientType() == com.baiyue.iam.notification.dto.NotificationDtos.RecipientType.PLATFORM_ROLE) {
|
||||
fail("该角色下暂无可接收消息的有效用户,请先为角色分配启用中的成员");
|
||||
}
|
||||
fail("未找到可接收消息的有效成员,请检查用户 ID 和目标企业");
|
||||
}
|
||||
return List.copyOf(unique.values());
|
||||
}
|
||||
|
||||
@@ -49,7 +55,7 @@ public class NotificationRecipientResolver {
|
||||
var role = roles.selectOne(new LambdaQueryWrapper<com.baiyue.iam.entity.IamRole>()
|
||||
.eq(com.baiyue.iam.entity.IamRole::getTenantId, request.tenantId())
|
||||
.eq(com.baiyue.iam.entity.IamRole::getRoleCode, request.roleCode()).eq(com.baiyue.iam.entity.IamRole::getStatus,"ACTIVE").last("LIMIT 1"));
|
||||
if (role == null) fail("NOTIFICATION_RECIPIENT_FORBIDDEN");
|
||||
if (role == null) fail("角色编码不存在,请检查角色编码是否正确");
|
||||
List<Long> ids = membershipRoles.selectList(new LambdaQueryWrapper<com.baiyue.iam.entity.IamMembershipRole>()
|
||||
.eq(com.baiyue.iam.entity.IamMembershipRole::getTenantId,request.tenantId())
|
||||
.eq(com.baiyue.iam.entity.IamMembershipRole::getRoleId,role.getId())).stream()
|
||||
|
||||
@@ -29,7 +29,7 @@ public class RbacRepository {
|
||||
|
||||
public IamRole findRole(Long id, long tenantId, String appCode) {
|
||||
return roleMapper.selectOne(new LambdaQueryWrapper<IamRole>()
|
||||
.eq(IamRole::getId, id).eq(IamRole::getTenantId, tenantId)
|
||||
.eq(IamRole::getId, id).in(IamRole::getTenantId, List.of(0L, tenantId))
|
||||
.eq(IamRole::getAppCode, appCode));
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ public class RbacRepository {
|
||||
|
||||
public List<IamRole> findRoles(long tenantId, String appCode) {
|
||||
return roleMapper.selectList(new LambdaQueryWrapper<IamRole>()
|
||||
.eq(IamRole::getTenantId, tenantId).eq(IamRole::getAppCode, appCode)
|
||||
.in(IamRole::getTenantId, List.of(0L, tenantId)).eq(IamRole::getAppCode, appCode)
|
||||
.orderByAsc(IamRole::getRoleCode));
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ public class RbacRepository {
|
||||
public Set<Long> findRolePermissionIds(long tenantId, Long roleId) {
|
||||
List<IamRolePermission> rows = rolePermissionMapper.selectList(
|
||||
new LambdaQueryWrapper<IamRolePermission>()
|
||||
.eq(IamRolePermission::getTenantId, tenantId)
|
||||
.in(IamRolePermission::getTenantId, List.of(0L, tenantId))
|
||||
.eq(IamRolePermission::getRoleId, roleId)
|
||||
.orderByAsc(IamRolePermission::getPermissionId));
|
||||
Set<Long> result = new LinkedHashSet<>();
|
||||
|
||||
@@ -64,7 +64,8 @@ public class MemberService {
|
||||
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()))
|
||||
.filter(role -> role != null && "MERCHANT".equals(role.getAppCode())
|
||||
&& (Long.valueOf(tenantId).equals(role.getTenantId()) || Long.valueOf(0L).equals(role.getTenantId())))
|
||||
.map(IamRole::getRoleName)
|
||||
.toList();
|
||||
return new PlatformCustomerMemberDtos.MemberResponse(membership.getId(), user.getId(),
|
||||
|
||||
@@ -85,3 +85,10 @@ INSERT IGNORE INTO iam_role_permission (tenant_id, role_id, permission_id, creat
|
||||
SELECT 0, r.id, p.id, CURRENT_TIMESTAMP(3)
|
||||
FROM iam_permission p JOIN iam_role r ON r.tenant_id=0 AND r.app_code='PLATFORM' AND r.status='ACTIVE'
|
||||
WHERE p.tenant_id=0 AND p.permission_code LIKE 'platform:notification%' AND r.role_code='PLATFORM_ADMIN';
|
||||
|
||||
-- All active platform employee roles can receive and read their own inbox messages.
|
||||
INSERT IGNORE INTO iam_role_permission (tenant_id, role_id, permission_id, created_at)
|
||||
SELECT 0, r.id, p.id, CURRENT_TIMESTAMP(3)
|
||||
FROM iam_permission p JOIN iam_role r ON r.tenant_id=0 AND r.app_code='PLATFORM' AND r.status='ACTIVE'
|
||||
WHERE p.tenant_id=0
|
||||
AND p.permission_code IN ('platform:notification:view','platform:notification:read','platform:notification:read-all');
|
||||
|
||||
@@ -145,7 +145,7 @@ components:
|
||||
tenantId: {type: integer, format: int64, minimum: 0}
|
||||
templateCode: {type: string, maxLength: 64}
|
||||
recipientType: {type: string, enum: [USER, MEMBERSHIP, TENANT_ROLE, TENANT_ALL, PLATFORM_ROLE]}
|
||||
recipientIds: {type: array, minItems: 1, maxItems: 1000, items: {type: integer, format: int64}, description: Required for USER or MEMBERSHIP; forbidden for role/all recipient types.}
|
||||
recipientIds: {type: array, minItems: 1, maxItems: 1000, items: {type: integer, format: int64}, description: Required for USER or MEMBERSHIP; forbidden for role/all recipient types. For a specified enterprise member, pass the user's ID and use USER; membership IDs are reserved for explicit membership-level calls.}
|
||||
roleCode: {type: string, minLength: 1, maxLength: 64, description: Required for TENANT_ROLE or PLATFORM_ROLE; forbidden otherwise.}
|
||||
variables: {type: object, maxProperties: 32, additionalProperties: {type: string, maxLength: 2000}}
|
||||
scheduledAt: {type: string, format: date-time, nullable: true}
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
package com.baiyue.iam.notification;
|
||||
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.iam.entity.IamMembership;
|
||||
import com.baiyue.iam.entity.IamRole;
|
||||
import com.baiyue.iam.entity.IamUser;
|
||||
import com.baiyue.iam.mapper.IamMembershipMapper;
|
||||
import com.baiyue.iam.mapper.IamMembershipRoleMapper;
|
||||
import com.baiyue.iam.mapper.IamRoleMapper;
|
||||
import com.baiyue.iam.mapper.IamUserMapper;
|
||||
import com.baiyue.iam.notification.dto.NotificationDtos.CreateNotificationRequest;
|
||||
import com.baiyue.iam.notification.dto.NotificationDtos.RecipientType;
|
||||
import com.baiyue.iam.notification.service.NotificationRecipientResolver;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class NotificationRecipientResolverTest {
|
||||
private final IamMembershipMapper memberships = mock(IamMembershipMapper.class);
|
||||
private final IamUserMapper users = mock(IamUserMapper.class);
|
||||
private final IamRoleMapper roles = mock(IamRoleMapper.class);
|
||||
private final IamMembershipRoleMapper membershipRoles = mock(IamMembershipRoleMapper.class);
|
||||
private final NotificationRecipientResolver resolver = new NotificationRecipientResolver();
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
ReflectionTestUtils.setField(resolver, "memberships", memberships);
|
||||
ReflectionTestUtils.setField(resolver, "users", users);
|
||||
ReflectionTestUtils.setField(resolver, "roles", roles);
|
||||
ReflectionTestUtils.setField(resolver, "membershipRoles", membershipRoles);
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolvesSpecifiedEnterpriseMemberByUserId() {
|
||||
IamMembership membership = new IamMembership();
|
||||
membership.setId(301L);
|
||||
membership.setTenantId(9L);
|
||||
membership.setUserId(2092537408743079938L);
|
||||
membership.setStatus("ACTIVE");
|
||||
IamUser user = new IamUser();
|
||||
user.setId(membership.getUserId());
|
||||
user.setDisplayName("测试成员");
|
||||
user.setStatus("ACTIVE");
|
||||
when(memberships.selectList(any())).thenReturn(List.of(membership));
|
||||
when(users.selectById(membership.getUserId())).thenReturn(user);
|
||||
|
||||
var recipients = resolver.resolve(request(RecipientType.USER, List.of(membership.getUserId())));
|
||||
|
||||
assertThat(recipients).containsExactly(new NotificationRecipientResolver.ResolvedRecipient(
|
||||
9L, 301L, membership.getUserId(), "测试成员", "MERCHANT"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void explainsWhenNoActiveRecipientCanBeResolved() {
|
||||
when(memberships.selectList(any())).thenReturn(List.of());
|
||||
|
||||
assertThatThrownBy(() -> resolver.resolve(request(RecipientType.USER, List.of(2092537408743079938L))))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessage("未找到可接收消息的有效成员,请检查用户 ID 和目标企业");
|
||||
}
|
||||
|
||||
@Test
|
||||
void explainsWhenRoleCodeDoesNotExist() {
|
||||
when(roles.selectOne(any())).thenReturn(null);
|
||||
|
||||
assertThatThrownBy(() -> resolver.resolve(roleRequest(RecipientType.PLATFORM_ROLE, "MISSING_ROLE")))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessage("角色编码不存在,请检查角色编码是否正确");
|
||||
}
|
||||
|
||||
@Test
|
||||
void explainsWhenRoleHasNoActiveMembers() {
|
||||
IamRole role = new IamRole();
|
||||
role.setId(501L);
|
||||
when(roles.selectOne(any())).thenReturn(role);
|
||||
when(membershipRoles.selectList(any())).thenReturn(List.of());
|
||||
|
||||
assertThatThrownBy(() -> resolver.resolve(roleRequest(RecipientType.PLATFORM_ROLE, "CUSTOMER_REVIEWER")))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessage("该角色下暂无可接收消息的有效用户,请先为角色分配启用中的成员");
|
||||
}
|
||||
|
||||
private CreateNotificationRequest request(RecipientType recipientType, List<Long> recipientIds) {
|
||||
return new CreateNotificationRequest(9L, "ORDER_STATUS_CHANGED", recipientType, recipientIds,
|
||||
null, Map.of(), null, "notification-test-key", null, null);
|
||||
}
|
||||
|
||||
private CreateNotificationRequest roleRequest(RecipientType recipientType, String roleCode) {
|
||||
return new CreateNotificationRequest(0L, "ORDER_STATUS_CHANGED", recipientType, null,
|
||||
roleCode, Map.of(), null, "notification-role-test-key", null, null);
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,8 @@ class MemberServiceTest {
|
||||
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("企业管理员");
|
||||
ownerRole.setId(50L); ownerRole.setTenantId(0L); ownerRole.setRoleName("企业管理员");
|
||||
ownerRole.setAppCode("MERCHANT");
|
||||
when(roleMapper.selectById(50L)).thenReturn(ownerRole);
|
||||
when(auth.summary("internal-token", 40L)).thenReturn(ApiResponse.success(
|
||||
new AuthAccountClient.AccountSummary(40L, "merchant_owner", "企业管理员", "ACTIVE")));
|
||||
|
||||
Reference in New Issue
Block a user