Skip to content

接口请求和响应数据记录 ​

本文档详细介绍了如何基于 Tio-Boot 框架实现接口请求和响应数据的记录功能,同时统计接口的耗时。我们将创建两个实现类:

  • MyHttpResponseStatisticsHandler 用于记录接口响应数据,并统计接口耗时。
  • DbRequestResponseStatisticsHandler 用于记录请求和响应数据,且仅保存 Content-Type 为 JSON 的响应数据。

下面将分别介绍各个步骤、数据库表设计、业务逻辑代码以及配置方法。


一、接口响应统计 —— ResponseStatisticsHandler ​

1. 需求描述 ​

在使用 Tio-Boot 框架时,我们需要实现接口访问统计的功能,统计接口的耗时数据,并将这些数据保存到数据库中。为此,我们将创建 ResponseStatisticsHandler 的实现类 MyHttpResponseStatisticsHandler,用于记录并保存每个 HTTP 请求的访问时间数据。保存数据的逻辑由我们自行实现。

2. 数据库表设计 ​

创建表用于存储接口响应统计数据:

sql
CREATE TABLE sys_http_response_statistics (
  id BIGINT NOT NULL,
  ip VARCHAR,
  user_id VARCHAR,
  method VARCHAR(10),
  uri VARCHAR(256),
  elapsed BIGINT,
  remark VARCHAR (256),
  creator VARCHAR (64) DEFAULT '',
  create_time TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updater VARCHAR (64) DEFAULT '',
  update_time TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
  deleted SMALLINT NOT NULL DEFAULT 0,
  tenant_id BIGINT NOT NULL DEFAULT 0
);

3. 保存接口数据到数据库 —— MyHttpResponseStatisticsService ​

业务逻辑类用于将接口响应统计数据保存到数据库中,代码如下:

java
import nexus.io.db.activerecord.Db;
import nexus.io.db.activerecord.Row;
import nexus.io.tio.http.common.HttpMethod;
import nexus.io.tio.utils.snowflake.SnowflakeIdUtils;

public class MyHttpResponseStatisticsService {

