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

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

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.添加依赖:

cn.dgg.bigdata apistat 1.0-SNAPSHOT 2.配置kafka地址

apiStat:
kafka:
bootstrap-servers: localhost:9200
3.加入注解:@EnableApiStat

7.结束
源码地址: https://github.com/ostarsier/apistatApi接口统计通用组件(附源码)

xianbin.yang 2020-11-19 21:03:49 24 收藏
分类专栏: java 文章标签: java spring boot
版权
目录

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.添加依赖:

cn.dgg.bigdata apistat 1.0-SNAPSHOT 2.配置kafka地址

apiStat:
kafka:
bootstrap-servers: localhost:9200
3.加入注解:@EnableApiStat

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

转载于: https://blog.csdn.net/appearbeauty/article/details/109824741

通用easyui开发框架是一个基于easyui前端框架的开发框架,提供了一套高效、易用的工具和组件,用于快速构建Web应用程序。该框架的源码包含了各种功能模块和插件,可以根据需求进行灵活的配置和扩展。 通用easyui开发框架的源码主要包括以下几个方面的内容: 1. 核心代码:该部分包含了框架的基础代码,包括初始化配置、路由管理、组件加载等功能。这些代码负责框架的整体运行和组件的加载与渲染。 2. 组件库:该部分包含了各种易用的组件,如表单组件、表格组件、树组件等。这些组件基于easyui进行了封装和扩展,提供了丰富的功能和样式。用户可以直接使用这些组件进行页面的构建。 3. 工具库:该部分包含了一些常用的工具类和方法,如日期处理、字符串处理、数组处理等。这些工具类和方法可以简化开发过程中的一些常见操作,提高开发效率。 4. 插件:该部分包含了一些可选的插件,如图表插件、地图插件、富文本编辑器等。这些插件可以根据项目需求进行选择和集成,扩展框架的功能。 通用easyui开发框架源码的设计思路主要是基于易用性和扩展性。通过提供简洁易上手的API和丰富多样的组件,使开发人员能够快速构建出美观且功能强大的Web应用程序。同时,框架的源码也提供了一系列的扩展点和接口,方便开发人员根据业务需求进行自定义开发。 总的来说,通用easyui开发框架源码是一个用于构建Web应用程序的开源框架,提供了丰富的组件和工具,能够大幅提高开发效率和代码质量。通过灵活配置和扩展,开发人员可以根据具体需求定制出符合自己项目业务的框架。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值