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

10分钟手撸一个API监控系统基础框架,吊打装逼犯_社会时事_02

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

数据库设计:



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


核心代码:

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:

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));
    }


}




相关阅读:

从phoenix hbase谈谈研发管理和技术选型

一例万级写入并发,百亿级数据,毫秒级返回架构分享

后端,你再不懂vue就out了

10分钟手撸一个API监控系统基础框架,吊打装逼犯_社会时事_03