#
cl
20 小时以前 35d8c09fd1ea3f72684c5921939fa20c92bd330b
#
10个文件已添加
16个文件已修改
718 ■■■■ 已修改文件
rsf-http-audit/pom.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/admin/HttpAuditLogAdminController.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/admin/HttpAuditRuleAdminController.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/admin/HttpAuditSysConfigAdminController.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/common/BaseRes.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/common/Cools.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/common/R.java 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/config/HttpAuditAutoConfiguration.java 44 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/config/HttpAuditJarDefaultsEnvironmentPostProcessor.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/config/HttpAuditOpenSearchConfiguration.java 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/config/OnOpenSearchLogStorageEnabled.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/open/HttpAuditOpenLogController.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/props/HttpAuditProperties.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/service/HttpAuditAsyncRecorder.java 48 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/service/HttpAuditCleanupService.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/service/HttpAuditLogSink.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/service/MysqlHttpAuditLogSink.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/service/OpenSearchHttpAuditLogSink.java 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/web/util/HttpAuditAdminQueryHelper.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-http-audit/src/main/resources/META-INF/rsf-http-audit/defaults.yml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-http-audit/src/main/resources/META-INF/spring.factories 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/common/datasource/HttpAuditDataSourceAspect.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/common/datasource/HttpAuditDataSourceAspect.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/CusBarcodeSyncViewQueryService.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/resources/application-dev.yml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/resources/application-prod.yml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-http-audit/pom.xml
@@ -41,11 +41,13 @@
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
        </dependency>
        <!--
        <dependency>
            <groupId>com.vincent</groupId>
            <artifactId>rsf-framework</artifactId>
            <version>1.0.0</version>
        </dependency>
        -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-core</artifactId>
@@ -59,6 +61,11 @@
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        <dependency>
            <groupId>org.opensearch.client</groupId>
            <artifactId>opensearch-rest-client</artifactId>
            <version>2.19.1</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/admin/HttpAuditLogAdminController.java
