日志记录提交

This commit is contained in:
2026-08-05 14:20:04 +08:00
parent 264458920b
commit 06f7f9d2f2
83 changed files with 3256 additions and 27 deletions
@@ -136,7 +136,8 @@ public class MemberService {
return response(tenantId, membership);
}
@BusinessAudit(event = "MEMBER_PASSWORD_RESET", resourceType = "MEMBERSHIP", resourceId = "#membershipId", description = "管理员重置成员密码", captureAfter = false)
@BusinessAudit(event = "MEMBER_PASSWORD_RESET", resourceType = "MEMBERSHIP", resourceId = "#membershipId",
description = "管理员重置成员密码", captureBefore = false, captureAfter = false)
public void resetPassword(long tenantId, Long membershipId, ResetMemberPasswordRequest request) {
IamMembership membership = required(tenantId, membershipId);
if ("OWNER".equals(membership.getMemberType())) {
@@ -143,7 +143,8 @@ public class PlatformInternalUserService {
}
@BusinessAudit(event = "PLATFORM_INTERNAL_USER_PASSWORD_RESET", resourceType = "MEMBERSHIP",
resourceId = "#membershipId", description = "重置平台内部用户密码", captureAfter = false)
resourceId = "#membershipId", description = "重置平台内部用户密码",
captureBefore = false, captureAfter = false)
public void resetPassword(Long membershipId, ResetInternalUserPasswordRequest request) {
IamMembership membership = required(membershipId);
IamUser user = requiredUser(membership.getUserId());
@@ -189,7 +189,8 @@ public class PlatformUserManagementService {
}
@BusinessAudit(event = "PLATFORM_USER_PASSWORD_RESET", resourceType = "MEMBERSHIP",
resourceId = "#membershipId", description = "平台重置用户密码", captureAfter = false)
resourceId = "#membershipId", description = "平台重置用户密码",
captureBefore = false, captureAfter = false)
public void resetPassword(Long membershipId, ResetPasswordRequest request) {
IamUser user = requiredUser(requiredPlatformMembership(membershipId).getUserId());
call(() -> authClient.reset(internalToken, user.getAuthUserId(),
@@ -0,0 +1,15 @@
USE baiyue_iam;
-- 可靠审计本地 Outbox;与 IAM 业务表位于同一数据库。
CREATE TABLE IF NOT EXISTS sys_audit_outbox (
event_id VARCHAR(64) NOT NULL, event_type VARCHAR(64) NOT NULL, resource_type VARCHAR(64) NOT NULL,
resource_id VARCHAR(128) NULL, service_name VARCHAR(64) NULL, payload_json JSON NOT NULL,
status VARCHAR(16) NOT NULL DEFAULT 'PENDING', attempt_count INT NOT NULL DEFAULT 0,
compensation_count INT NOT NULL DEFAULT 0,
next_retry_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), lease_owner VARCHAR(128) NULL,
lease_until DATETIME(3) NULL, last_error VARCHAR(1000) NULL, dead_at DATETIME(3) NULL,
sent_at DATETIME(3) NULL, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (event_id), KEY idx_audit_outbox_publish (status, next_retry_at, lease_until, created_at),
KEY idx_audit_outbox_retention (status, sent_at)
) ENGINE=InnoDB COMMENT='可靠审计事件本地发送箱';
@@ -0,0 +1,33 @@
USE baiyue_iam;
SET NAMES utf8mb4;
INSERT INTO iam_permission
(id, tenant_id, app_code, permission_type, parent_id, permission_code,
permission_name, route_path, http_method, sort_no, status, built_in, deleted)
VALUES
(5102070, 0, 'PLATFORM', 'MENU', 5103000, 'firstleg:fee-item:view',
'费用项目', '/master-data/fee-items', NULL, 40, 'ACTIVE', 1, 0),
(5102071, 0, 'PLATFORM', 'BUTTON', 5102070, 'firstleg:fee-item:create',
'新增费用项目', NULL, NULL, 10, 'ACTIVE', 1, 0),
(5102072, 0, 'PLATFORM', 'BUTTON', 5102070, 'firstleg:fee-item:edit',
'编辑或停用费用项目', NULL, NULL, 20, 'ACTIVE', 1, 0),
(5102073, 0, 'PLATFORM', 'BUTTON', 5102070, 'firstleg:fee-item:delete',
'删除费用项目', NULL, NULL, 30, 'ACTIVE', 1, 0),
(5102081, 0, 'PLATFORM', 'BUTTON', 5102030, 'firstleg:inbound:fee-detail:view',
'查看入库费用明细', NULL, NULL, 100, 'ACTIVE', 1, 0),
(5102082, 0, 'PLATFORM', 'BUTTON', 5102030, 'firstleg:inbound:fee-detail:edit',
'新增编辑入库费用明细', NULL, NULL, 110, 'ACTIVE', 1, 0),
(5102083, 0, 'PLATFORM', 'BUTTON', 5102030, 'firstleg:inbound:fee-detail:delete',
'删除入库费用明细', NULL, NULL, 120, 'ACTIVE', 1, 0),
(5102084, 0, 'PLATFORM', 'BUTTON', 5102030, 'firstleg:inbound:fee-detail:review',
'审核入库费用明细', NULL, NULL, 130, 'ACTIVE', 1, 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),
sort_no = VALUES(sort_no), status = 'ACTIVE', built_in = 1, deleted = 0;
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
SELECT 4000001, id, 0
FROM iam_permission
WHERE id IN (5102070, 5102071, 5102072, 5102073, 5102081, 5102082, 5102083, 5102084)
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
@@ -0,0 +1,70 @@
package com.baiyue.iam.service;
import com.baiyue.common.audit.BusinessAudit;
import com.baiyue.iam.dto.MemberDtos.ResetMemberPasswordRequest;
import com.baiyue.iam.dto.PlatformInternalUserDtos.ResetInternalUserPasswordRequest;
import com.baiyue.iam.dto.PlatformUserManagementDtos.ResetPasswordRequest;
import com.baiyue.iam.dto.RbacDtos.AssignRolesRequest;
import com.baiyue.iam.dto.RbacDtos.CreateRoleRequest;
import com.baiyue.iam.dto.RbacDtos.GrantPermissionsRequest;
import com.baiyue.iam.dto.RbacDtos.UpdateRoleRequest;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Method;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
class BusinessAuditCoverageTest {
@Test
void shouldAuditRbacChanges() throws Exception {
assertAudit(RbacService.class, "createRole", "ROLE_CREATE", "ROLE", "#request.roleCode",
long.class, String.class, CreateRoleRequest.class);
assertAudit(RbacService.class, "updateRole", "ROLE_UPDATE", "ROLE", "#roleId",
long.class, String.class, Long.class, UpdateRoleRequest.class);
assertAudit(RbacService.class, "changeStatus", "ROLE_STATUS_CHANGE", "ROLE", "#roleId",
long.class, String.class, Long.class,
com.baiyue.iam.dto.RbacDtos.ChangeRoleStatusRequest.class);
assertAudit(RbacService.class, "deleteRole", "ROLE_DELETE", "ROLE", "#roleId",
long.class, String.class, Long.class);
assertAudit(RbacService.class, "assignRoles", "MEMBER_ROLE_ASSIGN", "MEMBERSHIP", "#request.membershipId",
long.class, String.class, AssignRolesRequest.class);
assertAudit(RbacService.class, "grantPermissions", "ROLE_PERMISSION_GRANT", "ROLE", "#request.roleId",
long.class, String.class, GrantPermissionsRequest.class);
}
@Test
void shouldNeverCapturePasswordSnapshots() throws Exception {
assertPasswordAudit(MemberService.class, "resetPassword", long.class, Long.class,
ResetMemberPasswordRequest.class);
assertPasswordAudit(PlatformUserManagementService.class, "resetPassword", Long.class,
ResetPasswordRequest.class);
assertPasswordAudit(PlatformInternalUserService.class, "resetPassword", Long.class,
ResetInternalUserPasswordRequest.class);
}
@Test
void shouldAuditCustomerReview() throws Exception {
assertAudit(TenantService.class, "review", "TENANT_REVIEW", "TENANT_REVIEW", "#reviewId",
long.class, long.class, boolean.class, String.class);
}
private void assertAudit(Class<?> type, String methodName, String event, String resourceType,
String resourceId, Class<?>... parameterTypes) throws Exception {
BusinessAudit audit = annotation(type, methodName, parameterTypes);
assertEquals(event, audit.event());
assertEquals(resourceType, audit.resourceType());
assertEquals(resourceId, audit.resourceId());
}
private void assertPasswordAudit(Class<?> type, String methodName, Class<?>... parameterTypes) throws Exception {
BusinessAudit audit = annotation(type, methodName, parameterTypes);
assertFalse(audit.captureBefore());
assertFalse(audit.captureAfter());
}
private BusinessAudit annotation(Class<?> type, String methodName, Class<?>... parameterTypes) throws Exception {
Method method = type.getDeclaredMethod(methodName, parameterTypes);
return method.getAnnotation(BusinessAudit.class);
}
}