cl
2026-04-17 f7e46d204be81fd2ebb9e5a90728e945700a2c23
配置
4个文件已添加
5个文件已修改
302 ■■■■■ 已修改文件
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/config/HttpAuditAutoConfiguration.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/mapper/HttpAuditConfigMapper.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/props/HttpAuditDbConfigHolder.java 88 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/props/HttpAuditProperties.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/service/HttpAuditCleanupService.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/service/HttpAuditDbConfigService.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/service/HttpAuditRuleServiceImpl.java 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/resources/application-dev.yml 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/resources/application-prod.yml 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/config/HttpAuditAutoConfiguration.java
@@ -1,9 +1,12 @@
package com.vincent.rsf.httpaudit.config;
import com.vincent.rsf.httpaudit.mapper.HttpAuditLogMapper;
import com.vincent.rsf.httpaudit.mapper.HttpAuditConfigMapper;
import com.vincent.rsf.httpaudit.mapper.HttpAuditRuleMapper;
import com.vincent.rsf.httpaudit.props.HttpAuditProperties;
import com.vincent.rsf.httpaudit.service.HttpAuditAsyncRecorder;
import com.vincent.rsf.httpaudit.service.HttpAuditCleanupService;
import com.vincent.rsf.httpaudit.service.HttpAuditDbConfigService;
import com.vincent.rsf.httpaudit.service.HttpAuditOutboundRecorder;
import com.vincent.rsf.httpaudit.service.HttpAuditRuleService;
import com.vincent.rsf.httpaudit.service.HttpAuditRuleServiceImpl;
@@ -45,6 +48,16 @@
    }
    @Bean
    public HttpAuditCleanupService httpAuditCleanupService(HttpAuditLogMapper httpAuditLogMapper, HttpAuditProperties props) {
        return new HttpAuditCleanupService(httpAuditLogMapper, props);
    }
    @Bean
    public HttpAuditDbConfigService httpAuditDbConfigService(HttpAuditConfigMapper httpAuditConfigMapper) {
        return new HttpAuditDbConfigService(httpAuditConfigMapper);
    }
    @Bean
    public HttpAuditOutboundRecorder httpAuditOutboundRecorder(
            HttpAuditAsyncRecorder recorder,
            HttpAuditProperties props,
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/mapper/HttpAuditConfigMapper.java
New file
@@ -0,0 +1,15 @@
package com.vincent.rsf.httpaudit.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
import java.util.Map;
@Mapper
public interface HttpAuditConfigMapper {
    @Select("SELECT config_key, config_val FROM sys_http_audit_config WHERE deleted = 0 AND enabled = 1")
    List<Map<String, Object>> listEnabledConfig();
}
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/props/HttpAuditDbConfigHolder.java
New file
@@ -0,0 +1,88 @@
package com.vincent.rsf.httpaudit.props;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
 * 审计配置缓存
 */
public final class HttpAuditDbConfigHolder {
    public static final String KEY_WHITELIST_ONLY = "whitelist-only";
    public static final String KEY_EXCLUDE_AUDIT_SELF_PATHS = "exclude-audit-self-paths";
    public static final String KEY_RULE_CACHE_REFRESH_MS = "rule-cache-refresh-ms";
    public static final String KEY_QUERY_RESPONSE_MAX_CHARS = "query-response-max-chars";
    public static final String KEY_MAX_RESPONSE_STORE_CHARS = "max-response-store-chars";
    public static final String KEY_DEFAULT_REQUEST_STORE_CHARS = "default-request-store-chars";
    public static final String KEY_PATH_DESCRIPTIONS = "path-descriptions";
    public static final String KEY_CLEANUP_ENABLED = "cleanup-enabled";
    public static final String KEY_CLEANUP_RETENTION_DAYS = "cleanup-retention-days";
    private static final ConcurrentHashMap<String, String> CONFIG = new ConcurrentHashMap<>();
    private static final ObjectMapper MAPPER = new ObjectMapper();
    private HttpAuditDbConfigHolder() {
    }
    public static void replace(Map<String, String> map) {
        CONFIG.clear();
        if (map != null && !map.isEmpty()) {
            CONFIG.putAll(map);
        }
    }
    public static boolean getBoolean(String key, boolean defaultValue) {
        String raw = CONFIG.get(key);
        if (raw == null) {
            return defaultValue;
        }
        return "1".equals(raw) || "true".equalsIgnoreCase(raw.trim());
    }
    public static int getInt(String key, int defaultValue) {
        String raw = CONFIG.get(key);
        if (raw == null) {
            return defaultValue;
        }
        try {
            return Integer.parseInt(raw.trim());
        } catch (Exception ignore) {
            return defaultValue;
        }
    }
    public static long getLong(String key, long defaultValue) {
        String raw = CONFIG.get(key);
        if (raw == null) {
            return defaultValue;
        }
        try {
            return Long.parseLong(raw.trim());
        } catch (Exception ignore) {
            return defaultValue;
        }
    }
    public static Map<String, String> getPathDescriptions(Map<String, String> defaultValue) {
        String raw = CONFIG.get(KEY_PATH_DESCRIPTIONS);
        if (raw == null || raw.trim().isEmpty()) {
            return defaultValue;
        }
        try {
            Map<String, String> parsed = MAPPER.readValue(raw, new TypeReference<Map<String, String>>() {
            });
            return parsed == null ? defaultValue : parsed;
        } catch (Exception ignore) {
            return defaultValue;
        }
    }
    public static Map<String, String> snapshot() {
        return Collections.unmodifiableMap(CONFIG);
    }
}
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/props/HttpAuditProperties.java
@@ -25,6 +25,11 @@
    /** 规则缓存定时刷新间隔(毫秒) */
    private long ruleCacheRefreshMs = 60_000L;
    /** 定时清理开关 */
    private boolean cleanupEnabled = true;
    /** 保留天数 */
    private int cleanupRetentionDays = 180;
    /** 查询类响应最多保留字符数 */
    private int queryResponseMaxChars = 500;
@@ -53,7 +58,7 @@
    /** Filter 实际使用的前缀(受 excludeAuditSelfPaths 影响) */
    public List<String> getEffectiveExcludePrefixes() {
        List<String> list = excludePathPrefixes == null ? new ArrayList<>() : new ArrayList<>(excludePathPrefixes);
        if (!excludeAuditSelfPaths) {
        if (!isExcludeAuditSelfPaths()) {
            list.removeIf(p -> "/httpAuditLog".equals(p) || "/httpAuditRule".equals(p));
        }
        return list;
@@ -65,6 +70,42 @@
    /** 路径 -> 功能描述(按最长路径前缀匹配) */
    private Map<String, String> pathDescriptions = new LinkedHashMap<>();
    public boolean isWhitelistOnly() {
        return HttpAuditDbConfigHolder.getBoolean(HttpAuditDbConfigHolder.KEY_WHITELIST_ONLY, whitelistOnly);
    }
    public boolean isExcludeAuditSelfPaths() {
        return HttpAuditDbConfigHolder.getBoolean(HttpAuditDbConfigHolder.KEY_EXCLUDE_AUDIT_SELF_PATHS, excludeAuditSelfPaths);
    }
    public long getRuleCacheRefreshMs() {
        return HttpAuditDbConfigHolder.getLong(HttpAuditDbConfigHolder.KEY_RULE_CACHE_REFRESH_MS, ruleCacheRefreshMs);
    }
    public int getQueryResponseMaxChars() {
        return HttpAuditDbConfigHolder.getInt(HttpAuditDbConfigHolder.KEY_QUERY_RESPONSE_MAX_CHARS, queryResponseMaxChars);
    }
    public int getMaxResponseStoreChars() {
        return HttpAuditDbConfigHolder.getInt(HttpAuditDbConfigHolder.KEY_MAX_RESPONSE_STORE_CHARS, maxResponseStoreChars);
    }
    public int getDefaultRequestStoreChars() {
        return HttpAuditDbConfigHolder.getInt(HttpAuditDbConfigHolder.KEY_DEFAULT_REQUEST_STORE_CHARS, defaultRequestStoreChars);
    }
    public boolean isCleanupEnabled() {
        return HttpAuditDbConfigHolder.getBoolean(HttpAuditDbConfigHolder.KEY_CLEANUP_ENABLED, cleanupEnabled);
    }
    public int getCleanupRetentionDays() {
        return HttpAuditDbConfigHolder.getInt(HttpAuditDbConfigHolder.KEY_CLEANUP_RETENTION_DAYS, cleanupRetentionDays);
    }
    public Map<String, String> getPathDescriptions() {
        return HttpAuditDbConfigHolder.getPathDescriptions(pathDescriptions);
    }
    private static List<String> defaultExcludes() {
        List<String> list = new ArrayList<>();
        list.add("/actuator");
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/service/HttpAuditCleanupService.java
New file
@@ -0,0 +1,51 @@
package com.vincent.rsf.httpaudit.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.vincent.rsf.httpaudit.entity.HttpAuditLog;
import com.vincent.rsf.httpaudit.mapper.HttpAuditLogMapper;
import com.vincent.rsf.httpaudit.props.HttpAuditProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
/**
 * 审计日志定期清理
 */
@Slf4j
public class HttpAuditCleanupService {
    private final HttpAuditLogMapper httpAuditLogMapper;
    private final HttpAuditProperties props;
    public HttpAuditCleanupService(HttpAuditLogMapper httpAuditLogMapper, HttpAuditProperties props) {
        this.httpAuditLogMapper = httpAuditLogMapper;
        this.props = props;
    }
    @Scheduled(cron = "${http-audit.cleanup-cron:0 30 2 * * ?}")
    public void cleanup() {
        if (!props.isCleanupEnabled()) {
            return;
        }
        int retentionDays = props.getCleanupRetentionDays();
        if (retentionDays <= 0) {
            log.warn("http-audit 清理已跳过,retentionDays 配置无效:{}", retentionDays);
            return;
        }
        try {
            LocalDateTime cutoff = LocalDateTime.now().minusDays(retentionDays);
            Date cutoffDate = Date.from(cutoff.atZone(ZoneId.systemDefault()).toInstant());
            int count = httpAuditLogMapper.delete(new LambdaQueryWrapper<HttpAuditLog>()
                    .lt(HttpAuditLog::getCreateTime, cutoffDate));
            if (count > 0) {
                log.info("http-audit 清理完成,删除 {} 条,保留天数 {}", count, retentionDays);
            }
        } catch (Exception e) {
            log.warn("http-audit 清理失败", e);
        }
    }
}
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/service/HttpAuditDbConfigService.java
New file
@@ -0,0 +1,53 @@
package com.vincent.rsf.httpaudit.service;
import com.vincent.rsf.httpaudit.mapper.HttpAuditConfigMapper;
import com.vincent.rsf.httpaudit.props.HttpAuditDbConfigHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
 * 审计配置刷新
 */
@Slf4j
public class HttpAuditDbConfigService {
    private final HttpAuditConfigMapper httpAuditConfigMapper;
    public HttpAuditDbConfigService(HttpAuditConfigMapper httpAuditConfigMapper) {
        this.httpAuditConfigMapper = httpAuditConfigMapper;
    }
    @PostConstruct
    public void init() {
        refresh();
    }
    @Scheduled(fixedDelayString = "${http-audit.db-config-refresh-ms:60000}")
    public void refresh() {
        try {
            List<Map<String, Object>> rows = httpAuditConfigMapper.listEnabledConfig();
            Map<String, String> map = new HashMap<>();
            for (Map<String, Object> row : rows) {
                Object keyObj = row.get("config_key");
                if (keyObj == null) {
                    continue;
                }
                String key = String.valueOf(keyObj).trim();
                if (key.isEmpty()) {
                    continue;
                }
                Object valObj = row.get("config_val");
                map.put(key, valObj == null ? "" : String.valueOf(valObj));
            }
            HttpAuditDbConfigHolder.replace(map);
        } catch (Exception e) {
            log.warn("http-audit 配置刷新失败,使用内存/默认配置", e);
        }
    }
}
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/service/HttpAuditRuleServiceImpl.java
@@ -25,6 +25,7 @@
    private final HttpAuditProperties props;
    private final CopyOnWriteArrayList<HttpAuditRule> cache = new CopyOnWriteArrayList<>();
    private volatile long lastRefreshAt = 0L;
    public HttpAuditRuleServiceImpl(HttpAuditRuleMapper mapper, HttpAuditProperties props) {
        this.baseMapper = mapper;
@@ -37,8 +38,14 @@
    }
    @Override
    @Scheduled(fixedDelayString = "${http-audit.rule-cache-refresh-ms:60000}")
    @Scheduled(fixedDelay = 5000)
    public void refreshCache() {
        long now = System.currentTimeMillis();
        long interval = Math.max(5000L, props.getRuleCacheRefreshMs());
        if (now - lastRefreshAt < interval) {
            return;
        }
        lastRefreshAt = now;
        try {
            List<HttpAuditRule> list = list(new LambdaQueryWrapper<HttpAuditRule>()
                    .eq(HttpAuditRule::getDeleted, 0)
rsf-server/src/main/resources/application-dev.yml
@@ -143,17 +143,4 @@
  # enabled: true
  enabled: false
  # 审计数据源:primary / cus-item-sync
  datasource: primary
  whitelist-only: true
  # false:/httpAuditLog、/httpAuditRule 也会被 Filter 记录(调试用;生产建议 true)
  exclude-audit-self-paths: false
  rule-cache-refresh-ms: 60000
  query-response-max-chars: 500
  max-response-store-chars: 65535
  # 规则未填 request_max_chars 时默认;-1 表示请求体入库不截断
  default-request-store-chars: 65535
  path-descriptions:
    "/erp/order": "云仓-订单查询"
    "/erp/order/add": "云仓-单据下发"
    "/erp/order/addAll": "云仓-批量单据下发"
    "/erp/order/cancel": "云仓-取消单据"
  datasource: primary
rsf-server/src/main/resources/application-prod.yml
@@ -137,17 +137,4 @@
http-audit:
  enabled: true
  # 审计数据源:primary / cus-item-sync
  datasource: primary
  whitelist-only: true
  # false:/httpAuditLog、/httpAuditRule 也会被 Filter 记录(调试用;生产建议 true)
  exclude-audit-self-paths: false
  rule-cache-refresh-ms: 60000
  query-response-max-chars: 500
  max-response-store-chars: 65535
  # 规则未填 request_max_chars 时默认;-1 表示请求体入库不截断
  default-request-store-chars: 65535
  path-descriptions:
    "/erp/order": "云仓-订单查询"
    "/erp/order/add": "云仓-单据下发"
    "/erp/order/addAll": "云仓-批量单据下发"
    "/erp/order/cancel": "云仓-取消单据"
  datasource: primary