Api接口统计通用组件(附源码)

该博客介绍了使用Java和Spring Boot实现服务接口调用记录通用组件的方案。需求是记录接口调用情况并做成通用组件。设计思路是用自定义拦截器获取信息,异步发往Kafka再消费到ClickHouse。具体实现包括定义拦截器、获取接口参数等,还说明了同步到ClickHouse及外部系统使用方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

 

1.需求

2.设计思路

3.架构图

4.具体实现

1.定义拦截器

2.获取接口参数服务

3.对外需要导入的注解

5.同步到ClickHouse

6.外部系统使用

7.结束


1.需求

记录服务接口调用情况,包括通用字段:域名、IP、接口路径、查询参数、是否成功、请求时间、耗时、错误信息、接口名;以及业务自定义字段

做成通用组件的形式,最小化的侵入业务系统

 

2.设计思路

自定义Spring拦截器,获取通用字段,以及业务自定义字段放到ThreadLocal,接口调用完成后异步发送到Kafka,然后消费到ClickHouse进行统计

需要统计的服务只需要依赖本组件,加一个注解,并配置指定的kafka地址即可

3.架构图

 

4.具体实现

1.定义拦截器

在接口调用前初始化并从request里获取ip、域名、请求时间、请求参数等

接口调用后设置接口耗时、错误信息等;需要删除该线程的ThreadLocal对象,因为spring的请求线程会在线程池里复用,如果不删掉,后续请求会用到该线程的ThreadLocal数据

package cn.xianbin.apistat.interceptor;

import cn.xianbin.apistat.service.ApiStatService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
@Slf4j
public class ApiStatInterceptor implements HandlerInterceptor {

    @Autowired
    private ApiStatService apiStatService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        apiStatService.before(request);
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        apiStatService.after(ex);
        ApiStatService.threadLocal.remove();
    }

}

2.获取接口参数服务

package cn.xianbin.apistat.service;


import cn.xianbin.apistat.bean.ApiStatBean;
import cn.xianbin.apistat.utils.IpUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

@Slf4j
@Service
public class ApiStatService {

    private static DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");

    public static ThreadLocal<ApiStatBean> threadLocal = new ThreadLocal<>();

    @Resource(name = "apiStatKafkaTemplate")
    private KafkaTemplate<String, String> kafkaTemplate;

    /**
     * 业务代码调用
     */
    public static void apiName(String apiName) {
        ApiStatBean apiStatBean = threadLocal.get();
        if(apiStatBean == null){
            log.warn("api stat not init");
            return;
        }
        apiStatBean.setApi_name(apiName);
    }

    public void before(HttpServletRequest request) {
        LocalDateTime now = LocalDateTime.now();
        ApiStatBean apiStatBean = ApiStatBean.builder()
                .ip(IpUtil.getIP(request))
                .domain(domain(request))
                .path(request.getRequestURI())
                .query_param(request.getQueryString())
                .startTime(System.currentTimeMillis())
                .start_time(now.format(dateTimeFormatter))
                .build();
        threadLocal.set(apiStatBean);
    }

    public void after(Exception ex) {
        ApiStatBean apiStatBean = threadLocal.get();
        apiStatBean.setCost_time(System.currentTimeMillis() - apiStatBean.getStartTime());
        if (ex == null) {
            apiStatBean.setIs_success(1);
        } else {
            apiStatBean.setError(ex.getMessage());
            apiStatBean.setIs_success(0);
        }
        log();
    }

    public void log() {
        String invokeLog = JSONObject.toJSONString(threadLocal.get());
        log.debug("asyncSend={}", invokeLog);
        kafkaTemplate.send("api_stat_test", invokeLog);
    }

    private String domain(HttpServletRequest request) {
        return String.format("%s://%s:%s", request.getScheme(), request.getServerName(), request.getServerPort());
    }

}

 

3.对外需要导入的注解

用spring的@import注解导入我们的配置类,用@ComponentScan扫描我们的类

package cn.xianbin.apistat;

import cn.xianbin.apistat.config.ApiStatConfig;
import org.springframework.context.annotation.Import;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ApiStatConfig.class)
public @interface EnableApiStat {

}
package cn.xianbin.apistat.config;

import cn.xianbin.apistat.interceptor.ApiStatInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@ComponentScan("cn.dgg.bigdata.apistat")
@Configuration
public class ApiStatConfig implements WebMvcConfigurer {

    @Autowired
    private ApiStatInterceptor apiStatHandlerInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(apiStatHandlerInterceptor).addPathPatterns("/**");
    }
}

5.同步到ClickHouse

1.创建kafka引擎表

