# 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` 作为默认系统名称。