@@ -2,8 +2,8 @@
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.vincent.rsf.framework.common.Cools;
import com.vincent.rsf.framework.common.R;
import com.vincent.rsf.httpaudit.common.Cools;
import com.vincent.rsf.httpaudit.common.R;
import com.vincent.rsf.httpaudit.entity.HttpAuditLog;
import com.vincent.rsf.httpaudit.service.HttpAuditLogCrudService;
import com.vincent.rsf.httpaudit.web.util.HttpAuditAdminQueryHelper;
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/admin/HttpAuditRuleAdminController.java
@@ -2,8 +2,8 @@
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.vincent.rsf.framework.common.Cools;
import com.vincent.rsf.framework.common.R;
import com.vincent.rsf.httpaudit.common.Cools;
import com.vincent.rsf.httpaudit.common.R;
import com.vincent.rsf.httpaudit.entity.HttpAuditRule;
import com.vincent.rsf.httpaudit.service.HttpAuditRuleService;
import com.vincent.rsf.httpaudit.web.util.HttpAuditAdminQueryHelper;
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/admin/HttpAuditSysConfigAdminController.java
@@ -3,8 +3,8 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.vincent.rsf.framework.common.Cools;
import com.vincent.rsf.framework.common.R;
import com.vincent.rsf.httpaudit.common.Cools;
import com.vincent.rsf.httpaudit.common.R;
import com.vincent.rsf.httpaudit.entity.HttpAuditSysConfig;
import com.vincent.rsf.httpaudit.service.HttpAuditDbConfigService;
import com.vincent.rsf.httpaudit.service.HttpAuditSysConfigService;
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/common/BaseRes.java
New file
@@ -0,0 +1,13 @@
package com.vincent.rsf.httpaudit.common;
public interface BaseRes {
    String OK = "200-Success";
    String EMPTY = "201-Empty Data";
    String LIMIT = "202-No Authority";
    String PARAM = "203-Parameters Cannot Be Empty";
    String DENIED = "403-Please Re-Login";
    String REPEAT = "407-Already Exist";
    String NO_ACTIVATION = "409-Please Activate The System First";
    String ERROR = "500-Internal Server Error";
}
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/common/Cools.java
New file
@@ -0,0 +1,57 @@
package com.vincent.rsf.httpaudit.common;
import java.util.List;
import java.util.Map;
import java.util.Set;
public final class Cools {
    private Cools() {
    }
    public static boolean isEmpty(Object... objects) {
        for (Object obj : objects) {
            if (isEmpty(obj)) {
                return true;
            }
        }
        return false;
    }
    @SuppressWarnings("rawtypes")
    public static boolean isEmpty(Object o) {
        if (o == null) {
            return true;
        }
        if (o instanceof String) {
            if (o.toString().trim().equals("")) {
                return true;
            }
        } else if (o instanceof List) {
            if (((List) o).size() == 0) {
                return true;
            }
        } else if (o instanceof Map) {
            if (((Map) o).size() == 0) {
                return true;
            }
        } else if (o instanceof Set) {
            if (((Set) o).size() == 0) {
                return true;
            }
        } else if (o instanceof Object[]) {
            if (((Object[]) o).length == 0) {
                return true;
            }
        } else if (o instanceof int[]) {
            if (((int[]) o).length == 0) {
                return true;
            }
        } else if (o instanceof long[]) {
            if (((long[]) o).length == 0) {
                return true;
            }
        }
        return false;
    }
}
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/common/R.java
New file
@@ -0,0 +1,58 @@
package com.vincent.rsf.httpaudit.common;
import java.util.HashMap;
public class R extends HashMap<String, Object> {
    private static final long serialVersionUID = 1L;
    private static final String CODE = "code";
    private static final String MSG = "msg";
    private static final String DATA = "data";
    public R(Integer code, String msg) {
        super.put(CODE, code);
        super.put(MSG, msg);
    }
    public static R ok() {
        return parse(BaseRes.OK);
    }
    public static R ok(String msg) {
        R r = ok();
        r.put(MSG, msg);
        return r;
    }
    public static R ok(Object obj) {
        return parse(BaseRes.OK).add(obj);
    }
    public static R error() {
        return parse(BaseRes.ERROR);
    }
    public static R error(String msg) {
        R r = error();
        r.put(MSG, msg);
        return r;
    }
    public R add(Object obj) {
        this.put(DATA, obj);
        return this;
    }
    public static R parse(String message) {
        if (Cools.isEmpty(message)) {
            return parse(BaseRes.ERROR);
        }
        String[] msg = message.split("-");
        if (msg.length == 2) {
            return new R(Integer.parseInt(msg[0].replaceAll(" ", "")), msg[1]);
        } else {
            return parse("500-".concat(message));
        }
    }
}
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/config/HttpAuditAutoConfiguration.java
@@ -7,16 +7,22 @@
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.HttpAuditLogCrudService;
import com.vincent.rsf.httpaudit.service.HttpAuditLogCrudServiceImpl;
import com.vincent.rsf.httpaudit.service.HttpAuditLogSink;
import com.vincent.rsf.httpaudit.service.HttpAuditOutboundRecorder;
import com.vincent.rsf.httpaudit.service.HttpAuditRuleService;
import com.vincent.rsf.httpaudit.service.HttpAuditRuleServiceImpl;
import com.vincent.rsf.httpaudit.service.HttpAuditSysConfigService;
import com.vincent.rsf.httpaudit.service.HttpAuditSysConfigServiceImpl;
import com.vincent.rsf.httpaudit.service.MysqlHttpAuditLogSink;
import com.vincent.rsf.httpaudit.service.OpenSearchHttpAuditLogSink;
import com.vincent.rsf.httpaudit.web.HttpAuditFilter;
import com.vincent.rsf.httpaudit.web.OutboundHttpAuditInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.opensearch.client.RestClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
@@ -27,8 +33,12 @@
import org.springframework.core.env.Environment;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
/**
@@ -40,8 +50,10 @@
@EnableConfigurationProperties(HttpAuditProperties.class)
@ConditionalOnProperty(prefix = "http-audit", name = "enabled", havingValue = "true", matchIfMissing = true)
@MapperScan("com.vincent.rsf.httpaudit.mapper")
@Import({HttpAuditAdminApiAutoConfiguration.class, HttpAuditOpenUiAutoConfiguration.class})
@Import({HttpAuditAdminApiAutoConfiguration.class, HttpAuditOpenUiAutoConfiguration.class, HttpAuditOpenSearchConfiguration.class})
public class HttpAuditAutoConfiguration {
    private static final Logger log = LoggerFactory.getLogger(HttpAuditAutoConfiguration.class);
    @Bean
    public HttpAuditSysConfigService httpAuditSysConfigService(HttpAuditConfigMapper httpAuditConfigMapper) {
@@ -59,13 +71,33 @@
    }
    @Bean
    public HttpAuditAsyncRecorder httpAuditAsyncRecorder(HttpAuditLogMapper httpAuditLogMapper) {
        return new HttpAuditAsyncRecorder(httpAuditLogMapper);
    public HttpAuditAsyncRecorder httpAuditAsyncRecorder(
            HttpAuditProperties props,
            HttpAuditLogMapper httpAuditLogMapper,
            @Autowired(required = false) OpenSearchHttpAuditLogSink openSearchHttpAuditLogSink) {
        List<HttpAuditLogSink> sinks = new ArrayList<>();
        if (props.usesMysqlLogStorage()) {
            sinks.add(new MysqlHttpAuditLogSink(httpAuditLogMapper));
        }
        if (props.usesOpenSearchLogStorage()) {
            if (openSearchHttpAuditLogSink == null) {
                log.warn("http_audit_warn code=opensearch_sink_missing log_storage_mode={}", props.resolveLogStorageMode());
            } else {
                sinks.add(openSearchHttpAuditLogSink);
            }
        }
        if (sinks.isEmpty()) {
            log.warn("http_audit_warn code=no_log_sinks log_storage_mode={}", props.resolveLogStorageMode());
        }
        return new HttpAuditAsyncRecorder(sinks);
    }
    @Bean
    public HttpAuditCleanupService httpAuditCleanupService(HttpAuditLogMapper httpAuditLogMapper, HttpAuditProperties props) {
        return new HttpAuditCleanupService(httpAuditLogMapper, props);
    public HttpAuditCleanupService httpAuditCleanupService(
            HttpAuditLogMapper httpAuditLogMapper,
            HttpAuditProperties props,
            @Autowired(required = false) @Qualifier("httpAuditOpenSearchRestClient") RestClient httpAuditOpenSearchRestClient) {
        return new HttpAuditCleanupService(httpAuditLogMapper, props, httpAuditOpenSearchRestClient);
    }
    @Bean
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/config/HttpAuditJarDefaultsEnvironmentPostProcessor.java
New file
@@ -0,0 +1,51 @@
package com.vincent.rsf.httpaudit.config;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertySource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import java.io.IOException;
import java.util.List;
/**
 * jar 内默认配置,最低优先级;引用工程覆盖。
 */
public class HttpAuditJarDefaultsEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
    private static final Logger log = LoggerFactory.getLogger(HttpAuditJarDefaultsEnvironmentPostProcessor.class);
    private static final String RESOURCE = "META-INF/rsf-http-audit/defaults.yml";
    private static final String NAME = "httpAuditJarDefaults";
    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        if (environment.getPropertySources().contains(NAME)) {
            return;
        }
        Resource resource = new ClassPathResource(RESOURCE);
        if (!resource.exists()) {
            return;
        }
        try {
            YamlPropertySourceLoader loader = new YamlPropertySourceLoader();
            List<PropertySource<?>> loaded = loader.load(NAME, resource);
            for (PropertySource<?> ps : loaded) {
                environment.getPropertySources().addLast(ps);
            }
        } catch (IOException e) {
            log.warn("http_audit_warn code=jar_defaults_load_failed", e);
        }
    }
    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }
}
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/config/HttpAuditOpenSearchConfiguration.java
New file
@@ -0,0 +1,73 @@
package com.vincent.rsf.httpaudit.config;
import com.vincent.rsf.httpaudit.props.HttpAuditProperties;
import com.vincent.rsf.httpaudit.service.OpenSearchHttpAuditLogSink;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.opensearch.client.RestClient;
import org.opensearch.client.RestClientBuilder;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
/**
 * log-storage-mode 为 2 或 3 时加载
 */
