基于Spring Boot + HttpClient框架的多平台多接口数据一致性校验diff测试1

2021.11.26 此博客不再编辑,新增笔记见博客2

基于Spring Boot + HttpClient框架的多平台多接口数据一致性校验diff测试2

推荐巨好的网址:maven中央仓库 https://mvnrepository.com/


readme

整体设计:
目前读取多个被测系统的多个接口,框架为Spring Boot(2.5.0版本)+ 基于httpclient框架封装http/https请求(get post,鉴权方式支持token/Authroization
认证)。
接口返回结果提取2个系统/服务器的公共字段,重新封装为数据结构一致的JSONArray,重写的返回写入json日志文件,保存多个接口对比结果。
工具类支持json有序对比、无序对比、包含对比等公共方法,支持服务器文本-文本、文本-json的对比等。
支持多系统、多服务、多环境(K8S、K3S、Openstack、Rancher)、多模块、多接口、多文件等对比。

入口函数为reportRun的ReportAllRun方法,支持定时任务配置,每60秒执行一次
基于slf4j日志组件,把整个程序运行过程中的日志分别按分钟/小时/天维度、origin error类型保存

后端:本地文件记录+数据入库+日志结果保存
前端:每次运行的对比结果生成HTML报告(展示重要字段)


框架层级如下:
caseRun
dataCheck
fileOperate
frame
jsonDiff
logCheckHistory
logJsonHistory
logPath
reports
reportSystem
reportWeb
serverData
service
singleInterface
utils(compareDiff、handler、httpUtils、jdbc)

1. 入口函数:
/Users/qa/Desktop/2021/code/cloud/cloud-api/src/main/java/reportSystem.reportmepRun/ReportAllRun.java

2. caseRun
case.txt为多个待对比接口的存放文件,格式为模块名称***接口缩写---接口链接, 不同接口之间按,解析
case1mec计算主机***mechosts---http://10.121.xx.xx:xx/resources/xx-engine/api/hosts/,
caseAllRun为接口文件的解析与分支判断
case性能测试.txt为单接口的存放文件

3. dataCheck为待对比的多接口用例的入口函数:对不同接口请求以及返回的解析
case设计的格式一致
请求封装:
只需要根据不同接口功能定好类的名称+定义每个接口的URL,调用httpclient框架的不同方法(get post)
返回结果String类型转为JSONObject,调用接口解析的responseToParse(int count, JSONObject response)
个别接口返回为数组格式(开发不规范导致的)
返回解析:
获取当前case的类名,截取为geneLogName,作为存放需要对比的返回字段的json日志,每条case的结果各存放在一个日志文件中,方便后面对结果的对比
每运行一次,生成一次以类名命名的json日志,在生成结果之前调用fileOpera的不同方法(写 写前删除)
返回结果的解析:对JSONObject的解析、取值。创建JSONObject对象,把需要的字段存放到JSONObject,把JSONObject按照存放顺序有序的转为(add)JSONArray
JSONArray的结果调用fileOperate的仅写方法存到logJsonHistory的json日志(按照类名命名)

4. fileOperate工具类
封装了对文件的各种处理(仅读、仅写、写前删除、仅删除、文本的拷贝、遍历文件夹下的文件)
封装了对接口json结果、json对比结果以类名格式存储,作为参数传递
按照日志文件类型封装不同的方法
按照结果类型封装不同的方法(origin、error)

5. jsonDiff工具类
    封装了多接口的日志对比,有序对比 无序对比
    OrderDiff有序对比:将两个接口返回解析后的jsonlist放入set集合,判断所有集合是否在MEC CMO中存在(先判断key 再判断value)
    不存在则写入错误日志,记录为fail,否则pass

    noOrderDiff无序对比:与上面不同的是:
                        遍历B接口,判断A接口的value是否在B接口都存在 不存在则写入错误日志,记录为fail,否则pass
                        遍历A接口,判断B接口的value是否在A接口都存在 不存在则写入错误日志,记录为fail,否则pass

6. logCheckHistory 存放错误对比结果,如果pass则无文件产生,如果fail则diff日志名称以接口名(类名)命名生成
logJsonHistory 接口取的字段重新封装的json文件
logPath 定义接口类DataPathTag,指定cmo版本path、mec版本path等日志文件存放的相对路径

7. reportRun工具类
ReportAllRun为主函数的运行入口,封装了定时任务,5s后开始执行,60s执行一次
建立数据库连接,创建数据库驱动,拼接请求数据库,加载jdbc驱动程序,关闭数据连接
String url = "jdbc:mysql://" + ip + ":" + port + "/" + database + "?useUnicode=true&characterEncoding=utf8";

其他的类为待对比接口的数据入库操作,查询数据库 插入数据库
如ReportHost,在调用前面的方法结束后,将结果入库,入库字段
插入cmo_interface 按接口名插入,精确到秒
插入build_number 按时间维度生成,精确到秒
插入job_id 唯一id(取时间戳,截取前10位 2021-04-20 11:44:34,32位的唯一id拼接8位时间,最终返回40位的jobid,UUID uuid = UUID.randomUUID())
插入passed failures errors skip total 各自的用例数
无序对比结果入库,diff入库:issame  0 失败。错误对比日志不为空,接口返回不一致  1 成功。错误对比日志为空,接口返回一致

8. reports报告展示
每次运行结果按照时间戳转换为北京时间,生成html格式的文件,在浏览器访问

9. reportWeb工具类
ReportReadSQL读取数据表api_job需要展示的字段,逐一取出来,如String diff_info = rs.getString(13);把取出的字段逐一存放到list,
定义ArrayList,存放到allLineLists,为多个查询行数据。 List<List<String>> allLineLists = new ArrayList<>();

ReportHtml对html格式解析,对html整体定义,tr存储行 td存储列,插入一行数据,保存到文件中,数据是html格式
生成报告,html报告带当前的时间,北京时间

10. singleInterface工具类
单个接口校验,计算占比等

11. utils工具类
11.1 封装了httpUtils和handler
    是对httpclient框架的封装,其中HTTPCommonMethod根据公司的http处理(get post)
    鉴权分为2种方式:token + X-Auth-Token + Project
                   Authorization认证鉴权方式,注意value为Basic
11.2 封装了compareDiff工具类
    DataUtilsJSONList是对JSONArray的解析-读取接口返回解析后的json文件,把数组解析为jsonlist,再解析为单个json
    供其他对比方法调用
    具体解析:根据日志格式调整,判断无json字段或者json为空的情况
             json_list取值:[为第一次出现的位置
             从[算index,index+1为{ 取出的字符串为{},{},{},{},一个个的json
             按照},拆分 for循环遍历jsonlist,在取出的每个innerArray后去空白后追加},如果非最后一个数组则需要拼接}
             解析为单个json
             如果非最后一个数组则需要拼接}
             如果是最后一个数组则需要不拼接},因为最后的数组本身带}

12. jdbc 
对数据库的封装,以及时间戳转换,生成唯一id等

13. 后续计划:做成的页面支持log文件的下载等

调整框架:以下方法删除,改为配置文件读取、封装case

        // 获取当前方法名
        String getfn = Thread.currentThread().getStackTrace()[1].getMethodName();
        // 获取当前类名
        String getcn = Thread.currentThread().getStackTrace()[1].getClassName();
        System.out.println("getcn==" + getcn);
        // 整个类名为com.interfaceframe.bg.testcase.dataCheck.mepm.case1cmo边缘端资源管理边缘节点
        // dataCheck.的长度为10 截取dataCheck文件夹后面的类名作为日志名称
        String geneLogName = getcn.substring(getcn.indexOf("dataCheck") + 10, getcn.length());
        System.out.println("geneLogName==" + geneLogName);

        // 每次生成日志文件前删除上一次的log,只保留最新的运行日志
        FileWrite.deleteAllJsonLogFile(geneLogName);

14. 入库字段:CREATE TABLE `data_check` (
       `id` int NOT NULL AUTO_INCREMENT,
       `build_number` varchar(1000) DEFAULT NULL COMMENT '版本号',
       `job_id` varchar(1000) DEFAULT NULL COMMENT '每次运行自动化生成的唯一标识',
       `cmo_pagename` varchar(1000) DEFAULT NULL COMMENT 'cmo的页面名称',
       `mec_pagename` varchar(1000) DEFAULT NULL COMMENT 'mec的页面名称',
       `cmo_interface` varchar(1000) DEFAULT NULL COMMENT 'cmo的接口名称',
       `mec_interface` varchar(1000) DEFAULT NULL COMMENT 'mec的接口名称',
       `is_same` int DEFAULT NULL COMMENT '两边接口数据是否一致',
       `is_pass` int DEFAULT NULL COMMENT '是否通过 0未通过 1已通过',
       `pass_num` int DEFAULT NULL COMMENT '通过数目 0 1',
       `cmo_response` longtext COMMENT 'cmo的接口返回原始数据',
       `mec_response` longtext COMMENT 'mec的接口返回原始数据',
       `diff_info` longtext COMMENT '接口返回的具体差异',
       `comments` longtext COMMENT '注释',
       `total` int DEFAULT NULL COMMENT '总的case数目',
       `start_time` mediumtext COMMENT '每条case的开始时间',
       `end_time` mediumtext COMMENT '每条case的结束时间',
       `create_time` varchar(100) DEFAULT NULL COMMENT '入库时间',
       PRIMARY KEY (`id`),
       UNIQUE KEY `id_UNIQUE` (`id`)
     ) ENGINE=InnoDB AUTO_INCREMENT=6086 DEFAULT CHARSET=utf8 COMMENT='数据一致性检查表'

git代码提交量统计-统计该项目所有的代码量

git log  --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 - $2 } END { printf "added lines: %s, removed lines: %s, total lines: %s\n", add, subs, loc }'
 

git统计每个人的代码行数_Git代码行数统计命令

git代码提交量统计-统计该某个用户某段时间内的代码量

git log --author=username --since=2022-04-01 --until=2022-09-21 --format='%aN' | sort -u | while read name; do echo -en "$name\t"; git log --author="$name" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 - $2 } END { printf "added lines: %s, removed lines: %s, total lines: %s\n", add, subs, loc }' -; done

11月22-25

[root@node1 ~]# kubectl get pod -A|grep cert-manager |wc -l 查看某个命名空间数量

[root@node1 ~]# kubectl get pod -A|grep cert-manager 查看某个命名空间基本信息

[root@node1 ~]# kubectl get service -A -o wide|grep cert-manager 查看某个命名空间详细信息

[root@node1 ~]# kubectl describe service cert-manager-webhook -n cert-manager 查看某个命名空间具体service服务的详细描述信息

1. 平台k8s-集群k8s-pod

登录跳板机,登录cmo服务器19结尾

[root@node1 ~]# kubectl get pod -A|wc -l

156

[root@node1 ~]# kubectl get pod -A

过滤掉节点NODE为none的数据,拓扑图pod无node的不展示,因为还没有分配节点

[root@node1 ~]# kubectl get pod -A -o wide|awk '{print $2"|||"$4"|||pod|||"$8","}'|grep -v 'none'|wc -l
138

[root@node1 ~]# kubectl get pod -A -o wide|awk '{print $2"|||"$4"|||pod|||"$8","}'|grep -v 'none' > pod.txt

最终解析结果格式如,拷贝入代码pod.txt

vv-kubelet-ef0fc3c2-5ddfbb679f-8jb8k|||Running|||pod|||node1,



2. 平台k8s-集群k8s-services

登录跳板机,登录cmo服务器19结尾

[root@node1 ~]# kubectl get services -A|wc -l

74

[root@node1 ~]# kubectl get services -A |awk '{print $2"|||"$3"|||active|||service,"}'

nats-leaf|||NodePort|||active|||service,

nats-mgmt|||ClusterIP|||active|||service,

pushgateway-ttt|||NodePort|||active|||service,

[root@node1 ~]# kubectl get services -A -o wide|awk '{print $2"|||"$3"|||active|||service|||"$8","}' > service.txt

或者svc筛选出所有的service

[root@node1 ~]# egrep 'none' service.txt > service1.txt

Linux egrep命令用于在文件内查找指定的字符串。

egrep执行效果与"grep-E"相似,使用的语法及参数可参照grep指令,与grep的不同点在于解读字符串的方法。

egrep是用extended regular expression语法来解读的,而grep则用basic regular expression 语法解读,extended regular expression比basic regular expression的表达更规范。

语法
egrep [范本模式] [文件或目录] 
参数说明:

[范本模式] :查找的字符串规则。
[文件或目录] :查找的目标文件或目录。
实例
显示文件中符合条件的字符。例如,查找当前目录下所有文件中包含字符串"Linux"的文件,可以使用如下命令:

egrep Linux *

以上合并为一个shell,从一段文本中过滤不包含某些字符串的命令 参考 linux过滤字符串命令

[root@node1 ~]# kubectl get services -A -o wide|awk '{print $2"|||"$3"|||active|||service|||"$8","}' | grep -v 'none' |wc -l
73

数据太多想查看前几条   [root@node1 ~]# kubectl get services -A -o wide|head -n 2

过滤所有没有标签的service 即SELECTOR为none的数据

[root@node1 ~]# kubectl get services -A -o wide|awk '{print $2"|||"$3"|||active|||service|||"$8","}' | grep -v 'none' > service.txt

[root@node1 ~]# kubectl get services -A -o wide|awk '{print $2"|||"$3"|||active|||service|||"$8","}' | grep -v 'none'

实际业务需要

[root@node1 ~]#  kubectl get services -A -o wide|awk '{print $2"|||"$3"|||active|||service,"}'

最终解析结果格式如,拷贝入代码service.txt

以上取数据的原因:

过滤所有没有标签的service,没有标签的service并没有什么用。这种数据需要在txt删除,不需要对比校验,因为拓扑图是校验的有关联pod的service

pod的metadata里的labels需要和service的spec里的selector一致 

举个例子,如图是service

 是一个包含关系,service里的必须全部包含在pod里,service才能生效

最终解析结果格式如,拷贝入代码service.txt

prometheus-node-exporter|||ClusterIP|||active|||service|||app=prometheus,component=node-exporter,
nats|||NodePort|||active|||service|||app=nats,

去掉default     kubernetes     ClusterIP   10.96.0.1        <none>        443/TCP                                   2d    <none>



3. 平台k8s-集群k8s-node

登录跳板机,登录cmo服务器19结尾

使用 awk 过滤文本或文件中的字符串