CREATE TABLE api_stat.kafka_api_stat (
    `ip` String,
    `api_name` String,
	`domain` String,
    `path` String,
    `query_param` String,
    `start_time` DateTime64 ( 3, 'Asia/Shanghai' ),
    `is_success` UInt8,
    `cost_time` Int32,
    `error` String,
    `api_name` String
  ) ENGINE = Kafka('localhost:9092', 'topic', 'group1', 'JSONEachRow');

2.创建ClickHouse物理表

CREATE TABLE api_stat.api_stat (
    `ip` String,
    `api_name` String,
    `domain` String,
    `path` String,
    `query_param` String,
    `start_time` DateTime64 ( 3, 'Asia/Shanghai' ),
    `is_success` UInt8,
    `cost_time` Int32,
    `error` String,
    `api_name` String
  ) ENGINE = MergeTree()
  PARTITION BY toYYYYMM(start_time) 
  ORDER BY (domain, path, start_time);

3.创建kafka到物理表的视图

CREATE MATERIALIZED VIEW api_stat.consumer_api_stat TO api_stat.api_stat
    AS SELECT  ip,api_name,domain,path,query_param,start_time,is_success,cost_time,error,api_name FROM api_stat.kafka_api_stat;

6.外部系统使用

1.添加依赖:

<dependency>
  <groupId>cn.dgg.bigdata</groupId>
  <artifactId>apistat</artifactId>
  <version>1.0-SNAPSHOT</version>
</dependency>

2.配置kafka地址

apiStat:
  kafka:
    bootstrap-servers: localhost:9200

3.加入注解:@EnableApiStat

 

7.结束

源码地址: https://github.com/ostarsier/apistat

 

 

 

 

 

 

 

 

基于Node.js,提供网易云所有API接口数据,包括:登录,获取用户信息 , 歌单,收藏,mv, dj 数量 获取用户歌单 获取用户电台 获取用户关注列表 获取用户粉丝列表 获取用户动态 获取用户播放记录 获取精品歌单 获取歌单详情 搜索 搜索建议 获取歌词 歌曲评论 收藏单曲到歌单 专辑评论 歌单评论 mv 评论 电台节目评论 banner 获取歌曲详情 获取专辑内容 获取歌手单曲 获取歌手 mv 获取歌手专辑 获取歌手描述 获取相似歌手 获取相似歌单 相似 mv 获取相似音乐 获取最近 5 个听了这首歌的用户 获取每日推荐歌单 获取每日推荐歌曲 私人 FM 签到 喜欢音乐 垃圾桶 歌单 ( 网友精选碟 ) 新碟上架 热门歌手 最新 mv 推荐 mv 推荐歌单 推荐新音乐 推荐电台 推荐节目 独家放送 mv 排行 获取 mv 数据 播放 mv/视频 排行榜 歌手榜 云盘 电台 - 推荐 电台 - 分类 电台 - 分类推荐 电台 - 订阅 电台 - 详情 电台 - 节目 给评论点赞 获取动态 热搜列表(简略) 发送私信 发送私信歌单 新建歌单 收藏/取消收藏歌单 歌单分类 收藏的歌手列表 订阅的电台列表 相关歌单推荐 付费精选接口 音乐是否可用检查接口 登录状态 获取视频播放地址 发送/删除评论 热门评论 视频评论 退出登录 所有榜单 所有榜单内容摘要 收藏视频 收藏 MV 视频详情 相关视频 关注用户 新歌速递 喜欢音乐列表(无序) 收藏的 MV 列表 获取最新专辑 听歌打卡 获取视频标签下的视频 已收藏专辑列表 获取动态评论 歌单收藏者列表 云盘歌曲删除 热门话题 电台 - 推荐类型 电台 - 非热门类型 电台 - 今日优选 心动模式/智能播放 转发动态 删除动态 分享歌曲、歌单、mv、电台、电台节目到动态 通知-私信 通知-评论 通知-@我 通知-通知 设置 云盘数据详情 私信内容 我的数字专辑 batch批量请求接口 获取视频标签列表 全部mv 网易出品mv 收藏/取消收藏专辑 专辑动态信息 热搜列表(详细) 更换绑定手机 检测手机号码是否已注册 初始化昵称 更新歌单描述 更新歌单名 更新歌单标签 默认搜索关键词 删除歌单 电台banner 用户电台 热门电台 电台 - 节目详情 电台 - 节目榜 电台 - 新晋电台榜/热门电台榜 类别热门电台 云村热评 电台24小时节目榜 电台24小时主播榜 电台最热主播榜 电台主播新人榜 电台付费精品榜 歌手热门50首歌曲
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值