@Configuration
@Conditional(OnOpenSearchLogStorageEnabled.class)
public class HttpAuditOpenSearchConfiguration {
    @Bean(destroyMethod = "close")
    public RestClient httpAuditOpenSearchRestClient(HttpAuditProperties props) {
        HttpAuditProperties.OpenSearch oc = props.getOpenSearch();
        RestClientBuilder builder = RestClient.builder(buildHosts(oc));
        builder.setRequestConfigCallback(rc -> rc
                .setConnectTimeout((int) oc.getConnectTimeout().toMillis())
                .setSocketTimeout((int) oc.getSocketTimeout().toMillis()));
        if (StringUtils.isNotBlank(oc.getUsername())) {
            CredentialsProvider cp = new BasicCredentialsProvider();
            cp.setCredentials(AuthScope.ANY,
                    new UsernamePasswordCredentials(oc.getUsername(), oc.getPassword() == null ? "" : oc.getPassword()));
            builder.setHttpClientConfigCallback(hcb -> hcb.setDefaultCredentialsProvider(cp));
        }
        return builder.build();
    }
    @Bean
    public OpenSearchHttpAuditLogSink openSearchHttpAuditLogSink(
            @Qualifier("httpAuditOpenSearchRestClient") RestClient httpAuditOpenSearchRestClient,
            HttpAuditProperties props) {
        return new OpenSearchHttpAuditLogSink(httpAuditOpenSearchRestClient, props);
    }
    private static HttpHost[] buildHosts(HttpAuditProperties.OpenSearch oc) {
        String scheme = oc.getScheme() == null ? "http" : oc.getScheme();
        List<HttpHost> hosts = new ArrayList<>();
        for (String raw : oc.getUris()) {
            if (raw == null || raw.trim().isEmpty()) {
                continue;
            }
            String s = raw.trim();
            int colon = s.lastIndexOf(':');
            if (colon > 0 && colon < s.length() - 1) {
                String h = s.substring(0, colon);
                int port = Integer.parseInt(s.substring(colon + 1));
                hosts.add(new HttpHost(h, port, scheme));
            } else {
                hosts.add(new HttpHost(s, 9200, scheme));
            }
        }
        if (hosts.isEmpty()) {
            hosts.add(new HttpHost("localhost", 9200, scheme));
        }
        return hosts.toArray(new HttpHost[0]);
    }
}
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/config/OnOpenSearchLogStorageEnabled.java
New file
@@ -0,0 +1,18 @@
package com.vincent.rsf.httpaudit.config;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
 * log-storage-mode 为 2 或 3
 */