[root@node1 ~]# kubectl get node -A 
NAME                  STATUS   ROLES         AGE     VERSION
cv-kubelet-087b6772   Ready    agent         23h     v1.15.2-vk-cri-lecp-mvp-v1.1.1-74-gf918a7f
cv-kubelet-999c3d0f   Ready    agent         23h     v1.15.2-vk-cri-lecp-mvp-v1.1.1-74-gf918a7f
node1                 Ready    master,node   2d19h   v1.15.3
node2                 Ready    master,node   2d19h   v1.15.3
node3                 Ready    master,node   2d19h   v1.15.3
vv-kubelet-087b6772   Ready    agent         23h     v1.15.2-vk-cri-lecp-mvp-v1.1.1-67-g619e282
vv-kubelet-999c3d0f   Ready    agent         23h     v1.15.2-vk-cri-lecp-mvp-v1.1.1-67-g619e282

[root@node1 ~]# kubectl get node -A|grep -v 'agent'            过滤agent的node节点
NAME                  STATUS   ROLES         AGE     VERSION
node1                 Ready    master,node   2d19h   v1.15.3
node2                 Ready    master,node   2d19h   v1.15.3
node3                 Ready    master,node   2d19h   v1.15.3


[root@node1 ~]# kubectl get node -A|awk '/master/{print $0}'   

过滤ROLES为agent的数据,匹配master,node的节点

[root@node1 ~]# kubectl get node -A|awk '/master,node/{print $0}'       
node1                 Ready    master,node   2d19h   v1.15.3
node2                 Ready    master,node   2d19h   v1.15.3
node3                 Ready    master,node   2d19h   v1.15.3

[root@node1 ~]# kubectl get node -A|awk '/master/{print $1"|||active|||node,"}'     代码最终需要的格式
node1|||active|||node,
node2|||active|||node,
node3|||active|||node,



4. 平台k8s-集群k8s-ingress

登录跳板机,登录cmo服务器19结尾

[root@node1 ~]# kubectl get ingress -A |awk '{print $2"|||active|||ingress,"}'|wc -l
7
[root@node1 ~]# kubectl get ingress -A |awk '{print $2"|||active|||ingress,"}'
NAME|||active|||ingress,
isddc-grafana|||active|||ingress,
fileserver-ingress|||active|||ingress,

由于router的ingress数据取出来有重复的json,需要去重处理。需要对JSONArray去重处理

每个service下的router字段必须包含这个service关键的所有ingresshttps://jingyan.baidu.com/article/1e5468f9ac5982484961b738.html

  // ingress取出来的数据为8,其中3条数据重复了harbor-harbor-ingress
                // set集合中的元素不重复且去重,通过循环把JSONArray对象中的元素都添加到set集合且先输出元对象的内容来看效果
                System.out.println("去重前,数组长度==" + ja.size());

                Set set = new HashSet<>();
                for (int i = 0; i < ja.size(); i++) {
                    System.out.println("去重前,逐个取JSONObject==" + ja.get(i));
                    set.add(ja.get(i));
                }
                System.out.println("set集合=" + set);


                // 再把set集合转成字符串再转换成jsonArray对象
                System.err.println("--------------------------------------------分隔符--------------------------------------------");
                ja = JSONArray.parseArray(set.toString());
                for (int j = 0; j < ja.size(); j++) {
                    System.out.println("去重后,逐个取JSONObject==" + ja.get(j));
                }
                System.out.println("去重后,数组长度==" + ja.size());


                // JSONArray转json字符串
                String jaStr = ja.toString();
//                    System.out.println("debug-case11cmo拓扑管理节点拓扑k8singress-jaStr最终存入log的json格式==" + jaStr);
                log.info("debug-jaStr最终存入log的json格式=={}", jaStr);

完整代码

package dataCheck.cmo150;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import fileOperate.FileRead;
import fileOperate.FileWritecmo150;
import lombok.extern.slf4j.Slf4j;

import java.util.*;

