暂存提交
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.baiyue.firstleg</groupId>
|
||||
<artifactId>baiyue-firstleg-platform</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>audit-service</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.baiyue.shared</groupId>
|
||||
<artifactId>baiyue-common-audit</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baiyue.shared</groupId>
|
||||
<artifactId>baiyue-common-web</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baiyue.shared</groupId>
|
||||
<artifactId>baiyue-common-openapi</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.baiyue.audit;
|
||||
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* 审计微服务启动入口,初始化 Spring Boot 应用并扫描审计日志 Mapper。
|
||||
*/
|
||||
@MapperScan("com.baiyue.audit.mapper")
|
||||
@SpringBootApplication
|
||||
public class BaiyueAuditApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(BaiyueAuditApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.baiyue.audit.controller;
|
||||
|
||||
import com.baiyue.audit.dto.AuditIngestRequest;
|
||||
import com.baiyue.audit.service.AuditIngestService;
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 内部审计事件接收接口,校验服务间提交的审计数据并交由审计服务持久化。
|
||||
*/
|
||||
@Tag(
|
||||
name = "Internal audit ingestion / 内部审计写入",
|
||||
description = "Internal service-to-service API for immutable business audit events. / 服务间写入不可变业务审计事件的内部接口。"
|
||||
)
|
||||
@RestController
|
||||
@RequestMapping("/internal/audit/events")
|
||||
public class AuditIngestController {
|
||||
@Resource
|
||||
private AuditIngestService service;
|
||||
@Operation(
|
||||
summary = "Ingest business audit event / 写入业务审计事件",
|
||||
description = "Validates and stores one sanitized event in baiyue_audit.sys_audit_log. / 校验并将一条脱敏事件写入 baiyue_audit.sys_audit_log。"
|
||||
)
|
||||
@ApiResponses({
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "202", description = "Event accepted and stored / 事件已接收并存储"),
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "Invalid event payload / 事件载荷无效"),
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "500", description = "Audit storage failed / 审计存储失败")
|
||||
})
|
||||
@PostMapping
|
||||
@ResponseStatus(HttpStatus.ACCEPTED)
|
||||
public ApiResponse<Void> ingest(@Valid @RequestBody AuditIngestRequest event) {
|
||||
service.ingest(event);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.baiyue.audit.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.PositiveOrZero;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* 内部审计写入请求契约,承载操作对象、租户与用户、请求链路、执行结果及脱敏快照。
|
||||
*/
|
||||
@Schema(name = "AuditIngestRequest", description = "Internal business audit event / 内部业务审计事件")
|
||||
public record AuditIngestRequest(
|
||||
@Schema(description = "Event code / 事件编码", example = "WAREHOUSE_UPDATE", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank @Size(max = 64) String eventType,
|
||||
@Schema(description = "Resource type / 资源类型", example = "WAREHOUSE", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank @Size(max = 64) String resourceType,
|
||||
@Schema(description = "Resource identifier / 资源标识", example = "190000000000000001")
|
||||
@Size(max = 128) String resourceId,
|
||||
@Schema(description = "Bilingual event description / 中英文事件描述", example = "Update warehouse / 更新仓库")
|
||||
@Size(max = 255) String description,
|
||||
@Schema(description = "Request ID / 请求 ID", example = "req-20260728-001")
|
||||
@Size(max = 128) String requestId,
|
||||
@Schema(description = "Distributed trace ID / 分布式链路 ID", example = "trace-20260728-001")
|
||||
@Size(max = 128) String traceId,
|
||||
@Schema(description = "Business data tenant ID / 业务数据租户 ID", example = "10001")
|
||||
Long tenantId,
|
||||
@Schema(description = "Operator tenant ID / 操作人所属租户 ID", example = "0")
|
||||
Long operatorTenantId,
|
||||
@Schema(description = "Operator user ID / 操作人用户 ID", example = "90001")
|
||||
Long userId,
|
||||
@Schema(description = "Operator username / 操作人用户名", example = "platform.admin")
|
||||
@Size(max = 128) String username,
|
||||
@Schema(description = "Application scope / 应用权限域", example = "PLATFORM")
|
||||
@Size(max = 64) String appCode,
|
||||
@Schema(description = "Client type / 客户端类型", example = "WEB")
|
||||
@Size(max = 32) String clientType,
|
||||
@Schema(description = "HTTP method / HTTP 方法", example = "PUT")
|
||||
@Size(max = 16) String httpMethod,
|
||||
@Schema(description = "Request URI / 请求 URI", example = "/api/platform/v1/warehouse-admin/190000000000000001")
|
||||
@Size(max = 512) String requestUri,
|
||||
@Schema(description = "Client IP / 客户端 IP", example = "10.0.0.10")
|
||||
@Size(max = 64) String clientIp,
|
||||
@Schema(description = "Request duration in milliseconds / 请求耗时(毫秒)", example = "35")
|
||||
@PositiveOrZero long durationMs,
|
||||
@Schema(description = "Whether the business operation succeeded / 业务操作是否成功", example = "true")
|
||||
boolean success,
|
||||
@Schema(description = "Error code when failed / 失败时错误码", example = "WAREHOUSE_NOT_FOUND")
|
||||
@Size(max = 64) String errorCode,
|
||||
@Schema(description = "Sanitized error message / 脱敏后的错误信息", example = "Warehouse not found / 仓库不存在")
|
||||
@Size(max = 512) String errorMessage,
|
||||
@Schema(description = "Sanitized JSON snapshot before change / 变更前脱敏 JSON 快照", example = "{\"status\":\"INACTIVE\"}")
|
||||
String beforeData,
|
||||
@Schema(description = "Sanitized JSON snapshot after change / 变更后脱敏 JSON 快照", example = "{\"status\":\"ACTIVE\"}")
|
||||
String afterData,
|
||||
@Schema(description = "Event occurrence time in UTC / 事件发生时间(UTC)", example = "2026-07-28T07:30:00Z", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull Instant occurredAt,
|
||||
@Schema(description = "Source service name / 来源服务名称", example = "warehouse-service")
|
||||
@Size(max = 64) String serviceName
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package com.baiyue.audit.entity;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 业务审计日志持久化实体,对应 {@code sys_audit_log} 表。
|
||||
* 用于保存跨租户敏感操作的不可变审计证据,不用于领域事件投递。
|
||||
*/
|
||||
@Schema(name = "AuditLog", description = "Stored business audit log / 已存储业务审计日志")
|
||||
@Getter
|
||||
@Setter
|
||||
@TableName("sys_audit_log")
|
||||
public class AuditLogEntity {
|
||||
@Schema(description = "Audit log ID / 审计日志 ID", example = "190000000000000201")
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
private String eventType;
|
||||
private String resourceType;
|
||||
private String resourceId;
|
||||
private String description;
|
||||
private String requestId;
|
||||
private String traceId;
|
||||
private Long tenantId;
|
||||
private Long operatorTenantId;
|
||||
private Long userId;
|
||||
private String username;
|
||||
private String appCode;
|
||||
private String clientType;
|
||||
private String httpMethod;
|
||||
private String requestUri;
|
||||
private String clientIp;
|
||||
private Long durationMs;
|
||||
private Boolean success;
|
||||
private String errorCode;
|
||||
private String errorMessage;
|
||||
private String beforeData;
|
||||
private String afterData;
|
||||
private String serviceName;
|
||||
private Instant occurredAt;
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.baiyue.audit.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baiyue.audit.entity.AuditLogEntity;
|
||||
|
||||
/**
|
||||
* 审计日志 MyBatis-Plus Mapper,提供 {@code sys_audit_log} 表的单表写入和查询能力。
|
||||
*/
|
||||
public interface AuditLogMapper extends BaseMapper<AuditLogEntity> {
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.baiyue.audit.repository;
|
||||
|
||||
import com.baiyue.audit.dto.AuditIngestRequest;
|
||||
import com.baiyue.audit.entity.AuditLogEntity;
|
||||
import com.baiyue.audit.mapper.AuditLogMapper;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
/**
|
||||
* 审计事件持久化仓储,负责将内部审计请求映射并追加写入审计日志表。
|
||||
*/
|
||||
@Repository
|
||||
public class AuditEventRepository {
|
||||
private final AuditLogMapper mapper;
|
||||
|
||||
public AuditEventRepository(AuditLogMapper mapper) {
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
public void save(AuditIngestRequest event) {
|
||||
AuditLogEntity entity = new AuditLogEntity();
|
||||
entity.setEventType(event.eventType());
|
||||
entity.setResourceType(event.resourceType());
|
||||
entity.setResourceId(event.resourceId());
|
||||
entity.setDescription(event.description());
|
||||
entity.setRequestId(event.requestId());
|
||||
entity.setTraceId(event.traceId());
|
||||
entity.setTenantId(event.tenantId());
|
||||
entity.setOperatorTenantId(event.operatorTenantId());
|
||||
entity.setUserId(event.userId());
|
||||
entity.setUsername(event.username());
|
||||
entity.setAppCode(event.appCode());
|
||||
entity.setClientType(event.clientType());
|
||||
entity.setHttpMethod(event.httpMethod());
|
||||
entity.setRequestUri(event.requestUri());
|
||||
entity.setClientIp(event.clientIp());
|
||||
entity.setDurationMs(event.durationMs());
|
||||
entity.setSuccess(event.success());
|
||||
entity.setErrorCode(event.errorCode());
|
||||
entity.setErrorMessage(event.errorMessage());
|
||||
entity.setBeforeData(jsonOrNull(event.beforeData()));
|
||||
entity.setAfterData(jsonOrNull(event.afterData()));
|
||||
entity.setOccurredAt(event.occurredAt());
|
||||
entity.setServiceName(event.serviceName());
|
||||
mapper.insert(entity);
|
||||
}
|
||||
|
||||
private static String jsonOrNull(String value) {
|
||||
return value == null ? "null" : value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.baiyue.audit.service;
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import com.baiyue.audit.dto.AuditIngestRequest;
|
||||
import com.baiyue.audit.repository.AuditEventRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* 审计事件写入应用服务,在事务内接收并持久化不可变业务审计记录。
|
||||
*/
|
||||
@Service
|
||||
public class AuditIngestService {
|
||||
@Resource
|
||||
private AuditEventRepository repository;
|
||||
|
||||
|
||||
@Transactional
|
||||
public void ingest(AuditIngestRequest event) {
|
||||
repository.save(event);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
server:
|
||||
port: ${AUDIT_PORT:9700}
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: audit-service
|
||||
config:
|
||||
import:
|
||||
- optional:nacos:baiyue-common.yml?group=BAIYUE&refreshEnabled=true
|
||||
- optional:nacos:${spring.application.name}.yml?group=BAIYUE&refreshEnabled=true
|
||||
cloud:
|
||||
nacos:
|
||||
server-addr: ${NACOS_SERVER_ADDR:127.0.0.1:8848}
|
||||
username: ${NACOS_USERNAME:nacos}
|
||||
password: ${NACOS_PASSWORD:nacos}
|
||||
discovery:
|
||||
enabled: ${NACOS_DISCOVERY_ENABLED:true}
|
||||
register-enabled: ${NACOS_REGISTER_ENABLED:true}
|
||||
group: ${NACOS_GROUP:BAIYUE}
|
||||
namespace: ${NACOS_NAMESPACE:public}
|
||||
config.namespace: ${NACOS_NAMESPACE:public}
|
||||
|
||||
# 本地 IDEA 启动默认连接本机 MySQL;生产环境请通过 AUDIT_DB_* 环境变量覆盖。
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: ${AUDIT_DB_URL:jdbc:mysql://127.0.0.1:3306/baiyue_audit?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true}
|
||||
username: ${AUDIT_DB_USERNAME:root}
|
||||
password: ${AUDIT_DB_PASSWORD:123456}
|
||||
|
||||
baiyue.audit.enabled: false
|
||||
management.endpoints.web.exposure.include: health,info,prometheus
|
||||
@@ -0,0 +1,36 @@
|
||||
CREATE DATABASE IF NOT EXISTS baiyue_audit
|
||||
DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
USE baiyue_audit;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS sys_audit_log (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '审计日志自增ID',
|
||||
event_type VARCHAR(64) NOT NULL COMMENT '审计事件编码',
|
||||
resource_type VARCHAR(64) NOT NULL COMMENT '被操作资源类型',
|
||||
resource_id VARCHAR(128) DEFAULT NULL COMMENT '被操作资源ID',
|
||||
description VARCHAR(255) DEFAULT NULL COMMENT '操作说明',
|
||||
request_id VARCHAR(128) DEFAULT NULL COMMENT '请求ID,用于单次请求追踪',
|
||||
trace_id VARCHAR(128) DEFAULT NULL COMMENT '分布式链路追踪ID',
|
||||
tenant_id BIGINT DEFAULT NULL COMMENT '业务数据所属租户ID',
|
||||
operator_tenant_id BIGINT DEFAULT NULL COMMENT '操作主体所属租户ID',
|
||||
user_id BIGINT DEFAULT NULL COMMENT '操作用户ID',
|
||||
username VARCHAR(128) DEFAULT NULL COMMENT '操作用户名快照',
|
||||
app_code VARCHAR(64) DEFAULT NULL COMMENT '操作来源应用端',
|
||||
client_type VARCHAR(32) DEFAULT NULL COMMENT '客户端类型',
|
||||
http_method VARCHAR(16) DEFAULT NULL COMMENT 'HTTP请求方法',
|
||||
request_uri VARCHAR(512) DEFAULT NULL COMMENT '请求URI',
|
||||
client_ip VARCHAR(64) DEFAULT NULL COMMENT '客户端IP',
|
||||
duration_ms BIGINT NOT NULL DEFAULT 0 COMMENT '请求耗时(毫秒)',
|
||||
success TINYINT NOT NULL DEFAULT 0 COMMENT '操作是否成功',
|
||||
error_code VARCHAR(64) DEFAULT NULL COMMENT '失败时的错误码',
|
||||
error_message VARCHAR(512) DEFAULT NULL COMMENT '脱敏后的失败信息',
|
||||
before_data JSON DEFAULT NULL COMMENT '变更前数据快照(已脱敏)',
|
||||
after_data JSON DEFAULT NULL COMMENT '变更后数据快照(已脱敏)',
|
||||
service_name VARCHAR(64) DEFAULT NULL COMMENT '产生日志的服务名',
|
||||
occurred_at DATETIME(3) NOT NULL COMMENT '业务操作发生时间',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '审计服务落库时间',
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_audit_tenant_time (tenant_id, occurred_at),
|
||||
KEY idx_audit_event_time (event_type, occurred_at),
|
||||
KEY idx_audit_user_time (user_id, occurred_at),
|
||||
KEY idx_audit_request (request_id)
|
||||
) ENGINE=InnoDB COMMENT='统一业务审计日志表:追加记录敏感操作的主体、租户、资源、结果及前后快照;不同于消息Outbox和消费幂等表';
|
||||
Reference in New Issue
Block a user