AuditService.java 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. package com.example.hrlab.audit;
  2. import com.example.hrlab.common.Correlation;
  3. import com.example.hrlab.common.JsonUtils;
  4. import com.example.hrlab.security.SecurityUtils;
  5. import jakarta.servlet.http.HttpServletRequest;
  6. import java.nio.charset.StandardCharsets;
  7. import java.security.MessageDigest;
  8. import java.time.Clock;
  9. import java.time.OffsetDateTime;
  10. import java.util.HexFormat;
  11. import java.util.Map;
  12. import org.springframework.jdbc.core.JdbcTemplate;
  13. import org.springframework.stereotype.Service;
  14. import org.springframework.web.context.request.RequestContextHolder;
  15. import org.springframework.web.context.request.ServletRequestAttributes;
  16. @Service
  17. public class AuditService {
  18. private final JdbcTemplate jdbcTemplate;
  19. private final Clock clock;
  20. public AuditService(JdbcTemplate jdbcTemplate, Clock clock) {
  21. this.jdbcTemplate = jdbcTemplate;
  22. this.clock = clock;
  23. }
  24. public void record(String action, String entityType, Object entityId, Object before, Object after) {
  25. String beforeJson = JsonUtils.toJson(before == null ? Map.of() : before);
  26. String afterJson = JsonUtils.toJson(after == null ? Map.of() : after);
  27. String prevHash = jdbcTemplate.query("SELECT record_hash FROM audit_logs ORDER BY id DESC LIMIT 1",
  28. rs -> rs.next() ? rs.getString(1) : null);
  29. OffsetDateTime occurredAt = OffsetDateTime.now(clock);
  30. String correlationId = Correlation.currentId();
  31. String recordHash = hash(prevHash, action, entityType, String.valueOf(entityId), beforeJson, afterJson, correlationId, occurredAt.toString());
  32. RequestInfo requestInfo = requestInfo();
  33. jdbcTemplate.update("""
  34. INSERT INTO audit_logs(actor_user_id, action, entity_type, entity_id, before_json, after_json,
  35. ip_addr, user_agent, correlation_id, prev_hash, record_hash, occurred_at)
  36. VALUES (?, ?, ?, ?, ?::jsonb, ?::jsonb, ?, ?, ?, ?, ?, ?)
  37. """, SecurityUtils.currentUserIdOrNull(), action, entityType, entityId == null ? null : String.valueOf(entityId),
  38. beforeJson, afterJson, requestInfo.ip(), requestInfo.userAgent(), correlationId, prevHash, recordHash, occurredAt);
  39. }
  40. public String hash(String prevHash, String action, String entityType, String entityId, String beforeJson,
  41. String afterJson, String correlationId, String occurredAt) {
  42. try {
  43. MessageDigest digest = MessageDigest.getInstance("SHA-256");
  44. String material = String.join("|",
  45. prevHash == null ? "" : prevHash,
  46. action,
  47. entityType,
  48. entityId == null ? "" : entityId,
  49. beforeJson,
  50. afterJson,
  51. correlationId,
  52. occurredAt);
  53. return HexFormat.of().formatHex(digest.digest(material.getBytes(StandardCharsets.UTF_8)));
  54. } catch (Exception ex) {
  55. throw new IllegalStateException("审计哈希计算失败", ex);
  56. }
  57. }
  58. private RequestInfo requestInfo() {
  59. if (RequestContextHolder.getRequestAttributes() instanceof ServletRequestAttributes attrs) {
  60. HttpServletRequest request = attrs.getRequest();
  61. String ip = request.getHeader("X-Forwarded-For");
  62. if (ip == null || ip.isBlank()) {
  63. ip = request.getRemoteAddr();
  64. }
  65. return new RequestInfo(ip, request.getHeader("User-Agent"));
  66. }
  67. return new RequestInfo(null, null);
  68. }
  69. private record RequestInfo(String ip, String userAgent) {
  70. }
  71. }