@Slf4j
public class case11cmo拓扑管理节点拓扑k8singress {
    public static int responseToParse(int count, JSONObject response, String casename, String uriAbridge) {

        String path = "/Users/qa/Desktop/2021/code/cloud/cloud-api/src/main/java/logJsonHistory/cmo150/";
        // 每次生成日志文件前删除上一次的log,只保留最新的运行日志
        FileWritecmo150.deleteAllJsonLogFile(casename);

        try {

            // 默认从0开始,所以先在这里count++
            count++;
            String basePrint = "第" + count + "个请求,case11cmo拓扑管理节点拓扑k8singress:";
            String fatal = "FATAL FATAL FATAL !!!" + basePrint;

//            System.out.println(basePrint + "debug-response==" + response);
            log.info("{}debug-response=={}", basePrint, response);

            if (!response.isEmpty() || response != null) {

                // 创建JSONArray,把JSONObject放到JSONArray
                JSONArray ja = new JSONArray();

                // 获取JSONArray
                JSONArray network = response.getJSONObject("data").getJSONObject("network").getJSONArray("list").getJSONObject(0).getJSONArray("network");
                int networksize = network.size();
                System.out.println("case11cmo拓扑管理节点拓扑k8singress的network长度为=" + networksize);
//
                if (networksize != 0) {
                    for (int i = 0; i < networksize; i++) {

                        try {
                            if (network.getJSONObject(i).containsKey("router")) {
                                // 先判断kind为service,再去ingress的字段
                                JSONObject router = network.getJSONObject(i).getJSONArray("router").getJSONObject(0);
                                String kind = router.getString("kind");
                                System.out.println("第" + i + "个,debug-case11cmo拓扑管理节点拓扑k8singress的response.kind==" + kind);

                                if (kind.equals("ingress")) {
                                    String name = router.getJSONObject("properties").getString("name");
//                            String type = network.getJSONObject(i).getJSONObject("properties").getString("type");
                                    String status = router.getJSONObject("properties").getString("status");

//                                    System.out.println("第" + i + "个,debug-case11cmo拓扑管理节点拓扑k8singress的response.name==" + name);
//                            System.out.println("debug-case11cmo拓扑管理节点拓扑k8singress的response.status==" + status);
//                            System.out.println("debug-case11cmo拓扑管理节点拓扑k8singress的response.node==" + node);

                                    JSONObject jo = new JSONObject(true);
                                    // 不传true则排列无序 未按照存放的顺序打印
                                    jo.put("name", name);
//                            jo.put("type", type);
                                    jo.put("status", status);
                                    jo.put("kind", kind);

                                    // 把JSONObject放到JSONArray
                                    ja.add(jo);

                                    System.out.println("debug-case11cmo拓扑管理节点拓扑k8singress的jo JSONObject" + jo);
                                    System.out.println("debug-case11cmo拓扑管理节点拓扑k8singress的ja JSONObject" + ja);
                                    log.info("debug-jo JSONObject第{}个数组=={}", i, jo);
                                    log.info("debug-ja的JSONArray格式=={}", ja);

                                }
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }

                }

                // ingress取出来的数据为8,其中3条数据重复了harbor-harbor-ingress
                // set集合中的元素不重复且去重,通过循环把JSONArray对象中的元素都添加到set集合且先输出元对象的内容来看效果
                System.out.println("去重前,数组长度==" + ja.size());

                Set set = new HashSet<>();
                for (int i = 0; i < ja.size(); i++) {
                    System.out.println("去重前,逐个取JSONObject==" + ja.get(i));
                    set.add(ja.get(i));
                }
                System.out.println("set集合=" + set);


                // 再把set集合转成字符串再转换成jsonArray对象
                System.out.println("--------------------------------------------分隔符--------------------------------------------");
                ja = JSONArray.parseArray(set.toString());
                for (int j = 0; j < ja.size(); j++) {
                    System.out.println("去重后,逐个取JSONObject==" + ja.get(j));
                }
                System.out.println("去重后,数组长度==" + ja.size());


                // JSONArray转json字符串
                String jaStr = ja.toString();
//                    System.out.println("debug-case11cmo拓扑管理节点拓扑k8singress-jaStr最终存入log的json格式==" + jaStr);
                log.info("debug-jaStr最终存入log的json格式=={}", jaStr);


                System.out.println("casename==" + casename);
                FileWritecmo150.logJsonHistory(jaStr, casename);
//                        FileRead.originLogOnlyRead(path + casename + "origin_log.json");
                System.out.println(FileRead.originLogOnlyRead(path + casename + "_origin_log.json"));


            } else {
                System.err.println(fatal + "接口返回为空");
                log.error("{}接口返回为空", fatal);
            }


//            System.err.println("Finished~ " + basePrint + ",接口所有字段校验通过(json长度、value值)~");
            System.err.println("--------------------------------------------------------------------------------------------------------------------------------------------------------");
            System.err.println("--------------------------------------------------------------------------------------------------------------------------------------------------------");
            log.error("--------------------------------------------------------------------------------------------------------------------------------------------------------");
            log.error("--------------------------------------------------------------------------------------------------------------------------------------------------------");

        } catch (Exception e) {
            e.printStackTrace();
        }
        return 0;
    }

    public static JSONObject jsonObject = new JSONObject();

}


5. 平台k3s-集群mec16-node

k3s-mec14 mec16的接口

self.k3s_api = idict(

auth='/auth/v3/auth/tokens?_allow_anonymous=true',

project='/auth/v3/users/%(user_id)s/projects?_allow_anonymous=true',

nodes='/k3s/api/v1/nodes',

namespaces='/k3s/api/v1/namespaces',

pods='/k3s/api/v1/namespaces/%(namespace)s/pods',

如果查看所有的pods 则 /k3s/api/v1/pods, 去掉namespace

services='/k3s/api/v1/namespaces/%(namespace)s/services', 

如果查看所有的services 则 /k3s/api/v1/services, 去掉namespace

ingresses='/k3s/apis/networking.k8s.io/v1/namespaces/%(namespace)s/ingresses',

)

看service的labels 如果selector为none则不取此数据

正常数据为,service服务详情是有selector的,对应接口返回的["spec"]有["selector"]字段,如ceph无selector 如果不过滤则cmo为51个 mec为54个

"metadata": {

"name": "ceph",

"namespace": "default",

"uid": "127bea9a-be43-4ce6-934f-c396559f0fbe",

"resourceVersion": "13061" }

for (int i = 0; i < size; i++) {

                    // 正常数据为,service服务详情是有selector的,对应接口返回的["spec"]有["selector"]
                    if (items.getJSONObject(i).getJSONObject("spec").containsKey("selector")) {

                        String name = items.getJSONObject(i).getJSONObject("metadata").getString("name");
                        log.info("debug-" + getcn + "的response.name=={}", name);
//                    System.out.println("debug-case7openstack节点拓扑nodes_c_n的items的hypervisor_hostname==" + name);
//
                        JSONObject jo = new JSONObject(true);
                        // 不传true则排列无序 未按照存放的顺序打印
                        jo.put("name", name);
                        jo.put("status", "active");
                        jo.put("type", "service");

                        ja.add(jo);

                        log.info("debug-jo的JSONObject数组=={}", jo);
                        log.info("debug-ja的JSONArray格式=={}", ja);

                    }


mec-mec14 mec16的接口

self.mec_api = idict(

auth='/auth/v3/auth/tokens?_allow_anonymous=true',

project='/auth/v3/users/%(user_id)s/projects?_allow_anonymous=true',

cluster='/mec-engine/api/clusters?allow_anonymous=true',

hosts='/mec-engine/api/hosts?allow_anonymous=true',

vms='/mec-engine/api/vms?allow_anonymous=true',

networks='/mec-engine/api/networks?allow_anonymous=true',

subnet='/net/provider/v2.0/networks?id=%(network_id)s',

routers='/net/provider/v2.0/routers',

host_network='/mec-engine/api/hosts/%(host_id)s/networkattachments?follow=host_nic.network',

vm_network='/mec-engine/api/vms/%(vm_id)s/nics?follow=vnic_profile',

network_port='/net/provider/v2.0/ports?device_id=%(device_id)s',

floating_ip='/net/provider/v2.0/floatingips?logical_port=%(port_id)s',

provider_network='/mec-engine/api/openstacknetworkproviders/%(provider_id)s',

)

11月24发现的问题:mec接口返回HTML格式,其他k3s接口正常,修改调试postman的Accept为application/json, text/plain, */*



10月22

// 10月22日,兼容不同系统传参
getMethod.addRequestHeader(new Header("Accept", "application/json, text/plain, */*"));

getMethod.addRequestHeader(new Header("Accept", "application/json, text/plain, */*"));

10月16

在服务器用shell处理为代码需要的格式,写到文本文件,再去代码解析为json

case2server存储卷 pv

[root@node1 ~]# kubectl get pv -A | wc -l
41
[root@node1 ~]# kubectl get pv -A | awk '{print $1"|||"$5"|||"$2"|||"$3"|||"$6"|||"$7","}'

pvc-00698bda-06ad-4452-b231-5c55e581d6a1|||Bound|||10Gi|||RWO|||default/0927pvc1744|||managed-nfs-storage,
pvc-03b02307-b291-472a-9d90-90eb8eeed64e|||Bound|||100Gi|||RWO|||isddc/isddc-influxdb|||managed-nfs-storage,
pvc-0838957a-8a37-4c5d-a343-2e6bb6d05443|||Bound|||1Gi|||RWO|||default/autopvccephrbd0928152639|||managed-nfs-storage,
pvc-0fb438a3-d6eb-481a-be37-4b9a7c6c05b5|||Bound|||10Gi|||RWO|||isddc/isddc-vsftpd|||managed-nfs-storage,

case3server存储卷声明 pvc

[root@node1 ~]# kubectl get pvc -A |  wc -l
41
[root@node1 ~]# kubectl get pvc -A | awk '{print $2"|||"$1"|||"$3"|||"$5"|||"$6"|||"$4"|||"$7","}'

9月15-17

登录到服务器对k8s的node节点信息解析为代码需要的格式||| , 

kubectl get node -A|grep 'node'|awk '{print $1"|||"$2","}' > k8snode.txt

登录到服务器对k8s的pod节点信息解析为代码需要的格式||| , 

[root@node1 ~]# kubectl get pod -A|awk '{print $2"|||"$4","}' > k8spod.txt
[root@node1 ~]# cat k8spod.txt |wc -l
172
[root@node1 ~]# cat k8spod.txt|tail -n +150|head -n 50

把每个service的name写到文件

kubectl get svc -A -o wide|awk '{print $2}'>service.txt

用shell处理文本在每行后面追加|||active

sed 's/$/|||active,/g' service.txt

@Slf4j
public class case11serverk8sn_p {

    public static void main(String[] args) {
//        FileWrite.deleteAllLogCopyFile();
        startSearch();

    }

    public static void startSearch() {

        // 获取当前类名
        String casename = Thread.currentThread().getStackTrace()[1].getClassName().substring(10);

        System.out.println("getcn==" + casename);

        FileWrite.deleteAllJsonLogFile(casename);

        String path = "/Users/qa/Desktop/2021/code/cloud/cloud-api/src/main/java/logJsonHistory/";

        // 拼接的传参参数为中文,需要把中文放到map
        // 方法1:把待测试的词典从服务器,存到本地
        String filePathAll = "/Users/qa/Desktop/2021/code/cloud/cloud-api/src/main/java/k8s/";
        String pathk8snode = filePathAll + "node.txt";

//        System.out.println("文本路径为==" + pathk8snode);
        String[] k8snode = ReadFiles.getMap(pathk8snode);

        System.out.println("文本内容为:" + Arrays.toString(k8snode));

        Map<String, String> sldMap = new HashMap<String, String>();

        // 创建JSONArray,把JSONObject放到JSONArray
        JSONArray ja = new JSONArray();

        for (int i = 0; i < k8snode.length; i++) {

            JSONObject jo = new JSONObject(true);

            // 找打{开始的位置,拆分,开头到{为group_id,{到结尾为dict_json
            String name = k8snode[i].substring(0, k8snode[i].indexOf("|||"));
            String status = k8snode[i].substring(k8snode[i].indexOf("|||") + 3, k8snode[i].length() - 1);
//            System.out.println("k8snode[i]==" + k8snode[i]);
//            System.out.println("name==" + name);
//            System.out.println("status==" + status);

            jo.put("name", name);
            if (status.equals("Ready")) {
                jo.put("status", "active");
            } else {
                jo.put("status", "inactive");
            }
            jo.put("type", "nodes");
            // 拆分出来的group_id与dict_json中间有空格,trim去掉末尾空格
//            System.out.println("key=="+key);
//            System.out.println("value=="+value);
//            System.out.println("sldMAp==" + sldMap);

//            userIdMap.put("id", k8snode[i]);
            ja.add(jo);
//            System.out.println("k8s的node处理,第" + (i + 1) + "行解析后的map为----------" + ja.toString());
        }
        System.err.println("k8s的node处理,解析后的map为----------" + ja.toString());
        String jaStr = ja.toString();
        log.info("debug-jaStr最终存入log的json格式=={}", jaStr);

        FileWrite.logJsonHistory(jaStr, casename);

        FileRead.originLogOnlyRead(path + casename + "_origin_log.json");
        System.out.println(FileRead.originLogOnlyRead(path + casename + "_origin_log.json"));


    }
}

对工具类caseRead完善

/**
     * 返回数组,文本以逗号拆分,遇到空行跳过,遇到#注释不解析
     *
     * @param casePath
     * @return
     */
    public static String[] readTxt(String casePath) {
        StringBuilder builder = new StringBuilder();
        try {
            File file = new File(casePath);
            if (file.isFile() && file.exists()) {
                InputStreamReader isr = new InputStreamReader(new FileInputStream(file), "utf-8");
                BufferedReader br = new BufferedReader(isr);
                String lineTxt;

                int num = 0;
                long time1 = System.currentTimeMillis();

                // 如果文本中有空行,则过滤掉
                lineTxt = br.readLine();
                lineTxt.trim();
                while (lineTxt.startsWith(" ")) {// 这里判断是不是全角空格
                    lineTxt = lineTxt.substring(1, lineTxt.length()).trim();
                }
                while (lineTxt.endsWith(" ")) {
                    lineTxt = lineTxt.substring(0, lineTxt.length() - 1).trim();
                }

                while ((lineTxt = br.readLine()) != null) {
                    // 如果不想在控制台打印读取出来的文件内容,则注释掉下面的打印语句
//                    System.out.println(lineTxt);
                    if (!lineTxt.startsWith("#")) {
                        builder.append(lineTxt);
                        num++;
                    }
                }
                //System.out.println("总共"+num+"条数据!");
                long time2 = System.currentTimeMillis();
                long time = time1 - time2;
//                System.out.println("共花费" + time + "秒");
                br.close();
            } else {
                System.out.println("文件不存在!");
            }
        } catch (Exception e) {
            System.out.println("类---caseRead---文件读取错误!");
        }

        // builder内容返回为数组
        String[] strings = builder.toString().split(",");

        return strings;
    }

9月13

idea的项目中所有的文件全部变成了.java 且pom文件下载不了依赖,报错unable to import maven project see logs for details

解决办法:删除.idea文件

解决:IDEA unable to import maven project see logs for details问题+java http请求报java.net.SocketException: Permission denied:connect 问题 - ZealouSnesS - 博客园

解决之后再看右下角的event log,报错如下

7:32 PM    Out-of-Date IDE Version
                IDE version 2018.2.5 is less than the minimum recommended major version for Illuminated Cloud (2019). There may be issues due to incompatible plugin SDK changes. Please consider upgrading to a newer version.

发现com.jcraft仍未下载下来,还导致了本地正常的JSONObject slf4j等依赖包无法被引用,解决办法:在Maven-Repositories分别选中一项项,点击update

遇到jcraft依赖无论如何都下载不下来,经过耗时半天到9月14凌晨的排查,发现setting.xml不知道为啥没有如下配置了, 幸好有副本。。。添加回来即可

<mirror>
        <id>alimaven</id>
        <name>aliyun maven</name>
        <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
        <mirrorOf>central</mirrorOf>
    </mirror>
 
    <mirror>
        <id>nexus</id>
        <name>internal nexus repository</name>
        <url>http://192.168.1.xx:xx/nexus/content/groups/public/</url>
        <url>http://repo.maven.apache.org/maven2</url>
        <mirrorOf>central</mirrorOf>
    </mirror>

 

maven home dictionary:/Users/qa/.m2/wrapper/dists/apache-maven-3.8.1-bin/2l5mhf2pq2clrde7f7qp1rdt5m/apache-maven-3.8.1

user settings file:/Users/qa/.m2/settings_20210913.xml

 然后maven-Download Sources 经过2小时的漫长等待。。。终于ok,无数次告诉自己,不要怀疑,就是慢,也不需要翻墙哈。。。

 9月3

package utils.httpUtils;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.httpclient.protocol.Protocol;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import utils.handler.MySSLSocketFactory;

import java.io.IOException;
import java.nio.charset.Charset;

@Slf4j
public class GetTokenOpenStackTest {

    /**
     * OpenStack测试环境,开源代码里,登录接口auth/tokens生成的token在Response-Headers里,key为X-Subject-Token
     *
     * @return
     * @throws IOException
     */
    public static String loginResponse() throws IOException {

        String url_pre = "http://ip:8000/tokens?nocatalog";

        // 开始请求,域名、接口名==url+请求参数param(hashMap)
        String paramIsJson = "{\n" +
                "    \"auth\": {\n" +
                "        \"identity\": {\n" +
                "            \"methods\": [\n" +
                "                \"password\"\n" +
                "            ],\n" +
                "            \"password\": {\n" +
                "                \"user\": {\n" +
                "                    \"domain\": {\n" +
                "                        \"name\": \"default\"\n" +
                "                    },\n" +
                "                    \"name\": \"username\",\n" +
                "                    \"password\": \"passwd\"\n" +
                "                }\n" +
                "            }\n" +
                "        }\n" +
                "    }\n" +
                "}";

        String ostoken = getOSToken(paramIsJson, url_pre);
        // 2021年8月29日,新增对https的请求处理,调用sendHttps方法
//        String response = HTTPCommonMethod.sendHttps(paramIsJson, url_pre);
//        System.out.println("debug-原始返回结果response==" + response);


        return ostoken;
    }

    /**
     * post请求
     */

    public static String getOSToken(String json, String url) throws IOException { //json:请求url的参数
        // 2021.7.13 解决javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path validation failed: java.security.cert.CertPathValidatorException: validity check failed
        // 增加下面两行代码
        Protocol myhttps = new Protocol("https", new MySSLSocketFactory(), 443);
        Protocol.registerProtocol("https", myhttps);

        String getostoken = null;
        String obj = null;
        // 创建默认的httpClient实例
        CloseableHttpClient httpclient = HttpClients.createDefault();
        // 创建httppost
        HttpPost httppost = new HttpPost(url);
//        SslUtil.ignoreSsl();
        // header必传参数Content-type、Accept
        httppost.addHeader("Content-type", "application/json; charset=utf-8");
//        httppost.setHeader("Accept", "application/json");
        httppost.setHeader("Accept", "*/*");


        try {
            StringEntity strEntity = new StringEntity(json, Charset.forName("UTF-8"));  //对参数进行编码,防止中文乱码
            strEntity.setContentEncoding("UTF-8");
            httppost.setEntity(strEntity);
            CloseableHttpResponse response = httpclient.execute(httppost);
            try {
                // 获取相应实体Response-Body
                HttpEntity entity = response.getEntity();

                if (entity != null) {
                    obj = EntityUtils.toString(entity, "UTF-8");
//                    System.err.println("debug-obj================" + obj);
                }

                // 获取相应实体Response-Headers
                org.apache.http.Header[] allheaders = response.getAllHeaders();

                for (int i = 0; i < allheaders.length; i++) {
//                    System.out.println(allheaders[i]);

                    if (allheaders[i].getName().contains("X-Subject-Token")) {
                        getostoken = allheaders[i].getValue();
//                        System.err.println("登录OpenStack的Response-Header的token为==" + getostoken);
                    }
                }

//                // 单独调用以下方法取单个字段 如token
//                org.apache.http.Header[] headertoken = response.getHeaders("X-Subject-Token");
//                System.out.println("debug-headertoken==" + headertoken);
//                for (int i = 0; i < headertoken.length; i++) {
//                    System.err.println("-------------" + headertoken[i].getValue());
//
//                }

//                System.err.println("debug-headers==========" + allheaders.toString());
//

            } finally {
                response.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭连接,释放资源
            httpclient.close();
        }
        return getostoken;
    }


    public static void main(String[] args) throws IOException {

        System.err.println("debug当前方法-登录接口返回的token为==" + loginResponse());
//        System.err.println("debug-登录接口返回的projectid为==" + xprojectidF());
//        System.err.println("debug-登录接口返回的projectname为==" + xprojectnameF());

    }


}

8月26到8月30

javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path validation failed: java.security.cert.CertPathValidatorException: validity check failed
    at sun.security.ssl.Alert.createSSLException(Alert.java:131)
    at sun.security.ssl.TransportContext.fatal(TransportContext.java:353)
    at sun.security.ssl.TransportContext.fatal(TransportContext.java:296)
    at sun.security.ssl.TransportContext.fatal(TransportContext.java:291)
    at sun.security.ssl.CertificateMessage$T12CertificateConsumer.checkServerCerts(CertificateMessage.java:652)

解决办法:校验证书设置为false  或者安装证书  

搜索:HttpsURLConnection 屏蔽证书校验

参考博客:Java用HttpsURLConnection访问https网站的时候如何跳过SSL证书的验证?

Java用HttpsURLConnection访问https网站的时候如何跳过SSL证书的验证?_霓虹深处-CSDN博客

在HTTPCommonMethod工具类新增如下代码 

    // 2021年8月30新增对https接口请求的证书封装
    public static String sendHttps(String json, String URL) {
        StringBuilder stringBuilder = new StringBuilder();

        try {

            HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection();
            connection.setHostnameVerifier(
                    (urlHostName, session) -> true);
            connection.setRequestMethod("POST");
            connection.setDoOutput(true);

            //write header
            connection.setRequestProperty("Content-type", "application/json; charset=utf-8");
            connection.setRequestProperty("Accept", "*/*");

            //write body
            try (PrintWriter writer = new PrintWriter(connection.getOutputStream())) {
//                Map<String, String> foo = new HashMap<>();
//                foo.put("name", "HTTP");
//                foo.put("age", "18");
//                writer.write(JSONObject.toJSONString(foo));
                writer.write(json);
                writer.flush();
            }

            //read response
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
                String line = "";
                while ((line = reader.readLine()) != null) {
                    stringBuilder.append(line);
                }
//                System.out.println("debug-https返回结果==" + line);
            } finally {
                connection.disconnect();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

//        System.out.println("https登录接口返回结果===" + stringBuilder.toString());
        return stringBuilder.toString();
    }


    static {
        try {
            trustAllHttpsCertificates();
            HttpsURLConnection.setDefaultHostnameVerifier
                    (
                            (urlHostName, session) -> true
                    );
        } catch (Exception e) {
        }
    }

    private static void trustAllHttpsCertificates() throws NoSuchAlgorithmException, KeyManagementException {
        TrustManager[] trustAllCerts = new TrustManager[1];
        trustAllCerts[0] = new TrustAllManager();
        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, trustAllCerts, null);
        HttpsURLConnection.setDefaultSSLSocketFactory(
                sc.getSocketFactory());
    }

    private static class TrustAllManager implements X509TrustManager {
        public X509Certificate[] getAcceptedIssuers() {
            return null;
        }

        public void checkServerTrusted(X509Certificate[] certs, String authType) {
        }

        public void checkClientTrusted(X509Certificate[] certs, String authType) {
        }
    }

8月19

新增reportWeb包-ReportHtml,把diff测试对比的入库数据结果在前端展示出来

调试工具参考:W3School TIY Editor

package reportWeb;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import static java.lang.Thread.sleep;

public class ReportHtml {
    static String time = "";
    static String head = "<html>" +
            "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />" +
            "<body style=\"text-align: center;line-height:60px;margin-left:100px;border-style:solid;margin-right:100px;\">" +
            "        <h1 style=\"font-size:40px\">测试报告</h1>" +
            "<h1 style=\"font-size:20px\">time</head>" +
            "<table border=\"1\">";
    static String tail = "</table>" +
            "</body>" +
            "</html>";

    public static String showAtHTML(List<List<String>> lists) {
        String headWithTime = head.replace("time", "生成日期:" + getCurrentTime());
        StringBuilder html = new StringBuilder();
        html.append(headWithTime);
        html.append(getColumn(lists.get(0)));
        for (int n = 1; n < lists.size(); n++) {
            html.append(getLine(n, lists.get(n)));
        }
        html.append(tail);

        return writeToFile(html.toString());
    }

    /**
     * @param list
     * @return
     */
    private static String getColumn(List<String> list) {
        StringBuilder line = new StringBuilder();
        /**
         * <tr>
         *   <th>
         *   </th>
         *
         *   <th>
         *   </th>
         *
         *   <th>
         *   </th>
         * </tr>
         */
        // 行
        String trStart = "<tr bgcolor=\"#FFFF00\">";
        String trEnd = "</tr>";
        // 列
        String thStart = "<th>";
        String thEnd = "</th>";
        line.append(trStart);
        // 标题多个字段拼接
        for (int n = 0; n < list.size(); n++) {
            line.append(thStart + list.get(n) + thEnd);
        }
        line.append(trEnd);
        return line.toString();
    }

    /**
     * 插入一行数据
     *
     * @param index
     * @param list
     * @return
     */
    private static String getLine(int index, List<String> list) {
        StringBuilder line = new StringBuilder();
        String trStart = "<tr>";
        String trEnd = "</tr>";
        String thStart = "<th>";
        String thEnd = "</th>";
        line.append(trStart);
        // 加了一列数据表没有的字段,自增id
        line.append(thStart + index + thEnd);
        // 循环遍历数据表读取的字段
        for (int n = 0; n < list.size(); n++) {

            line.append(thStart + list.get(n) + thEnd);
        }
        line.append(trEnd);
        return line.toString();
    }

    /**
     * 保存到文件中,数据是html格式
     *
     * @param html
     * @return
     */
    private static String writeToFile(String html) {
        String path = "/Users/qa/Desktop/2021/code/cloud/cloud-api/src/main/java/reports/";
        String timehtml = "dataCheck" + getCurrentTime() + ".html";
        String htmlpath = path + timehtml;
//        String path = "/Users/qa/Desktop/2021/code/cloud/cloud-api/src/main/java/reports/dataCheck.html";
        try {
            File file = new File(htmlpath);
            if (file.exists()) {
                file.delete();
            }
            if (!file.exists()) {
                file.createNewFile();
            }
            FileWriter fileWriter = new FileWriter(file.getAbsoluteFile(), true);
            BufferedWriter bw = new BufferedWriter(fileWriter);
            bw.write(html);
            bw.close();
//            Runtime.getRuntime().exec(path);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return htmlpath;
    }

    private static String getCurrentTime() {
        SimpleDateFormat sdf = new SimpleDateFormat();
        sdf.applyPattern("yyyy-MM-dd HH:mm:ss");
        Date date = new Date();
        return sdf.format(date);
    }
}

8月18日

接口返回太不规范。。。。好无奈一直各种调整我的框架。。。基于各种的{"a":1,"b":2}直接返回的是400+个JSONObject,或者直接[]JSONArray

今天基于下图这种玩一样的json重新对框架做了调整。。。。。。。。

参考博客 Map集合中两个重要的取值方法---keySet()和entrySet()_u013506626的专栏-CSDN博客_keyset

8月17日

统一入口函数,增加定时任务/定时器/单线程,增加限制定时任务循环次数限制

Timer和TimerTask

@Slf4j
public class ReportAllRun {

    // 建立数据库连接
    public static Connection conn = null;

    public static PreparedStatement pst = null;

    // 创建数据库驱动:MySQL JDBC Connector版本为5.1.34,driver=com.mysql.jdbc.Driver。8.0.15版本后,driver=com.mysql.cj.jdbc.Driver。多了cj
//    public static String driver = "com.mysql.jdbc.Driver";
    public static String driver = "com.mysql.cj.jdbc.Driver";
    //数据库IP地址
    public static String ip = "10.121.xx.xx";
    //数据库端口
    public static int port = 3306;
    //连接的mysql的数据库名字
    public static String database = "xx";
    // mysql配置时的用户名
    public static String user = "root";
    // mysql配置时的密码
    public static String password = "passwd";
    // URL指向要访问得数据库名 database
//        String url = "jdbc:mysql://" + ip + ":" + port + "/" + database + "?useUnicode=true&characterEncoding=utf8";
    public static String urlToMysql = "jdbc:mysql://" + ip + ":" + port + "/" + database +
            "?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT&useSSL=false";


    // 加载jdbc驱动程序
    static {

        try {
            Class.forName("com.mysql.cj.jdbc.Driver").newInstance();
//            Class.forName("com.mysql.jdbc.Driver").newInstance();

        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InstantiationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }


    public static void main(String[] args) {
        /**
         * 参考博客 https://www.cnblogs.com/lingiu/p/3782813.html
         *
         */
        // Timer是jdk中提供的一个定时器工具,使用的时候会在主线程之外起一个单独的线程执行指定的计划任务,可以指定执行一次或者反复执行多次
        // TimerTask是一个实现了Runnable接口的抽象类,代表一个可以被Timer执行的任务

        // new一个Timer类,Timer的构造函数里会起一个单独的线程来执行计划任务
        Timer timer = new Timer();

        int number = 3;   //设置运行3次

        // new一个TimerTask的子类,重写run方法来指定具体的任务,我用匿名内部类的方式来实现了一个TimerTask的子类
        TimerTask timerTask = new TimerTask() {
            //从0开始计数,每运行一次timertask次数加一,运行制定次数后结束
            int count = 0;

            @Override
            public void run() {
                if (count < number) {
                    task();
                    count++;
                } else {
                    // cancel()方法用于取消此Timer并丢弃当前调度的任何任务
                    timer.cancel();
                }

                System.err.println("定时任务循环次数count==" + count);
            }


        };
        // 在指定的时间点time上调度一次。public void schedule(TimerTask task, long delay, long period) 时间单位为毫秒
        // delay延迟:0表示无延迟  1000为1秒延迟。1s后开始执行主函数,如果delay设置的太久,则需要等半天才开始执行main函数
        // period:间隔10s执行一次
        timer.schedule(timerTask, 1000, 10000);


//        timer.cancel();//.cancel()方法用于终止线程

    }

    private static void task() {
        try {

            // 非静态方法不可以直接调用类型.方法名,需要new
            // cmo边缘端资源管理边缘节点---mec计算主机
            ReportHost reportHost = new ReportHost();
            reportHost.reportHost(urlToMysql, user, password);


            // 静态方法可以直接调用类型.方法名,不需要new
            // cmo应用拓扑mep---mec应用管理mep
            ReportTopologyMep.reportTopologyMep(urlToMysql, user, password);


            // cmo应用拓扑mep_service---mec服务管理mep_service
            ReportTopologyMepService.reportTopologyMepService(urlToMysql, user, password);


            // cmo日志管理仓库管理---es日志管理仓库管理
            ReportLogService.reportLogservice(urlToMysql, user, password);


            List<List<String>> allLineLists = ReportReadSQL.reportReadsql(urlToMysql, user, password);
            String absolutePath = ReportHtml.showAtHTML(allLineLists);
            System.err.println("报告位置:" + absolutePath);
            log.info("报告位置:{}" + absolutePath);


        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

8月12日

针对不同平台的接口JSONArray的个数不同的情况增加日志写入操作

if (cmoja.size() != mecja.size()) {
    System.err.println(fail);
    // 增加要对比的JSONArray个数不同,则写入错误对比日志的判断
    FileWrite.failCompareOnlyWrite(cmopath拓扑管理mep_service, fail + "\n" +
            "【cmo系统】json_info内层判断错误:cmo系统内层JSON的数量为:【" + cmoja.size() + "】。" +
            "【mec系统】json_info内层判断错误:mec系统内层JSON的数量为:【" + mecja.size() + "】。\n" +
            "cmo要对比的JSONArray==" + cmoja + "。\n" +
            "mec要对比的JSONArray==" + mecja);

    System.err.println(cmopath拓扑管理mep_service.substring(cmopath拓扑管理mep_service.indexOf("History") + 8) + "的返回结果==" + cmoja);
    System.err.println(mecpath服务管理mep.substring(mecpath服务管理mep.indexOf("History") + 8) + "的返回结果==" + mecja);
} 

运行主函数报错

Registering current configuration as safe fallback point

今天改了些代码,项目突然启动不了了,日志一直卡在

Registering current configuration as safe fallback point

一动不动。配置文件什么的都检查了没有毛病哇!!!!!

奔溃。。。。。。。。。。。。。。。。。。。。。。。。

我一个断点也没打也启动不起来,用Run去启动也启动不起来。

后来想用控制变量法排查一下吧。我拉了一个新分支,然后试过了可以正常启动的。

然后我参考了一个博客试着去拉新的分支。。。结果好玩的一幕发生了。。。。。。。推代码失败,因为公司的WiFi,又给我连接到guest了。。。。运维加了一点安全认证,对MacBook Pro支持不好,还总是喜欢乱改。。。。。浪费了时间。。。。。

16:12:34,631 |-ERROR in ch.qos.logback.core.joran.action.AppenderRefAction - See http://logback.qos.ch/codes.html#appender_order for more details.
16:12:34,631 |-ERROR in ch.qos.logback.core.joran.action.AppenderRefAction - Could not find an appender named [FILETRACE]. Did you define it below instead of above in the configuration file?
16:12:34,631 |-ERROR in ch.qos.logback.core.joran.action.AppenderRefAction - See http://logback.qos.ch/codes.html#appender_order for more details.
16:12:34,631 |-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - End of configuration.
16:12:34,632 |-INFO in ch.qos.logback.classic.joran.JoranConfigurator@57e1b0c - Registering current configuration as safe fallback point

com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure

The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
    at com.mysql.cj.jdbc.exceptions.SQLError.createCommunicationsException(SQLError.java:174)
    at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:64)
    at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:833)
    at com.mysql.cj.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:453)
    at com.mysql.cj.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:246)
    at com.mysql.cj.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:198)
    at java.sql.DriverManager.getConnection(DriverManager.java:664)
    at java.sql.DriverManager.getConnection(DriverManager.java:247)
    at report.reportTopologyMep.reportAlltopology(reportTopologyMep.java:76)
    at report.reportTopologyMep.main(reportTopologyMep.java:296)
Caused by: com.mysql.cj.exceptions.CJCommunicationsException: Communications link failure

The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:61)
    at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:105)
    at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:151)
    at com.mysql.cj.exceptions.ExceptionFactory.createCommunicationsException(ExceptionFactory.java:167)
    at com.mysql.cj.protocol.a.NativeSocketConnection.connect(NativeSocketConnection.java:89)
    at com.mysql.cj.NativeSession.connect(NativeSession.java:144)
    at com.mysql.cj.jdbc.ConnectionImpl.connectOneTryOnly(ConnectionImpl.java:953)
    at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:823)
    ... 7 more
