SpringBoot项目集成es,写入接口请求相关信息

需求说明:近期,公司大佬提出将用户操作系统的信息记录写入es,方便后续追踪操作,排查问题。接受任务,开始开发。

首先的思路,肯定是使用拦截器拦截请求,获取请求参数,返回结果等常规信息。

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

    // 仅拦截对方法的访问,对于其他静态资源不进行拦截
    if (handler instanceof HandlerMethod) {
        HandlerMethod method = (HandlerMethod) handler;
        System.out.println("进入参数注解拦截");
        Map<String, String[]> parameterMap = request.getParameterMap();
        System.out.println(parameterMap);
    }


    return super.preHandle(request, response, handler);
}

查阅资料发现,拦截器不能拿到请求的返回值。

之后改变思路,尝试使用过滤器,看看能不能拿到返回结果。

此时,心里又有了疑惑,拦截器和过滤器,二者的功能似乎差别不大,区别是什么?

网上搜了下资料,这张图片说的比较清晰。

java请求过程:https://blog.csdn.net/Cxf007200/article/details/120933034

过滤器(Filter)
过滤器,是在java web中将你传入的request、response提前过滤掉一些信息,或者提前设置一些参数。然后再传入Servlet或Struts2的 action进行业务逻辑处理。比如过滤掉非法url(不是login.do的地址请求,如果用户没有登陆都过滤掉),或者在传入Servlet或Struts2的action前统一设置字符集,或者去除掉一些非法字符。

拦截器(Interceptor)
拦截器,是面向切面编程(AOP,Aspect Oriented Program)的。就是在你的Service或者一个方法前调用一个方法,或者在方法后调用一个方法。比如动态代理就是拦截器的简单实现,在你调用方法前打印出字符串(或者做其它业务逻辑的操作),也可以在你调用方法后打印出字符串,甚至在你抛出异常的时候做业务逻辑的操作。

出自:拦截器与过滤器的区别_Tang-CSDN博客_拦截器与过滤器的区别

过滤器中,需要对response使用代理类,才能拿到响应的返回值。


/**
 * @desc    返回值输出代理类,只能配合过滤器使用 parameterFilter
 * @param
 * @author  xfchen12
 * @date    2021/6/21 14:59
 */
@Component
public class ResponseWrapper extends HttpServletResponseWrapper {

    private ByteArrayOutputStream bytes = new ByteArrayOutputStream();
    private HttpServletResponse response;
    private PrintWriter pwrite;

    public ResponseWrapper(HttpServletResponse response) {
        super(response);
        this.response = response;
    }

    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        return new MyServletOutputStream(bytes); // 将数据写到 byte 中
    }

    /**
     * 重写父类的 getWriter() 方法,将响应数据缓存在 PrintWriter 中
     */
    @Override
    public PrintWriter getWriter() throws IOException {
        try{
            pwrite = new PrintWriter(new OutputStreamWriter(bytes, "utf-8"));
        } catch(UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return pwrite;
    }

    /**
     * 获取缓存在 PrintWriter 中的响应数据
     * @return
     */
    public byte[] getBytes() {
        if(null != pwrite) {
            pwrite.close();
            return bytes.toByteArray();
        }

        if(null != bytes) {
            try {
                bytes.flush();
            } catch(IOException e) {
                e.printStackTrace();
            }
        }
        return bytes.toByteArray();
    }

    class MyServletOutputStream extends ServletOutputStream {
        private ByteArrayOutputStream ostream ;

        public MyServletOutputStream(ByteArrayOutputStream ostream) {
            this.ostream = ostream;
        }

        @Override
        public void write(int b) throws IOException {
            ostream.write(b); // 将数据写到 stream 中
        }

        @Override
        public boolean isReady() {
            return false;
        }

        @Override
        public void setWriteListener(WriteListener writeListener) {

        }
    }



}


    @SneakyThrows
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest r = (HttpServletRequest) servletRequest;
        System.out.println(r.getMethod());
        Map<String, String[]> parameterMap = servletRequest.getParameterMap();
        System.out.println(parameterMap);
        Iterator<String> iter = parameterMap.keySet().iterator();
        StringBuffer stringBuffer = new StringBuffer();
        while (iter.hasNext()) {
            String key = iter.next();
            String[] strings = parameterMap.get(key);
            String string = strings[0];
            stringBuffer.append("," + key + ":" + string);
        }
        HttpServletRequest req = (HttpServletRequest) servletRequest;
        HttpServletRequest req2 = (HttpServletRequest) servletRequest;
        HttpServletResponse resp = (HttpServletResponse) servletResponse;
        ResponseWrapper mResp = new ResponseWrapper(resp); // 包装响应对象 resp 并缓存响应数据
        filterChain.doFilter(req, mResp);
        byte[] bytes = mResp.getBytes(); // 获取响应数据
        String s = String.valueOf(stringBuffer);
        if (s.startsWith(",")) {
            s = s.substring(1);
        }
        Date date = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String d = sdf.format(date);
        date = sdf.parse(d);
        StringBuffer requestURL = req.getRequestURL();
        String paramter = "date:" + date + "; " +
                "request:" + requestURL + "; " +
                "paramters:" + s + "; " +
                "response:" + new String(bytes, "utf-8");
        System.out.println(paramter);

        EsParam esParam = new EsParam("","",String.valueOf(new Date()), "111",String.valueOf(requestURL),"", s, new String(bytes, "utf-8"),"");
        EsClientUtil.createDoc(esParam);

        JSONObject json = GetRequestJsonUtils.getRequestJsonObject(req2);
        System.out.println(json);

    }

