This commit is contained in:
2026-08-13 12:22:31 +08:00
parent 16dc03a2e2
commit e742abd1cf
49 changed files with 821 additions and 50 deletions
@@ -69,7 +69,7 @@ public class PlatformTenantController {
}
@GetMapping("/tenants/{tenantId:\\d+}")
@PreAuthorize("hasAnyAuthority('platform:customer:list','platform:customer:view')")
@PreAuthorize("hasAuthority('platform:customer:list')")
@Operation(summary = "查询企业客户档案", description = "返回企业基础资料、销售负责人、账期、注册地址以及入驻和审批时间。")
public ApiResponse<TenantResponse> tenant(@PathVariable long tenantId,
@AuthenticationPrincipal AuthenticatedUser user) {
@@ -89,7 +89,7 @@ public class PlatformTenantController {
}
@GetMapping("/tenants/{tenantId:\\d+}/members")
@PreAuthorize("hasAnyAuthority('platform:customer:list','platform:customer:view')")
@PreAuthorize("hasAuthority('platform:customer:list')")
@Operation(summary = "查询企业客户成员及账号", description = "平台只读查询指定企业的全部成员,包含 OWNER 企业管理员,并返回登录账号。")
public ApiResponse<List<PlatformCustomerMemberDtos.MemberResponse>> members(
@PathVariable long tenantId, @AuthenticationPrincipal AuthenticatedUser user) {
@@ -115,7 +115,7 @@ public class ProfileResourceController {
}
@GetMapping("/api/platform/v1/iam/tenants/{tenantId}/qualifications")
@PreAuthorize("hasAnyAuthority('platform:customer:list','platform:customer:view')")
@PreAuthorize("hasAuthority('platform:customer:list')")
@Operation(summary = "查询企业客户资质", description = "只读查询企业客户资质元数据,不授予审核能力。")
public ApiResponse<List<IamTenantQualification>> platformQualifications(
@PathVariable long tenantId, @AuthenticationPrincipal AuthenticatedUser user) {
@@ -124,7 +124,7 @@ public class ProfileResourceController {
}
@GetMapping("/api/platform/v1/iam/tenants/{tenantId}/addresses")
@PreAuthorize("hasAnyAuthority('platform:customer:list','platform:customer:view')")
@PreAuthorize("hasAuthority('platform:customer:list')")
@Operation(summary = "查询企业客户地址", description = "只读查询企业客户地址,不开放平台端写入。")
public ApiResponse<List<IamTenantAddress>> platformAddresses(
@PathVariable long tenantId, @AuthenticationPrincipal AuthenticatedUser user) {
@@ -67,14 +67,14 @@ VALUES
(5106009,0,'PLATFORM','BUTTON',5106000,'platform:notification:task:retry','重试消息任务','/api/platform/v1/notification-tasks/{id}/retry','PUT',609,'ACTIVE',0),
(5106011,0,'PLATFORM','BUTTON',5106001,'platform:notification:read','标记消息已读','/api/platform/v1/notifications/{id}/read','PUT',610,'ACTIVE',0),
(5106012,0,'PLATFORM','BUTTON',5106001,'platform:notification:read-all','全部标记已读','/api/platform/v1/notifications/read-all','PUT',611,'ACTIVE',0)
ON DUPLICATE KEY UPDATE permission_name=VALUES(permission_name), route_path=VALUES(route_path), http_method=VALUES(http_method), status='ACTIVE', deleted=0;
ON DUPLICATE KEY UPDATE permission_type=VALUES(permission_type), parent_id=VALUES(parent_id), permission_name=VALUES(permission_name), route_path=VALUES(route_path), http_method=VALUES(http_method), status='ACTIVE', deleted=0;
INSERT INTO iam_permission (id, tenant_id, app_code, permission_type, parent_id, permission_code, permission_name, route_path, http_method, sort_no, status, deleted)
VALUES
(5106010,0,'MERCHANT','API',0,'merchant:notification:view','查看我的消息','/api/merchant/v1/notifications','GET',900,'ACTIVE',0),
(5106013,0,'MERCHANT','BUTTON',5106010,'merchant:notification:read','标记消息已读','/api/merchant/v1/notifications/{id}/read','PUT',901,'ACTIVE',0),
(5106014,0,'MERCHANT','BUTTON',5106010,'merchant:notification:read-all','全部标记已读','/api/merchant/v1/notifications/read-all','PUT',902,'ACTIVE',0)
ON DUPLICATE KEY UPDATE permission_name=VALUES(permission_name), route_path=VALUES(route_path), status='ACTIVE', deleted=0;
ON DUPLICATE KEY UPDATE permission_type=VALUES(permission_type), parent_id=VALUES(parent_id), permission_name=VALUES(permission_name), route_path=VALUES(route_path), http_method=VALUES(http_method), status='ACTIVE', deleted=0;
INSERT IGNORE INTO iam_role_permission (tenant_id, role_id, permission_id, created_at)
SELECT 0, r.id, p.id, CURRENT_TIMESTAMP(3)
+4 -4
View File
@@ -89,7 +89,7 @@ paths:
get:
tags: [Platform Tenant / 平台租户]
summary: 查询企业客户档案
description: 需要独立的 `platform:customer:view` 权限,返回完整联系方式。
description: 需要 `platform:customer:list` 权限,返回完整联系方式。
responses:
'200': {$ref: '#/components/responses/TenantSuccess'}
'403': {$ref: '#/components/responses/Forbidden'}
@@ -113,7 +113,7 @@ paths:
get:
tags: [Platform Tenant / 平台租户]
summary: 查询企业客户成员及账号
description: 平台只读查询指定客户的全部企业成员,包含 OWNER 企业管理员并返回登录账号;需要 `platform:customer:view` 权限。
description: 平台只读查询指定客户的全部企业成员,包含 OWNER 企业管理员并返回登录账号;需要 `platform:customer:list` 权限。
responses:
'200':
description: 企业成员查询成功
@@ -142,7 +142,7 @@ paths:
get:
tags: [Platform Tenant / 平台租户]
summary: 查询企业客户地址
description: 需要 `platform:customer:view` 权限;只读接口,不提供平台端地址写入。
description: 需要 `platform:customer:list` 权限;只读接口,不提供平台端地址写入。
responses:
'200':
description: 企业客户地址查询成功
@@ -157,7 +157,7 @@ paths:
get:
tags: [Platform Tenant / 平台租户]
summary: 查询企业客户资质
description: 需要 `platform:customer:view` 权限;只读查询资质元数据,不授予审核权限。
description: 需要 `platform:customer:list` 权限;只读查询资质元数据,不授予审核权限。
responses:
'200':
description: 企业客户资质查询成功
@@ -31,7 +31,8 @@ class IamOpenApiContractTest {
.contains("id: {type: string, pattern: '^\\d+$'")
.contains("/api/platform/v1/iam/tenants/{tenantId}/addresses:")
.contains("/api/platform/v1/iam/tenants/{tenantId}/qualifications:")
.contains("platform:customer:view")
.contains("platform:customer:list")
.doesNotContain("platform:customer:view")
.contains("TenantAddressListApiResponse:")
.contains("TenantQualificationListApiResponse:")
.contains("InternalUserListApiResponse:")
@@ -13,17 +13,17 @@ class PlatformCustomerResourceContractTest {
@Test
void shouldHandleCase01() throws Exception {
assertReadOnlyViewContract("platformAddresses",
assertListPermissionContract("platformAddresses",
"/api/platform/v1/iam/tenants/{tenantId}/addresses");
assertReadOnlyViewContract("platformQualifications",
assertListPermissionContract("platformQualifications",
"/api/platform/v1/iam/tenants/{tenantId}/qualifications");
}
private void assertReadOnlyViewContract(String methodName, String path) throws Exception {
private void assertListPermissionContract(String methodName, String path) throws Exception {
Method method = ProfileResourceController.class.getMethod(
methodName, long.class, AuthenticatedUser.class);
assertEquals(path, method.getAnnotation(GetMapping.class).value()[0]);
assertEquals("hasAuthority('platform:customer:view')",
assertEquals("hasAuthority('platform:customer:list')",
method.getAnnotation(PreAuthorize.class).value());
}
}
@@ -52,7 +52,7 @@ class PlatformTenantControllerContractTest {
void shouldHandleCase03() throws Exception {
Method method = PlatformTenantController.class.getMethod("tenant", long.class, AuthenticatedUser.class);
assertEquals("hasAuthority('platform:customer:view')",
assertEquals("hasAuthority('platform:customer:list')",
method.getAnnotation(PreAuthorize.class).value());
}
@@ -0,0 +1,27 @@
package com.baiyue.iam.notification;
import com.baiyue.iam.notification.controller.NotificationInboxController;
import org.junit.jupiter.api.Test;
import org.springframework.security.access.prepost.PreAuthorize;
import java.util.Arrays;
import static org.assertj.core.api.Assertions.assertThat;
class NotificationInboxPermissionContractTest {
@Test
void separatesReadOperationsFromInboxViewing() {
assertPermission("list", "hasAuthority(#app + ':notification:view')");
assertPermission("unread", "hasAuthority(#app + ':notification:view')");
assertPermission("detail", "hasAuthority(#app + ':notification:view')");
assertPermission("read", "hasAuthority(#app + ':notification:read')");
assertPermission("readAll", "hasAuthority(#app + ':notification:read-all')");
}
private static void assertPermission(String methodName, String expected) {
var method = Arrays.stream(NotificationInboxController.class.getDeclaredMethods())
.filter(candidate -> candidate.getName().equals(methodName))
.findFirst().orElseThrow();
assertThat(method.getAnnotation(PreAuthorize.class).value()).isEqualTo(expected);
}
}
@@ -5,7 +5,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import static org.assertj.core.api.Assertions.assertThat;
class NotificationSqlContractTest {
@Test void definesTablesIsolationIdempotencyAndPermissions() throws Exception {String sql;try(var in=getClass().getResourceAsStream("/db/notification-center.sql")){assertThat(in).isNotNull();sql=new String(in.readAllBytes(),StandardCharsets.UTF_8);}assertThat(sql).contains("iam_notification_template","iam_notification_task","iam_notification_recipient","iam_notification_inbox","iam_notification_event_inbox","uk_notification_task_no","uk_notification_task_idempotency","uk_notification_source_event","uk_notification_recipient","idx_notification_inbox_list","platform:notification:template:status","platform:notification:task:retry","merchant:notification:view");}
@Test void definesTablesIsolationIdempotencyAndPermissions() throws Exception {String sql;try(var in=getClass().getResourceAsStream("/db/notification-center.sql")){assertThat(in).isNotNull();sql=new String(in.readAllBytes(),StandardCharsets.UTF_8);}assertThat(sql).contains("iam_notification_template","iam_notification_task","iam_notification_recipient","iam_notification_inbox","iam_notification_event_inbox","uk_notification_task_no","uk_notification_task_idempotency","uk_notification_source_event","uk_notification_recipient","idx_notification_inbox_list","platform:notification:template:status","platform:notification:task:retry","platform:notification:read","platform:notification:read-all","merchant:notification:view","merchant:notification:read","merchant:notification:read-all").contains("'MENU',5106000,'platform:notification:template:list'","'MENU',5106000,'platform:notification:task:list'");}
@Test void unifiedMigrationContainsNotificationBaselineAndSevenIndexAssertion() throws Exception {
Path direct=Path.of("deploy","sql","头程数据库统一增量-MySQL8.sql");