From abc8b127e1b5e307fd5f2f77229802a7902f270e Mon Sep 17 00:00:00 2001 From: wkc <978997012@qq.com> Date: Wed, 6 May 2026 10:18:28 +0800 Subject: [PATCH] Improve external API logging readability --- AGENTS.md | 1 + ...port-2026-04-30-interface-call-log-rule.md | 17 +++++++ doc/~$上虞_客户内码客户_历史利率_映射表.xlsx | Bin 165 -> 0 bytes ruoyi-admin/.DS_Store | Bin 8196 -> 10244 bytes .../ruoyi/common/utils/http/HttpUtils.java | 45 ++++++++++++++---- .../LoanPricingCustomerMapService.java | 7 +++ .../service/LoanRateHistoryService.java | 7 +++ 7 files changed, 68 insertions(+), 9 deletions(-) create mode 100644 doc/implementation-report-2026-04-30-interface-call-log-rule.md delete mode 100644 doc/~$上虞_客户内码客户_历史利率_映射表.xlsx diff --git a/AGENTS.md b/AGENTS.md index b0feec4..5a59a28 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,6 +16,7 @@ - 开发完成后必须执行与本次改动直接对应的验证步骤,完成验证后才能结束本次任务 - 如果是接口开发完成,先重启后端进程,确保最新代码已经生效,再调用接口进行测试 - 接口测试时必须覆盖多种情况,至少包含正常场景、必填/参数错误场景、分支场景;如接口逻辑包含状态、类型、金额、期限等关键条件,需要分别验证对应分支 +- 每次调用外部接口进行测试或联调时,必须在后端日志中完整输出请求 URL、请求参数和返回参数 - 如果是前端页面开发完成,必须启动前端页面并调用浏览器检查功能是否正常,确认页面展示、交互流程、接口联动和关键提示信息符合预期 - 测试结束后,自动结束测试时开启的前后端进程 diff --git a/doc/implementation-report-2026-04-30-interface-call-log-rule.md b/doc/implementation-report-2026-04-30-interface-call-log-rule.md new file mode 100644 index 0000000..e22524e --- /dev/null +++ b/doc/implementation-report-2026-04-30-interface-call-log-rule.md @@ -0,0 +1,17 @@ +# 实施记录 - 外部接口调用日志 + +## 日期 + +2026-04-30 + +## 修改内容 + +- 在根目录 `AGENTS.md` 的测试规范中新增外部接口调用日志要求:每次调用外部接口进行测试或联调时,必须在后端日志中完整输出请求 URL、请求参数和返回参数。 +- 补齐客户映射接口、历史贷款合同接口、通用 `HttpUtils` 外部接口调用日志,输出请求 URL、请求参数和返回参数。 +- 将外部接口日志调整为多行可读格式:请求 URL、请求参数、返回参数分段输出,参数对象和返回对象使用 pretty JSON 展开。 +- 调整单元测试,覆盖客户映射外呼日志、历史贷款合同外呼日志。 + +## 验证 + +- 已检查规则保存路径为项目根目录 `AGENTS.md`。 +- 已执行 `mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingCustomerMapServiceTest,LoanRateHistoryServiceTest -Dsurefire.failIfNoSpecifiedTests=false test`,测试通过。 diff --git a/doc/~$上虞_客户内码客户_历史利率_映射表.xlsx b/doc/~$上虞_客户内码客户_历史利率_映射表.xlsx deleted file mode 100644 index 3c3a794080821fc614a37e537889505a44d8e2ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 165 ZcmZQB&rVh#9WXPLGh{O)Gbqpn0sshg4^aRB diff --git a/ruoyi-admin/.DS_Store b/ruoyi-admin/.DS_Store index 5f707857019bfe3a2d702e0adf2be21e1eb5a091..9d39d1b0553087eafdb2ec482d46d2471fcf6fd7 100644 GIT binary patch literal 10244 zcmeI1Yitx%6oBuUmUh+=J1wo%aZ6WAEUdw1#XmYAqKA~VU{ zb06p4*>m=s`S#u=gg`hEYa}E<2odq7Qoa+587?|b@4QRkJ#-LoKCwq5h84lq*~n;~ zw%6f#!1I9T0nY=T2Rskl3LZda(@T_?^h(bIo(DV+WIRCM4*|TX3~@QZr2Fc?!rcO( zEW~d2!n#Z=C<9!ExSU`TNALnR6(ySre2W2WI`#*+UWm&HCfRfXeDeYP$-s9g;65GK z54h?CLQHz4=K;?H`5vHYQ$TDIC(%f1`u^R}ZKt8(YY++wXB8C}`--LFJv|4lRF9)N z2|KLDchOd_Wf_rlU5)J2wbZa&-fEeSs+(HeouJ1wRN39Hn=vaDwi0H{cIWo60iRFw z4a>^dSYt5QSRYu~6dbP)j5Ri_T1^|xXCIEj8kl8sVow#a` zVJBPJc|^gA$SAYtBDY8M-62V{mEOLc{R4x;vYfEBZp}~~J*l;+j%E+bB{rJ3Ick|* zaV?2v^+zouF=FCKX;d|$2~^fv45LSXglR|PmSJ>Rw(jVbIXK{GhaEab7s?tM7_zOn z)9NlZpyT>XtBgLQbCgcDcWQAPrO$Wru1f#?4=h^N)Vx0Y;Fj%EC30!m9H~MYLeH6w zzF*hkgQ^|X%$RN-=u7D_=U{(QxAlmj^_uF4<|~DI-&9GdTz1dBiZYOh=np4!=ZIV* zitbufO0~2*hK6jjVsy2bC{Ln3K4qR#t?Z&Bt#J*@7@x2}sgrtbG)t6Krnp#IBK01^ zuhu8qtfxT=Dt(3;(F~s)W6xP3ty21v+$y<)b*_;rl!2%YqKGwHE2`PK&W4Efd z(>bb{^dp^PW34zgWFJ)HT4xGFi+`d`*z0~-*iR?5$8^VX2hr+|YDF2O%eBN}t_@|S za*5KVbc^zN5x;faOzIfU=xY|$Vof@lYC?-xwdSKk``nuA(3(&K>n>*^Q&N0Ykp>bb zTgYBAN{*0YV5N1OO$WRIj)IuFBg2m7T zA!vaxbU+vEfF9_DJrIE?Xs{nFNI((}!x4BAj>0iG0Z+rT@Cv*Nuff}J7T$vo;6wNn z&cl~51>eF?@H1S7D?*_#ODGab1X(B(<_HUfTA@K`7Q(_Np^JTbA)QJ?8Ef2%prYWf z223S>v?rXLIhFjI9@@O6W9u!ON*C^Olc&7m?#ckosMYIRGQr29H65VS!`_YctPF8u z-HnBuIGw^2yU#DptxjiPZf5pjl4yrTnv-%d*RGi_Vv@+Sc(ZOHHap}Z*7Pq~Dq=#) zg4>k*!R02m#o4ju!gjDoevXGF67O*A#4)2;rF+-<1*)D@p?t~XXHHjhI~(c z#OVGL3P6NP2w+49VKr=kjTqCd&<@+76QjD@jp@BG3@XO80V80-D8yk59))pu9G-Av z`?wq1&%yKXBD@5z!yE7>yan&TDVT&a@GeIC$M89P1sCB`P7H6$j$!!aY78fP;-+Dl z2e5}md=YVoir70qG~!G{-?ph zdE*(llru1#Vc?Do1NUVZXk{4qSRMmk$YbE?f55=3NX{W4Ba2twb{;5T76<70fBUun z|G(}0;u-9D;CAu=6t?xWh0)^lNkFdg^sGIA_ddMoMfE0_bf;jE^+y0U=(%DY-c*;L snYi^3@0(!KosK&0bd)#f&j4@#Pu)+*e{#Cz?f+A~|K{lX=IsCf0$FZf=Kufz literal 8196 zcmeHMU2GIp6u#fIv@>*|0~A?sCoB|#kOfMqY0Hn<{wW|(U|YH^g=Mxg(t+tr*_qt} zrLi&bg+Jqy#($qQ5_vG7#1~CeL?0DRFvbU>#-9g#@I~dpGjpfJZmAE(7}2@O+;e}< zx%Zwk-#4>omNACbg1(Ni7-LMN%a=+ORd*=L>zH1g1v7r%wg*n8xz#P)YuNIX7&Xu2)z0IfSzE85Nb4Qe{=;{`8h-MA-v#2T`lQ``-Nj{-KU#I4h% zK3Q(DjenCUOHcUpMd*_#&8(7VsXdwAKDj@u%q`O4JBK~fvE98!-X)n;s$Ft&>wshL z${YTC&wyhUhHN*h%o)(Efr6!ZMvG;o%@YQVRl5Uu$Fg=guIVq+oAHceoe#wt_qA$e&fIx5 zvfM|}v_10?)5v#^n7TLEtGNS)t(*3to}%fRIm_s>wIO2;l9F_-I<|1py>V5YPBl}= znMVtzcU-9z#bA%b5>@Wk>2Nz7-QPu5E{U`b?Y?}8s_ye=+VTd~dT6k6l`40+ttY!wBp&@e74aZtq zRrP?s?RL%eI)@FLj&wEL`$3xPa|gA&(K$je3`e)oXmCqu+&AjbP0tA?DH=P;i(Y@Z z7F`c~SQJ}Wt)|s(QTarqTV&crlb}zxl`}xBowzl%sa3pL`|c6m2m&qDrs_f!k*Tbn zwX^N)Adzj9on=q6^Xww~fL&pqv9H+o>?ZpKKm{bsL=|eV2+NSf{b)oJn$e0*?8Y9X z(T_oxI1CrZZ~~(^iN|pYXK)th@D!fMi+Bky<2Ag2i@1b0@eZ!yBYcccaRXoD8+?nK z_!Yn5mM}wDAjE`)!crkAtQOV?jY5;KLD(ql6gq{Bpb3WsM;PYk7bfVoKlCRQ!f|R$ zdV~<&!cTz0g*($N{?L}K+vM$c4c)HK<^-F6&w|AARcjlXHn-dcB1i57SpP_Y0QZyF z65u|-MG0oFaVE-(Cs0$#zZg!l7!ozVUkF5_*yhxc&>ABMR9l2HExek?)qj>$;I&!tE%rt`Mt*oSC* z0{xRs?&@9{KYh&4|2wAt{(o2d7#%)}Koo&Lj{wTs)9tOKGu<=J&$SbD9i+=IzTP;O xfeBSXI!-c3$4TD)!;snuQuS?8p&aLuMyUMb9|Hc$&+X{_AHDwrwElOh-vLjjal-%r diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java index fe8bb5c..fc1ebab 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java @@ -21,6 +21,8 @@ import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONWriter; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.utils.StringUtils; import org.springframework.http.*; @@ -75,7 +77,7 @@ public class HttpUtils try { String urlNameString = StringUtils.isNotBlank(param) ? url + "?" + param : url; - log.info("sendGet - {}", urlNameString); + log.info("后端外部接口调用开始\n请求URL:{}\n请求参数:\n{}", urlNameString, formatLogValue(param)); URL realUrl = new URL(urlNameString); URLConnection connection = realUrl.openConnection(); connection.setRequestProperty("accept", "*/*"); @@ -88,7 +90,8 @@ public class HttpUtils { result.append(line); } - log.info("recv - {}", result); + log.info("后端外部接口调用完成\n请求URL:{}\n请求参数:\n{}\n返回参数:\n{}", + urlNameString, formatLogValue(param), formatLogValue(result)); } catch (ConnectException e) { @@ -150,7 +153,7 @@ public class HttpUtils StringBuilder result = new StringBuilder(); try { - log.info("sendPost - {}", url); + log.info("后端外部接口调用开始\n请求URL:{}\n请求参数:\n{}", url, formatLogValue(param)); URL realUrl = new URL(url); URLConnection conn = realUrl.openConnection(); conn.setRequestProperty("accept", "*/*"); @@ -169,7 +172,8 @@ public class HttpUtils { result.append(line); } - log.info("recv - {}", result); + log.info("后端外部接口调用完成\n请求URL:{}\n请求参数:\n{}\n返回参数:\n{}", + url, formatLogValue(param), formatLogValue(result)); } catch (ConnectException e) { @@ -219,7 +223,7 @@ public class HttpUtils String urlNameString = url + "?" + param; try { - log.info("sendSSLPost - {}", urlNameString); + log.info("后端外部接口调用开始\n请求URL:{}\n请求参数:\n{}", urlNameString, formatLogValue(param)); SSLContext sc = SSLContext.getInstance("SSL"); sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom()); URL console = new URL(urlNameString); @@ -245,7 +249,8 @@ public class HttpUtils result.append(new String(ret.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8)); } } - log.info("recv - {}", result); + log.info("后端外部接口调用完成\n请求URL:{}\n请求参数:\n{}\n返回参数:\n{}", + urlNameString, formatLogValue(param), formatLogValue(result)); conn.disconnect(); br.close(); } @@ -327,7 +332,8 @@ public class HttpUtils requestEntity, responseType ); - log.info("---------------------->POST(form-urlencoded) 请求成功,URL:{},响应结果:{}", url, response.getBody()); + log.info("后端外部接口调用完成\n请求URL:{}\n请求参数:\n{}\n返回参数:\n{}", + url, formatLogValue(params), formatLogValue(response.getBody())); return response.getBody(); } catch (Exception e) { throw new RuntimeException("POST(form-urlencoded) 请求失败,URL:" + url + ",异常信息:" + e.getMessage(), e); @@ -364,10 +370,31 @@ public class HttpUtils requestEntity, responseType ); - log.info("---------------------->POST(JSON) 请求成功,URL:{},响应结果:{}", url, response.getBody()); + log.info("后端外部接口调用完成\n请求URL:{}\n请求参数:\n{}\n返回参数:\n{}", + url, formatLogValue(requestBody), formatLogValue(response.getBody())); return response.getBody(); } catch (Exception e) { throw new RuntimeException("POST(JSON) 请求失败,URL:" + url + ",异常信息:" + e.getMessage(), e); } } -} \ No newline at end of file + + public static String formatLogValue(Object value) + { + if (value == null) + { + return "null"; + } + if (value instanceof CharSequence) + { + return value.toString(); + } + try + { + return JSON.toJSONString(value, JSONWriter.Feature.PrettyFormat); + } + catch (Exception e) + { + return String.valueOf(value); + } + } +} diff --git a/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/LoanPricingCustomerMapService.java b/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/LoanPricingCustomerMapService.java index 3f50e51..33a10f2 100644 --- a/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/LoanPricingCustomerMapService.java +++ b/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/LoanPricingCustomerMapService.java @@ -2,11 +2,13 @@ package com.ruoyi.loanpricing.service; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.http.HttpUtils; import com.ruoyi.loanpricing.domain.vo.CustomerMapRecordVO; import java.net.URI; import java.util.Collections; import java.util.List; import lombok.Data; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; @@ -17,6 +19,7 @@ import org.springframework.web.util.UriComponentsBuilder; * 客户号与客户内码映射查询服务。 */ @Service +@Slf4j public class LoanPricingCustomerMapService { private final RestTemplate restTemplate; @@ -67,6 +70,10 @@ public class LoanPricingCustomerMapService .encode() .toUri(); CustomerMapResponse response = restTemplate.getForObject(uri, CustomerMapResponse.class); + log.info("后端外部接口调用完成\n请求URL:{}\n请求参数:\n{}\n返回参数:\n{}", + uri, + HttpUtils.formatLogValue(Collections.singletonMap("cust_id", normalizedCustId)), + HttpUtils.formatLogValue(response)); if (response == null) { throw new ServiceException("客户号映射接口无返回"); diff --git a/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/LoanRateHistoryService.java b/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/LoanRateHistoryService.java index 6309614..3aaabd5 100644 --- a/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/LoanRateHistoryService.java +++ b/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/LoanRateHistoryService.java @@ -2,11 +2,13 @@ package com.ruoyi.loanpricing.service; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.http.HttpUtils; import com.ruoyi.loanpricing.domain.vo.HistoryLoanContractVO; import java.net.URI; import java.util.Collections; import java.util.List; import lombok.Data; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; @@ -14,6 +16,7 @@ import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; @Service +@Slf4j public class LoanRateHistoryService { private final RestTemplate restTemplate; @@ -50,6 +53,10 @@ public class LoanRateHistoryService .encode() .toUri(); HistoryLoanContractResponse response = restTemplate.getForObject(uri, HistoryLoanContractResponse.class); + log.info("后端外部接口调用完成\n请求URL:{}\n请求参数:\n{}\n返回参数:\n{}", + uri, + HttpUtils.formatLogValue(Collections.singletonMap("cust_isn", normalizedCustIsn)), + HttpUtils.formatLogValue(response)); if (response == null) { throw new ServiceException("历史贷款合同接口无返回");