package com.vincent.rsf.server.api.feign; import com.vincent.rsf.httpaudit.model.HttpAuditDecision; import com.vincent.rsf.httpaudit.service.HttpAuditOutboundRecorder; import feign.Client; import feign.Request; import feign.Response; import feign.Util; import lombok.RequiredArgsConstructor; import java.io.IOException; import java.nio.charset.StandardCharsets; /** * Feign 出站写 sys_http_audit_log(与 RestTemplate 共用规则与截断) */ @RequiredArgsConstructor public class AuditingFeignClient implements Client { private static final String FN_FEIGN = "HTTP出站(Feign)"; private final Client delegate; private final HttpAuditOutboundRecorder outboundRecorder; @Override public Response execute(Request request, Request.Options options) throws IOException { if (!outboundRecorder.isAuditEnabled()) { return delegate.execute(request, options); } String url = request.url(); String method = request.httpMethod().name(); byte[] reqBytes = request.body() == null ? new byte[0] : request.body(); String reqText = new String(reqBytes, StandardCharsets.UTF_8); HttpAuditDecision dec = outboundRecorder.decideOutbound(url, method, reqText); long t0 = System.currentTimeMillis(); Response resp; try { resp = delegate.execute(request, options); } catch (Throwable t) { outboundRecorder.saveOutbound(FN_FEIGN, url, method, reqText, dec, null, null, t0, t); if (t instanceof IOException) { throw (IOException) t; } if (t instanceof RuntimeException) { throw (RuntimeException) t; } if (t instanceof Error) { throw (Error) t; } throw new IOException(t); } if (!dec.isAudit()) { return resp; } byte[] respBytes; try { respBytes = Util.toByteArray(resp.body().asInputStream()); } catch (IOException e) { outboundRecorder.saveOutbound(FN_FEIGN, url, method, reqText, dec, resp.status(), null, t0, e); throw e; } String resText = new String(respBytes, StandardCharsets.UTF_8); outboundRecorder.saveOutbound(FN_FEIGN, url, method, reqText, dec, resp.status(), resText, t0, null); return Response.builder() .status(resp.status()) .reason(resp.reason()) .headers(resp.headers()) .body(respBytes) .request(resp.request()) .build(); } }