ThirdPartyApiLogAspect.java 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. package com.zsElectric.boot.charging.aspect;
  2. import cn.hutool.core.util.StrUtil;
  3. import com.fasterxml.jackson.annotation.JsonInclude;
  4. import com.fasterxml.jackson.databind.ObjectMapper;
  5. import com.zsElectric.boot.charging.entity.ThirdPartyApiLog;
  6. import com.zsElectric.boot.charging.service.ThirdPartyApiLogService;
  7. import com.zsElectric.boot.common.util.electric.ChargingUtil;
  8. import com.zsElectric.boot.common.util.electric.RequestParmsEntity;
  9. import com.zsElectric.boot.common.util.electric.ResponseParmsEntity;
  10. import jakarta.servlet.http.HttpServletRequest;
  11. import lombok.RequiredArgsConstructor;
  12. import lombok.extern.slf4j.Slf4j;
  13. import org.aspectj.lang.ProceedingJoinPoint;
  14. import org.aspectj.lang.annotation.Around;
  15. import org.aspectj.lang.annotation.Aspect;
  16. import org.aspectj.lang.annotation.Pointcut;
  17. import org.springframework.stereotype.Component;
  18. import org.springframework.web.context.request.RequestContextHolder;
  19. import org.springframework.web.context.request.ServletRequestAttributes;
  20. import java.time.LocalDateTime;
  21. import java.util.Arrays;
  22. import java.util.HashMap;
  23. import java.util.HashSet;
  24. import java.util.Map;
  25. import java.util.Set;
  26. /**
  27. * 第三方接口日志记录切面
  28. * 拦截 LinkDataController 和 ChargingBusinessController 的所有方法,自动记录请求和响应日志
  29. *
  30. * @author system
  31. * @since 2025-01-02
  32. */
  33. @Slf4j
  34. @Aspect
  35. @Component
  36. @RequiredArgsConstructor
  37. public class ThirdPartyApiLogAspect {
  38. private final ThirdPartyApiLogService thirdPartyApiLogService;
  39. private final ObjectMapper objectMapper;
  40. private final ChargingUtil chargingUtil;
  41. /** 需要保留的关键请求头 */
  42. private static final Set<String> IMPORTANT_HEADERS = new HashSet<>(Arrays.asList(
  43. "content-type", "authorization", "content-length"
  44. ));
  45. /**
  46. * 定义切入点: LinkDataController 的所有方法
  47. */
  48. @Pointcut("execution(* com.zsElectric.boot.charging.controller.LinkDataController.*(..))")
  49. public void linkDataControllerPointcut() {
  50. }
  51. /**
  52. * 定义切入点: ChargingBusinessController 的所有方法
  53. */
  54. @Pointcut("execution(* com.zsElectric.boot.charging.controller.ChargingBusinessController.*(..))")
  55. public void chargingBusinessControllerPointcut() {
  56. }
  57. /**
  58. * 环绕通知: 记录 LinkDataController 接口日志
  59. */
  60. @Around("linkDataControllerPointcut()")
  61. public Object aroundLinkDataController(ProceedingJoinPoint joinPoint) throws Throwable {
  62. return recordApiLog(joinPoint, "LinkDataController", 2); // 2-接收推送
  63. }
  64. /**
  65. * 环绕通知: 记录 ChargingBusinessController 接口日志
  66. */
  67. @Around("chargingBusinessControllerPointcut()")
  68. public Object aroundChargingBusinessController(ProceedingJoinPoint joinPoint) throws Throwable {
  69. return recordApiLog(joinPoint, "ChargingBusinessController", 1); // 1-请求出去
  70. }
  71. /**
  72. * 记录接口日志的通用方法
  73. */
  74. private Object recordApiLog(ProceedingJoinPoint joinPoint, String controllerName, Integer logType) throws Throwable {
  75. long startTime = System.currentTimeMillis();
  76. ThirdPartyApiLog apiLog = new ThirdPartyApiLog();
  77. // 获取 HttpServletRequest
  78. ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
  79. HttpServletRequest request = attributes != null ? attributes.getRequest() : null;
  80. try {
  81. // 设置基本信息
  82. apiLog.setLogType(logType);
  83. apiLog.setControllerName(controllerName);
  84. LocalDateTime now = LocalDateTime.now();
  85. apiLog.setCreatedTime(now);
  86. apiLog.setUpdatedTime(now);
  87. if (request != null) {
  88. // 请求信息
  89. apiLog.setRequestMethod(request.getMethod());
  90. apiLog.setRequestUrl(request.getRequestURL().toString());
  91. apiLog.setInterfaceName(request.getRequestURI());
  92. apiLog.setInterfaceDescription(getInterfaceDescription(request.getRequestURI()));
  93. apiLog.setClientIp(getClientIp(request));
  94. apiLog.setUserAgent(request.getHeader("User-Agent"));
  95. // 请求头(只保留关键信息)
  96. apiLog.setRequestHeaders(getSimplifiedHeaders(request));
  97. // 请求参数
  98. Map<String, String[]> parameterMap = request.getParameterMap();
  99. if (!parameterMap.isEmpty()) {
  100. // 简化参数格式,单值的数组转为简单字符串
  101. Map<String, Object> simplifiedParams = new HashMap<>();
  102. for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
  103. String[] values = entry.getValue();
  104. if (values != null && values.length == 1) {
  105. simplifiedParams.put(entry.getKey(), values[0]);
  106. } else {
  107. simplifiedParams.put(entry.getKey(), values);
  108. }
  109. }
  110. apiLog.setRequestParams(objectMapper.writeValueAsString(simplifiedParams));
  111. }
  112. // 请求体(从切点参数中获取)
  113. Object[] args = joinPoint.getArgs();
  114. if (args != null && args.length > 0) {
  115. for (Object arg : args) {
  116. if (arg != null && !(arg instanceof HttpServletRequest) && !(arg instanceof jakarta.servlet.http.HttpServletResponse)) {
  117. // 序列化时忽略null值
  118. ObjectMapper cleanMapper = objectMapper.copy();
  119. cleanMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
  120. apiLog.setRequestBody(cleanMapper.writeValueAsString(arg));
  121. // 尝试提取业务字段
  122. extractBusinessFields(arg, apiLog);
  123. // 尝试解密请求数据(针对 RequestParmsEntity 类型)
  124. decryptRequestData(arg, apiLog);
  125. break;
  126. }
  127. }
  128. }
  129. }
  130. // 执行目标方法
  131. Object result = joinPoint.proceed();
  132. // 记录响应
  133. long endTime = System.currentTimeMillis();
  134. apiLog.setResponseTime(endTime - startTime);
  135. apiLog.setResponseStatus(200);
  136. apiLog.setIsSuccess(1);
  137. if (result != null) {
  138. // 序列化时忽略null值
  139. ObjectMapper cleanMapper = objectMapper.copy();
  140. cleanMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
  141. apiLog.setResponseBody(cleanMapper.writeValueAsString(result));
  142. // 解密响应数据(针对 ResponseParmsEntity 类型)
  143. decryptResponseData(result, apiLog);
  144. }
  145. // 异步保存日志
  146. thirdPartyApiLogService.saveLogAsync(apiLog);
  147. return result;
  148. } catch (Exception e) {
  149. // 记录异常
  150. long endTime = System.currentTimeMillis();
  151. apiLog.setResponseTime(endTime - startTime);
  152. apiLog.setResponseStatus(500);
  153. apiLog.setIsSuccess(0);
  154. apiLog.setErrorMessage(e.getMessage());
  155. // 异步保存日志
  156. thirdPartyApiLogService.saveLogAsync(apiLog);
  157. throw e;
  158. }
  159. }
  160. /**
  161. * 解密请求数据
  162. * 针对 LinkDataController 接收的 RequestParmsEntity 类型参数进行解密
  163. */
  164. private void decryptRequestData(Object requestBody, ThirdPartyApiLog apiLog) {
  165. try {
  166. if (requestBody instanceof RequestParmsEntity) {
  167. RequestParmsEntity requestParms = (RequestParmsEntity) requestBody;
  168. String encryptedData = requestParms.getData();
  169. if (StrUtil.isNotBlank(encryptedData)) {
  170. // 使用 ChargingUtil 解密(与业务代码保持一致)
  171. String decryptedData = chargingUtil.decryptData(encryptedData);
  172. apiLog.setDecryptedRequestData(decryptedData);
  173. log.debug("请求数据解密成功: {}", decryptedData);
  174. }
  175. }
  176. } catch (Exception e) {
  177. log.warn("解密请求数据失败: {}", e.getMessage());
  178. }
  179. }
  180. /**
  181. * 解密响应数据
  182. * 针对返回的 ResponseParmsEntity 类型响应进行解密
  183. */
  184. private void decryptResponseData(Object responseBody, ThirdPartyApiLog apiLog) {
  185. try {
  186. if (responseBody instanceof ResponseParmsEntity) {
  187. ResponseParmsEntity responseParms = (ResponseParmsEntity) responseBody;
  188. String encryptedData = responseParms.getData();
  189. if (StrUtil.isNotBlank(encryptedData)) {
  190. // 使用 ChargingUtil 解密(与业务代码保持一致)
  191. String decryptedData = chargingUtil.decryptData(encryptedData);
  192. apiLog.setDecryptedResponseData(decryptedData);
  193. log.debug("响应数据解密成功: {}", decryptedData);
  194. }
  195. }
  196. } catch (Exception e) {
  197. log.warn("解密响应数据失败: {}", e.getMessage());
  198. }
  199. }
  200. /**
  201. * 提取业务字段
  202. */
  203. private void extractBusinessFields(Object requestBody, ThirdPartyApiLog apiLog) {
  204. try {
  205. String json = objectMapper.writeValueAsString(requestBody);
  206. Map<String, Object> map = objectMapper.readValue(json, Map.class);
  207. // 提取运营商ID
  208. if (map.containsKey("OperatorID")) {
  209. apiLog.setOperatorId(String.valueOf(map.get("OperatorID")));
  210. } else if (map.containsKey("operatorID")) {
  211. apiLog.setOperatorId(String.valueOf(map.get("operatorID")));
  212. }
  213. // 提取充电站ID
  214. if (map.containsKey("StationID")) {
  215. apiLog.setStationId(String.valueOf(map.get("StationID")));
  216. } else if (map.containsKey("stationID")) {
  217. apiLog.setStationId(String.valueOf(map.get("stationID")));
  218. }
  219. // 提取充电桩ID
  220. if (map.containsKey("ConnectorID")) {
  221. apiLog.setConnectorId(String.valueOf(map.get("ConnectorID")));
  222. } else if (map.containsKey("connectorID")) {
  223. apiLog.setConnectorId(String.valueOf(map.get("connectorID")));
  224. }
  225. // 提取业务流水号
  226. if (map.containsKey("StartChargeSeq")) {
  227. apiLog.setBusinessSeq(String.valueOf(map.get("StartChargeSeq")));
  228. } else if (map.containsKey("startChargeSeq")) {
  229. apiLog.setBusinessSeq(String.valueOf(map.get("startChargeSeq")));
  230. } else if (map.containsKey("EquipAuthSeq")) {
  231. apiLog.setBusinessSeq(String.valueOf(map.get("EquipAuthSeq")));
  232. } else if (map.containsKey("equipAuthSeq")) {
  233. apiLog.setBusinessSeq(String.valueOf(map.get("equipAuthSeq")));
  234. } else if (map.containsKey("EquipBizSeq")) {
  235. apiLog.setBusinessSeq(String.valueOf(map.get("EquipBizSeq")));
  236. } else if (map.containsKey("equipBizSeq")) {
  237. apiLog.setBusinessSeq(String.valueOf(map.get("equipBizSeq")));
  238. }
  239. } catch (Exception e) {
  240. log.debug("提取业务字段失败: {}", e.getMessage());
  241. }
  242. }
  243. /**
  244. * 获取简化的请求头信息(只保留关键字段)
  245. */
  246. private String getSimplifiedHeaders(HttpServletRequest request) {
  247. try {
  248. Map<String, String> headers = new HashMap<>();
  249. for (String headerName : IMPORTANT_HEADERS) {
  250. String headerValue = request.getHeader(headerName);
  251. if (StrUtil.isNotBlank(headerValue)) {
  252. // 对于 authorization 只保留类型,不记录完整 token
  253. if ("authorization".equalsIgnoreCase(headerName) && headerValue.length() > 20) {
  254. headers.put(headerName, headerValue.substring(0, 20) + "...");
  255. } else {
  256. headers.put(headerName, headerValue);
  257. }
  258. }
  259. }
  260. return headers.isEmpty() ? null : objectMapper.writeValueAsString(headers);
  261. } catch (Exception e) {
  262. log.error("获取请求头失败: {}", e.getMessage());
  263. return null;
  264. }
  265. }
  266. /**
  267. * 获取接口中文说明
  268. * 根据接口路径返回对应的中文说明
  269. */
  270. private String getInterfaceDescription(String uri) {
  271. if (StrUtil.isBlank(uri)) {
  272. return null;
  273. }
  274. // LinkDataController 接口说明
  275. if (uri.contains("/query_token")) {
  276. return "获取token";
  277. } else if (uri.contains("/notification_start_charge_result")) {
  278. return "推送启动充电结果";
  279. } else if (uri.contains("/notification_equip_charge_status")) {
  280. return "推送充电状态";
  281. } else if (uri.contains("/notification_stop_charge_result")) {
  282. return "推送停止充电结果";
  283. } else if (uri.contains("/notification_charge_order_info")) {
  284. return "推送充电订单信息";
  285. } else if (uri.contains("/notification_stationStatus")) {
  286. return "设备状态变化推送";
  287. }
  288. // ChargingBusinessController 接口说明
  289. else if (uri.contains("/queryEquipBusinessPolicy")) {
  290. return "查询业务策略信息";
  291. } else if (uri.contains("/queryEquipAuth")) {
  292. return "请求设备认证";
  293. } else if (uri.contains("/queryStationsInfo")) {
  294. return "查询充电站信息";
  295. } else if (uri.contains("/queryStationStatus")) {
  296. return "设备接口状态查询";
  297. } else if (uri.contains("/startCharging")) {
  298. return "请求启动充电";
  299. } else if (uri.contains("/queryChargingStatus")) {
  300. return "查询充电状态";
  301. } else if (uri.contains("/stopCharging")) {
  302. return "请求停止充电";
  303. }
  304. return null;
  305. }
  306. /**
  307. * 获取客户端真实IP
  308. */
  309. private String getClientIp(HttpServletRequest request) {
  310. String ip = request.getHeader("X-Forwarded-For");
  311. if (StrUtil.isNotBlank(ip) && !"unknown".equalsIgnoreCase(ip)) {
  312. // 多次反向代理后会有多个IP值,第一个为真实IP
  313. int index = ip.indexOf(',');
  314. if (index != -1) {
  315. return ip.substring(0, index);
  316. }
  317. return ip;
  318. }
  319. ip = request.getHeader("X-Real-IP");
  320. if (StrUtil.isNotBlank(ip) && !"unknown".equalsIgnoreCase(ip)) {
  321. return ip;
  322. }
  323. return request.getRemoteAddr();
  324. }
  325. }