package com.zy.acs.manager.common.interceptor; import com.alibaba.fastjson.JSON; import com.zy.acs.framework.common.Cools; 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.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.Data; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; import org.springframework.web.util.ContentCachingRequestWrapper; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Date; import java.util.LinkedHashMap; import java.util.Map; @Slf4j @ControllerAdvice public class IntegrationRecordAdvice implements ResponseBodyAdvice { private static final String HEADER_APP_KEY = "appkey"; private static final String HEADER_CALLER = "caller"; @Autowired private IntegrationRecordService integrationRecordService; @Resource private SnowflakeIdWorker snowflakeIdWorker; @Override public boolean supports(@NotNull MethodParameter methodParameter, @NotNull Class> aClass) { return true; } @Override public Object beforeBodyWrite(Object body, @NotNull MethodParameter methodParameter, @NotNull MediaType mediaType, @NotNull Class> aClass, @NotNull ServerHttpRequest serverHttpRequest, @NotNull ServerHttpResponse serverHttpResponse) { if (!(serverHttpRequest instanceof ServletServerHttpRequest)) { return body; } HttpServletRequest request = ((ServletServerHttpRequest) serverHttpRequest).getServletRequest(); IntegrationRequestContext context = IntegrationOpenApiInterceptor.getContext(request); if (context == null) { return body; } IntegrationRecord record = null; try { record = buildRecord(body, request, context, null); } catch (Exception 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, Exception failure) { Date now = new Date(); RequestSnapshot payload = buildSnapshot(request); String reqContent = !Cools.isEmpty(payload.getParameters()) ? JSON.toJSONString(payload.getParameters()) : payload.getJson(); IntegrationRecord record = new IntegrationRecord( String.valueOf(snowflakeIdWorker.nextId()).substring(3), // 编号 context.getNamespaceType().desc, // 名称空间 payload.getUri(), // 接口地址 request.getHeader(HEADER_APP_KEY), // 平台密钥 context.getNamespaceType().caller, // 调用方标识 IntegrationDirectionType.INBOUND.value, // 方向[非空] String.valueOf(context.getStartAt()), // 时间戳 IpTools.gainRealIp(request), // 客户端IP reqContent, // 请求内容 JSON.toJSONString(responseBody), // 响应内容 null, // 异常内容 0, // 结果 (int) (System.currentTimeMillis() - context.getStartAt()), // 耗时 StatusType.ENABLE.val, // 状态 now, // 添加时间[非空] now, // 修改时间[非空] context.getHandler() // 备注 ); applyResult(record, responseBody, failure); return record; } private void applyResult(IntegrationRecord record, Object responseBody, Exception failure) { if (failure != null) { record.setErr("Request failed: " + failure.getMessage()); return; } if (!(responseBody instanceof R)) { record.setErr("Invalid response body structure. Expected: { code, msg, data }."); return; } R r = (R) responseBody; Integer code = parseInteger(r.get("code")); if (code == null) { record.setErr("Missing or invalid response field: code."); return; } if (code == 200) { record.setResult(1); } } private Integer parseInteger(Object codeObj) { if (codeObj == null) { return null; } if (codeObj instanceof Integer) { return (Integer) codeObj; } try { return Integer.parseInt(String.valueOf(codeObj)); } catch (NumberFormatException e) { return null; } } private RequestSnapshot buildSnapshot(HttpServletRequest request) { Map params = flattenParameters(request.getParameterMap()); return new RequestSnapshot( request.getMethod(), request.getRequestURI(), request.getQueryString(), request.getContentType(), params.isEmpty() ? null : params, normalizeBody(readBody(request), request.getContentType()) ); } private String normalizeBody(String body, String contentType) { if (Cools.isEmpty(body)) { return null; } boolean isJson = !Cools.isEmpty(contentType) && contentType.toLowerCase().contains("json"); if (isJson) { try { Object parsed = JSON.parse(body); return JSON.toJSONString(parsed, false); } catch (Exception ignore) { } } return body.replaceAll("[\\n\\r\\t]", "").trim(); } private Map flattenParameters(Map rawParams) { Map flattened = new LinkedHashMap<>(); if (rawParams == null) { return flattened; } rawParams.forEach((key, values) -> { if (values == null) { flattened.put(key, null); } else if (values.length == 1) { flattened.put(key, values[0]); } else { flattened.put(key, Arrays.asList(values)); } }); return flattened; } private String readBody(HttpServletRequest request) { HttpServletRequest target = unwrapCachingRequest(request); if (target instanceof ContentCachingRequestWrapper) { ContentCachingRequestWrapper wrapper = (ContentCachingRequestWrapper) target; byte[] buffer = wrapper.getContentAsByteArray(); if (buffer.length > 0) { Charset charset = charset(wrapper.getCharacterEncoding()); return new String(buffer, charset); } } return null; } private HttpServletRequest unwrapCachingRequest(HttpServletRequest request) { HttpServletRequest current = request; while (current instanceof HttpServletRequestWrapper) { if (current instanceof ContentCachingRequestWrapper) { return current; } current = (HttpServletRequest) ((HttpServletRequestWrapper) current).getRequest(); } return request; } private Charset charset(String encoding) { if (Cools.isEmpty(encoding)) { return StandardCharsets.UTF_8; } try { return Charset.forName(encoding); } catch (Exception e) { return StandardCharsets.UTF_8; } } @Data private static class RequestSnapshot { private final String method; private final String uri; private final String query; private final String contentType; private final Map parameters; private final String json; RequestSnapshot(String method, String uri, String query, String contentType, Map parameters, String json) { this.method = method; this.uri = uri; this.query = query; this.contentType = contentType; this.parameters = parameters; this.json = json; } } }