# httpcall4j **Repository Path**: lsukey/httpcall4j ## Basic Information - **Project Name**: httpcall4j - **Description**: httpcall4j 是一个功能强大且易于使用的Java HTTP客户端库。它不仅简化了HTTP请求的发送,还提供了自动日志记录、失败重试和可扩展的请求处理等高级功能,特别适合在微服务架构中进行可靠的服务间通信。 - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-08-12 - **Last Updated**: 2026-01-21 ## Categories & Tags **Categories**: web-dev-toolkits **Tags**: None ## README # httpcall4j `httpcall4j` 是一个功能强大且易于使用的Java HTTP客户端库。它不仅简化了HTTP请求的发送,还提供了全面的**出站(Egress)**和**入站(Ingress)**流量日志记录、失败重试和可扩展的请求处理等高级功能,特别适合在微服务架构中进行可靠的服务间通信和全面的API可观测性建设。 ## 核心功能 - **统一的调用日志**:无论是作为客户端调用外部服务(出站),还是作为服务端接收外部调用(入站),`httpcall4j` 都能将每一次HTTP交互的详细信息(包括请求头、请求体、响应状态码、响应体、调用方IP等)统一记录到数据库中,方便审计和调试。 - **无侵入式入站请求日志**:只需在您的Controller方法上添加一个 `@LogIncomingCall` 注解,即可自动记录所有外部系统对您API的调用,完全不干扰现有业务逻辑。这对于API审计、问题排查以及与第三方系统对接至关重要。 - **流式API**:提供简洁、易读的API,轻松构建和发送出站HTTP请求。 - **返回日志ID**:每次调用(无论是出站还是入站,成功还是失败)都会返回一个唯一的日志ID (`logId`),便于在分布式系统中进行端到端的问题追踪。 - **失败调用重试**:内置重试服务 (`HttpCallRerunService`),可以根据 `logId` 方便地重试失败的HTTP调用。 - **可扩展的请求处理**:通过实现 `HttpCallProcessor` 接口,可以在出站请求发送前对其进行动态修改。无论是添加认证Token、进行数字签名,还是修改请求头/体,都可以通过自定义处理器轻松实现。 - **高度可定制**:支持自定义业务系统名称 (`systemName`) 和功能名称 (`functionName`),便于在复杂的业务场景中进行日志分类和检索。同时支持通过HTTP Header动态识别调用方系统。 - **兼容Spring Boot 2 & 3**:无需修改代码即可在不同版本的Spring Boot项目中无缝集成。 ## 快速开始 ### 1. 添加Maven依赖 `httpcall4j` 提供了对 Spring Boot 2 和 Spring Boot 3 的支持。请根据您的项目版本选择合适的 starter。 - 暂未发布中央仓库,请自行编译 #### Spring Boot 3 项目 如果您的项目使用 Spring Boot 3.x,请添加以下依赖: ```xml com.ebo httpcall4j-springboot3-start 1.0.0 ``` #### Spring Boot 2 项目 如果您的项目使用 Spring Boot 2.x,请添加以下依赖: ```xml com.ebo httpcall4j-springboot-start 1.0.0 ``` ### 2. 配置数据库 `httpcall4j` 需要一个数据源Bean来进行数据库操作。请确保您的Spring环境中已经配置好了数据源。 - **Spring Boot 2**: 需要 `javax.sql.DataSource` 类型的Bean。 - **Spring Boot 3**: 需要 `jakarta.sql.DataSource` 类型的Bean。 ### 3. 创建数据库表 在使用前,请在您的数据库中执行以下SQL语句来创建 `http_call_log` 表: ```sql CREATE TABLE `http_call_log` ( `id` bigint NOT NULL AUTO_INCREMENT, `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `system_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '系统名称', `function_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '功能名称', `url` varchar(255) NOT NULL, `method` varchar(10) NOT NULL, `content_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, `headers` text, `body` text, `response_status_code` int DEFAULT NULL COMMENT '响应码', `response_body` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT '响应内容', `status` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT 'SUCCESS, FAILED', `rerun_of_log_id` bigint DEFAULT NULL COMMENT '原始日志Id', `ip` varchar(45) DEFAULT NULL COMMENT '调用方IP', `processor_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '处理bean', `extensions` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT '扩展信息', PRIMARY KEY (`id`) ) COMMENT ='接口调用记录'; ``` ## 使用方式 ### `HttpCallRequest` 参数说明 | 参数名 | 类型 | 是否必须 | 描述 | | --- | --- | --- | --- | | `url` | `String` | 是 | 目标请求的URL。 | | `method` | `String` | 是 | HTTP请求方法,如 "GET", "POST"。 | | `headers` | `Map` | 否 | HTTP请求头。 | | `body` | `Object` | 否 | 请求体,可以是任何Java对象(如实体类、Map)或字符串。 | | `contentType` | `ContentType` | 否 | 请求的Content-Type,默认为 `ContentType.APPLICATION_JSON`。 | | `retryable` | `boolean` | 否 | 请求失败时是否允许重试,默认为 `false`。如果设置为 `true`,将使用默认的重试策略。 | | `retryStrategy` | `RetryStrategy` | 否 | 自定义重试策略。如果提供了此项,它将覆盖 `retryable` 的设置。 | | `systemName` | `String` | 否 | 业务系统名称,用于日志分类。支持通过HTTP Header动态识别。 | | `ip` | `String` | 否 | 调用方IP地址。 | | `functionName` | `String` | 否 | 调用的具体功能名称,用于日志分类。 | | `processorName` | `String` | 否 | 指定要使用的 `RequestProcessor` 的Spring Bean名称。 | | `extensions` | `Object` | 否 | 扩展信息,可以存储任何Java对象或字符串,如果不是字符串,会自动进行JSON序列化。 | ### 同步调用 ```java import com.ebo.httpcall4j.core.HttpCall; import com.ebo.httpcall4j.model.enums.HttpMethod; public void syncCall() { HttpCallRequest request = new HttpCallRequest(); request.setUrl("http://api.example.com/users"); request.setMethod(HttpMethod.POST); request.setBody("{"name": "test"}"); request.setCallingSystem("my-app"); request.setFunctionName("createUser"); try { HttpCallResponse response = HttpCall.execute(request,HttpCallProcessor.class); System.out.println("Log ID: " + response.getLogId()); System.out.println("Status Code: " + response.getStatusCode()); System.out.println("Response Body: " + response.getBody()); } catch (HttpCallException e) { System.err.println("Request failed. Log ID: " + e.getLogId()); e.printStackTrace(); } } ``` ### 异步调用 `httpcall4j` 通过 `callAsync` 方法提供异步HTTP请求的支持,该方法会立即返回一个 `CompletableFuture`。 ```java import com.ebo.httpcall4j.core.HttpCall; import com.ebo.httpcall4j.model.enums.HttpMethod; public void asyncCall() { HttpCallRequest request = new HttpCallRequest(); request.setUrl("https://api.example.com/data"); request.setMethod(HttpMethod.GET); CompletableFuture future = HttpCall.executeAsync(request,HttpCallProcessor.class); future.thenAccept(response -> { System.out.println("Log ID: " + response.getLogId()); System.out.println("Response: " + response.getBody()); }).exceptionally(ex -> { if (ex.getCause() instanceof HttpCallException) { HttpCallException httpEx = (HttpCallException) ex.getCause(); System.err.println("Request failed. Log ID: " + httpEx.getLogId()); } return null; }); } ``` ## 高级功能与扩展 ### 自动重试 `httpcall4j` 支持自动重试失败的请求,并支持指数退避策略。 **1. 使用默认重试策略** 默认情况下,所有请求不使用重试策略,可以设置 `retryable` 为 `true`,开启重试,默认重试`3`次,分别间隔`1000`毫秒。 ```java // 所有请求都会应用全局重试策略(最多3次,固定间隔1000ms)。 HttpCallRequest request = new HttpCallRequest(); request.setRetryable(true); HttpCallResponse response = HttpCall.execute(request,HttpCallProcessor.class); ``` **2. 使用自定义重试策略** 您可以为单个请求指定自定义的重试策略,这将覆盖全局默认策略。`RetryStrategy` 支持设置重试次数、初始间隔和用于指数退避的乘数。 ```java // 为单个请求自定义重试策略,这将覆盖全局默认策略。 RetryStrategy customStrategy = new RetryStrategy(5, 500, 2.0); // 最多重试5次,初始间隔500ms,每次间隔时间乘以2 request.setRetryable(true); request.setRetryStrategy(customStrategy); HttpCallResponse responseWithCustomRetry = HttpCall.execute(request,HttpCallProcessor.class); ``` ### 失败调用重试 对于任何记录在案的失败调用,您都可以通过其 `logId` 进行手动重试。 ```java @Autowired private HttpCallRerunService rerunService; public void retryFailedCall(Long failedLogId) { try { HttpCallResponse newResponse = rerunService.rerun(failedLogId); if (newResponse.isSuccess()) { System.out.println("Rerun successful! New Log ID: " + newResponse.getLogId()); } else { System.out.println("Rerun failed again. New log ID: " + newResponse.getLogId()); } } catch (Exception e) { System.err.println("Rerun failed: " + e.getMessage()); } } ``` 重试的调用会生成一条新的日志,并通过 `rerun_of_log_id` 字段关联到原始的失败日志。 ### 动态请求处理与Token刷新 `httpcall4j` 的核心亮点之一是其可扩展的请求处理机制。通过实现 `HttpCallProcessor` 接口,您可以将认证、签名、业务状态判断等通用逻辑从业务代码中解耦出来,实现更清晰、更可维护的设计。 #### 1. 核心接口:`HttpCallProcessor` `HttpCallProcessor` 接口包含两个核心方法,允许您在请求生命周期的关键节点注入自定义逻辑: - **`beforeRequest(HttpCallRequest request)`**: - **时机**: 在HTTP请求发送前调用。 - **作用**: 此方法用于在请求发送前动态修改 `HttpCallRequest` 对象。您可以利用它来执行各种预处理任务,例如: - **动态添加认证Token**: 从缓存或认证服务获取最新的Token,并将其添加到请求头中。 - **请求签名**: 根据请求内容(如URL、参数、时间戳)生成数字签名,并附加到请求头或参数中,以确保请求的完整性和安全性。 - **添加通用参数**: 为所有请求统一添加追踪ID、客户端版本等通用信息。 - **`isSuccess(HttpCallResponse response)`**: - **时机**: 在收到HTTP响应后调用。 - **作用**: 此方法用于判断一个HTTP响应是否代表业务层面的成功。默认实现仅检查HTTP状态码是否为 `2xx`,但在很多场景下,这并不足够。例如,一个返回 `200 OK` 的响应,其响应体中可能包含 `{"code": -1, "msg": "业务错误"}`。您可以重写此方法,根据响应体内容来判断业务是否真正成功,这对于自动重试机制尤为重要。 #### 2. 实现 `HttpCallProcessor` 创建一个 `HttpCallProcessor` 的实现类,并将其注册为Spring Bean。 **示例:实现动态Token和请求签名** ```java @Component // 将处理器注册为Spring Bean public class MyAuthProcessor implements HttpCallProcessor { private final AuthService authService; private final SignatureService signatureService; public MyAuthProcessor(AuthService authService, SignatureService signatureService) { this.authService = authService; this.signatureService = signatureService; } @Override public void beforeRequest(HttpCallRequest request) { // 1. 添加动态认证Token String token = authService.getFreshToken(); request.getHeaders().put("Authorization", "Bearer " + token); // 2. 添加数字签名 String signature = signatureService.sign(request); request.getHeaders().put("X-Signature", signature); } @Override public boolean isSuccess(HttpCallResponse response) { // 除了检查HTTP状态码,还检查业务返回码 if (response.getStatusCode() >= 200 && response.getStatusCode() < 300) { try { // 假设响应体是JSON字符串 JSONObject json = JSONObject.parseObject(response.getBody()); // 自定义业务成功判断逻辑,例如 code == 0 return json.getInteger("code") == 0; } catch (Exception e) { // JSON解析失败或字段不存在,视为业务失败 return false; } } return false; } } ``` #### 3. 在调用时使用处理器 在发起HTTP请求时,通过 `HttpCall` 的静态方法指定要使用的 `HttpCallProcessor` 的类。`httpcall4j` 会自动从Spring容器中获取对应的Bean实例并执行它。 ```java public void secureCall() { HttpCallRequest request = new HttpCallRequest(); request.setUrl("http://api.example.com/secure/data"); request.setMethod(HttpMethod.GET); try { // 关键一步:在执行时传入Processor的Class // httpcall4j会自动从Spring容器中查找MyAuthProcessor的实例 HttpCallResponse response = HttpCall.execute(request, MyAuthProcessor.class); if (response.isSuccess()) { System.out.println("业务处理成功!"); } else { System.out.println("业务处理失败。"); } } catch (HttpCallException e) { System.err.println("请求失败. Log ID: " + e.getLogId()); } } ``` 这种设计完美地解决了Token过期、需要统一签名等问题。即使是失败后通过 `HttpCallRerunService` 进行重试,`httpcall4j` 也会确保在重试前再次调用 `MyAuthProcessor` 的 `beforeRequest` 方法,从而获取一个全新的Token和签名,确保重试的有效性。 ## 记录传入的HTTP调用 在某些场景下,您可能希望记录由第三方系统调用您的API所产生的HTTP请求和响应。`httpcall4j` 提供了一种低侵入性的方式来实现这一目标,即通过AOP(面向切面编程)和一个自定义注解。 **注意**: 为了使上述代码正常工作,请确保您的项目中包含了 `spring-boot-starter-web` 和 `spring-boot-starter-aop` 依赖。 ### 在Controller中使用 最后,在您的Controller方法上添加 `@LogIncomingCall` 注解即可。该注解支持以下属性: - **`functionName`**: 为该调用指定一个功能名称,用于日志记录和分类。如果未提供,将默认使用被注解的方法名作为功能名称。 - **`systemName`**: **(必填)** 指定该接口所属的内部业务系统名称,例如:`CRM`, `ERP`, `CSM`。此名称用于在日志中标识和归类接口。 - **`systemNameHeader`**: **(可选)** 指定一个HTTP Header的名称,用于从请求中动态获取调用方系统的标识。如果指定了此Header名称,并且请求中包含了对应的Header,则其值将覆盖 `systemName` 的静态配置。这对于多租户或多系统集成的场景非常有用。 **示例:记录入站请求并指定系统名称** ```java @RestController @RequestMapping("/api/callbacks") public class CallbackController { @PostMapping("/notify") @LogIncomingCall(functionName = "processThirdPartyNotification", systemName = "ThirdPartyIntegration", systemNameHeader = "X-Caller-System") public ResponseEntity handleNotification(@RequestBody String payload) { // ... 您的业务逻辑 ... System.out.println("Received payload: " + payload); return ResponseEntity.ok("Notification processed"); } } ``` 通过这种方式,每当 `/api/callbacks/notify` 端点被调用时,`httpcall4j` 会通过AOP切面和Spring MVC拦截器协同工作,自动拦截该请求。它会捕获请求的详细信息(URL、方法、请求头、请求体、调用方IP等),并在请求处理完全结束后(**包括被全局`@ExceptionHandler`捕获的异常情况**),记录下最终的响应结果到 `http_call_log` 表中。这确保了即使在发生异常时,也能获得完整的调用日志,从而实现了对传入调用的无侵入式、高可靠性的日志记录。同时,您可以根据 `X-Caller-System` Header 动态识别调用方系统,或者使用 `ThirdPartyIntegration` 作为默认系统名称。