Skip to content

接口访问统计 ​

本文档详细介绍如何在 Tio-Boot 框架中实现接口访问统计功能。通过实现 RequestStatisticsHandler 接口,我们创建了 DbRequestStatisticsHandler 类,用于记录每个 HTTP 请求的关键信息,并通过异步任务将数据保存到数据库中,为后续问题排查和系统性能分析提供数据依据。


1. 需求概述 ​

在使用 Tio-Boot 框架时,我们需要对 HTTP 请求进行统计,记录每个请求的基本信息(如请求 ID、channel ID、IP 地址、请求方法、URI、User-Agent、header、body 等)。通过对这些数据进行分析,可以有效帮助我们进行问题排查以及系统性能监控。


2. 实现步骤 ​

2.1 请求 ID 获取 ​

Tio-Boot 为每个请求分配了两个标识:request id 和 channel id。获取方式及输出如下:

java
log.info("request id:{}", request.getId());
log.info("channel id:{}", Long.parseLong(request.getChannelContext().getId()));

输出示例:

request id:1
channel id:1831315515085565952

在业务层将这两个标识传递下去,便于在日志记录中追踪和定位问题。


2.2 创建 DbRequestStatisticsHandler 类 ​

2.2.1 数据库表设计 ​

首先,在数据库中创建用于保存 HTTP 请求统计数据的表。以下是 PostgreSQL 数据库下的建表 SQL 语句:

sql
DROP TABLE IF EXISTS sys_http_request_statistics;
CREATE TABLE sys_http_request_statistics (
  id BIGINT NOT NULL PRIMARY KEY,
  channel_id BIGINT,
  ip VARCHAR,
  user_id VARCHAR,
  method VARCHAR,
  uri VARCHAR,
  user_agent VARCHAR,
  header TEXT,
  body TEXT,
  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
);

2.2.2 业务类实现:插入数据库 ​

接下来编写业务类 DbRequestStatisticsService,用于将 HTTP 请求统计数据插入到数据库中。代码如下:

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

import nexus.io.db.activerecord.Db;

public class DbRequestStatisticsService {
  public static final String sql = "INSERT INTO sys_http_request_statistics (id,channel_id,ip,user_id,method,uri,user_agent,header,body) values(?,?,?,?,?,?,?,?,?)";

  public void saveDb(long id, long channel_id, String clientIp, Object userId, String method, String uri, String user_agent, String header, String body) {
    Db.updateBySql(sql, id, channel_id, clientIp, userId, method, uri, user_agent, header, body);
  }
}

2.2.3 实现 RequestStatisticsHandler 的 count 方法 ​

通过继承 RequestStatisticsHandler 接口,实现 count 方法,在方法中提取请求中的关键信息,并使用异步任务调用业务层方法保存数据。代码如下:

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

import nexus.io.jfinal.aop.Aop;
import nexus.io.tio.boot.admin.services.DbRequestStatisticsService;
import nexus.io.tio.boot.http.handler.internal.RequestStatisticsHandler;
import nexus.io.tio.http.common.HttpMethod;
import nexus.io.tio.http.common.HttpRequest;
import nexus.io.tio.http.common.RequestLine;
import nexus.io.tio.http.common.utils.HttpIpUtils;
import nexus.io.tio.utils.snowflake.SnowflakeIdUtils;
import nexus.io.tio.utils.thread.TioThreadUtils;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class DbRequestStatisticsHandler implements RequestStatisticsHandler {

  DbRequestStatisticsService dbRequestStatisticsService = Aop.get(DbRequestStatisticsService.class);

  @Override
  public void count(HttpRequest request) {
    RequestLine requestLine = request.getRequestLine();
    HttpMethod method = requestLine.getMethod();
    String uri = requestLine.getPathAndQuery();
    long channel_id = Long.parseLong(request.getChannelContext().getId());
    String userAgent = request.getUserAgent();
    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 bodyString = request.getBodyString();

    // 接口访问统计在拦截器之前运行,此时 还有解出id
    Object userId = request.getAttribute("userId");
    String clientIp = HttpIpUtils.getRealIp(request);

    // 使用ExecutorService异步执行任务
    TioThreadUtils.submit(() -> {
      try {
        long id = SnowflakeIdUtils.id();
        dbRequestStatisticsService.saveDb(id, channel_id, clientIp, userId, method.toString(), uri, userAgent, header.toString(), bodyString);
      } catch (Exception e) {
        log.error(e.getMessage(), e);
      }
    });
  }
}

上述代码中:

  • 从 HttpRequest 对象中提取了请求方法、URI、channel id、User-Agent、header 信息以及请求体数据。
  • 使用 HttpIpUtils.getRealIp 获取客户端 IP。
  • 利用 SnowflakeIdUtils 生成唯一 ID。
  • 通过 TioThreadUtils.submit 异步提交任务,调用 DbRequestStatisticsService.saveDb 方法保存数据,确保主线程不被阻塞。

2.3 配置 TioBootServer ​

最后,需要在 Tio-Boot Server 中配置 DbRequestStatisticsHandler,使其在每次 HTTP 请求到来时自动调用 count 方法记录请求数据。配置代码如下:

java
package nexus.io.admin.blog.config;

import nexus.io.tio.boot.admin.handler.DbRequestStatisticsHandler;
import nexus.io.tio.boot.server.TioBootServer;

public class TioBootServerConfig {

  public void config() {
    // 获取 TioBootServer 实例
    TioBootServer server = TioBootServer.me();

    // 将 DbRequestStatisticsHandler 设置到 TioBootServer
    server.setRequestStatisticsHandler(new DbRequestStatisticsHandler());
  }
}

通过以上配置,当 Tio-Boot Server 接收到 HTTP 请求时,系统会自动调用 DbRequestStatisticsHandler 的 count 方法,将请求相关数据异步保存到数据库中。


3. 数据统计效果 ​

当系统接收到 HTTP 请求后,DbRequestStatisticsHandler 会自动记录请求信息并保存到数据库中。下面是一个数据记录的示例:

json
{
  "id": 469717282465456128,
  "channel_id": 1880014355744837633,
  "ip": "0:0:0:0:0:0:0:1",
  "user_id": null,
  "method": "GET",
  "uri": "/_next/static/chunks/webpack-dee5989a200fd06b.js",
  "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
  "header": "",
  "body": null,
  "remark": null,
  "creator": "",
  "create_time": "2025-01-16T12:09:28.983977",
  "updater": "",
  "update_time": "2025-01-16T12:09:28.983977",
  "deleted": 0,
  "tenant_id": 0
}

该记录包含了请求的关键信息,可用于后续对接口访问情况的监控、分析和问题排查。


该语句利用 date_trunc 函数将 update_time 截断到秒,并统计每秒内的请求数量,然后按时间降序排序,方便我们观察系统在各个时间点的访问峰值。


5. 附录 ​

  • Tio-Boot Admin 内置支持
    Tio-Boot Admin 已内置了 DbRequestStatisticsHandler 的实现,用户可以直接使用或根据具体需求进行二次开发和扩展。

MIT Licensed