public class OnOpenSearchLogStorageEnabled implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Integer mode = context.getEnvironment().getProperty("http-audit.log-storage-mode", Integer.class, 1);
        int m = (mode != null && (mode == 2 || mode == 3)) ? mode : 1;
        return m == 2 || m == 3;
    }
}
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/open/HttpAuditOpenLogController.java
@@ -2,8 +2,8 @@
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.vincent.rsf.framework.common.Cools;
import com.vincent.rsf.framework.common.R;
import com.vincent.rsf.httpaudit.common.Cools;
import com.vincent.rsf.httpaudit.common.R;
import com.vincent.rsf.httpaudit.entity.HttpAuditLog;
import com.vincent.rsf.httpaudit.props.HttpAuditProperties;
import com.vincent.rsf.httpaudit.service.HttpAuditLogCrudService;
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/props/HttpAuditProperties.java
@@ -3,7 +3,9 @@
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -16,6 +18,15 @@
public class HttpAuditProperties {
    private boolean enabled = true;
    /** 1 数据库 2 OpenSearch 3 双写;未填或其它值同 1 */
    private int logStorageMode = 1;
    /** 仅 2、3 使用 */
    private OpenSearch openSearch = new OpenSearch();
    /** 仅 1、3;无多数据源可省略 */
    private String datasource = "primary";
    /** 是否注册 /httpAuditRule、/httpAuditLog、/httpAuditSysConfig 等管理接口 */
    private boolean adminApiEnabled = true;
@@ -115,6 +126,34 @@
        return HttpAuditDbConfigHolder.getPathDescriptions(pathDescriptions);
    }
    public int resolveLogStorageMode() {
        if (logStorageMode == 2 || logStorageMode == 3) {
            return logStorageMode;
        }
        return 1;
    }
    public boolean usesMysqlLogStorage() {
        int m = resolveLogStorageMode();
        return m == 1 || m == 3;
    }
    public boolean usesOpenSearchLogStorage() {
        int m = resolveLogStorageMode();
        return m == 2 || m == 3;
    }
    @Data
    public static class OpenSearch {
        private List<String> uris = new ArrayList<>(Collections.singletonList("localhost:9200"));
        private String scheme = "http";
        private String username = "";
        private String password = "";
        private String indexName = "http_audit_log";
        private Duration connectTimeout = Duration.ofSeconds(5);
        private Duration socketTimeout = Duration.ofSeconds(30);
    }
    private static List<String> defaultExcludes() {
        List<String> list = new ArrayList<>();
        list.add("/actuator");
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/service/HttpAuditAsyncRecorder.java
@@ -1,55 +1,25 @@
package com.vincent.rsf.httpaudit.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.vincent.rsf.httpaudit.entity.HttpAuditLog;
import com.vincent.rsf.httpaudit.mapper.HttpAuditLogMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.List;
/**
 * 异步落库;失败时打全量日志,不向业务抛错
 * 异步写入已配置的日志目标;单路失败不影响其他路与业务
 */
@Slf4j
@RequiredArgsConstructor
public class HttpAuditAsyncRecorder {
    private final HttpAuditLogMapper httpAuditLogMapper;
    private final ObjectMapper objectMapper = new ObjectMapper();
    private final List<HttpAuditLogSink> sinks;
    public HttpAuditAsyncRecorder(List<HttpAuditLogSink> sinks) {
        this.sinks = sinks;
    }
    @Async("httpAuditExecutor")
    public void save(HttpAuditLog entity) {
        try {
            httpAuditLogMapper.insert(entity);
        } catch (Throwable t) {
            try {
                Map<String, Object> dump = new LinkedHashMap<>();
                dump.put("serviceName", entity.getServiceName());
                dump.put("scopeType", entity.getScopeType());
                dump.put("uri", entity.getUri());
                dump.put("method", entity.getMethod());
                dump.put("functionDesc", entity.getFunctionDesc());
                dump.put("queryString", entity.getQueryString());
                dump.put("requestBody", entity.getRequestBody());
                dump.put("responseBody", entity.getResponseBody());
                dump.put("httpStatus", entity.getHttpStatus());
                dump.put("okFlag", entity.getOkFlag());
                dump.put("spendMs", entity.getSpendMs());
                dump.put("clientIp", entity.getClientIp());
                dump.put("errorMessage", entity.getErrorMessage());
                String json = objectMapper.writeValueAsString(dump);
                log.error("http-audit 落库失败,全量JSON如下:{}", json, t);
            } catch (JsonProcessingException je) {
                log.error("http-audit 落库失败且序列化审计内容失败,requestBody.length={}, responseBody.length={}",
                        entity.getRequestBody() == null ? -1 : entity.getRequestBody().length(),
                        entity.getResponseBody() == null ? -1 : entity.getResponseBody().length(),
                        t);
                log.error("序列化异常", je);
            }
        for (HttpAuditLogSink sink : sinks) {
            sink.write(entity);
        }
    }
}
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/service/HttpAuditCleanupService.java
@@ -1,14 +1,20 @@
package com.vincent.rsf.httpaudit.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
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.opensearch.client.Request;
import org.opensearch.client.ResponseException;
import org.opensearch.client.RestClient;
import org.springframework.scheduling.annotation.Scheduled;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.Date;
/**
@@ -19,10 +25,14 @@
    private final HttpAuditLogMapper httpAuditLogMapper;
    private final HttpAuditProperties props;
    private final RestClient openSearchRestClient;
    private final ObjectMapper objectMapper = new ObjectMapper();
    public HttpAuditCleanupService(HttpAuditLogMapper httpAuditLogMapper, HttpAuditProperties props) {
    public HttpAuditCleanupService(HttpAuditLogMapper httpAuditLogMapper, HttpAuditProperties props,
                                   RestClient openSearchRestClient) {
        this.httpAuditLogMapper = httpAuditLogMapper;
        this.props = props;
        this.openSearchRestClient = openSearchRestClient;
    }
    @Scheduled(cron = "${http-audit.cleanup-cron:0 30 2 * * ?}")
@@ -35,16 +45,51 @@
            log.warn("http-audit 清理已跳过,retentionDays 配置无效:{}", retentionDays);
            return;
        }
        LocalDateTime cutoff = LocalDateTime.now().minusDays(retentionDays);
        Date cutoffDate = Date.from(cutoff.atZone(ZoneId.systemDefault()).toInstant());
        if (props.usesMysqlLogStorage()) {
            cleanupMysql(cutoffDate, retentionDays);
        }
        if (props.usesOpenSearchLogStorage() && openSearchRestClient != null) {
            cleanupOpenSearch(cutoffDate, retentionDays);
        }
    }
    private void cleanupMysql(Date cutoffDate, int retentionDays) {
        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);
                log.info("http-audit MySQL 清理完成,删除 {} 条,保留天数 {}", count, retentionDays);
            }
        } catch (Exception e) {
            log.warn("http-audit 清理失败", e);
            log.warn("http-audit MySQL 清理失败", e);
        }
    }
    private void cleanupOpenSearch(Date cutoffDate, int retentionDays) {
        try {
            String index = props.getOpenSearch().getIndexName();
            String cutoffIso = cutoffDate.toInstant().atOffset(ZoneOffset.UTC).toString();
            ObjectNode body = objectMapper.createObjectNode();
            ObjectNode query = objectMapper.createObjectNode();
            ObjectNode range = objectMapper.createObjectNode();
            range.set("create_time", objectMapper.createObjectNode().put("lt", cutoffIso));
            query.set("range", range);
            body.set("query", query);
            Request request = new Request("POST", "/" + index + "/_delete_by_query?refresh=false&conflicts=proceed");
            request.setJsonEntity(objectMapper.writeValueAsString(body));
            openSearchRestClient.performRequest(request);
            log.debug("http-audit OpenSearch delete_by_query 已提交 retentionDays={}", retentionDays);
        } catch (ResponseException ex) {
            int code = ex.getResponse() != null ? ex.getResponse().getStatusLine().getStatusCode() : -1;
            if (code == 404) {
                log.debug("http-audit OpenSearch 清理跳过,索引不存在");
            } else {
                log.warn("http-audit OpenSearch 清理失败 status={}", code, ex);
            }
        } catch (Exception e) {
            log.warn("http-audit OpenSearch 清理失败", e);
        }
    }
}
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/service/HttpAuditLogSink.java
New file
@@ -0,0 +1,11 @@
package com.vincent.rsf.httpaudit.service;
import com.vincent.rsf.httpaudit.entity.HttpAuditLog;
/**
 * 审计日志写入目标;单路失败不影响其他路与业务
 */
public interface HttpAuditLogSink {
    void write(HttpAuditLog entity);
}
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/service/MysqlHttpAuditLogSink.java
New file
@@ -0,0 +1,54 @@
package com.vincent.rsf.httpaudit.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.vincent.rsf.httpaudit.entity.HttpAuditLog;
import com.vincent.rsf.httpaudit.mapper.HttpAuditLogMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.util.LinkedHashMap;
import java.util.Map;
/**
 * 写入 MySQL sys_http_audit_log
 */
@Slf4j
@RequiredArgsConstructor
public class MysqlHttpAuditLogSink implements HttpAuditLogSink {
    private final HttpAuditLogMapper httpAuditLogMapper;
    private final ObjectMapper objectMapper = new ObjectMapper();
    @Override
    public void write(HttpAuditLog entity) {
        try {
            httpAuditLogMapper.insert(entity);
        } catch (Throwable t) {
            try {
                Map<String, Object> dump = new LinkedHashMap<>();
                dump.put("serviceName", entity.getServiceName());
                dump.put("scopeType", entity.getScopeType());
                dump.put("uri", entity.getUri());
                dump.put("method", entity.getMethod());
                dump.put("functionDesc", entity.getFunctionDesc());
                dump.put("queryString", entity.getQueryString());
                dump.put("requestBody", entity.getRequestBody());
                dump.put("responseBody", entity.getResponseBody());
                dump.put("httpStatus", entity.getHttpStatus());
                dump.put("okFlag", entity.getOkFlag());
                dump.put("spendMs", entity.getSpendMs());
                dump.put("clientIp", entity.getClientIp());
                dump.put("errorMessage", entity.getErrorMessage());
                String json = objectMapper.writeValueAsString(dump);
                log.error("http-audit 落库失败,全量JSON如下:{}", json, t);
            } catch (JsonProcessingException je) {
                log.error("http-audit 落库失败且序列化审计内容失败,requestBody.length={}, responseBody.length={}",
                        entity.getRequestBody() == null ? -1 : entity.getRequestBody().length(),
                        entity.getResponseBody() == null ? -1 : entity.getResponseBody().length(),
                        t);
                log.error("序列化异常", je);
            }
        }
    }
}
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/service/OpenSearchHttpAuditLogSink.java
New file
@@ -0,0 +1,72 @@
package com.vincent.rsf.httpaudit.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.vincent.rsf.httpaudit.entity.HttpAuditLog;
import com.vincent.rsf.httpaudit.props.HttpAuditProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.opensearch.client.Request;
import org.opensearch.client.ResponseException;
import org.opensearch.client.RestClient;
import java.time.ZoneOffset;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
/**
 * 写入 OpenSearch 索引
 */
@Slf4j
@RequiredArgsConstructor
public class OpenSearchHttpAuditLogSink implements HttpAuditLogSink {
    private static final ObjectMapper JSON = new ObjectMapper()
            .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    private final RestClient restClient;
    private final HttpAuditProperties props;
    @Override
    public void write(HttpAuditLog entity) {
        try {
            String index = props.getOpenSearch().getIndexName();
            String docId = entity.getId() != null ? String.valueOf(entity.getId()) : UUID.randomUUID().toString();
            String path = "/" + index + "/_doc/" + docId + "?refresh=false";
            Request request = new Request("PUT", path);
            request.setJsonEntity(JSON.writeValueAsString(toDocument(entity)));
            restClient.performRequest(request);
        } catch (ResponseException ex) {
            int code = ex.getResponse() != null ? ex.getResponse().getStatusLine().getStatusCode() : -1;
            log.error("http-audit OpenSearch 写入失败 uri={} status={}", entity.getUri(), code, ex);
        } catch (Throwable t) {
            log.error("http-audit OpenSearch 写入失败 uri={}", entity.getUri(), t);
        }
    }
    private static Map<String, Object> toDocument(HttpAuditLog e) {
        Map<String, Object> m = new LinkedHashMap<>();
        m.put("id", e.getId());
        m.put("service_name", e.getServiceName());
        m.put("scope_type", e.getScopeType());
        m.put("uri", e.getUri());
        m.put("io_direction", e.getIoDirection());
        m.put("method", e.getMethod());
        m.put("function_desc", e.getFunctionDesc());
        m.put("query_string", e.getQueryString());
        m.put("request_body", e.getRequestBody());
        m.put("response_body", e.getResponseBody());
        m.put("response_truncated", e.getResponseTruncated());
        m.put("http_status", e.getHttpStatus());
        m.put("ok_flag", e.getOkFlag());
        m.put("spend_ms", e.getSpendMs());
        m.put("client_ip", e.getClientIp());
        m.put("error_message", e.getErrorMessage());
        if (e.getCreateTime() != null) {
            m.put("create_time", e.getCreateTime().toInstant().atOffset(ZoneOffset.UTC).toString());
        }
        m.put("deleted", e.getDeleted());
        return m;
    }
}
rsf-http-audit/src/main/java/com/vincent/rsf/httpaudit/web/util/HttpAuditAdminQueryHelper.java
@@ -2,7 +2,7 @@
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.vincent.rsf.framework.common.Cools;
import com.vincent.rsf.httpaudit.common.Cools;
import java.util.HashMap;
import java.util.Map;
rsf-http-audit/src/main/resources/META-INF/rsf-http-audit/defaults.yml
New file
@@ -0,0 +1,4 @@
http-audit:
  enabled: true
  log-storage-mode: 1
  datasource: primary
rsf-http-audit/src/main/resources/META-INF/spring.factories
@@ -1,2 +1,4 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.vincent.rsf.httpaudit.config.HttpAuditAutoConfiguration
org.springframework.boot.env.EnvironmentPostProcessor=\
com.vincent.rsf.httpaudit.config.HttpAuditJarDefaultsEnvironmentPostProcessor
rsf-open-api/src/main/java/com/vincent/rsf/openApi/common/datasource/HttpAuditDataSourceAspect.java
@@ -1,25 +1,26 @@
package com.vincent.rsf.openApi.common.datasource;
import com.vincent.rsf.httpaudit.props.HttpAuditProperties;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
 * http-audit 数据源切换
 * http-audit 数据源切换;不切 props 包,避免切面内调 {@link HttpAuditProperties} 自递归
 */
@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE + 10)
public class HttpAuditDataSourceAspect {
    @Value("${http-audit.datasource:primary}")
    private String dataSource;
    @Autowired
    private HttpAuditProperties httpAuditProperties;
    @Around("execution(* com.vincent.rsf.httpaudit..*(..))")
    @Around("execution(* com.vincent.rsf.httpaudit..*(..)) && !execution(* com.vincent.rsf.httpaudit.props..*(..))")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        String selected = resolveDataSource();
        DataSourceContextHolder.push(selected);
@@ -31,6 +32,10 @@
    }
    private String resolveDataSource() {
        if (httpAuditProperties.resolveLogStorageMode() == 2) {
            return DataSourceNames.PRIMARY;
        }
        String dataSource = httpAuditProperties.getDatasource();
        if ("jdxaj-log".equalsIgnoreCase(dataSource)) {
            return DataSourceNames.JDXAJ_LOG;
        }
rsf-server/src/main/java/com/vincent/rsf/server/common/datasource/HttpAuditDataSourceAspect.java
@@ -1,25 +1,26 @@
package com.vincent.rsf.server.common.datasource;
import com.vincent.rsf.httpaudit.props.HttpAuditProperties;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
 * http-audit 数据源切换
 * http-audit 数据源切换;不切 props 包,避免切面内调 {@link HttpAuditProperties} 自递归
 */
@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE + 10)
public class HttpAuditDataSourceAspect {
    @Value("${http-audit.datasource:primary}")
    private String dataSource;
    @Autowired
    private HttpAuditProperties httpAuditProperties;
    @Around("execution(* com.vincent.rsf.httpaudit..*(..))")
    @Around("execution(* com.vincent.rsf.httpaudit..*(..)) && !execution(* com.vincent.rsf.httpaudit.props..*(..))")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        String selected = resolveDataSource();
        DataSourceContextHolder.push(selected);
@@ -31,6 +32,10 @@
    }
    private String resolveDataSource() {
        if (httpAuditProperties.resolveLogStorageMode() == 2) {
            return DataSourceNames.PRIMARY;
        }
        String dataSource = httpAuditProperties.getDatasource();
        if ("dj-cloud-wms".equalsIgnoreCase(dataSource)) {
            return DataSourceNames.DJ_CLOUD_WMS;
        }
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/CusBarcodeSyncViewQueryService.java
@@ -1,6 +1,5 @@
package com.vincent.rsf.server.manager.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -18,6 +17,7 @@
import org.springframework.transaction.annotation.Transactional;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
@@ -104,30 +104,39 @@
            log.warn("dj-cloud-wms 数据源未配置,跳过 cus_barcode_sync_view");
            return Collections.emptyList();
        }
        List<CusBarcodeSyncView> rows = cusBarcodeSyncViewMapper.selectList(
                buildBarcodeOrQuery(codes)
                        .select(
                                CusBarcodeSyncView::getBarcode,
                                CusBarcodeSyncView::getItemName,
                                CusBarcodeSyncView::getItemSpec,
                                CusBarcodeSyncView::getUnitNo));
        List<CusBarcodeSyncView> rows = new ArrayList<>();
        for (String code : codes) {
            List<CusBarcodeSyncView> part = cusBarcodeSyncViewMapper.selectList(
                    Wrappers.<CusBarcodeSyncView>lambdaQuery()
                            .eq(CusBarcodeSyncView::getBarcode, code)
                            .select(
                                    CusBarcodeSyncView::getBarcode,
                                    CusBarcodeSyncView::getItemName,
                                    CusBarcodeSyncView::getItemSpec,
                                    CusBarcodeSyncView::getUnitNo)
                            .last("LIMIT 1"));
            if (part != null && !part.isEmpty()) {
                rows.addAll(part);
            }
        }
        return toViewMaps(rows);
    }
    private LambdaQueryWrapper<CusBarcodeSyncView> buildBarcodeOrQuery(List<String> codes) {
        LambdaQueryWrapper<CusBarcodeSyncView> wrapper = Wrappers.lambdaQuery();
        wrapper.and(q -> {
            for (int i = 0; i < codes.size(); i++) {
                String code = codes.get(i);
                if (i == 0) {
                    q.eq(CusBarcodeSyncView::getBarcode, code);
                } else {
                    q.or().eq(CusBarcodeSyncView::getBarcode, code);
                }
            }
        });
        return wrapper;
    }
    // 原单次 OR 拼接(避免 IN/OR 单条时可改回)
    // private LambdaQueryWrapper<CusBarcodeSyncView> buildBarcodeOrQuery(List<String> codes) {
    //     LambdaQueryWrapper<CusBarcodeSyncView> wrapper = Wrappers.lambdaQuery();
    //     wrapper.and(q -> {
    //         for (int i = 0; i < codes.size(); i++) {
    //             String code = codes.get(i);
    //             if (i == 0) {
    //                 q.eq(CusBarcodeSyncView::getBarcode, code);
    //             } else {
    //                 q.or().eq(CusBarcodeSyncView::getBarcode, code);
    //             }
    //         }
    //     });
    //     return wrapper;
    // }
    private List<Map<String, Object>> toViewMaps(List<CusBarcodeSyncView> rows) {
        if (rows == null || rows.isEmpty()) {
rsf-server/src/main/resources/application-dev.yml
@@ -138,11 +138,5 @@
      sync-cron: "0/3 * * * * ?"
      recover-cron: "0/5 * * * * ?"
# HTTP 接口审计(rsf-http-audit,不引入依赖则无审计;enabled=false 关闭 Filter 与管理接口)
# admin-api-enabled:是否注册 /httpAuditRule、/httpAuditLog、/httpAuditSysConfig
# 简易页默认开启;simple-ui-token 非空则校验请求头(公网建议配置)
http-audit:
  enabled: true
#  enabled: false
  # 审计数据源:primary / dj-cloud-wms / jdxaj-log
  datasource: jdxaj-log
rsf-server/src/main/resources/application-prod.yml
@@ -149,10 +149,5 @@
      sync-cron: "0 0/1 * * * ?"
      recover-cron: "0 0/2 * * * ?"
# HTTP 接口审计(rsf-http-audit,引入依赖即生效,可 enabled=false 关闭)
# whitelist-only=true:仅 sys_http_audit_rule 命中规则才写审计;无规则时不落库。false:排除路径外全量记录。
# rule-cache-refresh-ms:规则表缓存刷新间隔(毫秒)
http-audit:
  enabled: true
  # 审计数据源:primary / dj-cloud-wms / jdxaj-log
  datasource: jdxaj-log