Caused by: java.net.ConnectException: Operation timed out (Connection timed out)
    at java.net.PlainSocketImpl.socketConnect(Native Method)
    at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:476)
    at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:218)
    at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:200)
    at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:394)
    at java.net.Socket.connect(Socket.java:606)
    at com.mysql.cj.protocol.StandardSocketFactory.connect(StandardSocketFactory.java:155)
    at com.mysql.cj.protocol.a.NativeSocketConnection.connect(NativeSocketConnection.java:63)
    ... 10 more

Process finished with exit code 0
 

8月11日

两个平台返回字段的大小写忽略入库

 String state = data.getJSONObject(i).getString("state");
                    // 由于mec的state是大写的ACTIVE,INACTIVE需要转为小写
                    if (state.matches("INACTIVE|ACTIVE")) {
                        // 一个字符串,在其中 stringObject 的所有大写字符全部被转换为了小写字符
                        state = state.toLowerCase();

                        // toUpperCase() 方法将字符串小写字符转换为大写
//                        String toUp = "aaa";
//                        toUp = toUp.toUpperCase();
//                        System.out.println("toUp==" + toUp);

7月13日

javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path validation failed: java.security.cert.CertPathValidatorException: validity check failed问题解决

参考博客   HttpClient_javax.net.ssl.SSLHandshakeException: sun.security.validator 问题解决,与环境有关 - 码上快乐

javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path validation failed: java.security.cert.CertPathValidatorException: validity check failed
    at sun.security.ssl.Alert.createSSLException(Alert.java:131)
    at sun.security.ssl.TransportContext.fatal(TransportContext.java:353)
    at sun.security.ssl.TransportContext.fatal(TransportContext.java:296)
    at sun.security.ssl.TransportContext.fatal(TransportContext.java:291)
    at sun.security.ssl.CertificateMessage$T12CertificateConsumer.checkServerCerts(CertificateMessage.java:652)
    at sun.security.ssl.CertificateMessage$T12CertificateConsumer.onCertificate(CertificateMessage.java:471)
    at sun.security.ssl.CertificateMessage$T12CertificateConsumer.consume(CertificateMessage.java:367)
    at sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:376)
    at sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:444)
    at sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:422)
    at sun.security.ssl.TransportContext.dispatch(TransportContext.java:183)
    at sun.security.ssl.SSLTransport.decode(SSLTransport.java:154)
    at sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1279)
    at sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1188)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:401)
    at sun.security.ssl.SSLSocketImpl.ensureNegotiated(SSLSocketImpl.java:808)
    at sun.security.ssl.SSLSocketImpl.access$200(SSLSocketImpl.java:75)
    at sun.security.ssl.SSLSocketImpl$AppOutputStream.write(SSLSocketImpl.java:1093)
    at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)
    at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140)
    at org.apache.commons.httpclient.HttpConnection.flushRequestOutputStream(HttpConnection.java:828)
    at org.apache.commons.httpclient.HttpMethodBase.writeRequest(HttpMethodBase.java:2116)
    at org.apache.commons.httpclient.HttpMethodBase.execute(HttpMethodBase.java:1096)
    at org.apache.commons.httpclient.HttpMethodDirector.executeWithRetry(HttpMethodDirector.java:398)
    at org.apache.commons.httpclient.HttpMethodDirector.executeMethod(HttpMethodDirector.java:171)
    at org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:397)
    at org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:323)
    at utils.httpUtils.HTTPCommonMethod.doGet(HTTPCommonMethod.java:154)
    at singleInterface.cmopmCaseRun.startSearch(cmopmCaseRun.java:69)
    at singleInterface.cmopmCaseRun.main(cmopmCaseRun.java:24)
Caused by: sun.security.validator.ValidatorException: PKIX path validation failed: java.security.cert.CertPathValidatorException: validity check failed
    at sun.security.validator.PKIXValidator.doValidate(PKIXValidator.java:369)
    at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:275)
    at sun.security.validator.Validator.validate(Validator.java:271)
    at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:312)
    at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:221)
    at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:128)
    at sun.security.ssl.CertificateMessage$T12CertificateConsumer.checkServerCerts(CertificateMessage.java:636)
    ... 25 more
Caused by: java.security.cert.CertPathValidatorException: validity check failed
    at sun.security.provider.certpath.PKIXMasterCertPathValidator.validate(PKIXMasterCertPathValidator.java:135)
    at sun.security.provider.certpath.PKIXCertPathValidator.validate(PKIXCertPathValidator.java:238)
    at sun.security.provider.certpath.PKIXCertPathValidator.validate(PKIXCertPathValidator.java:146)
    at sun.security.provider.certpath.PKIXCertPathValidator.engineValidate(PKIXCertPathValidator.java:85)
    at java.security.cert.CertPathValidator.validate(CertPathValidator.java:292)
    at sun.security.validator.PKIXValidator.doValidate(PKIXValidator.java:364)
    ... 31 more
Caused by: java.security.cert.CertificateExpiredException: NotAfter: Mon Jul 12 15:35:07 CST 2021
    at sun.security.x509.CertificateValidity.valid(CertificateValidity.java:277)
    at sun.security.x509.X509CertImpl.checkValidity(X509CertImpl.java:677)
    at sun.security.provider.certpath.BasicChecker.verifyValidity(BasicChecker.java:190)
    at sun.security.provider.certpath.BasicChecker.check(BasicChecker.java:144)
    at sun.security.provider.certpath.PKIXMasterCertPathValidator.validate(PKIXMasterCertPathValidator.java:125)
    ... 36 more
