需求
web项目需要实现文件内容对比功能,开发语言是java,也就是通过java实现类似于svn的文件对比功能
实现效果
效果图如下
后端代码引入
首先引入对比的核心jar包
<!--对比工具依赖--> <dependency> <groupId>io.github.java-diff-utils</groupId> <artifactId>java-diff-utils</artifactId> <version>4.9</version> </dependency>
页面流程实现,点击列表页按钮 历史版本 查看历史版本记录
历史版本跳转方法
<li> <a class="btn btn-info" onclick="getHistoryVersion();" shiro:hasPermission="project:publishCosFiles:history"> <i class="fa fa-history"></i> 历史版本 </a> </li> //查看当前目录的历史版本 function getHistoryVersion(){ $.modal.openTab('历史版本',prefix + "/getHistoryVersion?platform="+$('#platform').val()+"&title="+$("#title").val()); }
controller方法,接收页面传参跳转到历史版本列表页面
/** * 获取目录历史版本页面 */ @RequestMapping("/getHistoryVersion") @RequiresPermissions("project:publishCosFiles:history") public String getHistoryVersion(HttpServletRequest request) throws Exception { String title = request.getParameter("title"); String platform = request.getParameter("platform"); request.setAttribute("title", title); request.setAttribute("platform", platform); return prefix + "/publishCosFileVersion"; }
历史版本列表页面
列表页面代码 publishCosFileVersion.html
<!DOCTYPE html> <html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"> <head> <th:block th:include="include :: header('文件版本列表')" /> <style> .my-search-collapse { width: 100%; background: #fff; border-radius: 6px; margin-top: 10px; padding-top: 10px; padding-bottom: 6px; box-shadow: 1px 1px 3px rgba(0,0,0,.2); } </style> </head> <body class="gray-bg"> <div class="container-div"> <div class="row"> <div class="col-sm-12 my-search-collapse"> <div> <div style="color:red;">当前目录:[[${title}]]</div> </div> </div> <div class="col-sm-12 search-collapse"> <form id="formId"> <div class="select-list"> <ul> <input type="hidden" id="path" name="path" th:value="${title}"> <input type="hidden" id="platform" name="platform" th:value="${platform}"> <li> 文件名:<input type="text" name="name"/> </li> <li> <input name="createUser" type="hidden" id="createUser"/> 操作人:<input id="createUserName" name="createUserName" readonly type="text" onclick="selectUserDeptTree('createUser','createUserName')" > </li> <li> 备注信息:<input type="text" name="remark"/> </li> <li class="input-daterange input-group"> 修改时间: <input type="text" name="createStartDate" readonly id="createStartDate"/> 至 <input type="text" name="createEndDate" readonly id="createEndDate"/> </li> <li> <a class="btn btn-primary btn-rounded btn-sm" onclick="search1();"><i class="fa fa-search"></i> 搜索</a> <a class="btn btn-warning btn-rounded btn-sm" onclick="reset1();"><i class="fa fa-refresh"></i> 重置</a> </li> <li style="float: right"> <button style="width: 40px;" class="btn btn-default btn-outline" type="button" onclick="search1();" name="refresh" aria-label="刷新" title="刷新"><i class="glyphicon glyphicon-refresh icon-refresh"></i> </button> </li> </ul> </div> </form> </div> <div class="col-sm-12 select-table table-striped"> <table id="bootstrap-table" data-mobile-responsive="true"></table> </div> </div> </div> <div th:include="include :: footer"></div> <script th:inline="javascript"> var viewImgFlag = [[${@permission.hasPermi('project:publishCosFiles:showImage')}]]; var prefix = ctx + "project/publishFileVersion"; $(function() { var options = { url: prefix + "/cmsFileVersionList", modalName: "文件版本", showExport: false, pagination: false, showSearch: false, showToggle: false, showRefresh:false, showColumns:false, sortName:'name', sortOrder:'asc', columns: [ { title: '', align: 'center', formatter: function(value, row, index) { var actions = []; //所有的加版本号 actions.push('<a class="" href="javascript:" onclick="addChildHtml(\'' + row.name + '\', this,'+row.id+' )"><i class="glyphicon glyphicon-plus icon-plus"></i></a>'); return actions.join(''); } }, { checkbox: true }, { field : 'id', title : '主键id', visible: false }, { field : 'name', title : '文件名', formatter: function(value, row, index) { var showname = value; if (value.length > 15) { showname = value.substr(0,14)+"..."; } var html = "<div title='"+value+"'>"+showname+"</div>"; return html; }, sortable:true }, { field : 'versionNum', title : '版本号', }, { field : 'createDate', title : '修改时间', sortable:true }, { field : 'size', title : '文件大小', formatter: function(value, row, index) { if(row.delFlag === 1){ // 删除的时候大小展示已删除 return "已删除"; } var fileSize = row.size; if (fileSize != null) { var number1024 = 1<<10; var number1048576 = 1<<20; var number1073741824 = 1<<30; if (fileSize > number1024) { if (fileSize > number1048576) { if (fileSize > number1073741824) { var fixed = (fileSize/number1073741824).toFixed(2); return fixed+"GB"; }else { var fixed = (fileSize/number1048576).toFixed(2); return fixed+"MB"; } }else { var fixed = (fileSize/number1024).toFixed(2); return fixed+"KB"; } }else { return fileSize+"B"; } }else { return "-"; } } }, { field : 'createUserName', title : '修改人', }, { field : 'remark', title : '备注信息', formatter: function(value, row, index) { return $.table.tooltip(value); } }, { title: '操作', align: 'left', cellStyle: function (value, row, index) { return {css: {"overflow": "hidden", "text-overflow": "ellipsis", "white-space": "nowrap"}} }, formatter: function(value, row, index) { var actions = []; var fileType = row.type; if (fileType != null && ("jpg" == fileType.toLowerCase() || "png" == fileType.toLowerCase() || "gif" == fileType.toLowerCase() || "jpeg" == fileType.toLowerCase())) { actions.push('<a class="btn btn-success btn-xs ' + viewImgFlag + '" href="javascript:void(0)" onclick="viewImage(\'' + row.bakPathPrefix +"/"+ row.bakName +'\')"><i class="fa fa-leaf"></i>预览</a> '); } return actions.join(''); } }] }; $.table.init(options); initLaydate(); }); /*账户管理-新增-选择人部门树*/ function selectUserDeptTree(treeId,treeName) { // url 可选参数, 其他参数 请自定扩展 // permission 权限 0.无权限 1.按照登录人数据权限 // showType 展示方式 0.人员 1.人员+工号 // checkType 选中类型 0.多选 1.单选 // showLevel 展示层级 var url = ctx + "system/userDeptTree?showType=1&showLevel=2&checkType=1"; var options = { title: '选择部门', width: "500", url: url, callBack: function (index, layero) { // 是否允许选父级 var body = layer.getChildFrame('body', index); $("#"+treeId).val(body.find('#ids').val()); $("#"+treeName).val(body.find('#names').val()); layer.close(index); } }; $.modal.openOptions(options); } function initLaydate() { layui.use('laydate', function() { var laydate = layui.laydate; var startDate = laydate.render({ elem: '#createStartDate', //max: $('#createEndDate').val(), type: 'datetime', theme: 'molv', trigger: 'click' /*done: function (value, date) { // 结束时间大于开始时间 if (value !== '') { endDate.config.min.year = date.year; endDate.config.min.month = date.month - 1; endDate.config.min.date = date.date; } else { endDate.config.min.year = ''; endDate.config.min.month = ''; endDate.config.min.date = ''; } }*/ }); var endDate = laydate.render({ elem: '#createEndDate', //max:$.common.getNowFormatDate(), type: 'datetime', theme: 'molv', trigger: 'click' /*done: function (value, date) { // 开始时间小于结束时间 if (value !== '') { startDate.config.max.year = date.year; startDate.config.max.month = date.month - 1; startDate.config.max.date = date.date; } else { startDate.config.max.year = ''; startDate.config.max.month = ''; startDate.config.max.date = ''; } }*/ }); }) } //预览图片 function viewImage(filePath) { var url = ctx + 'project/publishCosFiles/showImage?filePath=' + filePath; $.modal.open("图片预览", url , '600', '500',function (index) { layer.close(index); }); } //加载当前文件的历史版本 function addChildHtml(name,tdThis,id) { // 判断是否是加号,加号的话展开并加载子类,如果是减号的话则删除掉子类 if ($(tdThis).children('i').hasClass('glyphicon-plus')) { // 加载子类数据 var html = ""; var formData = {"path":$("#path").val(),"platform":$("#platform").val(),"name":name}; $.ajax({ url: prefix + "/getOneFileHistory", type: 'post', dataType: "json", data: formData, success: function(result) { if(result.code == web_status.SUCCESS){ $.each(result.data,function (i,k) { html += '<tr child-index="'+i+'" class="child-tr pid-'+id+'">'; html +='<td style="text-align: center; "/>'; html +='<td/>'; if(k.name==null){ html +='<td style="">-</td>'; }else { var showname = k.name; if (showname.length > 15) { showname = showname.substr(0,14)+"..."; } html += '<td title="'+k.name+'">'+showname+'</td>'; } html +='<td style="">'+k.versionNum+'</td>'; html +='<td style="">'+k.createDate+'</td>'; var fileSize = k.size; if (fileSize != null) { var number1024 = 1<<10; var number1048576 = 1<<20; var number1073741824 = 1<<30; if (fileSize > number1024) { if (fileSize > number1048576) { if (fileSize > number1073741824) { var fixed = (fileSize/number1073741824).toFixed(2); html +='<td style="">'+fixed+'GB</td>'; }else { var fixed = (fileSize/number1048576).toFixed(2); html +='<td style="">'+fixed+'MB</td>'; } }else { var fixed = (fileSize/number1024).toFixed(2); html +='<td style="">'+fixed+'KB</td>'; } }else { html +='<td style="">'+fileSize+'B</td>'; } }else { html +='<td style="">-</td>'; } html +='<td style="">'+k.createUserName+'</td>'; html +='<td style="">'+$.table.tooltip(k.remark);+'</td>'; html +='<td style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: left; ">'; var fileType = k.type; if (fileType != null && ("jpg" == fileType.toLowerCase() || "png" == fileType.toLowerCase() || "gif" == fileType.toLowerCase() || "jpeg" == fileType.toLowerCase())) { html +='<a class="btn btn-success btn-xs ' + viewImgFlag + '" href="javascript:void(0)" onclick="viewImage(\'' + k.bakPathPrefix +"/"+ k.bakName +'\')"><i class="fa fa-leaf"></i>预览</a> '; } if(i!=0){ //不是第一个的时候加还原 html +='<a class="btn btn-warning btn-xs " href="javascript:void(0)" onclick="rollbackFile('+k.id+')">'; html +='<i class="fa fa-mail-reply"/>还原</a> '; }else { // 是第一个的时候如果是删除的加还原 if(k.delFlag === 1){ html +='<a class="btn btn-warning btn-xs " href="javascript:void(0)" onclick="rollbackFile('+k.id+')">'; html +='<i class="fa fa-mail-reply"/>还原</a> '; } } if (fileType != null && ("html" == fileType.toLowerCase() || "shtml" == fileType.toLowerCase() || "css" == fileType.toLowerCase() || "js" == fileType.toLowerCase()|| "txt" == fileType.toLowerCase())) { html +='<a class="btn btn-info btn-xs " href="javascript:void(0)" onclick="comparedFile('+k.id+')">'; html +='<i class="fa fa-exchange"/>对比</a>'; } html +='</td>'; html +='</tr>'; }); $(tdThis).parent().parent().after(html); }else{ if("未登录或登录超时。请重新登录"==res.msg){ window.location=ctx+"login"; }else { $.modal.alertError(result.msg); } } } }); // 写入之类成功之后在去掉加号等样式,防止出错 $(tdThis).children('i').removeClass('glyphicon-plus'); $(tdThis).children('i').removeClass('icon-plus'); $(tdThis).children('i').addClass('glyphicon-minus'); $(tdThis).children('i').addClass('icon-minus'); }else { // 减号的时候点击将子类数据移除,然后将减号变加号 $('.pid-'+id+'').remove(); $(tdThis).children('i').removeClass('glyphicon-minus'); $(tdThis).children('i').removeClass('icon-minus'); $(tdThis).children('i').addClass('glyphicon-plus'); $(tdThis).children('i').addClass('icon-plus'); } } //还原文件的方法 function rollbackFile(id){ $.modal.confirm("确认要还原该文件吗?", function() { var url = prefix + "/rollbackFile"; var data = { "id": id }; $.operate.submit(url, "post", "json", data,function () { $.table.search(); }); }); } //对比选择 function comparedFile(id){ $.modal.openTab('对比选择',prefix + "/selectCompared/?id="+id); } function search1() { var start = $("#createStartDate").val(); var end = $("#createEndDate").val(); var startdate = new Date(start); var enddate = new Date(end); if (startdate.getTime() > enddate.getTime()) { $.modal.alertError("开始时间不能大于结束时间"); return false; } $.table.search(); } function reset1() { $("#createUser").val(""); $.form.reset(); } </script> </body> </html>
点击列表页之后跳转文件对比controller方法
@RequestMapping("/comparedThisFile") public String comparedThisFile(HttpServletRequest request, ModelMap mmap) { String text1path = null; String text2path = null; //String htmlPath = null; String platform = request.getParameter("platform"); try { String leftId = request.getParameter("leftId"); String rightId = request.getParameter("rightId"); String profile = ConfigConstant.cosTempPath; PublishFileVersion left = publishFileVersionService.selectPublishFileVersionById(Long.parseLong(leftId)); // 获取左边文件路径 String bakpath = left.getBakPathPrefix(); String bakname = left.getBakName(); //file.text_0 String filename = left.getName(); String downname = filename +"_"+left.getVersionNum(); if (filename.contains(".")) { downname = filename.replace(".","_"+left.getVersionNum()+"."); } if(left.getObfuscateFlag()==1){ // 混淆的时候文件名用混淆的那个名字 bakname = left.getObfuscateSourceName(); } String leftkey = bakpath + bakname; PublishFileVersion right = publishFileVersionService.selectPublishFileVersionById(Long.parseLong(rightId)); // 获取右边文件路径 String bakpath2 = right.getBakPathPrefix(); String bakname2 = right.getBakName(); //file.text_0 String filename2 = right.getName(); String downname2 = filename2 +"_"+right.getVersionNum(); if (filename2.contains(".")) { downname2 = filename2.replace(".","_"+right.getVersionNum()+"."); } if(right.getObfuscateFlag()==1){ // 混淆的时候文件名用混淆的那个名字 bakname2 = right.getObfuscateSourceName(); } String rightkey = bakpath2 + bakname2; if (Constants.Platform.COS.getValue().equals(Integer.valueOf(platform))) { CosClientUtil cosClientUtil = new CosClientUtil(); try { //下载到服务器 text1path = profile + downname; cosClientUtil.download(leftkey, text1path); } catch (InterruptedException e) { e.printStackTrace(); } try { //下载到服务器 text2path = profile + downname2; cosClientUtil.download(rightkey, text2path); } catch (InterruptedException e) { e.printStackTrace(); } }else { text1path = bakpath+File.separator+bakname; text2path = bakpath2+File.separator+bakname2; } List<String> diffString = DiffHandleUtils.diffString(text1path,text2path,downname,downname2); //System.out.println("======diff::::::"+diffString); //在服务器生成一个diff.html文件,打开便可看到两个文件的对比 //htmlPath = profile + "compareresult.html"; String string = DiffHandleUtils.generateDiffString(diffString); //System.out.println("======res::::::"+string); mmap.put("right", string); }catch (Exception e) { e.printStackTrace(); }finally { if (Constants.Platform.COS.getValue().equals(Integer.valueOf(platform))) { //删除下载的临时文件 if (StringUtils.isNotEmpty(text1path)) { boolean delete = new File(text1path).delete(); } if (StringUtils.isNotEmpty(text2path)) { boolean delete = new File(text2path).delete(); } } } return prefix+"/newComparedFile"; }
文件对比工具类主要方法 DiffHandleUtils.java
package com.dongao.project.utils; import com.github.difflib.UnifiedDiffUtils; import com.github.difflib.patch.Patch; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.nio.file.Files; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; /** * @Author zhangw * @Date 2022/4/21 18:04 * @Version 1.0 */ public class DiffHandleUtils { /** * 对比两文件的差异,返回原始文件+diff格式 * * @param original 原文件内容 * @param revised 对比文件内容 */ public static List<String> diffString(List<String> original, List<String> revised) { return diffString(original, revised, null, null); } /** * 对比两文件的差异,返回原始文件+diff格式 * * @param original 原文件内容 * @param revised 对比文件内容 * @param originalFileName 原始文件名 * @param revisedFileName 对比文件名 */ public static List<String> diffString(List<String> original, List<String> revised, String originalFileName, String revisedFileName) { originalFileName = originalFileName == null ? "原始文件" : originalFileName; revisedFileName = revisedFileName == null ? "对比文件" : revisedFileName; //两文件的不同点 Patch<String> patch = com.github.difflib.DiffUtils.diff(original, revised); //生成统一的差异格式 List<String> unifiedDiff = UnifiedDiffUtils.generateUnifiedDiff(originalFileName, revisedFileName, original, patch, 0); if (unifiedDiff.size() == 0) { //如果两文件没差异则插入如下 unifiedDiff.add("--- " + originalFileName); unifiedDiff.add("+++ " + revisedFileName); unifiedDiff.add("@@ -0,0 +0,0 @@"); } else if (unifiedDiff.size() >= 3 && !unifiedDiff.get(2).contains("@@ -1,")) { //如果第一行没变化则插入@@ -0,0 +0,0 @@ unifiedDiff.add(2, "@@ -0,0 +0,0 @@"); } //原始文件中每行前加空格 List<String> original1 = original.stream().map(v -> " " + v).collect(Collectors.toList()); //差异格式插入到原始文件中 return insertOrig(original1, unifiedDiff); } /** * 对比两文件的差异,返回原始文件+diff格式 * * @param filePathOriginal 原文件路径 * @param filePathRevised 对比文件路径 */ public static List<String> diffString(String filePathOriginal, String filePathRevised,String originalName, String revisedName) { //原始文件 List<String> original = null; //对比文件 List<String> revised = null; File originalFile = new File(filePathOriginal); File revisedFile = new File(filePathRevised); try { original = Files.readAllLines(originalFile.toPath()); revised = Files.readAllLines(revisedFile.toPath()); } catch (IOException e) { e.printStackTrace(); } return diffString(original, revised, originalName, revisedName); } /** * 通过两文件的差异diff生成 html文件,打开此 html文件便可看到文件对比的明细内容 * * @param diffString 调用上面 diffString方法获取到的对比结果 * @param htmlPath 生成的html路径,如:/user/var/mbos/ent/21231/diff.html * HTML输出接受一个Javascript对象,该对象可能有以下配置项: * * inputFormat: 输入数据的格式: 'diff' 或者 'json', 默认是'diff' * outputFormat: 输出数据的格式: 'line-by-line' 或者 'side-by-side', 默认是'line-by-line' * showFiles: 在对比之前查看文件列表,true 或者false,默认是false * matching: 匹配level: 'lines'用于匹配行, 'words' 用于匹配行和单词,或者设置为'none',默认为none * matchWordsThreshold: 单词相似度下限, 默认是0.25 * matchingMaxComparisons: 为了匹配一组变化最多执行的比较次数,默认是2500 * maxLineLengthHighlight: 如果行数小于此值,则仅仅执行差异突出显示,默认是10000 * templates: 使用预备好的编译的模板替换部分html的对象。 * rawTemplates: 具有原始未编译模板的对象替换部分html。 * 更多参考 https://github.com/rtfpessoa/diff2html/tree/master/src/templates */ public static void generateDiffHtml(List<String> diffString, String htmlPath) { StringBuilder builder = new StringBuilder(); for (String line : diffString) { builder.append(line); builder.append("\n"); } String template = "<!DOCTYPE html>\n" + "<html lang=\"en-us\">\n" + " <head>\n" + " <meta charset=\"utf-8\" />\n" + " <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.7.1/styles/github.min.css\" />\n" + " <link rel=\"stylesheet\" type=\"text/css\" href=\"https://cdn.jsdelivr.net/npm/diff2html/bundles/css/diff2html.min.css\" />\n" + " <script type=\"text/javascript\" src=\"https://cdn.jsdelivr.net/npm/diff2html/bundles/js/diff2html-ui.min.js\"></script>\n" + " </head>\n" + " <script>\n" + " const diffString = `\n" + "temp\n" + "`;\n" + "\n" + "\n" + " document.addEventListener('DOMContentLoaded', function () {\n" + " var targetElement = document.getElementById('diffElement');\n" + " var configuration = {\n" + " drawFileList: true,\n" + " fileListToggle: false,\n" + " fileListStartVisible: false,\n" + " fileContentToggle: false,\n" + " matching: 'words',\n" + " outputFormat: 'side-by-side',\n" + " synchronisedScroll: true,\n" + " highlight: true,\n" + " renderNothingWhenEmpty: true,\n" + " };\n" + " var diff2htmlUi = new Diff2HtmlUI(targetElement, diffString, configuration);\n" + " diff2htmlUi.draw();\n" + " diff2htmlUi.highlightCode();\n" + " });\n" + " </script>\n" + " <body>\n" + " <div id=\"diffElement\"></div>\n" + " </body>\n" + "</html>"; String string = builder.toString(); string = string.replace("/", "\\/"); template = template.replace("temp", string); FileWriter f = null; //文件读取为字符流 try { f = new FileWriter(htmlPath); BufferedWriter buf = new BufferedWriter(f); //文件加入缓冲区 buf.write(template); //向缓冲区写入 buf.close(); //关闭缓冲区并将信息写入文件 f.close(); } catch (IOException e) { e.printStackTrace(); } } /** * 通过两文件的差异diff生成 string,打开此 html文件便可看到文件对比的明细内容 * @param diffString * @return */ public static String generateDiffString(List<String> diffString) { StringBuilder builder = new StringBuilder(); builder.append("`"); for (String line : diffString) { //对比页面开始符号冲突 if (line.contains("`")) { line = line.replace("`","\\`"); } //</script>结束标志</冲突 if (line.contains("</")) { line = line.replace("</", "<\\/"); } //正则\冲突 if (line.contains("\\") && line.contains("^")) { line = line.replace("\\", "\\\\"); } //页面取值冲突 ${} if (line.contains("${")) { line = line.replace("${", "$\\{"); } builder.append(line); builder.append("\n"); } builder.append("`"); String string = builder.toString(); return string; } //统一差异格式插入到原始文件 public static List<String> insertOrig(List<String> original, List<String> unifiedDiff) { List<String> result = new ArrayList<>(); //unifiedDiff中根据@@分割成不同行,然后加入到diffList中 List<List<String>> diffList = new ArrayList<>(); List<String> d = new ArrayList<>(); for (int i = 0; i < unifiedDiff.size(); i++) { String u = unifiedDiff.get(i); if (u.startsWith("@@") && !"@@ -0,0 +0,0 @@".equals(u) && !u.contains("@@ -1,")) { List<String> twoList = new ArrayList<>(); twoList.addAll(d); diffList.add(twoList); d.clear(); d.add(u); continue; } if (i == unifiedDiff.size() - 1) { d.add(u); List<String> twoList = new ArrayList<>(); twoList.addAll(d); diffList.add(twoList); d.clear(); break; } d.add(u); } //将diffList和原始文件original插入到result,返回result for (int i = 0; i < diffList.size(); i++) { List<String> diff = diffList.get(i); List<String> nexDiff = i == diffList.size() - 1 ? null : diffList.get(i + 1); //含有@@的一行 String simb = i == 0 ? diff.get(2) : diff.get(0); String nexSimb = nexDiff == null ? null : nexDiff.get(0); //插入到result insert(result, diff); //解析含有@@的行,得到原文件从第几行开始改变,改变了多少(即增加和减少的行) Map<String, Integer> map = getRowMap(simb); if (null != nexSimb) { Map<String, Integer> nexMap = getRowMap(nexSimb); int start = 0; if (map.get("orgRow") != 0) { start = map.get("orgRow") + map.get("orgDel") - 1; } int end = nexMap.get("revRow") - 2; //插入不变的 insert(result, getOrigList(original, start, end)); } if (simb.contains("@@ -1,") && null == nexSimb) { insert(result, getOrigList(original, 0, original.size() - 1)); } else if (null == nexSimb && map.get("orgRow") < original.size()) { insert(result, getOrigList(original, map.get("orgRow"), original.size() - 1)); } } return result; } //将源文件中没变的内容插入result public static void insert(List<String> result, List<String> noChangeContent) { for (String ins : noChangeContent) { result.add(ins); } } //解析含有@@的行得到修改的行号删除或新增了几行 public static Map<String, Integer> getRowMap(String str) { Map<String, Integer> map = new HashMap<>(); if (str.startsWith("@@")) { String[] sp = str.split(" "); String org = sp[1]; String[] orgSp = org.split(","); //源文件要删除行的行号 map.put("orgRow", Integer.valueOf(orgSp[0].substring(1))); //源文件删除的行数 map.put("orgDel", Integer.valueOf(orgSp[1])); String[] revSp = org.split(","); //对比文件要增加行的行号 map.put("revRow", Integer.valueOf(revSp[0].substring(1))); map.put("revAdd", Integer.valueOf(revSp[1])); } return map; } //从原文件中获取指定的部分行 public static List<String> getOrigList(List<String> original1, int start, int end) { List<String> list = new ArrayList<>(); if (start <= end && end < original1.size()) { for (; start <= end; start++) { list.add(original1.get(start)); } } return list; } }
返回页面展示结果如开始,newComparedFile.html 页面代码
<!DOCTYPE html> <html lang="en-us"> <head> <meta charset="utf-8" /> <link rel="stylesheet" type="text/css" th:href="@{/css/github.min.css}"/> <link rel="stylesheet" type="text/css" th:href="@{/css/diff2html.min.css}"/> <script type="text/javascript" th:src="@{/js/diff2html-ui.min.js}"></script> <script type="text/javascript" th:src="@{/js/jquery.min.js}"></script> </head> <script> const diffString = [(${right})]; document.addEventListener('DOMContentLoaded', function () { var targetElement = document.getElementById('diffElement'); var configuration = { //在diff之前显示文件列表:true或false,默认值为true drawFileList: true, //允许切换文件摘要列表:true或false,默认值为true fileListToggle: false, //选择文件摘要列表是否开始可见:true或false,默认值为false fileListStartVisible: false, //允许切换每个文件内容:true或false,默认值为true fileContentToggle: false, //匹配level: 'lines'用于匹配行, 'words' 用于匹配行和单词,或者设置为'none',默认为none matching: 'words', //输出数据的格式: 'line-by-line' 或者 'side-by-side', 默认是'line-by-line' outputFormat: 'side-by-side', synchronisedScroll: true, highlight: true, renderNothingWhenEmpty: true, }; var diff2htmlUi = new Diff2HtmlUI(targetElement, diffString, configuration); diff2htmlUi.draw(); diff2htmlUi.highlightCode(); var texts = $(".d2h-code-wrapper").find("td[class='d2h-info']").find("div"); if (texts.length) { $.each(texts,function (index,value) { var _this = $(this); //console.log(_this.text()) _this.text(""); }); } }); </script> <body> <div id="diffElement"></div> </body> </html>
参考文献:https://github.com/rtfpessoa/diff2html#diff2htmlui-api
本文主要用于记录日常开发,仅供学习参考