前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >10分钟手撸一个API监控系统基础框架,吊打装逼犯

10分钟手撸一个API监控系统基础框架,吊打装逼犯

作者头像
肉眼品世界
发布2020-11-11 10:59:09
3410
发布2020-11-11 10:59:09
举报
文章被收录于专栏:肉眼品世界肉眼品世界

最近,api老不稳定呀,要等用户反馈才知道问题,老板火了,问同事做过没,没做过呀,小码农我只能翻身干活儿,这个需求被自己想起到基本框架实现,也就10来分钟的样子;能准确理解需求,然后迅速转化为代码实现,属于现学现用,很多不大会,但只要花时间基本都可以会,在自我看来几乎是没有上限的,只是感叹时间在哪儿,能超过这种理解能力的应该大有人在,不过超过这种水平的人一般不在我们面前装逼,因为确实是高手;还有一种偶尔写了一个文章就开始装了,大多半桶水,必须吐槽一下,没时间写文章,吐槽一定还是有时间的,兴趣之一

直接上代码,有需要讨论的欢迎讨论,有不完善的欢迎纠正

数据库设计:

代码语言:javascript
复制

Create Table

CREATE TABLE `api_info` (
  `api_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `api_url` char(200) DEFAULT NULL,
  `api_name` char(200) NOT NULL DEFAULT '""' COMMENT 'api名称',
  `method` char(10) DEFAULT 'GET' COMMENT 'GET/POST/DELETE/PUT',
  `header` char(255) NOT NULL DEFAULT 'application/json' COMMENT 'application',
  `params` char(255) DEFAULT NULL COMMENT '参数',
  `result` text NOT NULL COMMENT '结果',
  `extract_variable_value` char(200) DEFAULT '""' COMMENT '提取变量值',
  `time_interval` int(11) NOT NULL DEFAULT '5' COMMENT '请求时间间隔,单位秒',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `group_id` int(10) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`api_id`),
  UNIQUE KEY `api_url` (`api_url`,`params`)
) ENGINE=InnoDB AUTO_INCREMENT=54 DEFAULT CHARSET=utf8mb4

核心代码:

代码语言:javascript
复制
package io.xx.xx.api.cron;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import io.xx.xx.api.entity.ApiMonitor;
import io.xx.xx.api.service.ApiMonitorService;
import io.xx.xx.core.bean.OkHttpUtils;
import io.xx.xx.framework.service.MailSendService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static java.util.concurrent.TimeUnit.MILLISECONDS;

@Configuration      //1.主要用于标记配置类,兼备Component的效果。
@EnableScheduling   // 2.开启定时任务
@EnableAsync
public class ApiMonitorCron implements Runnable {

    private static ExecutorService pool;
    private static final Logger logger = LoggerFactory.getLogger(ApiMonitorCron.class);

    private MailSendService mailSendService;
    @Autowired
    private ApiMonitorService apiMonitorService;
    static AtomicInteger i = new AtomicInteger(-1);
    static AtomicInteger n = new AtomicInteger(0);
    static List<ApiMonitor> apiInfolist = new ArrayList<>();

    public static void main(String[] args) {
        //maximumPoolSize设置为2 ,拒绝策略为AbortPolic策略,直接抛出异常
        pool = new ThreadPoolExecutor(3, 5, 1000, MILLISECONDS, new SynchronousQueue<Runnable>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
        for (int i = 0; i < 5; i++) {
            pool.execute(new ApiMonitorCron());
        }
    }

    @Async
    @Scheduled(cron = "0/10 * * * * ?")
    public void configureTasks() {
        apiInfolist = apiMonitorService.listAll();
        n = new AtomicInteger(apiInfolist.size() - 1);
        i = new AtomicInteger(-1);
        pool = new ThreadPoolExecutor(3, 5, 5000, MILLISECONDS, new SynchronousQueue<Runnable>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
        for (int i = 0; i < 5; i++) {
            pool.execute(new ApiMonitorCron());
        }
    }

    public void run() {
        System.err.println("执行静态定时任务时间: " + LocalDateTime.now() + "i:" + i.get());
        // ApiMonitor apiMonitor  = apiInfolist.get(i.get());
        ApiMonitor apiMonitor = new ApiMonitor();
        String result = "";
        Map<String, Object> map = new ConcurrentHashMap<>();
        if (i.get() < n.get()) {
            //   System.out.println(i.get());
            System.out.println(Thread.currentThread().getName());
            apiMonitor = apiInfolist.get(i.incrementAndGet());
            String url = apiMonitor.getApi_url();
            String params = apiMonitor.getParams();
            String targetResult = apiMonitor.getResult();
            //  JSONObject targetJo = JSON.parseObject(targetResult);
            String header = apiMonitor.getHeader();

            if (apiMonitor.getMethod().equals("POST")) {
                if (header.equals("Content-Type:application/json")) {
                    result = OkHttpUtils.httpPostJson(url, params);
                    map = check(result, apiMonitor);
                } else if (header.equals("Content-Type:application/graphql")) {
                    String strs[] = header.split("\\:");
                    Map<String, String> map2 = new ConcurrentHashMap<>();
                    map2.put(strs[0], strs[1]);
                    result = OkHttpUtils.postDataByGraphql(url, params, map2);
                    map = check(result, apiMonitor);
                }
                boolean flag = (boolean) map.get("flag");
                if (flag == false) {
                    System.err.println((String) map.get("message") + " 报警 id:" + apiMonitor.getApi_id() + ";" + LocalDateTime.now() + ";return result:" + result + ";extract_variable_value:" + apiMonitor.getExtract_variable_value());
                } else {
                    //成功记录日志
                    //logger.info((String)map.get("message")+" success id:"+ apiMonitor.getApi_id() + ";" + LocalDateTime.now() + ";return result:" + result + ";extract_variable_value:" + apiMonitor.getExtract_variable_value());
                }

            } else if (apiMonitor.getMethod().equals("GET")) {
                if (url.indexOf("?") >= 0) {
                    url = url + "&" + params;
                } else {
                    url = url + "?" + params;
                }
                result = OkHttpUtils.httpGet(url);
                map = check(result, apiMonitor);

            }
            boolean flag = (boolean) map.get("flag");
            if (flag == false) {
                System.err.println((String) map.get("message") + " 报警 id:" + apiMonitor.getApi_id() + ";" + LocalDateTime.now() + ";return result:" + result + ";extract_variable_value:" + apiMonitor.getExtract_variable_value());
            } else {
                //成功记录日志
                //logger.info((String)map.get("message")+" success id:"+ apiMonitor.getApi_id() + ";" + LocalDateTime.now() + ";return result:" + result + ";extract_variable_value:" + apiMonitor.getExtract_variable_value());
            }

        } else {
            // System.out.println("new i before:"+i);
            //  i = new AtomicInteger(0);
            // i.incrementAndGet();
        }

    }

    private static HashMap check(String result, ApiMonitor apiMonitor) {
        boolean flag = true;
        HashMap map = new HashMap();
        String message = "";
        String extract_variable_value = apiMonitor.getExtract_variable_value();
        List<String> valueList = Arrays.asList(extract_variable_value.split("\\,"));
        JSONObject jo = JSON.parseObject(result);
        if (result == null || result.equals("")) {
            //报警
            flag = false;
            message = "resut null 报警:id:";
        } else if (extract_variable_value == null || extract_variable_value.equals("")) {
            if (jo.getInteger("code") != 1000 && jo.getString("code").equals("1000") == false) {
                flag = false;
                message = "extract_variable_value null code 1000";
            }

        } else {

            for (int j = 0; j < valueList.size(); j++) {
                String extra_value = valueList.get(j);
                int pos = result.indexOf(extra_value);
                // 创建 Pattern 对象
                Pattern r = Pattern.compile(extra_value);
                Matcher m = r.matcher(result);
                boolean findFlag = m.find();
                // boolean isMatch = Pattern.matches(extra_value, result);

                if (findFlag == false) {
                    flag = false;
                    message = "extract_variable_value compare ";
                }
            }

        }
        map.put("flag", flag);
        map.put("message", message);
        return map;
    }
}

OKhttpUtils:

代码语言:javascript
复制
package io.xx.xx.core.bean;
import okhttp3.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpMethod;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

/**
 * @author
 * @since 2020-09-25
 */
public class OkHttpUtils {

    private static final Logger log = LoggerFactory.getLogger(OkHttpUtils.class);

    private static final String HTTP_JSON = "application/json; charset=utf-8";
    private static final String HTTP_GRAPHQL = "application/graphql; charset=utf-8";
    private static final String HTTP_FORM = "application/x-www-form-urlencoded; charset=utf-8";

    private static final OkHttpClient okHttpClient = new OkHttpClient.Builder()
            .connectTimeout(120, TimeUnit.SECONDS)
            .readTimeout(120, TimeUnit.SECONDS)
            .writeTimeout(120, TimeUnit.SECONDS)
            .build();


    /**
     * get请求
     * 对于小文档,响应体上的string()方法非常方便和高效。
     * 但是,如果响应主体很大(大于1 MB),则应避免string(),
     * 因为它会将整个文档加载到内存中。在这种情况下,将主体处理为流。
     *
     * @param url
     * @return
     */
    public static String httpGet(String url) {
        if (url == null || "".equals(url)) {
            log.error("url为null!");
            return "";
        }

        Request.Builder builder = new Request.Builder();
        Request request = builder.get().url(url).build();
        try {
            Response response = okHttpClient.newCall(request).execute();
            if (response.code() == 200) {
                log.info("http GET 请求成功; [url={}]", url);
                return response.body().string();
            } else {
                log.warn("Http GET 请求失败; [errorCode = {} , url={}]", response.code(), url);
            }
        } catch (IOException e) {
            throw new RuntimeException("同步http GET 请求失败,url:" + url, e);
        }
        return null;
    }

    public static String httpGet(String url, Map<String, String> headers) {
        if (CollectionUtils.isEmpty(headers)) {
            return httpGet(url);
        }

        Request.Builder builder = new Request.Builder();
        headers.forEach((String key, String value) -> builder.header(key, value));
        Request request = builder.get().url(url).build();
        try {
            Response response = okHttpClient.newCall(request).execute();
            if (response.code() == 200) {
                log.info("http GET 请求成功; [url={}]", url);
                return response.body().string();
            } else {
                log.warn("Http GET 请求失败; [errorxxCode = {} , url={}]", response.code(), url);
            }
        } catch (IOException e) {
            throw new RuntimeException("同步http GET 请求失败,url:" + url, e);
        }
        return null;
    }

    /**
     * 同步 POST调用 无Header
     *
     * @param url
     * @param json
     * @return
     */
    public static String httpPostJson(String url, String json) {
        if (url == null || "".equals(url)) {
            log.error("url为null!");
            return "";
        }

        MediaType JSON = MediaType.parse(HTTP_JSON);
        RequestBody body = RequestBody.create(JSON, json);
        Request.Builder requestBuilder = new Request.Builder().url(url);
        Request request = requestBuilder.post(body).build();
        try {
            Response response = okHttpClient.newCall(request).execute();
            if (response.code() == 200) {
                log.info("http Post 请求成功; [url={}, requestContent={}]", url, json);
                return response.body().string();
            } else {
                log.warn("Http POST 请求失败; [ errorCode = {}, url={}, param={}]", response.code(), url, json);
            }
        } catch (IOException e) {
            throw new RuntimeException("同步http请求失败,url:" + url, e);
        }
        return null;
    }

    /**
     * 同步 POST调用 有Header
     *
     * @param url
     * @param headers
     * @param json
     * @return
     */
    public static String httpPostJson(String url, Map<String, String> headers, String json) {
        if (CollectionUtils.isEmpty(headers)) {
            httpPostJson(url, json);
        }

        MediaType JSON = MediaType.parse(HTTP_JSON);
        RequestBody body = RequestBody.create(JSON, json);
        Request.Builder requestBuilder = new Request.Builder().url(url);
        headers.forEach((k, v) -> requestBuilder.addHeader(k, v));
        Request request = requestBuilder.post(body).build();
        try {
            Response response = okHttpClient.newCall(request).execute();
            if (response.code() == 200) {
                log.info("http Post 请求成功; [url={}, requestContent={}]", url, json);
                return response.body().string();
            } else {
                log.warn("Http POST 请求失败; [ errorCode = {}, url={}, param={}]", response.code(), url, json);
            }
        } catch (IOException e) {
            throw new RuntimeException("同步http请求失败,url:" + url, e);
        }
        return null;
    }

    /**
     * 提交表单
     * @param url
     * @param content
     * @param headers
     * @return
     */
    public static String postDataByForm(String url, String content, Map<String, String> headers) {
        MediaType JSON = MediaType.parse(HTTP_FORM);
        RequestBody body = RequestBody.create(JSON, content);

        Request.Builder requestBuilder = new Request.Builder().url(url);
        if (headers != null && headers.size() > 0) {
            headers.forEach((k, v) -> requestBuilder.addHeader(k, v));
        }
        Request request = requestBuilder
                .post(body)
                .build();

        Response response = null;
        try {
            response = okHttpClient.newCall(request).execute();
            if (response.code() == 200) {
                log.info("postDataByForm; [postUrl={}, requestContent={}, responseCode={}]", url, content, response.code());
                return response.body().string();
            } else {
                log.warn("Http Post Form请求失败,[url={}, param={}]", url, content);
            }
        } catch (IOException e) {
            log.error("Http Post Form请求失败,[url={}, param={}]", url, content, e);
            throw new RuntimeException("Http Post Form请求失败,url:" + url);
        }
        return null;
    }
    public static String postDataByGraphql(String url, String content, Map<String, String> headers) {
        MediaType JSON = MediaType.parse(HTTP_GRAPHQL);
        RequestBody body = RequestBody.create(JSON, content);

        Request.Builder requestBuilder = new Request.Builder().url(url);
        if (headers != null && headers.size() > 0) {
            headers.forEach((k, v) -> requestBuilder.addHeader(k, v));
        }
        Request request = requestBuilder
                .post(body)
                .build();

        Response response = null;
        try {
            response = okHttpClient.newCall(request).execute();
            if (response.code() == 200) {
                log.info("postDataByForm; [postUrl={}, requestContent={}, responseCode={}]", url, content, response.code());
                return response.body().string();
            } else {
                log.warn("Http Post Form请求失败,[url={}, param={}]", url, content);
            }
        } catch (IOException e) {
            log.error("Http Post Form请求失败,[url={}, param={}]", url, content, e);
            throw new RuntimeException("Http Post Form请求失败,url:" + url);
        }
        return null;
    }

    /**
     * 异步Http调用参考模板:Get、Post、Put
     * 需要异步调用的接口一般情况下你需要定制一个专门的Http方法
     *
     * @param httpMethod
     * @param url
     * @param content
     * @return
     */
    @Deprecated
    public static Future<Boolean> asyncHttpByJson(HttpMethod httpMethod, String url, Map<String, String> headers, String content) {
        MediaType JSON = MediaType.parse(HTTP_JSON);
        RequestBody body = RequestBody.create(JSON, content);

        Request.Builder requestBuilder = new Request.Builder()
                .url(url);

        if (!CollectionUtils.isEmpty(headers)) {
            headers.forEach((key, value) -> requestBuilder.header(key, value));
        }

        switch (httpMethod) {
            case GET:
                requestBuilder.get();
                break;
            case POST:
                requestBuilder.post(body);
                break;
            default:
        }

        Request request = requestBuilder.build();
        Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                log.error("异步http {} 请求失败,[url={}, param={}]", httpMethod.name(), url, content);
                throw new RuntimeException("异步http请求失败,url:" + url);
            }

            @Override
            public void onResponse(Call call, final Response response) throws IOException {
                if (response.code() == 200) {
                    System.out.println("需要加入异步回调操作");
                } else {
                    log.error("异步http {} 请求失败,错误码为{},请求参数为[url={}, param={}]", httpMethod.name(), response.code(), url, content);
                }
            }
        });
        return new AsyncResult(true);
    }

    /**
     * lambda表达式异步调用http模板,不建议使用
     *
     * @param request
     * @param failure
     * @param respConsumer
     */
    public static void asyncCall(Request request, Consumer<Exception> failure, Consumer<Response> respConsumer) {
        okHttpClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                failure.accept(e);
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                respConsumer.accept(response);
            }
        });
    }

    //test
    public static void main(String[] args) {
        String url = "http://www.baidu.com";
        System.out.println(httpGet(url));
    }

}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-11-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 肉眼品世界 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档