debug-原始返回结果response==null
Exception in thread "main" java.lang.NullPointerException
    at singleInterface.cmopmCaseRun.startSearch(cmopmCaseRun.java:71)
    at singleInterface.cmopmCaseRun.main(cmopmCaseRun.java:24)
 

为什么会有这个问题  参考博客   java在访问https资源时,忽略证书信任问题_lizeyang的专栏-CSDN博客_java调用https跳过证书

1)https通信过程
客户端在使用HTTPS方式与Web服务器通信时有以下几个步骤,如图所示。
(1)客户使用https的URL访问Web服务器,要求与Web服务器建立SSL连接。
(2)Web服务器收到客户端请求后,会将网站的证书信息(证书中包含公钥)传送一份给客户端。
(3)客户端的浏览器与Web服务器开始协商SSL连接的安全等级,也就是信息加密的等级。
(4)客户端的浏览器根据双方同意的安全等级,建立会话密钥,然后利用网站的公钥将会话密钥加密,并传送给网站。
(5)Web服务器利用自己的私钥解密出会话密钥。
(6)Web服务器利用会话密钥加密与客户端之间的通信。

2)java程序的证书信任规则
如上文所述,客户端会从服务端拿到证书信息。调用端(客户端)会有一个证书信任列表,拿到证书信息后,会判断该证书是否可信任。
如果是用浏览器访问https资源,发现证书不可信任,一般会弹框告诉用户,对方的证书不可信任,是否继续之类。
Java虚拟机并不直接使用操作系统的keyring,而是有自己的security manager。与操作系统类似,jdk的security manager默认有一堆的根证书信任。如果你的https站点证书是花钱申请的,被这些根证书所信任,那使用java来访问此https站点会非常方便。因此,如果用java访问https资源,发现证书不可信任,则会报文章开头说到的错误
 

解决办法:忽略证书

在python里的解决办法如下

requests.post(url, data=payload, proxies=proxies, verify=True)
默认verify=True
调用的时候要用verify=False

在java解决办法如下

在handler工具类新增MySSLSocketFactory

在httpsUtils工具类的HTTPCommonMethod新增2行代码

import org.apache.commons.httpclient.protocol.Protocol;
import utils.handler.MySSLSocketFactory;

public class HTTPCommonMethod {

    /**
     * get 请求,只需将变动的参数传入params中即可
     *
     * @param url
     * @param params
     * @return
     */
    public static String requestURL;

