由于项目中使用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 = "---";