拿到入参出参后,我们在测试环境下载es、kibana 包,并解压。

ES: https://www.elastic.co/cn/downloads/elasticsearch

Kibana: https://www.elastic.co/cn/downloads/kibana

Logstash: https://www.elastic.co/cn/downloads/logstash

Filebeat: https://www.elastic.co/cn/downloads/beats/filebeat

进入es解压后目录。

修改config下yml配置文件

cluster.name: MyES  #集群名称
node.name: node01    #本节点名称
network.host: 0.0.0.0     #所有机器都可监听
http.port: 9200        #默认端口
cluster.initial_master_nodes: ["node01"]  #主节点名称,与上面配置的保持一致

启动es目录下 bin/es ,如果启动报错 jvm报错,修改config下jvm.options,配置启动内存大小

-Xms512m
-Xmx512m

启动后访问 http://ip:9200 端口,出现如下版本信息则启动成功。

解压kibana,进入config目录,修改yml配置文件

server.port: 5601
server.host: "172.31.131.12"
elasticsearch.hosts: ["http://172.31.131.12:9200"] #ES所在的ip
elasticsearch.username: "elk" 
elasticsearch.password: "1qaz@WSX_1qaz@wsx"

启动 kibana/bin/kibana,访问5601端口

es和kibana启动完毕。

写入es工具类,根据esip和端口修改连接配置。



    public static RestHighLevelClient getClient() {
        //创建HttpHost
        HttpHost host = new HttpHost(HOST, PORT);
        // 创建RestClientBuilder
        RestClientBuilder builder = RestClient.builder(host);
        // 创建RestHighLevelClient
        RestHighLevelClient client = new RestHighLevelClient(builder);
        return client;
    }

    public static void createDoc(EsParam esTestVo) throws IOException {
        //准备一个json数据
        long time = System.currentTimeMillis();
        esTestVo.setId(String.valueOf(time));
        ObjectMapper mapper = new ObjectMapper();
        String json = mapper.writeValueAsString(esTestVo);

        //准备request对象
        IndexRequest request = new IndexRequest(esTestVo.getIndex(), "string", String.valueOf(time));
        request.source(json, XContentType.JSON);

        //通过client对象连接es
        RestHighLevelClient client = getClient();
        IndexResponse response = client.index(request, RequestOptions.DEFAULT);

        //输出
        System.out.println(request.toString());
        System.out.println(response.getResult());
    }

这时考虑到一个问题,请求信息写入es,如果采用同步的方式,会造成接口响应延迟,因为考虑采用异步写入的方式。因过滤器对异步支持不太友好,此时再次转变思路,使用注解定义切点,在切面中可拿到请求值、响应值,对异步写入请求信息到es处理也更加方便。

自定义注解

/**
 * @desc    es写入参数切面注解
 * @param
 * @author  xfchen12
 * @date    2021/7/1 15:46
 */
@Target(ElementType.METHOD) //注解放置的目标位置,METHOD是可注解在方法级别上
@Retention(RetentionPolicy.RUNTIME) //注解在哪个阶段执行
@Documented
public @interface EsLog {
    String operModul() default ""; // 操作模块
    String operDesc() default "";  // 操作说明
    String index() default "";  // es索引目录
}

切面


    /**
     * @desc 正常返回通知,连接点正常执行完成后执行,如果连接点抛出异常,则不会执行
     * @param joinPoint 切入点
     * @param result    返回结果
     * 需要异步处理,不阻塞主线程
     * @author xfchen12
     * @date 2021/7/2 11:20
     */
    @AfterReturning(value = "operLogPoinCut()", returning = "result")
    public void saveOperLog(JoinPoint joinPoint, Object result) {
        try {
            // 获取RequestAttributes
            RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
            // 从获取RequestAttributes中获取HttpServletRequest的信息
            HttpServletRequest request = (HttpServletRequest) requestAttributes
                    .resolveReference(RequestAttributes.REFERENCE_REQUEST);
            // 需要异步处理,不阻塞主线程
            esClientUtil.logEs(joinPoint, result, request);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

其中切面中的util.loges方法采用异步调用,方法加上@Async注解,启动类加上开启异步的注解@EnableAsync(proxyTargetClass = true)

执行切点方法后,控制台打印信息,显示写入es成功。

2021-07-02 17:32:48.451 [I/O dispatcher 25] WARN  org.elasticsearch.client.RestClient -request [PUT http://172.31.131.12:9200/pomp-sap/string/1625218367116?timeout=1m] returned 2 warnings: [299 Elasticsearch-7.13.2-4d960a0733be83dd2543ca018aa4ddc42e956800 "Elasticsearch built-in security features are not enabled. Without authentication, your cluster could be accessible to anyone. See https://www.elastic.co/guide/en/elasticsearch/reference/7.13/security-minimal-setup.html to enable security."],[299 Elasticsearch-7.13.2-4d960a0733be83dd2543ca018aa4ddc42e956800 "[types removal] Specifying types in document index requests is deprecated, use the typeless endpoints instead (/{index}/_doc/{id}, /{index}/_doc, or /{index}/_create/{id})."]
index {[pomp-sap][string][1625218367116], source[n/a, actual length: [20.3kb], max length: 2kb]}
CREATED

这时我们进入kibana界面,选择discover页签,就可以看到我们写入的index了

选择左侧kibana下的index patterns,将这些索引添加至kibana,再点击overview,选择discover,就可以看到我们通过接口写入的索引内容了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值