    // get请求 有参
    public static String doGet(String url, Map<String, String> params, int count) throws ClientProtocolException, IOException {
        try {
            // 2021.7.13 解决javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path validation failed: java.security.cert.CertPathValidatorException: validity check failed
            // 增加下面两行代码
            Protocol myhttps = new Protocol("https", new MySSLSocketFactory(), 443);
            Protocol.registerProtocol("https", myhttps);

MySSLSocketFactory代码如下 

package utils.handler;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.commons.httpclient.ConnectTimeoutException;
import org.apache.commons.httpclient.params.HttpConnectionParams;
import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;

public class MySSLSocketFactory implements ProtocolSocketFactory {
    static {
        System.out.println(">>>>in MySSLSocketFactory>>");
    }

    private SSLContext sslcontext = null;

    private SSLContext createSSLContext() {
        SSLContext sslcontext = null;
        try {
            sslcontext = SSLContext.getInstance("SSL");
            sslcontext.init(null,
                    new TrustManager[]{new TrustAnyTrustManager()},
                    new java.security.SecureRandom());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        }
        return sslcontext;
    }

    private SSLContext getSSLContext() {
        if (this.sslcontext == null) {
            this.sslcontext = createSSLContext();
        }
        return this.sslcontext;
    }

    public Socket createSocket(Socket socket, String host, int port,
                               boolean autoClose) throws IOException, UnknownHostException {
        return getSSLContext().getSocketFactory().createSocket(socket, host,
                port, autoClose);
    }

    public Socket createSocket(String host, int port) throws IOException,
            UnknownHostException {
        return getSSLContext().getSocketFactory().createSocket(host, port);
    }

    public Socket createSocket(String host, int port, InetAddress clientHost,
                               int clientPort) throws IOException, UnknownHostException {
        return getSSLContext().getSocketFactory().createSocket(host, port,
                clientHost, clientPort);
    }

    public Socket createSocket(String host, int port, InetAddress localAddress,
                               int localPort, HttpConnectionParams params) throws IOException,
            UnknownHostException, ConnectTimeoutException {
        if (params == null) {
            throw new IllegalArgumentException("Parameters may not be null");
        }
        int timeout = params.getConnectionTimeout();
        SocketFactory socketfactory = getSSLContext().getSocketFactory();
        if (timeout == 0) {
            return socketfactory.createSocket(host, port, localAddress,
                    localPort);
        } else {
            Socket socket = socketfactory.createSocket();
            SocketAddress localaddr = new InetSocketAddress(localAddress,
                    localPort);
            SocketAddress remoteaddr = new InetSocketAddress(host, port);
            socket.bind(localaddr);
            socket.connect(remoteaddr, timeout);
            return socket;
        }
    }

    private static class TrustAnyTrustManager implements X509TrustManager {

        public void checkClientTrusted(X509Certificate[] chain, String authType)
                throws CertificateException {
        }

        public void checkServerTrusted(X509Certificate[] chain, String authType)
                throws CertificateException {
        }

        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[]{};
        }
    }
}

运行结果如下 多了>>>>in MySSLSocketFactory>> 

7月9日
自动化框架调整

// json数组解析(用数组的比较少,不知道这边RD为啥选取这种格式,百度阿里没见过这种返回)
// 如果最外层的json为Object数组,则需要去掉json前后的[]为标准的json再去解析,左开右闭,从1开始算
if (jsonStr.startsWith("[")) {
    jsonStr = jsonStr.substring(1, jsonStr.length() - 1);
    System.out.println("重新解析后的jsonnew==" + jsonStr);
    JSONObject json = JSONObject.fromObject(jsonStr);
    System.out.println("json==" + json);

    // 再对json最外层解析
    for (Object k : json.keySet()) {
        Object v = json.get(k);
        map.put(k.toString(), v);
    }

    // 标准的json返回格式为{ key : val } 或 { key :  [] } 或 { key :  [ {key : value} ] }
    // [{},{},{}]是标准的json 但代码这样返回不好。最早公司返回过jsonarray
    // 一般公司的格式要求 {errno, data, errmsg}  {code, data, msg} 数据都是在data里
} else {

    // json最外层解析
    System.out.println("debug-jsonStr==" + jsonStr);
    JSONObject json = JSONObject.fromObject(jsonStr);

    for (Object k : json.keySet()) {
        Object v = json.get(k);
        map.put(k.toString(), v);
    }
}

return map;

一致性

 if (response.startsWith("[")) {
//                JSONArray responseJson = JSONArray.parseArray(response);
//                System.out.println("对于返回格式为JSONArray的,重新解析后的responseJson==" + responseJson);
                response = "{\"records\":" + response + "}";
                System.out.println("debug-对于返回格式为JSONArray的,重新解析后的返回结果response==" + response);

            }

            // 把String转为JSONObject
            JSONObject responseJson = JSONObject.parseObject(response);

            if (uriAbridge.equals("metricpmmetric")) {
                int type = cmo性能管理.responseToParse(i, responseJson, casename, uriAbridge);
                System.err.println(pass);
                log.info(pass);

5月28

https://域名/接口名?参数&entity_type=cluster|host|vm

上面的请求在浏览器请求会自动变为,在自动化框架需要对请求参数进行urlencode,否则会报错

https://域名/接口名?参数&entity_type=cluster%7Chost%7Cvm

请求里的 |   转换为 %7C

httpclient的封装

package utils.httpUtils;

public class HTTPCommonMethod {

    /**
     * get 请求,只需将变动的参数传入params中即可
     *
     * @param url
     * @param params
     * @return
     */
    public static String requestURL;

    // get请求 有参
    public static String doGet(String url, Map<String, String> params, int count) {
        try {
            // header必传参数Content-type
            Header header = new Header("Content-type", "application/json; charset=utf-8");

            String response = "";

            // HttpClient是Apache Jakarta Common下的子项目,用来提供高效的、最新的、功能丰富的支持HTTP协议的客户端编程工具包,并且它支持HTTP协议最新的版本和建议。
            // HttpClient已经应用在很多的项目中,比如Apache Jakarta上很著名的另外两个开源项目Cactus和HTMLUnit都使用了HttpClient。
            // 使用HttpClient发送请求、接收响应
            HttpClient httpClient = new HttpClient();

            if (url != null) {
                // NameValuePair是简单名称值对节点类型。多用于Java像url发送Post请求。在发送post请求时用该list来存放参数
//                getParamsList(url_online, params, count);
                // 预发环境value替换线上环境value
                List<NameValuePair> qparams_pre = getParamsList_pre(params);
                if (qparams_pre != null && qparams_pre.size() > 0) {
                    String formatParams = EncodingUtil.formUrlEncode(qparams_pre.toArray(new NameValuePair[qparams_pre.size()]),
                            "utf-8");
                    url = url.indexOf("?") < 0 ? url + "?" + formatParams : url + "&" + formatParams;

                }
                requestURL = url;

                System.out.println("第" + (count + 1) + "条日志,预发环境pre请求的url==" + url);
                GetMethod getMethod = new GetMethod(url);

                getMethod.addRequestHeader(header);
                // header必传参数Accept
                getMethod.addRequestHeader(new Header("Accept", "application/json; charset=utf-8"));


                // mec系统header需要传很多参数,cmo不需要
                if (url.contains("xx.xx.xx.xx")) {

//                getMethod.addRequestHeader(new Header("Content-type", "application/json; charset=utf-8"));
//                getMethod.addRequestHeader(new Header("Content-type", "application/json, text/plain, */*"));
                    //http协议,就协议本身而言,是一种文本协议。文本协议可读性比较好,但是协议所占的字节较多。目前http服务端响应普遍是json格式。其实是文本格式都行的。xml也算
                    //java栈会接触较多的xml。而且xml的表达性更强,尤其针对字符类型
                    //请求里的Header,Accept为标识返回协议json/xml的参数。在浏览器抓到的请求为xml但是自动化要改为Accept: application/json
                    //长连接的保活机制:  https://blog.csdn.net/qq_22642239/article/details/109024544

                    // token方法鉴权
//                    String token = "gAAAAABggAvwOexg5QnF6cmtpyTJgIZgs4nHi3TZ_S92gThuvKO1CDBZ8HV54KVRBalvlCvC-PdrobgmSo5340xWFGOY6gfCBFvmBO9OLVdHRO_ZXGeVwAylFkCKTbKvtMMKVbRmaQhGF-GsQqK7qP9guOW1Sg-2JHGGeJQPsXsLwPX_-4f6peQ";

//                    getMethod.addRequestHeader(new Header("X-Auth-Token", token));
//                    getMethod.addRequestHeader(new Header("token", token));

                    // header中添加特有参数
//                    getMethod.addRequestHeader(new Header("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36"));
                    // 注意,Project的value会根据域名而变化,如果改了请求地址需要在这里修改
                    getMethod.addRequestHeader(new Header("Project", "f9994991ce45499b941ed234bd207fc3"));

                    /*
                    4月21,自动化header传参里一定不能出现Accept-Encoding:gzip, deflate
                    header里的参数不能乱传,不需要的不要传。只需要传token X-Auth-Token Accept Project Content-type
                    因为没有规范的接口文档,从页面抓取接口做工具
                    自己写的bug导致查了整整一天。。。。
                    排查方法:放到2个框架运行此xml格式的接口,都在报错,乱码。但是其他接口正常,怀疑是传参造成的

                    建议:规范开发接口文档:接口名、传参
                    返回报错不正准确,Project老子传了,传的是正确的值。。老特么报Not fonund ProjectId误导老子
                    */

                    /**
                     *以下死亡代码千万要注释掉Accept-Encoding
                     * HTTP Header中Accept-Encoding 是浏览器发给服务器,声明浏览器支持的编码类型[1]
                     * 常见的有:
                     * Accept-Encoding: compress, gzip //支持compress 和gzip类型
                     * Accept-Encoding: //默认是identity
                     * 如果request中没有Accept-Encoding 那么服务器会假设所有的Encoding都是可以被接受的
                     * 每个接口请求传参中保存:Content-type 和 Accept
                     */
//                   getMethod.addRequestHeader(new Header("Accept-Encoding", "gzip, deflate"));

                    /*
                    token鉴权的2种方式,参考博客
                    https://blog.csdn.net/weixin_36260016/article/details/114089891
                    https://blog.iprac.cn/blogs/55.html
                    Http请求Authorization认证:传Authorization时选择Basic Auth 填写用户名 密码。此次header不需要填写token、X-Auth-Token,需要填写Project
                    token登录:header需要填写token、X-Auth-Token、Project。当然必不可少的Content-Type:application/json、Accept:application/json
                     */
                    // 以下是Authorization认证鉴权方式,注意value为Basic
                    String encoding = DatatypeConverter.printBase64Binary("admin:engine".getBytes("UTF-8"));  //username  password 自行修改  中间":"不可少
                    getMethod.addRequestHeader(new Header("Authorization", "Basic " + encoding));


                    System.out.println("mec带header的请求走到这里了==");

//                    int PRETTY_PRINT_INDENT_FACTOR = 4;
//                    String TEST_XML_STRING = response;
//                    String jsonPrettyPrintString = "";
//
//                    try {
//                        JSONObject xmlJSONObj = XML.toJSONObject(TEST_XML_STRING);
//                        jsonPrettyPrintString = xmlJSONObj.toString(PRETTY_PRINT_INDENT_FACTOR);
//                        System.out.println("200Response Code下xml转化为json后的格式==" + jsonPrettyPrintString);
//                    } catch (JSONException je) {
//                        System.out.println(je.toString());
//                    }

//                    response = jsonPrettyPrintString;


                }

//            if (null != header) {
//                Iterator var8 = header.entrySet().iterator();
//
//                while (var8.hasNext()) {
//                    Map.Entry<String, String> entry = (Map.Entry)var8.next();
//                    getMethod.addRequestHeader((String)entry.getKey(), (String)entry.getValue());
//                }
//            }
                //System.out.println(getMethod.getRequestHeader("User-Agent"));


                int statusCode = httpClient.executeMethod(getMethod);
                // 如果请求失败则打印出失败的返回码
                if (statusCode != 200) {
                    System.out.println("第" + statusCode + "【" + count + "】条日志,预发环境请求出错,错误码为=======" + statusCode);
                    return response;
                } else {
                    System.out.println("请求状态码statusCode=" + statusCode);
                }

                response = new String(getMethod.getResponseBody(), "utf-8");
//                System.out.println("response返回的内容:" + response);

            }
            return response;


        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;

    }


    /**
     * post请求
     */

    public static String doPost(String json, String URL) throws IOException { //json:请求url的参数
        String obj = null;
        // 创建默认的httpClient实例
        CloseableHttpClient httpclient = HttpClients.createDefault();
        // 创建httppost
        HttpPost httppost = new HttpPost(URL);

        // header必传参数Content-type、Accept
        httppost.addHeader("Content-type", "application/json; charset=utf-8");
        httppost.setHeader("Accept", "application/json");

        try {
            StringEntity strEntity = new StringEntity(json, Charset.forName("UTF-8"));  //对参数进行编码,防止中文乱码
            strEntity.setContentEncoding("UTF-8");
            httppost.setEntity(strEntity);
            CloseableHttpResponse response = httpclient.execute(httppost);
            try {
                //获取相应实体
                HttpEntity entity = response.getEntity();
                if (entity != null) {
                    obj = EntityUtils.toString(entity, "UTF-8");
                }

            } finally {
                response.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭连接,释放资源
            httpclient.close();
        }
        return obj;
    }


    // 参数格式化
    private static List<NameValuePair> getParamsList_pre(Map<String, String> paramsMap) {
        if (paramsMap != null && paramsMap.size() != 0) {
            List<NameValuePair> params = new ArrayList();
            Iterator var2 = paramsMap.entrySet().iterator();

            while (var2.hasNext()) {
                Map.Entry<String, String> map = (Map.Entry) var2.next();

                // 预发环境最新版本日志回放,请求参数打开以下if else,注释掉最后一行

                // 参数格式化,commons-httpclient自带的方法NameValuePair会自动将==转为=,还有特殊符号格式化
                // NameValuePair是简单名称值对节点类型。多用于Java像url发送Post请求。在发送post请求时用该list来存放参数
                params.add(new NameValuePair(map.getKey() + "", map.getValue() + ""));

//                params.add(new NameValuePair(map.getKey() + "", map.getValue() + ""));
            }

            return params;
        } else {
            return null;
        }
    }
}


5月22 

slf4j包冲突问题解决

SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/Users/qa/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/Users/qa/.m2/repository/org/apache/logging/log4j/log4j-slf4j-impl/2.7/log4j-slf4j-impl-2.7.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.

上边的大概意思是说logback-classic 包和slf4j-log4j12 包,关于org/slf4j/impl/StaticLoggerBinder.class 这个类发生了冲突
发生这个错误的原因,首先logback 日志的开发者和log4j 的开发者据说是一波人,而springboot 默认日志是,较新的logback 日志。但是在以前流行的日志却是log4j ,而且很多的第三方工具都含有log4j 得引入
而我们在项目开发中,难免会引入各种各样的工具包,所以,基本上springboot 项目,如果不注意,肯定会出现这种冲突的

解决办法:pom文件排除log4j 和 slf4j

<dependency>
    <groupId>com.qcloud</groupId>
    <artifactId>cos_api</artifactId>
    <version>5.2.4</version>
    <!--排除log4j以及slf4j自带的日志-->
    <exclusions>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
        </exclusion>
        <exclusion>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!--        以下打开后会引起和slf4j的包冲突-->
<!--        <dependency>-->
<!--            <groupId>org.apache.logging.log4j</groupId>-->
<!--            <artifactId>log4j-slf4j-impl</artifactId>-->
<!--            <version>2.7</version>-->
<!--        </dependency>-->

重新加载maven 不在报错,日志正常写入

拓展:logback-spring.xml配置不生成新日志文件

<fileNamePattern>${LOG_HOME}/info.%d{yyyy-MM-dd HH}-%i.log</fileNamePattern>

只要配置里有类似这个参数,就必须要带%d %i
<maxFileSize>500MB</maxFileSize>
因为这是按照文件大小进行切分,同一个日期可能会产生超出指定的文件大小的日志

如果去了大小限制,就是一天一个文件,即时超过500M也不会再进行切分了

Chapter 4: Appenders


maven报错解决 找到画波浪线的包,点击error 或者warn的图标 在控制台的详细信息找到报错信息 进入个人本地仓库/Users/qa/.m2/repository/删除报错的pom文件

点击报错的地址,如pom文件从阿里云官网(https://maven.aliyun.com/mvn下载到本地再上传到/Users/qa/.m2/repository/报错的路径下

org.springframework.session:spring-session-bom:pom:2021.0.0 failed to transfer from https://maven.aliyun.com/repository/public during a previous attempt. This failure was cached in the local repository and resolution is not reattempted until the update interval of aliyun-public has elapsed or updates are forced. Original error: Could not transfer artifact org.springframework.session:spring-session-bom:pom:2021.0.0 from/to aliyun-public (https://maven.aliyun.com/repository/public): transfer failed for https://maven.aliyun.com/repository/public/org/springframework/session/spring-session-bom/2021.0.0/spring-session-bom-2021.0.0.pom

进入/Users/qa/.m2/repository/org/springframework/session/spring-session-bom/2021.0.0 删除下载不下来后缀为LastUpdated的依赖

maven仓库中的LastUpdated文件生成原因及删除

重新刷新maven 

刷新后正常 不再飘红报错

如jsoup的1.11.3和1.13.3版本下载不下来可以在pom文件把要下载的依赖的版本改低

<!-- https://mvnrepository.com/artifact/org.jsoup/jsoup -->
<dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.8.3</version>
</dependency>

/Users/qa/.m2/repository/org/jsoup/jsoup/1.13.1


debug sql的时候注意数据类型,String类型的按 " ',' " 拼接  第一个' 为自己的字段结束的标识, 第二个' 为下个字段开始的标识     

int类型按 "," 拼接

5月21

java: Compilation failed: internal java compiler error

Preference - Java Compiler - 项目的版本选择 1.8 (之前为7)

5月20

springboot生成数据库相关配置:强烈推荐easy code(IDEA插件EasyCode 一键生成entity、controller、service、dao、mapper)

1 插件:Preference-Plugins-搜索easy code-安装此插件,重启idea

idea community 右侧无database

安装idea ultimate 右侧有database

但是 在社区版community的idea 代码中用@Slf4j  ,log.info  log.error这些都是好的。改为ultimate版后就如图了

@Slf4j可以识别,log变量不能识别

解决办法:Preference-Plugins-搜索 Hrisey Plugin(我的pom文件有Lombok),重启

解决办法:参考Intellij IDEA 识别不了@Slf4j和log的问题_小夏要科学进步-CSDN博客

下载完easy code,点击database

url填写 jdbc:mysql://ip:port/database_name

点击Test Connection 报错如下
[08S01] Communications link failure

The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
java.io.EOFException: Can not read response from server. Expected to read 4 bytes, read 0 bytes before connection was unexpectedly lost.

解决办法:翻Q导致的 ,把翻Q软件关闭即可

2 插件:Preference-Plugins-搜索idea-mybatis-generator-安装此插件,重启idea

重启完毕点击plugin-mybatis generator


 

注意url填写 jdbc:mysql://ip:port

如果连接数据库成功,但是里面看不到数据表,则点击刷新按钮--成功

右击数据表,点击EasyCode-Generate Code,弹框选择api 选择controller dao entity service的存放路径,勾选All、统一配置

点击yes

在src/main/java/frame业务目录下生成controller dao entity service Service DataCheckDefault.java

在src/main/resources/mapper 路径下生成DataCheckDao.xml文件,对数据表的增删改查


错误日志记录 fail为1 pass为2

    /**
     * 检查错误日志是否存在,对比结果入库
     */
    // 删除原始origin_log.json和error_log.json,防止日志重复写入
    public static int checkFailLogExist(String logpath) {

        // 1错误对比日志不为空,对比失败。2错误对比日志为空,对比成功

        try {
            // File.separator代表系统目录中的间隔符,说白了就是斜线 '\',不过有时候需要双线,有时候是单线,用这个静态变量就解决兼容问题了。

            // logpath为生成json结果对比的日志
            logpath = logpath.substring(logpath.indexOf("History") + 8, logpath.length() - 15);
//            System.out.println("logpath==" + logpath);
            File file_fail = new File(pathAllCheck + File.separator + logpath + "fail_log.txt");

            // 删除错误日志
            if (file_fail.exists()) {
//                System.out.println("错误对比日志不为空,对比失败");
                log.error("错误对比日志不为空,对比失败");
                return 1;

            } else {
                return 2;
            }


        } catch (Exception e) {
            e.printStackTrace();
        }

        return 0;
    }

效果

5月19

如果引用外部库  mysql-connector-java-8.0.版本的jar 则 jdbc驱动类:com.mysql.jdbc.Driver  改成 com.mysql.cj.jdbc.Driver

如果引用外部库  mysql-connector-java-5.0.版本的jar 则 jdbc驱动类:com.mysql.jdbc.Driver 

报错解决

java.lang.ClassNotFoundException: com.mysql.jdbc.Driver
 at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
 at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:355)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
 at java.lang.Class.forName0(Native Method)
 at java.lang.Class.forName(Class.java:264)
 at utils.jdbc.DBModel.reportAll(DBModel.java:106)
 at utils.jdbc.DBModel.main(DBModel.java:236)

pom文件配置如下

<!-- 2021.5新增https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.3.7</version>
</dependency>


<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<!--如果引用外部库  mysql-connector-java-8.0.版本的jar 则 jdbc驱动类:com.mysql.jdbc.Driver  改成 com.mysql.cj.jdbc.Driver-->
<!--如果引用外部库  mysql-connector-java-5.0.版本的jar 则 jdbc驱动类:com.mysql.jdbc.Driver -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.25</version>
</dependency>

依赖包没有下载下来,解决办法:翻Q  超级好用 研究了一下午,发现mysql依赖包竟然下载不下。。。


Maven使用中遇到的问题-Lambda expressions are not supported at language level '7' 

解决办法:

点击File|Project Structure..或者idea项目右上方的图标调整项目结构设置

将Sources中Language Level 由级别7调整到级别8(高于报错的级别)

完美解决

如遇com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure

com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure

The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
    at com.mysql.cj.jdbc.exceptions.SQLError.createCommunicationsException(SQLError.java:174)
    at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:64)
  
Caused by: com.mysql.cj.exceptions.CJCommunicationsException: Communications link failure

The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    ... 7 more
Caused by: java.io.EOFException: Can not read response from server. Expected to read 4 bytes, read 0 bytes before connection was unexpectedly lost.
    at com.mysql.cj.protocol.FullReadInputStream.readFully(FullReadInputStream.java:67)

解决办法:关闭代理/翻Q软件  参考以下博客

解决:com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure(真实有效)_向小凯同学学习-CSDN博客  解决:com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure(真实有效)


本地/Users/qa/.m2/repository有依赖包 但是代码的依赖包都不能正常引用了,不知道啥原因

1.查看一下maven的jar包仓库   .m2目录,看看下面有没有你用的包
2.看看pom.xml,查看有没有你用的jar包
3.是否有集成maven,安装之后,maven还需要设置好对你本地 lib目录jar的引入

4.如果最后还是不能解决,那就放大招,把.m2仓库的jar全部删除(可以打包备份),然后重启idea重新下载maven依赖

先备份下repository下的全部依赖包

将当前目录下所有文件和文件夹打包为oldjar.zip

zip -q -r oldjar.zip ./

Linux 删除除了某个文件之外的所有文件

ls |grep -v oldjar.zip |xargs rm -rf

4L的大招 给我解决了。我导入本地包,把maven库里的对应的资源都干掉了,还是报错。最后全部干掉,就行了

项目执行maven clean install

翻Q了果然下载依赖的速度快,66666666

5月18

完成数据库表结构设计

-- 查看数据表结构
desc api_job;

-- 数据表字段解释
explain api_job;

/**
使用explain可以分析mysql的查询性能,查看
表的读取顺序
数据读取操作的操作类型
哪些索引可以使用
哪些索引被实际使用
表之间的引用
每张表有多少行被优化器查询

开慢查询去记录一些执行时间比较久的SQL语句,找出这些SQL语句并不意味着完事了,些时我们常常用到explain这个命令来查看一个这些SQL语句的执行计划。
查看该SQL语句有没有使用上了索引,有没有做全表扫描,这都可以通过explain命令来查看
**/
explain select * from api_job limit 10;

-- 查看创建SQL的DDL语句
show create table api_test;

-- 数据一致性对比数据表结构设计
CREATE TABLE `data_check` (
  `id` int NOT NULL AUTO_INCREMENT,
  `build_number` varchar(1000) DEFAULT NULL COMMENT '版本号',
  `job_id` varchar(1000) DEFAULT NULL COMMENT '每次运行自动化生成的唯一标识',
    `cmo_pagename` varchar(1000) DEFAULT NULL COMMENT 'cmo的页面名称',
  `mec_pagename` varchar(1000) DEFAULT NULL COMMENT 'mec的页面名称',
  `cmo_interface` varchar(1000) DEFAULT NULL COMMENT 'cmo的接口名称',
  `mec_interface` varchar(1000) DEFAULT NULL COMMENT 'mec的接口名称',
    `is_same` int DEFAULT NULL COMMENT '两边接口数据是否一致',
    `is_pass` int DEFAULT NULL COMMENT '是否通过 0未通过 1已通过',
    `pass_num` int DEFAULT NULL COMMENT '通过数目 0 1',
    `cmo_response` longtext DEFAULT NULL COMMENT 'cmo的接口返回原始数据',
  `mec_response` longtext DEFAULT NULL COMMENT 'mec的接口返回原始数据',
    `diff_info` longtext DEFAULT NULL COMMENT '接口返回的具体差异',
  `comments` longtext DEFAULT NULL COMMENT '注释',
    `total` int DEFAULT NULL COMMENT '总的case数目',
  `start` long DEFAULT NULL COMMENT '每条case的开始时间',
  `end` long DEFAULT NULL  COMMENT '每条case的结束时间',
  `creat_time` varchar(100) DEFAULT NULL  COMMENT '入库时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `id_UNIQUE` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6011 DEFAULT CHARSET=utf8
COMMENT='数据一致性检查表'

5月14

参考一篇非常非常不错的博客    logback的使用和logback.xml详解 - 行走在云端的愚公 - 博客园

1. 基于spring boot使用logback.xml配置文件生成日志文件信息

记录代码运行过程中的全局日志,采用slf4j + logback.xml配置日志

在pom.xml添加依赖

<!-- logback配置,缺一不可。Logging dependencies -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.25</version>
</dependency>

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
    <version>1.2.3</version>
</dependency>

2. 在IDEA-> Preferences

> Preferences | Build, Execution, Deployment-> Compiler-> Annotation Processors-> 勾选Enable annotation processing

在pom添加依赖

<!--新增lombok 支持slf4j注解-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <scope>compile</scope>
    <version>1.18.16</version>
</dependency>

3. IDEA安装lombok插件并重启

Mac:IntelliJ IDEA -> Preferences -> Plugins

搜索Lombok   安装-重启idea

4. logback.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<!--根节点configuration scan: 当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true-->
<configuration scan="true">
    <property name="LOG_HOME" value="/Users/qa/Desktop/2021/code/cloud/logs"/>

    <!-- 日志编码 -->
    <property name="CHARSET" value="utf-8"></property>
    <!-- 日志文件的日志记录格式 精确到毫秒-->
    <property name="ERROR_PATTERN"
              value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L-%msg%n"/>
    <property name="INFO_PATTERN"
              value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L-%msg%n"/>
    <property name="WARN_PATTERN"
              value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L-%msg%n"/>
    <property name="TRACE_PATTERN"
              value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L-%msg%n"/>

    <!--appender>:负责写日志的组件,它有两个必要属性name和class。name指定appender名称,class指定appender的全限定名-->
    <appender name="stdout"
              class="ch.qos.logback.core.ConsoleAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} - [ %-5level ] [ %logger{50} : %line ] - %msg%n</pattern>
        </layout>
    </appender>

    <!-- 日志记录器,日期滚动记录 -->
    <appender name="FILEERROR"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${LOG_HOME}/error.log</file>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <!--滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件-->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--按照年月日,天配置,在每天凌晨的时候当天的日志生成以日命名的log 当天的不带日期后缀-->
            <fileNamePattern>${LOG_HOME}/error.%d{yyyy-MM-dd}-%i.log</fileNamePattern>

            <!--按照年月日时分,分钟配置,在每分钟的时候,日志生成以分钟命名的log 同一分钟的日志会一直在同一个文件持续写入-->
            <!--<fileNamePattern>${LOG_HOME}/error.%d{yyyy-MM-dd HH:mm}-%i.log</fileNamePattern>-->

            <!--按照年月日时分秒,秒配置,在每秒的时候,日志生成以秒命名的log -->
            <!--<fileNamePattern>${LOG_HOME}/error.%d{yyyy-MM-dd HH:mm:ss}-%i.log</fileNamePattern>-->

            <!--按照年月日时,小时配置,在每小时的时候,日志生成以小时命名的log 同一小时的日志会一直在同一个文件持续写-->
            <!--<fileNamePattern>${LOG_HOME}/error.%d{yyyy-MM-dd HH}-%i.log</fileNamePattern>-->

            <!--日志文件的大小,默认值是10MB-->
            <maxFileSize>500MB</maxFileSize>
            <maxHistory>100</maxHistory>
        </rollingPolicy>
        <!-- 追加方式记录日志 -->
        <append>true</append>
        <!-- 日志文件的格式 -->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${ERROR_PATTERN}</pattern>
            <charset>${CHARSET}</charset>
        </encoder>
        <!-- 此日志文件只记录error级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>error</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>


    <!-- 日志记录器,日期滚动记录 -->
    <appender name="FILEINFO"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${LOG_HOME}/info.log</file>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy
                class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_HOME}/info.%d{yyyy-MM-dd}-%i.log</fileNamePattern>

            <maxFileSize>500MB</maxFileSize>
            <maxHistory>100</maxHistory>
        </rollingPolicy>
        <!-- 追加方式记录日志 -->
        <append>true</append>
        <!-- 日志文件的格式 -->
        <encoder
                class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--<pattern>${ERROR_PATTERN}</pattern>-->
            <!--我改为了INFO_PATTERN-->
            <pattern>${INFO_PATTERN}</pattern>
            <charset>${CHARSET}</charset>
        </encoder>
        <!-- 此日志文件只记录info级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>info</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>


<!--    &lt;!&ndash; 日志记录器,日期滚动记录 &ndash;&gt;-->
<!--    <appender name="FILEWARN"-->
<!--              class="ch.qos.logback.core.rolling.RollingFileAppender">-->
<!--        &lt;!&ndash; 正在记录的日志文件的路径及文件名 &ndash;&gt;-->
<!--        <file>${LOG_HOME}/warn.log</file>-->
<!--        &lt;!&ndash; 日志记录器的滚动策略,按日期,按大小记录 &ndash;&gt;-->
<!--        <rollingPolicy-->
<!--                class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">-->
<!--            <fileNamePattern>${LOG_HOME}/warn.%d{yyyy-MM-dd HH}-%i.log</fileNamePattern>-->

<!--            <maxFileSize>500MB</maxFileSize>-->
<!--            <maxHistory>100</maxHistory>-->
<!--        </rollingPolicy>-->
<!--        &lt;!&ndash; 追加方式记录日志 &ndash;&gt;-->
<!--        <append>true</append>-->
<!--        &lt;!&ndash; 日志文件的格式 &ndash;&gt;-->
<!--        <encoder-->
<!--                class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">-->
<!--            <pattern>${WARN_PATTERN}</pattern>-->
<!--            <charset>${CHARSET}</charset>-->
<!--        </encoder>-->
<!--        &lt;!&ndash; 此日志文件只记录warn级别的 &ndash;&gt;-->
<!--        <filter class="ch.qos.logback.classic.filter.LevelFilter">-->
<!--            <level>warn</level>-->
<!--            <onMatch>ACCEPT</onMatch>-->
<!--            <onMismatch>DENY</onMismatch>-->
<!--        </filter>-->
<!--    </appender>-->


<!--    &lt;!&ndash; 日志记录器,日期滚动记录 &ndash;&gt;-->
<!--    <appender name="FILETRACE"-->
<!--              class="ch.qos.logback.core.rolling.RollingFileAppender">-->
<!--        &lt;!&ndash; 正在记录的日志文件的路径及文件名 &ndash;&gt;-->
<!--        <file>${LOG_HOME}/trace.log</file>-->
<!--        &lt;!&ndash; 日志记录器的滚动策略,按日期,按大小记录 &ndash;&gt;-->
<!--        <rollingPolicy-->
<!--                class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">-->
<!--            <fileNamePattern>${LOG_HOME}/trace.%d{yyyy-MM-dd HH}-%i.log</fileNamePattern>-->

<!--            <maxFileSize>500MB</maxFileSize>-->
<!--            <maxHistory>100</maxHistory>-->
<!--        </rollingPolicy>-->
<!--        &lt;!&ndash; 追加方式记录日志 &ndash;&gt;-->
<!--        <append>true</append>-->
<!--        &lt;!&ndash; 日志文件的格式 &ndash;&gt;-->
<!--        <encoder-->
<!--                class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">-->
<!--            <pattern>${TRACE_PATTERN}</pattern>-->
<!--            <charset>${CHARSET}</charset>-->
<!--        </encoder>-->
<!--        &lt;!&ndash; 此日志文件只记录trace级别的 &ndash;&gt;-->
<!--        <filter class="ch.qos.logback.classic.filter.LevelFilter">-->
<!--            <level>trace</level>-->
<!--            <onMatch>ACCEPT</onMatch>-->
<!--            <onMismatch>DENY</onMismatch>-->
<!--        </filter>-->
<!--    </appender>-->


    <root level="INFO">
        <appender-ref ref="FILEERROR"/>
        <appender-ref ref="FILEINFO"/>
        <appender-ref ref="FILEWARN"/>
        <appender-ref ref="FILETRACE"/>
    </root>

</configuration>

发现在实际运用的时候,日志无法按日期分割,所有日志都在第一天部署的那个日期的文件里面(开始配置的按天维度切割)

参考博客 logback日志无法按日期分割的问题 - 汪神 - 博客园

区别就是文件名的fileNamePattern是怎么设置的,可以直接按年月日配置,序号i是给其他切换文件策略用的。如果秒够用,不加i一秒产生很多日志,引发一秒内切换文件,也会导致日志文件切换出问题

按照小时切割日志

当前小时(如17:45)的日志写在info.log  error.log 超过当前小时(如此时为18:01),程序重命名为按照小时命名的日志info.2021-05-11 17-0.log  error.2021-05-11 17-0.log

当前日(如今天为5月14日)的日志写在info.log  error.log 超过当前日(如第二天为5月15日 00:01),程序重命名为按照日命名的日志info.2021-05-14 0.log  error.2021-05-14 0.log

5月15的日志写在info.log  error.log

5. 实际应用,在主方法程序的类名上面加注解@Slf4j  加完可以用log.error 等存日志

注解就是lombok自动帮你放一个log对象

如果不加@Slf4j注解 ,则需要在每个类下方添加

private static Logger log = LoggerFactory.getLogger(caseAllRun.class);   类名.class 

导包为

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

其实和方法1一样的,2种写法

import lombok.extern.slf4j.Slf4j; // 和@Slf4j一起使用
//import org.slf4j.Logger;           和Logger一起使用
//import org.slf4j.LoggerFactory;    和LoggerFactory一起使用

@Slf4j
public class caseAllRun {
//    private static Logger log = LoggerFactory.getLogger(caseAllRun.class);

    public static void main(String[] args) {
        log.error("错误-------");
        log.info("正确------");
        log.debug("测试----");

        startSearch();
    }

效果

=======================================================================================

遇到的问题以及解决

1. 运行主函数代码过程中,控制台报错分析(logback报错ch.qos.logback.core.joran.util.PropertySetter)

14:31:43,201 |-ERROR in ch.qos.logback.core.joran.util.PropertySetter@6c3708b3 - Failed to invoke valueOf{} method in class [ch.qos.logback.core.util.FileSize] with value [500M]
14:31:43,201 |-WARN in ch.qos.logback.core.joran.util.PropertySetter@6c3708b3 - Failed to set property [maxFileSize] to value "500M".  ch.qos.logback.core.util.PropertySetterException: Conversion to type [class ch.qos.logback.core.util.FileSize] failed.

参考博客  spring boot logback-spring.xml maxFileSize not working with SizeAndTimeBasedRollingPolicy - HelloJava菜鸟社区

解决办法:分析具体报错信息,value[500M] 修改logback.xml配置的maxFileSize为500MB

<appender>

    <rollingPolicy>

        <maxFileSize>500MB</maxFileSize>

    </rollingPolicy>

</appender>

2. 运行主函数代码过程中,控制台报错分析(logback报错java.lang.IllegalArgumentException: All tokens consumed but was expecting "}"

fileNamePattern多写了{  两个{

4月28

蛋疼,改框架了,之前辛苦设计的方法用不到了,舍不得删掉。。。留念。  改为配置文件读取

// 获取当前方法名
String getfn = Thread.currentThread().getStackTrace()[1].getMethodName();
// 获取当前类名
String getcn = Thread.currentThread().getStackTrace()[1].getClassName();
System.out.println("getcn==" + getcn);
// 整个类名为com.interfaceframe.bg.testcase.dataCheck.cmo边缘节点1
// dataCheck.的长度为10 截取dataCheck文件夹后面的类名作为日志名称
String geneLogName = getcn.substring(getcn.indexOf("dataCheck") + 10, getcn.length());
System.out.println("geneLogName==" + geneLogName);

// 每次生成日志文件前删除上一次的log,只保留最新的运行日志
FileWrite.deleteAllJsonLogFile(geneLogName);

接口请求改为配置形式,fileOpera新增caseRead类

package fileOperate;

import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;

public class caseRead {

    // 定义casePath的全局路径
    public static String casePath = "/Users/qa/Desktop/2021/code/cloud/cloud-api/src/main/java/dataCheck/case.txt";

    public static void main(String[] args) {

        System.out.println(casePath);
        String[] strings = readTxt(casePath);
        System.out.println("读到的文件数组为:" + Arrays.toString(strings));
    }

    /**
     * 返回数组,文本以,拆分
     *
     * @param casePath
     * @return
     */
    public static String[] readTxt(String casePath) {
        StringBuilder builder = new StringBuilder();
        try {
            File file = new File(casePath);
            if (file.isFile() && file.exists()) {
                InputStreamReader isr = new InputStreamReader(new FileInputStream(file), "utf-8");
                BufferedReader br = new BufferedReader(isr);
                String lineTxt;

                int num = 0;
                long time1 = System.currentTimeMillis();

                // 定义读取的文件中含有#的初始值,即注释行
                boolean flag1 = false;
                // 定义注释行的个数
                int commentsLineNum = 0;

                while ((lineTxt = br.readLine()) != null) {
                    // 如果不想在控制台打印读取出来的文件内容,则注释掉下面的打印语句
//                    System.out.println(lineTxt);

                    // 最终打印出的数据,不含有注释行
                    ArrayList<String> outputContents = new ArrayList<String>();
                    // 如果读取的行包含# 则从list删除
                    if (lineTxt.contains("#")) {

                        if (flag1) {
                            for (int i = 0; i < commentsLineNum; i++) {
                                outputContents.remove(outputContents.size() - 1);
                            }
                            commentsLineNum = 0;
                        } else {
                            flag1 = true;
                        }
                    } else {
                        // 如果不包含注释行,则添加到最终的结果里。追加到builder
                        outputContents.add(lineTxt);
                        commentsLineNum++;

                        builder.append(lineTxt);
//                    builder.append(",");
                        num++;
//                    System.out.println("总共" + num + "条数据!");
                    }

                    for (String outputContent : outputContents) {
//                        System.out.println("测试"+outputContent);
                    }

                }
                //System.out.println("总共"+num+"条数据!");
                long time2 = System.currentTimeMillis();
                long time = time1 - time2;
//                System.out.println("共花费" + time + "秒");
                br.close();
            } else {
                System.out.println("文件不存在!");
            }
        } catch (Exception e) {
            System.out.println("文件读取错误!");
        }

        // builder内容返回为数组
        String[] strings = builder.toString().split(",");

        return strings;
    }


    /**
     * 返回String,文本以,拆分
     *
     * @param casePath
     * @return
     */
    public static String getStringTxt(String casePath) {
        StringBuilder builder = new StringBuilder();
        try {
            File file = new File(casePath);
            if (file.isFile() && file.exists()) {
                InputStreamReader isr = new InputStreamReader(new FileInputStream(file), "utf-8");
                BufferedReader br = new BufferedReader(isr);
                String lineTxt = null;
                int num = 0;
                long time1 = System.currentTimeMillis();
                while ((lineTxt = br.readLine()) != null) {
                    // 如果不想在控制台打印读取出来的文件内容,则注释掉下面的打印语句
//                    System.out.println(lineTxt);
                    builder.append(lineTxt);
//                    builder.append(",");
                    num++;
//                    System.out.println("总共" + num + "条数据!");
                }
                //System.out.println("总共"+num+"条数据!");
                long time2 = System.currentTimeMillis();
                long time = time1 - time2;
//                System.out.println("共花费" + time + "秒");
                br.close();
            } else {
                System.out.println("文件不存在!");
            }
        } catch (Exception e) {
            System.out.println("文件读取错误!");
        }
        return builder.toString();
    }


    /**
     * 返回数组,文本&&&&拼接以{拆分
     *
     * @param casePath
     * @return
     */
    public static String[] getMap(String casePath) {
        StringBuilder builder = new StringBuilder();
        try {
            File file = new File(casePath);
            if (file.isFile() && file.exists()) {
                InputStreamReader isr = new InputStreamReader(new FileInputStream(file), "utf-8");
                BufferedReader br = new BufferedReader(isr);
                String lineTxt = null;
                int num = 0;
                long time1 = System.currentTimeMillis();
                while ((lineTxt = br.readLine()) != null) {
                    // 如果不想在控制台打印读取出来的文件内容,则注释掉下面的打印语句
//                    System.out.println(lineTxt);
                    builder.append(lineTxt);
                    // 行与行之间按&&&& 分割
                    builder.append("&&&&");

                    num++;
//                    System.out.println("总共" + num + "条数据!");
                }

//                System.err.println("builder==" + builder);

                //System.out.println("总共"+num+"条数据!");
                long time2 = System.currentTimeMillis();
                long time = time1 - time2;
//                System.out.println("共花费" + time + "秒");
                br.close();
            } else {
                System.out.println("文件不存在!");
            }
        } catch (Exception e) {
            System.out.println("文件读取错误!");
        }
        String[] strings = builder.toString().split("&&&&");
        return strings;
    }


}


4月27

-- 两张表求和
Select sum(a.numa+b.numb) FROM a JOIN b ON a.id=b.id;


4月26

取2个系统的接口,完成接口结果公共字段格式的封装,结果存为JSONArray的日志文件,以代码类名命名

完成对日志文件的封装:仅读、仅写、写且删除、仅删除

完成对接口json结果、json对比结果的格式存储。按照日志文件类型封装不同的方法(json、report)。按照结果类型封装不同的方法(origin、error )

4月25

String转JSONObject  JSONObject转JSONArray 

问题描述:
将一个JSONObject add到一个JSONArray中两次,讲JSONArray put到数据库中,然后使用get请求,获取这个JSONArray后,第二个JSONObject出现奇怪符号,如{"$ref":"$[0]"},{"$ref":"$[0]"}]

问题出现原因:
JSONArray如果add同一个元素(比如a)两次及以上时,只有第一次add a时存放数据,其它位置,存放指向第一次add的a在JSONArray中的位置指针。

Java JSONArray的一个坑_家住海边,见过大风大浪-CSDN博客_java jsonarray put

拿着代码调来调去,粗心导致犯的错。。。

调整后

                            /* 创建JSONObject对象,把key value放到JSONObject
                             JSONObject为每次创建出来的对象,不要设置为全局,加班加的蒙圈了,开始放到了for循环外层。。。https://blog.csdn.net/u014487025/article/details/82711925
                            JSONArray如果add同一个元素(比如a)两次及以上时,只有第一次add a时存放数据,其它位置,存放指向第一次add的a在JSONArray中的位置指针
                            */

                            /* 按放到JSONObject的顺序(如 name address cluster status )打印JSONObject/JSONArray。只需要在创建json对象的时候,后面ordered传true
                             之前一直报错是因为之前用的jar包方式,不支持true。改为maven配置后就可以了。。。默认不传true,则会按照value的名称排序(目前看是,具体要看JSONObject怎么定义的)
                             */

                            JSONObject jo = new JSONObject(true);

                            jo.put("name", name);
                            jo.put("address", address);
                            jo.put("cluster", cluster);
                            jo.put("status", status);
                            System.out.println("debug-jo第" + i + "个数组==" + jo);

                            // 把JSONObject放到JSONOArray
                            ja.add(jo);

//                            ja = JSON.parseArray("[" + jo.toString() + "]");
//                            System.out.println("debug-ja==" + ja);


//                            FileWrite.deleteAllLogFile(getcn);

                        }

                        System.out.println("debug-ja最终存入log的格式==" + ja);

                        FileWrite.originLogOnlyWrite(ja.toString(), getcn);



HttpClient关闭控制台的DEBUG输出-亲测可用2021  参考我之前写的博客

HttpClient关闭控制台的DEBUG输出-亲测可用2021_QA杉哥的博客-CSDN博客

如果你的工程是非父子结构的则

在/Users/qa/Desktop/2021/code/cloud/cloud-api/src/main/resources/   下新建logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
    <!-- definition of appender STDOUT -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%-4relative [%thread] %-5level %logger{35} - %msg %n</pattern>
        </encoder>
    </appender>

    <root level="ERROR">
        <!-- appender referenced after it is defined -->
        <appender-ref ref="STDOUT"/>
    </root>
</configuration>

4月23

选取Spring Boot框架

生成地址见官网Spring Boot   点击Spring Initializr   进入https://start.spring.io/

            

点击generate 生成jar包,解压导入到IDEA即可

1 和新公司的开发要setting.xml 每个公司独有的一份配置哈
2 修改默认的pom.xml配置,默认的java.version是11,改为8即jdk1.8.0版本(本地版本)否则会报错(IDEA 启动时报 error:java 无效的源发行版11,解决办法参考填坑之路!IDEA 启动时报 error:java 无效的源发行版11_大誌的博客-CSDN博客 )

error:java 无效的源发行版11 解决方案

2.1 打开右上角 projectStructure 修改Project 和 Modules下的版本 (某度到只有这一步,我的还不行)

2.2 打开Setting->Build,Exectuion,Deployment->Compiler->Java compiler 设置版本,保存重新编译ok

2.3 maven配置pom.xml里source和target设置为8

3 修改默认的pom.xml配置,修改artifactId的值为cloud-parent 父项目(可以不改 改了更清晰)

              

新增的工具类无法导入,一直报错,很奇怪的是import的时候不联想,后来啥都没干,联想方式导入(utils.handler.*)就突然又可以了。。。

4月22

token鉴权的2种方式,参考博客

Http请求Authorization认证:传Authorization时选择Basic Auth 填写用户名 密码。此次header不需要填写token、X-Auth-Token,需要填写Project

token登录:header需要填写token、X-Auth-Token、Project。当然必不可少的Content-Type:application/json、Accept:application/json

java http请求验证_具有基本身份验证Java的HTTP请求_阿拉兔儿蕾的博客-CSDN博客

Java:httpUrlConnection在请求头 (Header)中添加 Authorization 身份认证信息-爱实践

String encoding = DatatypeConverter.printBase64Binary("username:passwd".getBytes("UTF-8"));  //username  password 自行修改  中间":"不可少
getMethod.addRequestHeader(new Header("Authorization", "Basic " + encoding));

HTTP Header 详解 -Requests部分

Header解释示例
Accept指定客户端能够接收的内容类型Accept: text/plain, text/html
Accept-Charset浏览器可以接受的字符编码集。Accept-Charset: iso-8859-5
Accept-Encoding指定浏览器可以支持的web服务器返回内容压缩编码类型。Accept-Encoding: compress, gzip
AuthorizationHTTP授权的授权证书Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
CookieHTTP请求发送时,会把保存在该请求域名下的所有cookie值一起发送给web服务器。Cookie: $Version=1; Skin=new;
Content-Length请求的内容长度Content-Length: 348
Content-Type请求的与实体对应的MIME信息Content-Type: application/x-www-form-urlencoded
Expect请求的特定的服务器行为Expect: 100-continue
From发出请求的用户的EmailFrom: user@email.com
Host指定请求的服务器的域名和端口号Host: www.zcmhi.com

4月21

接口运行结果代码乱码


a���[��
�8N���]��3�K�Eʸe�����a�;(�ĕͱ:9���JUeg�zw^
�\ϵu`:�G�ǏK�1r�<]�4�F'n� ��a��8���/�������-����?��    ��UV
a���[��
�8N���]��3�K�Eʸe�����a�;(�ĕͱ:9���JUeg�zw^
�\ϵu`:�G�ǏK�1r�<]�4�F'n� ��a��8���/�������-����?��    ��UV四月 21, 2021 8:19:03 下午 org.apache.commons.httpclient.HttpMethodBase getResponseBody
a���[��
�8N���]��3�K�Eʸe�����a�;(�ĕͱ:9���JUeg�zw^
�\ϵu`:�G�ǏK�1r�<]�4�F'n� ��a��8���/�������-����?��    ��UV

http协议,就协议本身而言,是一种文本协议。文本协议可读性比较好,但是协议所占的字节较多。目前http服务端响应普遍是json格式。其实是文本格式都行的。xml也算

java栈会接触较多的xml。而且xml的表达性更强,尤其针对字符类型

请求里的Header,Accept为标识返回协议json/xml的参数。在浏览器抓到的请求为xml但是自动化要改为Accept: application/json

长连接的保活机制:  https://blog.csdn.net/qq_22642239/article/details/109024544

解决办法:json xml反序列化转换、把xml转化为json格式

postman 浏览器之所以支持xml转json是因为工具本身支持 自动化的话需要自己封装框架

参考 Quickest way to convert XML to JSON in Java 

 Quickest way to convert XML to JSON in Java - Stack Overflow

  • 先接收接口的原始格式响应,也就是xml格式响应
  • 将xml 转为java 的对象数据类型,需要下载jar包
  • 如果还需要转换成json对象的话,再序列化一下即可,序列化也有jar包

导入的maven配置

<!--4月23新增把xml转化为json格式-->
<dependency>
    <groupId>org.json</groupId>
    <artifactId>json</artifactId>
    <version>20180813</version>
</dependency>

import org.json.JSONObject;
import org.json.XML;
import org.json.JSONException;

public class Main {

    public static int PRETTY_PRINT_INDENT_FACTOR = 4;
    public static String TEST_XML_STRING =
        "<?xml version=\"1.0\" ?><test attrib=\"moretest\">Turn this to JSON</test>";

    public static void main(String[] args) {
        try {
            JSONObject xmlJSONObj = XML.toJSONObject(TEST_XML_STRING);
            String jsonPrettyPrintString = xmlJSONObj.toString(PRETTY_PRINT_INDENT_FACTOR);
            System.out.println(jsonPrettyPrintString);
        } catch (JSONException je) {
            System.out.println(je.toString());
        }
    }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
非常感谢您的提问。可以为您提供一个基于 Spring Boot 和 Forest HTTP 框架调用京东云批量发送短信 OpenAPI 的示例代码。代码如下: ```java import java.io.IOException; import java.security.NoSuchAlgorithmException; import java.util.*; import org.springframework.stereotype.Service; import com.dtflys.forest.backend.HttpBackend; import com.dtflys.forest.backend.httpclient.HttpclientBackend; import com.dtflys.forest.config.ForestConfiguration; import com.dtflys.forest.converter.json.ForestJsonConverter; import com.dtflys.forest.converter.xml.ForestXmlConverter; import com.dtflys.forest.exceptions.ForestRuntimeException; import com.dtflys.forest.http.ForestRequest; import com.dtflys.forest.http.ForestResponse; import com.dtflys.forest.interceptor.Interceptor; import com.dtflys.forest.retryer.BackOffRetryer; import com.dtflys.forest.utils.ForestDataType; import com.dtflys.forest.utils.ReflectUtils; @Service public class JDCloudSmsService { private final String appKey = "your_app_key"; private final String secret = "your_secret"; private final String smsApiHost = "sms.jcloud.com"; private final String smsApiPath = "/v1/sms/sendBatchSMS"; private final ForestConfiguration configuration; public JDCloudSmsService() { configuration = ForestConfiguration.configuration(); configuration.setCacheEnabled(false); configuration.setBackend(new HttpclientBackend()); configuration.setRetryer(new BackOffRetryer()); configuration.setJsonConverter(new ForestJsonConverter()); configuration.setXmlConverter(new ForestXmlConverter()); configuration.setVariableValue("appKey", appKey); } public void sendBatchSms(List<String> mobiles, String signName, String templateId, Map<String, String> params) throws NoSuchAlgorithmException, IOException { // 构建 Forest 请求对象 ForestRequest request = configuration.createRequest(); // 设置请求地址 request.url("https://" + smsApiHost + smsApiPath); // 设置请求头 Map<String, Object> headers = new HashMap<>(); headers.put("X-JCloud-App-Key", appKey); headers.put("X-JCloud-Request-Nonce", UUID.randomUUID().toString()); headers.put("X-JCloud-Request-Timestamp", String.valueOf(System.currentTimeMillis())); headers.put("Content-Type", "application/json;charset=UTF-8"); headers.put("Authorization", getAuthorizationHeaderValue(secret, appKey, headers")); request.headers(headers); // 设置请求参数 Map<String, Object> map = new HashMap<>(); map.put("mobiles", mobiles); map.put("signName", signName); map.put("templateId", templateId); map.put("templateParams", params); request.body(map, ForestDataType.JSON); // 发送请求 ForestResponse response = request.post(); // 处理响应 if (response.isError()) { throw new ForestRuntimeException("JDCloud SMS API error: " + response.getContent()); } else { System.out.println("JDCloud SMS API response: " + response.getContent()); } } private String getAuthorizationHeaderValue(String secret, String appKey, Map<String, Object> headers) throws NoSuchAlgorithmException { StringBuilder sb = new StringBuilder(); sb.append("JCloud ").append("appKey=").append(appKey).append(","); sb.append("signatureMethod=").append("HmacSHA256").append(","); sb.append("timestamp=").append(headers.get("X-JCloud-Request-Timestamp")).append(","); sb.append("nonce=").append(headers.get("X-JCloud-Request-Nonce")).append(","); String queryString = ReflectUtils.toQueryString(headers, false); sb.append(queryString); return sb.toString(); } } ``` 该示例代码中,我们使用了 Forest HTTP 框架来进行 HTTP 请求的构建和发送,并使用了 Spring Boot 框架来管理该服务。 具体实现步骤如下: 1. 创建 Forest 配置对象 `configuration`,设置 Forest 配置信息,包括 HTTP 请求后端实现、JSON/XML 转换器、重试机制等; 2. 根据京东云短信 OpenAPI 的要求,构建 HTTP 请求对象 `request`,并设置请求地址、请求头、请求参数等; 3. 发送 HTTP POST 请求,并获取响应对象 `response`; 4. 解析响应内容,根据业务逻辑进行处理。在该示例代码中,如果响应的状态码为错误状态,直接抛出 `ForestRuntimeException` 异常,否则打印响应内容。 注意:示例代码中的 `appKey` 和 `secret` 需要您自行替换为您在京东云账号中创建的短信应用的 `appKey` 和 `secret`。 希望这个示例能对您有所帮助,如果您有任何问题或建议,欢迎与我随时交流。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

东方狱兔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值