zy-acs-manager/src/main/java/com/zy/acs/manager/common/filter/IntegrationRequestCachingFilter.java
New file @@ -0,0 +1,51 @@ package com.zy.acs.manager.common.filter; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import org.springframework.util.AntPathMatcher; import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.util.ContentCachingRequestWrapper; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * Wraps open-api requests so downstream interceptors can safely read the body multiple times. */ @Component @Order(Ordered.HIGHEST_PRECEDENCE + 5) public class IntegrationRequestCachingFilter extends OncePerRequestFilter { private static final AntPathMatcher MATCHER = new AntPathMatcher(); private static final String[] LOG_PATTERNS = {"/api/open/**"}; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { HttpServletRequest wrapped = request instanceof ContentCachingRequestWrapper ? request : new ContentCachingRequestWrapper(request); filterChain.doFilter(wrapped, response); } @Override protected boolean shouldNotFilter(HttpServletRequest request) { String uri = request.getRequestURI(); for (String pattern : LOG_PATTERNS) { if (MATCHER.match(pattern, uri)) { return false; } } return true; } @Override protected boolean shouldNotFilterAsyncDispatch() { return true; } } zy-acs-manager/src/main/java/com/zy/acs/manager/common/interceptor/IntegrationOpenApiInterceptor.java
@@ -3,7 +3,8 @@ import com.zy.acs.framework.common.Cools; import com.zy.acs.manager.common.annotation.IntegrationAuth; import com.zy.acs.manager.core.domain.type.NamespaceType; import lombok.Value; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.stereotype.Component; @@ -16,10 +17,7 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** @@ -45,13 +43,9 @@ NamespaceType namespaceType = integrationAuth.name() == null ? NamespaceType.NONE : integrationAuth.name(); IntegrationRequestContext context = new IntegrationRequestContext( namespaceType, namespaceType.name, handlerMethod.getBeanType().getSimpleName() + "#" + handlerMethod.getMethod().getName(), request.getMethod(), request.getRequestURI(), request.getQueryString(), System.currentTimeMillis(), buildRequestCache(request) buildPayload(request) ); request.setAttribute(ATTR_CONTEXT, context); return true; @@ -73,19 +67,17 @@ return AnnotatedElementUtils.findMergedAnnotation(handlerMethod.getBeanType(), IntegrationAuth.class); } private Map<String, Object> buildRequestCache(HttpServletRequest request) { Map<String, Object> cache = new LinkedHashMap<>(); cache.put("method", request.getMethod()); cache.put("uri", request.getRequestURI()); cache.put("query", request.getQueryString()); cache.put("contentType", request.getContentType()); cache.put("parameters", flattenParameters(request.getParameterMap())); cache.put("headers", extractHeaders(request)); private RequestPayload buildPayload(HttpServletRequest request) { Map<String, Object> params = flattenParameters(request.getParameterMap()); String body = readBody(request); if (!Cools.isEmpty(body)) { cache.put("body", body); } return Collections.unmodifiableMap(cache); return new RequestPayload( request.getMethod(), request.getRequestURI(), request.getQueryString(), request.getContentType(), params.isEmpty() ? null : params, Cools.isEmpty(body) ? null : body ); } private Map<String, Object> flattenParameters(Map<String, String[]> rawParams) { @@ -103,19 +95,6 @@ } }); return flattened; } private Map<String, Object> extractHeaders(HttpServletRequest request) { Map<String, Object> headers = new LinkedHashMap<>(); Enumeration<String> headerNames = request.getHeaderNames(); if (headerNames == null) { return headers; } List<String> names = Collections.list(headerNames); for (String name : names) { headers.put(name, request.getHeader(name)); } return headers; } private String readBody(HttpServletRequest request) { @@ -141,15 +120,23 @@ } } @Value @Getter @AllArgsConstructor public static class IntegrationRequestContext { NamespaceType namespaceType; String namespace; String handler; String method; String uri; String query; long startAt; Map<String, Object> requestSnapshot; private final NamespaceType namespaceType; private final String handler; private final long startAt; private final RequestPayload payload; } @Getter @AllArgsConstructor public static class RequestPayload { private final String method; private final String uri; private final String query; private final String contentType; private final Map<String, Object> parameters; private final String body; } } zy-acs-manager/src/main/java/com/zy/acs/manager/common/interceptor/IntegrationRecordAdvice.java
@@ -5,13 +5,12 @@ import com.zy.acs.framework.common.R; import com.zy.acs.framework.common.SnowflakeIdWorker; import com.zy.acs.manager.common.interceptor.IntegrationOpenApiInterceptor.IntegrationRequestContext; import com.zy.acs.manager.common.interceptor.IntegrationOpenApiInterceptor.RequestPayload; import com.zy.acs.manager.common.utils.IpTools; import com.zy.acs.manager.manager.entity.IntegrationRecord; import com.zy.acs.manager.manager.enums.IntegrationDirectionType; import com.zy.acs.manager.manager.enums.StatusType; import com.zy.acs.manager.manager.service.IntegrationRecordService; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; @@ -62,34 +61,43 @@ if (context == null) { return body; } IntegrationRecord record = null; try { IntegrationRecord record = buildRecord(body, request, context); integrationRecordService.syncRecord(record); record = buildRecord(body, request, context, null); } catch (Exception e) { log.error("Failed to persist integration log for {}", context.getHandler(), e); log.error("Failed to build integration log for {}", context.getHandler(), e); record = buildRecord(null, request, context, e); } finally { if (record != null) { try { integrationRecordService.syncRecord(record); } catch (Exception persistEx) { log.error("Failed to persist integration log for {}", context.getHandler(), persistEx); } } } return body; } private IntegrationRecord buildRecord(Object responseBody, HttpServletRequest request, IntegrationRequestContext context) { IntegrationRequestContext context, Exception failure) { Date now = new Date(); ResultView resultView = resolveResult(responseBody); RequestPayload payload = context.getPayload(); IntegrationRecord record = new IntegrationRecord(); record.setUuid(nextUuid()); record.setNamespace(context.getNamespace()); record.setUrl(context.getUri()); record.setNamespace(context.getNamespaceType().name()); record.setUrl(payload.getUri()); record.setAppkey(request.getHeader(HEADER_APP_KEY)); record.setCaller(resolveCaller(request)); record.setDirection(IntegrationDirectionType.INBOUND.value); record.setTimestamp(String.valueOf(context.getStartAt())); record.setClientIp(IpTools.gainRealIp(request)); record.setRequest(safeToJson(context.getRequestSnapshot())); record.setRequest(safeToJson(payload)); record.setResponse(safeToJson(responseBody)); record.setErr(resultView.getError()); record.setResult(resultView.getResult()); applyResult(record, responseBody, failure); record.setCostMs(cost(context.getStartAt())); record.setStatus(StatusType.ENABLE.val); record.setCreateTime(now); @@ -98,18 +106,27 @@ return record; } private ResultView resolveResult(Object body) { private void applyResult(IntegrationRecord record, Object body, Exception failure) { if (failure != null) { record.setResult(0); record.setErr(failure.getMessage()); return; } if (!(body instanceof R)) { return ResultView.unknown(); record.setResult(null); record.setErr(null); return; } R response = (R) body; Integer code = parseInteger(response.get("code")); if (code == null) { return ResultView.unknown(); record.setResult(null); record.setErr(null); return; } boolean success = code == 200; String error = success ? null : safeToString(response.get("msg")); return new ResultView(success ? 1 : 0, error); record.setResult(success ? 1 : 0); record.setErr(success ? null : safeToString(response.get("msg"))); } private Integer parseInteger(Object codeObj) { @@ -163,16 +180,5 @@ private String safeToString(Object value) { return value == null ? null : String.valueOf(value); } @Getter @AllArgsConstructor private static class ResultView { private final Integer result; private final String error; private static ResultView unknown() { return new ResultView(null, null); } } }