| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778 |
- package com.example.hrlab.audit;
- import com.example.hrlab.common.Correlation;
- import com.example.hrlab.common.JsonUtils;
- import com.example.hrlab.security.SecurityUtils;
- import jakarta.servlet.http.HttpServletRequest;
- import java.nio.charset.StandardCharsets;
- import java.security.MessageDigest;
- import java.time.Clock;
- import java.time.OffsetDateTime;
- import java.util.HexFormat;
- import java.util.Map;
- import org.springframework.jdbc.core.JdbcTemplate;
- import org.springframework.stereotype.Service;
- import org.springframework.web.context.request.RequestContextHolder;
- import org.springframework.web.context.request.ServletRequestAttributes;
- @Service
- public class AuditService {
- private final JdbcTemplate jdbcTemplate;
- private final Clock clock;
- public AuditService(JdbcTemplate jdbcTemplate, Clock clock) {
- this.jdbcTemplate = jdbcTemplate;
- this.clock = clock;
- }
- public void record(String action, String entityType, Object entityId, Object before, Object after) {
- String beforeJson = JsonUtils.toJson(before == null ? Map.of() : before);
- String afterJson = JsonUtils.toJson(after == null ? Map.of() : after);
- String prevHash = jdbcTemplate.query("SELECT record_hash FROM audit_logs ORDER BY id DESC LIMIT 1",
- rs -> rs.next() ? rs.getString(1) : null);
- OffsetDateTime occurredAt = OffsetDateTime.now(clock);
- String correlationId = Correlation.currentId();
- String recordHash = hash(prevHash, action, entityType, String.valueOf(entityId), beforeJson, afterJson, correlationId, occurredAt.toString());
- RequestInfo requestInfo = requestInfo();
- jdbcTemplate.update("""
- INSERT INTO audit_logs(actor_user_id, action, entity_type, entity_id, before_json, after_json,
- ip_addr, user_agent, correlation_id, prev_hash, record_hash, occurred_at)
- VALUES (?, ?, ?, ?, ?::jsonb, ?::jsonb, ?, ?, ?, ?, ?, ?)
- """, SecurityUtils.currentUserIdOrNull(), action, entityType, entityId == null ? null : String.valueOf(entityId),
- beforeJson, afterJson, requestInfo.ip(), requestInfo.userAgent(), correlationId, prevHash, recordHash, occurredAt);
- }
- public String hash(String prevHash, String action, String entityType, String entityId, String beforeJson,
- String afterJson, String correlationId, String occurredAt) {
- try {
- MessageDigest digest = MessageDigest.getInstance("SHA-256");
- String material = String.join("|",
- prevHash == null ? "" : prevHash,
- action,
- entityType,
- entityId == null ? "" : entityId,
- beforeJson,
- afterJson,
- correlationId,
- occurredAt);
- return HexFormat.of().formatHex(digest.digest(material.getBytes(StandardCharsets.UTF_8)));
- } catch (Exception ex) {
- throw new IllegalStateException("审计哈希计算失败", ex);
- }
- }
- private RequestInfo requestInfo() {
- if (RequestContextHolder.getRequestAttributes() instanceof ServletRequestAttributes attrs) {
- HttpServletRequest request = attrs.getRequest();
- String ip = request.getHeader("X-Forwarded-For");
- if (ip == null || ip.isBlank()) {
- ip = request.getRemoteAddr();
- }
- return new RequestInfo(ip, request.getHeader("User-Agent"));
- }
- return new RequestInfo(null, null);
- }
- private record RequestInfo(String ip, String userAgent) {
- }
- }
|