  public void updateDb(HttpMethod method, String uri, Object userId, String clientIp, long elapsed) {
    Row row = new Row();
    row.set("id", SnowflakeIdUtils.id());
    row.set("ip", clientIp);
    row.set("user_id", userId);
    row.set("method", method.toString());
    row.set("uri", uri);
    row.set("elapsed", elapsed);

    try {
      Db.save("sys_http_response_statistics", row);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

4. 收集访问数据 —— MyHttpResponseStatisticsHandler ​

实现 ResponseStatisticsHandler 接口,在 count 方法中提取请求信息和响应数据,并调用业务逻辑保存统计信息。代码如下:

java
import nexus.io.jfinal.aop.Aop;
import nexus.io.tio.boot.http.handler.ResponseStatisticsHandler;
import nexus.io.tio.http.common.HttpMethod;
import nexus.io.tio.http.common.HttpRequest;
import nexus.io.tio.http.common.HttpResponse;
import nexus.io.tio.http.common.RequestLine;
import nexus.io.tio.http.common.utils.HttpIpUtils;
import nexus.io.tio.utils.thread.TioThreadUtils;

public class MyHttpResponseStatisticsHandler implements ResponseStatisticsHandler {

  @Override
  public void count(HttpRequest request, RequestLine requestLine, HttpResponse httpResponse, Object userId,
      long elapsed) {
    HttpMethod method = requestLine.getMethod();

    String requestURI = requestLine.getPath();
    String clientIp = HttpIpUtils.getRealIp(request);

    // 异步保存
    TioThreadUtils.getFixedThreadPool().submit(() -> {
      Aop.get(MyHttpResponseStatisticsService.class).updateDb(method, requestURI, userId, clientIp, elapsed);
    });
  }
}

5. 配置 MyHttpResponseStatisticsHandler ​

在 TioBootServer 配置中,将 MyHttpResponseStatisticsHandler 设置为响应统计处理器,代码如下:

java
import nexus.io.annotation.AConfiguration;
import nexus.io.annotation.Initialization;
import nexus.io.tio.boot.server.TioBootServer;

@AConfiguration
public class TioBootServerConfig {

  @Initialization
  public void config() {
    TioBootServer server = TioBootServer.me();
    MyHttpResponseStatisticsHandler dbResponseStatisticsHandler = new MyHttpResponseStatisticsHandler();
    server.setResponseStatisticsHandler(dbResponseStatisticsHandler);
  }
}

6. 数据效果示例 ​

sys_http_response_statistics 表中记录示例如下:

idipuser_idmethodurielapsedremarkcreatorcreate_timeupdaterupdate_timedeletedtenant_id
3928907705140428800:0:0:0:0:0:0:1nilGET/1618/6/2024 12:08:37.80792918/6/2024 12:08:37.80792900
3928908612435312640:0:0:0:0:0:0:1nilGET/018/6/2024 12:08:59.43974418/6/2024 12:08:59.43974400

二、接口请求和响应数据记录 —— RequestResponseStatistics ​

1. 记录需求描述 ​

除了统计响应时间外,我们还希望同时记录请求和响应数据。为此,我们创建新的数据表来存储请求和响应数据,同时在处理响应时筛选出 Content-Type 为 JSON 的响应数据,确保只保存 JSON 格式的数据。

2. 创建数据表 ​

创建新的数据表 sys_http_request_response_statistics,用于同时记录请求和响应数据:

sql
DROP TABLE IF EXISTS sys_http_request_response_statistics;
CREATE TABLE sys_http_request_response_statistics (
  id BIGINT NOT NULL PRIMARY KEY,
  channel_id BIGINT NOT NULL,
  request_id BIGINT NOT NULL,
  request_ip VARCHAR,
  request_uri VARCHAR(256),
  request_header TEXT,
  request_content_type VARCHAR(128),
  request_body TEXT,
  response_status_code INT,
  response_body TEXT,
  user_id VARCHAR,
  elapsed BIGINT,
  update_time TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
  deleted SMALLINT NOT NULL DEFAULT 0,
  tenant_id BIGINT NOT NULL DEFAULT 0
);

3. 创建业务逻辑类 —— DbRequestResponseStatisticsService ​

扩展现有业务逻辑,合并请求和响应数据的保存。代码如下:

java
package nexus.io.tio.boot.admin.services;

import nexus.io.db.activerecord.Db;
import nexus.io.db.activerecord.Row;
import nexus.io.tio.utils.mcid.McIdUtils;

public class DbRequestResponseStatisticsService {
  public void save(long channelId, long requestId, String requestIp, String requestUri, String requestHeader, String contentType,
      //
      String requestBody, int responseStatusCode, String responseBody, Object userId, long elapsed) {
    Row row = new Row();
    if (userId != null) {
      if (userId instanceof String) {
        row.set("user_id", (String) userId);
      } else if (userId instanceof Long) {
        row.set("user_id", userId.toString());
      }
    }
    row.set("id", McIdUtils.id());
    row.set("channel_id", channelId);
    row.set("request_id", requestId);
    row.set("request_ip", requestIp);
    row.set("request_uri", requestUri);
    row.set("request_header", requestHeader);
    row.set("request_content_type", contentType);
    row.set("request_body", requestBody);
    row.set("response_status_code", responseStatusCode);
    row.set("response_body", responseBody);
    row.set("elapsed", elapsed);

    try {
      Db.save("sys_http_request_response_statistics", row);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

4. 扩展 ResponseStatisticsHandler —— DbRequestResponseStatisticsHandler ​

实现 ResponseStatisticsHandler 接口,在 count 方法中记录请求和响应数据,并筛选出 Content-Type 为 JSON 的响应。代码如下:

java
package nexus.io.tio.boot.admin.handler;

import nexus.io.jfinal.aop.Aop;
import nexus.io.tio.boot.admin.services.DbRequestResponseStatisticsService;
import nexus.io.tio.boot.http.handler.internal.ResponseStatisticsHandler;
import nexus.io.tio.http.common.HeaderValue;
import nexus.io.tio.http.common.HttpRequest;
import nexus.io.tio.http.common.HttpResponse;
import nexus.io.tio.http.common.RequestLine;
import nexus.io.tio.http.common.utils.HttpIpUtils;
import nexus.io.tio.utils.thread.TioThreadUtils;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class DbRequestResponseStatisticsHandler implements ResponseStatisticsHandler {

  DbRequestResponseStatisticsService service = Aop.get(DbRequestResponseStatisticsService.class);

  @Override
  public void count(HttpRequest request, RequestLine requestLine, HttpResponse httpResponse, Object userId, long elapsed) {

    Long requestId = request.getId();
    String clientIp = HttpIpUtils.getRealIp(request);
    String requestUri = requestLine.toString();
    long id = Long.parseLong(request.getChannelContext().getId());
    String contentType = request.getContentType();

    String authorization = request.getHeader("authorization");
    String token = request.getHeader("token");
    StringBuffer header = new StringBuffer();
    if (authorization != null) {
      header.append("authorization:").append(authorization).append("\n");
    }

    if (token != null) {
      header.append("token:").append(token).append("\n");
    }

    String requestBody = request.getBodyString();

    int responseStatusCode = httpResponse.getStatus().getStatus();
    HeaderValue contentTypeHeader = httpResponse.getContentType();

    //避免处理器对数据进行gzip编码,获取到编码后的数据
    byte[] body = httpResponse.getBody();
    // 异步保存
    TioThreadUtils.execute(() -> {
      String responseContentType = null;
      String responseBody = null;
      if (contentTypeHeader != null) {
        responseContentType = contentTypeHeader.toString();
        if (responseContentType.contains("application/json")) {
          if (body != null && body.length > 0 && body.length < 1024) {
            responseBody = new String(body);
            if (responseBody != null && responseBody.indexOf('\0') != -1) {
              responseBody = null;
              log.error("The response_body contains illegal characters and will not save the data.:{}", requestUri);
            }
          }
          service.save(id, requestId, clientIp, requestUri, header.toString(), contentType, requestBody, responseStatusCode, responseBody, userId, elapsed);
        }
      }
    });
    ;
  }
}

5. 配置 TioBootServer —— 使用 DbRequestResponseStatisticsHandler ​

在 TioBootServer 配置中,将新的 DbRequestResponseStatisticsHandler 设置为响应统计处理器,代码如下:

java
import nexus.io.tio.boot.admin.handler.DbRequestResponseStatisticsHandler;
import nexus.io.annotation.AConfiguration;
import nexus.io.annotation.Initialization;
import nexus.io.tio.boot.server.TioBootServer;

@AConfiguration
public class TioBootServerConfig {

  @Initialization
  public void config() {
    TioBootServer server = TioBootServer.me();
    server.setResponseStatisticsHandler(new DbRequestResponseStatisticsHandler());
  }
}

6. 数据效果示例 ​

sys_http_request_response_statistics 表中的一条记录示例如下:

json
{
  "id": 7070054257091008,
  "channel_id": 1833969868340445184,
  "request_id": 4,
  "request_ip": "0:0:0:0:0:0:0:1",
  "request_uri": "GET /api/v1/chat/create?name=hi&school_id=2 HTTP/1.1",
  "request_header": "authorization:Bearer PAfvyWa268y0on8MbxHhfYY21rvi9Sx8\n",
  "request_content_type": null,
  "request_body": null,
  "response_status_code": 200,
  "response_body": "{\"code\":1,\"data\":{\"name\":\"hi\",\"id\":\"f96bb45f-8b0d-46fe-9918-279f12f4c60e\"},\"ok\":true}",
  "user_id": "1",
  "elapsed": 218,
  "update_time": "2024-09-04T04:00:53.25183",
  "deleted": 0,
  "tenant_id": 0
}

四、 常用 SQL 查询 ​

为了对系统的接口访问数据进行进一步分析,可以使用如下 SQL 语句按秒统计并发请求数:

sql
SELECT
  date_trunc('second', update_time) AS SECOND,
  COUNT(*) AS concurrent_count
FROM
  sys_http_request_response_statistics
GROUP BY
  date_trunc('second', update_time)
ORDER BY
  SECOND DESC;

三、总结 ​

本文档详细描述了如何在 Tio-Boot 框架下实现接口请求和响应数据记录功能,包括以下部分:

  1. 接口响应统计:

    • 创建数据库表 sys_http_response_statistics。
    • 实现业务类 MyHttpResponseStatisticsService 保存数据。
    • 编写 MyHttpResponseStatisticsHandler 实现 ResponseStatisticsHandler 接口,记录接口耗时数据。
    • 配置 TioBootServer,将统计处理器注册到服务器中。
  2. 请求和响应数据记录:

    • 创建数据库表 sys_http_request_response_statistics 用于存储请求和响应数据。
    • 编写业务逻辑类 DbRequestResponseStatisticsService 实现数据保存。
    • 编写 DbRequestResponseStatisticsHandler,在响应时收集请求和响应数据,仅保存 Content-Type 为 JSON 的响应数据。
    • 配置 TioBootServer,将新的统计处理器注册到服务器中。

通过上述步骤和代码示例,我们实现了对接口请求和响应数据的全面统计,不仅可以统计接口耗时,还能保存详细的请求与响应信息,为后续调优、问题排查和数据分析提供坚实的数据支持。

MIT Licensed