由于项目中使用nacos做配置中心,测试环境和生产环境经常出现配置不同导致程序问题。为避免以后此类事情出现,所以要做一个文本对比小工具。

把工程打包成 可执行  jar,mian方法运行,执行完毕自动打开浏览器输出对比文本内容。

参考上一篇文章: https://www.cnblogs.com/meow-world/articles/15209517.html

1.创建maven工程

2.创建入口main方法类

3.读取main方法参数中外部配置文件

4.读取nacos测试和生产配置文件

5.对比文本内容

6.展示对比后有差异的文本

1.创建maven工程

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>aicloud.com</groupId>
    <artifactId>DiffTools</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>    <dependencies>
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>4.9.0</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-core</artifactId>
            <version>5.7.10</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.74</version>
        </dependency>
        <!--对比工具依赖-->
        <dependency>
            <groupId>io.github.java-diff-utils</groupId>
            <artifactId>java-diff-utils</artifactId>
            <version>4.9</version>
        </dependency>
    </dependencies>
</project>

2.创建入口main方法类

package diff;
import diff.nacos.DiffContent;
import diff.nacos.ReadLocalConfig;
import diff.nacos.ReadResources;
import diff.nacos.ShowDiff;
import java.io.IOException;
 * @date 2021/8/27
public class TestMain {
    private static String path = "D:/temp/test.html";
    public static void main(String[] args) throws IOException {
        String configPath = args[0];
        ReadLocalConfig.readLocalConfig(configPath);
        ReadResources.readNacosConfig();
        DiffContent.diff();
        ShowDiff.show(path);

3.读取main方法参数中外部配置文件

public class ReadLocalConfig {
    public static String nacosTestUrl;
    public static String nacosTestNameSpaceId;
    public static String nacosProdUrl;
    public static String nacosProdNameSpaceId;
    public static void readLocalConfig(String configPath) throws IOException {
        File file = new File(configPath);
        InputStream inputStream = new FileInputStream(file);
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        String next;
        while ((next = bufferedReader.readLine()) != null) {
            if (StrUtil.isNotBlank(next)) {
                int index = next.indexOf(":") + 1;
                String substring = next.substring(index).trim();
                if (next.startsWith("nacosTestUrl")) {
                    nacosTestUrl = substring;
                if (next.startsWith("nacosTestNameSpaceId")) {
                    nacosTestNameSpaceId = substring;
                if (next.startsWith("nacosProdUrl")) {
                    nacosProdUrl = substring;
                if (next.startsWith("nacosProdNameSpaceId")) {
                    nacosProdNameSpaceId = substring;
        if (StrUtil.isBlank(nacosTestUrl)) {
            throw new RuntimeException("配置文件地址错误");

4.读取nacos测试和生产配置

package diff.nacos;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import diff.nacos.model.NacosPerConfig;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static diff.nacos.ReadLocalConfig.*;
 * 读取nacos命令空间下的内容
 * @author wangzhihui
 * @date 2021/8/27
public class ReadResources {
    //读取到的配置问价集合
    //key dataId : value: 文本内容
    public static Map<String, List<String>> configTestMap = new HashMap<>();
    public static Map<String, List<String>> configProdMap = new HashMap<>();
    public static OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
    public static void readNacosConfig() throws IOException {
        List<NacosPerConfig> testList = doRequest(nacosTestUrl, nacosTestNameSpaceId);
        List<NacosPerConfig> prodList = doRequest(nacosProdUrl, nacosProdNameSpaceId);
        testList.forEach(item -> configTestMap.put(item.getDataId(), Arrays.asList(item.getContent().split("\n"))));
        prodList.forEach(item -> configProdMap.put(item.getDataId(), Arrays.asList(item.getContent().split("\n"))));
  private static List<NacosPerConfig> doRequest(String url, String nameSpaceId) throws IOException {
        HttpUrl.Builder builder = HttpUrl.parse(url).newBuilder();
        builder.addQueryParameter("dataId", null);
        builder.addQueryParameter("group", null);
        builder.addQueryParameter("appName", null);
        builder.addQueryParameter("config_tags", null);
        builder.addQueryParameter("pageNo", "1");
        builder.addQueryParameter("pageSize", "50");
        builder.addQueryParameter("tenant", nameSpaceId);
        builder.addQueryParameter("search", "accurate");
        Request request = new Request.Builder()
                .url(builder.build())
                .get()
                .build();
        Response response = okHttpClient.newCall(request).execute();
        String jsonStr = response.body().string();
        JSONObject jsonObject = JSON.parseObject(jsonStr, JSONObject.class);
        List<NacosPerConfig> list = jsonObject.getObject("pageItems", new TypeReference<List<NacosPerConfig>>() {
        return list;

5.对比文本内容

import cn.hutool.core.collection.CollectionUtil;
import com.github.difflib.DiffUtils;
import com.github.difflib.patch.AbstractDelta;
import com.github.difflib.patch.Chunk;
import com.github.difflib.patch.Patch;
import java.util.*;
public class DiffContent {
    public static Map<String, List<String>> ERROR_MAP = new HashMap<>();
    public static void diff() {
        ReadResources.configTestMap.forEach((k, original) -> {
            List<String> revised = ReadResources.configProdMap.get(k);
            if (CollectionUtil.isEmpty(revised)) {
                ERROR_MAP.put(k, Arrays.asList("--- 缺失"));
            if (CollectionUtil.isNotEmpty(revised)) {
                diffDetail(revised, original, k);
public static void diffDetail(List<String> original, List<String> revised, String dataId) {
        Patch<String> diff = DiffUtils.diff(original, revised);
        List<AbstractDelta<String>> deltas = diff.getDeltas();
        List<String> errorList = new LinkedList<>();
        deltas.forEach(delta -> {
            switch (delta.getType()) {
                case INSERT:
                    //新增
                    Chunk<String> insert = delta.getTarget();
                    int position = insert.getPosition();
                    String errorInsert = "+++ " + (position + 1) + " " + insert.getLines();
                    errorList.add(errorInsert);
                    break;
                case CHANGE://修改
                    Chunk<String> source = delta.getSource();
                    Chunk<String> target1 = delta.getTarget();
                    String errorChange = "+-- " + (source.getPosition() + 1) + " " + source.getLines() + "\n+ " + "" + (target1.getPosition() + 1) + " " + target1.getLines();
                    errorList.add(errorChange);
                    break;
                case DELETE:
                    //删除
                    Chunk<String> delete = delta.getSource();
                    String errorDelete = "--- " + (delete.getPosition() + 1) + " " + delete.getLines();
                    errorList.add(errorDelete);
                    break;
                case EQUAL:
                    break;
        ERROR_MAP.put(dataId, errorList);

6.展示对比后有差异的文本

import cn.hutool.core.collection.CollectionUtil;
import java.awt.*;
import java.io.*;
import java.net.URI;
import java.nio.charset.StandardCharsets;
public class ShowDiff {
    public static void show(String path) throws IOException {
     //打开默认浏览器
if (Desktop.isDesktopSupported()) { writeData(path); URI uri = URI.create(path); Desktop desktop = Desktop.getDesktop(); if (desktop.isSupported(Desktop.Action.BROWSE)) { desktop.browse(uri); private static void writeData(String path) throws IOException { File file = createFile(path); FileOutputStream fos = new FileOutputStream(file); OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8); osw.write(appendHtmlData()); osw.flush(); @SuppressWarnings("all") private static File createFile(String path) throws IOException { File file = new File(path); if (!file.exists()) { file.createNewFile(); return file; private static String appendHtmlData() { StringBuilder builder = new StringBuilder(); builder.append(htmlPrefix); builder.append("<h1>一、字符说明</h1>"); builder.append("<ul>") .append("<li>").append(deltaInsert).append(":新增").append("</li>") .append("<li>").append(deltaChange).append(":修改").append("</li>") .append("<li>").append(deltaDelete).append(":删除").append("</li>") .append("</ul>"); builder.append("<h1>二、错误列表</h1>"); DiffContent.ERROR_MAP.forEach((k, errorList) -> { if (CollectionUtil.isNotEmpty(errorList)) { builder.append("<p style=\"color:#FF0000\">").append(k).append("</p>"); errorList.forEach(error -> { builder.append(error).append("<br><br>"); builder.append(htmlSuffix); return builder.toString(); private final static String htmlPrefix = "<!DOCTYPE html>\n" + "<html>\n" + "<head>\n" + "<meta charset=\"utf-8\">\n" + "<title>测试/生产配置diff</title>\n" + "</head>\n" + "<body>"; private final static String htmlSuffix = "</body>\n" + "</html>"; private final static String deltaInsert = "+++"; private final static String deltaChange = "+--"; private final static String deltaDelete = "---";