暂存提交

This commit is contained in:
2026-07-30 18:01:36 +08:00
commit 2f4e159356
368 changed files with 20370 additions and 0 deletions
@@ -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);
}
}