SpringBoot实现文件内容对比

news2024/9/22 17:31:25

     背景

        在上一篇博客中,我实践了WORD转换成PDF/TXT的实现方式,本周接到一个新的需求,恰好就用上了这个成果。需求如下:客户提供一个WORD范本给用户,用户范本进行修改后,再反馈给客户。反馈的成果多种多样,可以是WORD,PDF或TXT,然后客户希望有一个文件对比的功能,将范本和用户修改的内容进行比对,以此来找出用户修改了哪些内容。不出所料,这个光荣而艰巨的任务又落到了雷袭的头上。

     代码实践 

        这个小需求其实没多少技术含量,就当是一个练手小游戏吧。参考了网上的诸多实践后,我决定这么规划:后台提供接口,将范本和用户提交的文件转换成TXT,并获取TXT内容。前端得到后台的TXT内容后,通过JS函数分析比对内容,并将新增,修改,删除的内容分类展示出来,以下是代码实践。

        1、在原来的后端代码的基础上,增加一个转换方法,对上传的文件进行转换,输出文件内容。

package com.leixi.fileTrans.utils;

import com.aspose.words.Document;
import com.aspose.words.SaveFormat;
import com.leixi.fileTrans.pojo.FileResponse;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.io.RandomAccessRead;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
import org.springframework.web.multipart.MultipartFile;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.UUID;

/**
 *
 * @author leixiyueqi
 * @since 2024/09/03 19:39
 */
public class FileUtils {

    private static final String outPath = "D:\\upload\\";

    public static FileResponse compareFile(MultipartFile leftFile, MultipartFile rightFile) throws Exception {
        String leftPath = transFileToTxt(leftFile);
        String rightPath = transFileToTxt(rightFile);
        FileResponse fileResponse = new FileResponse();
        fileResponse.setFileLeftStr(readText(leftPath));
        fileResponse.setFileRightStr(readText(rightPath));
        return fileResponse;
    }


    private static String transFileToTxt(MultipartFile file) throws Exception {
        String fileName =file.getOriginalFilename();
        String suffix = fileName.substring(fileName.lastIndexOf(".") + 1);
        String filePath = outPath + UUID.randomUUID() + ".txt";
        switch (suffix) {
            case "doc":
            case "docx":
                 transDocToTxt(file, filePath); break;
            case "txt":
                 file.transferTo(new File(filePath));break;
            case "pdf":
                 transPdfToTxt(file, filePath);break;
            default:
                throw new RuntimeException("不支持的文件类型");
        }
        return filePath;
    }

    private static void transDocToTxt(MultipartFile file, String filePath) throws Exception {
        Document doc = new Document(file.getInputStream());
        doc.save(filePath, SaveFormat.TEXT);
    }

    public static void transPdfToTxt(MultipartFile file, String filePath) throws Exception {
        BufferedWriter wr = null;
        File output = new File(filePath);
        PDDocument pd = Loader.loadPDF(file.getBytes());
        pd.save("CopyOf" + file.getName().split("\\.")[0] + ".pdf");
        PDFTextStripper stripper = new PDFTextStripper();
        wr = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(output)));
        stripper.writeText(pd, wr);
        if (pd != null) {
            pd.close();
        }
        wr.close();
    }

    private static String readText(String filePath) {
        StringBuilder contentBuilder = new StringBuilder();

        try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
            String currentLine;

            while ((currentLine = br.readLine()) != null) {
                String[] arr = currentLine.split("\\|\\|");
                if (arr.length > 1) {
                    contentBuilder.append(arr[1]);
                } else {
                    contentBuilder.append(currentLine);
                }
                contentBuilder.append(System.lineSeparator()); // 添加换行符
            }
            return contentBuilder.toString();
        } catch (IOException e) {
            throw new RuntimeException("读取文件失败", e);
        }
    }
}

        2、添加一个Controller方法

    @PostMapping("/compare")
    public Object compare(@RequestParam(value = "leftFile") MultipartFile leftFile,
                          @RequestParam(value = "rightFile") MultipartFile rightFile) throws Exception{
        FileResponse response = FileUtils.compareFile(leftFile, rightFile);
        return response;
    }

        3、在resources/static下添加一个compare.html文件,内容如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document Comparison</title>
    <style>
    .wrap {
        background-color: #fff;
        border-radius: 4px;
        padding: 10px;
    }
    .top {
        margin: 0 -10px;
        display: flex;
        align-items: center;
        padding-left: 20px;
    }
    .text-view-box {
        height: 100%;
        min-height: 600px;
        margin: 0px -10px;
        width: calc(100% + 18px);
        position: relative;
        background: #fff;
        border-radius: 8px;
        padding: 10px;
        overflow: hidden;
    }

    .text-march-box {
        width: 100%;
        display: flex;
        overflow: hidden;
        justify-content: space-between;
    }

    .text-march-box._01 {
        margin: 0px 10px 10px 10px;
        padding-bottom: 10px;
        border-bottom: 1px solid #dcdfe6;
        width: calc(100% - 20px);
    }

    .text-march-box._02 {
        height: calc(100% - 80px);
        overflow-y: auto;
        overflow-x: hidden;
    }

    .text-march-box._02::-webkit-scrollbar {
        width: 3px;
        height: 3px;
    }

    .text-view-item {
        height: 100%;
        margin: 0px 10px;
        width: 50%;
        box-sizing: border-box;
        display: flex;
    }

    .c_warning {
        color: #409eff;
    }

    .text-view-name {
        padding-bottom: 10px;
        position: relative;
        color: #1f2424;
        font-size: 15px;
        font-weight: bold;
    }

    .file-name {
        font-size: 13px;
        padding-bottom: 10px;
        display: flex;
        align-items: center;
    }

    .source-text {
        border: 1px solid #dcdfe6;
        border-radius: 2px;
        padding: 10px;
        font-size: 14px;
        color: #606266;
        background: #f2f6fc;
        min-height: 100%;
        width: 100%;
        font-family: fangsong;
        line-height: 20px;
    }
    
    .spin-box {
        position: absolute;
        top: 0px;
        width: 50%;
        left: 0px;
        height: 100%;
        display: flex;
        align-items: center;
        justify-content: center;
    }

    .spin-box._02 {
        right: 0px;
        left: auto;
    }

    body {
        font-family: Arial, sans-serif;
        background-color: #f4f4f9;
        margin: 0;
        padding: 20px;
    }

    label {
        font-weight: bold;
        color: #333333;
    }

    input[type="file"] {
        padding: 10px;
        border-radius: 4px;
        outline: none;
        transition: border-color 0.3s;
    }

    input[type="file"]:focus {
        border-color: #007bff;
    }

    button {
        padding: 10px 20px;
        background-color: #007bff;
        color: #ffffff;
        border: none;
        border-radius: 4px;
        cursor: pointer;
        transition: background-color 0.3s;
    }

    button:hover {
        background-color: #0056b3;
    }

    #oldFileInput::file-selector-button{
        padding: 6px 10px;
        background-color: #1E9FFF;
        border: 1px solid #1E9FFF;
        border-radius: 3px;
        cursor: pointer;
        color: #fff;
        font-size: 12px;
    }

    #newFileInput::file-selector-button{
        padding: 6px 10px;
        background-color:#1E9FFF;
        border: 1px solid #1E9FFF;
        border-radius: 3px;
        cursor: pointer;
        color: #fff;
        font-size: 12px;
    }
    </style>
</head>
<body>
<div class="wrap">
    <div class="top">
        <label for="oldFileInput">当前文档:</label>
        <input type="file" id="oldFileInput" style="margin-right: 16px">

        <label for="newFileInput">对比文档:</label>
        <input type="file" id="newFileInput"  >

        <button type="primary" style="margin-left: 16px" onclick="handleCompare()">文档比对</button>
    </div>

    <!-- 假设这里有一个用于显示文件对比结果的地方 -->
    <div class="text-view-box" style = "display: none" id="resultDiv">
        <div class="text-march-box">
            <div class="text-view-item">
                <div class="text-view-name">源文件:</div>
                <div class="file-name"><span id = "leftFileName"/></div>
            </div>
            <div class="text-view-item">
                <div class="text-view-name">
                    <span class="c_warning">对比文件:</span>
                </div>
                <div class="file-name"><span id = "rightFileName"/></div>
            </div>
        </div>
        <div class="text-march-box _02">
            <div class="text-view-item">
                <div class="source-text" id="sourceText"></div>
            </div>
            <div class="text-view-item">
                <div class="source-text" id="targetHtml"></div>
            </div>
        </div>
        <div class="spin-box" id="spinBox"></div>
        <div class="spin-box _02" id="spinBox2"></div>
    </div>
</div>
</body>
<script src="./js/diff.min.js"></script>
<script>
    window.onload = function() {
        showResultDiv(false);
    };
    function handleCompare() {
        console.log("开始比较文档!")
        if (oldFileInput.files[0] && newFileInput.files[0]) {
            console.log(`比较文档: ${oldFileInput.files[0].name} 和 ${newFileInput.files[0].name}`);
            let formData = new FormData();
            formData.append('leftFile', oldFileInput.files[0]);
            formData.append('rightFile', newFileInput.files[0]);
            fetch('/leixi/compare', {
                method: 'POST',
                body: formData
            }).then(response => response.json())
            .then(data => {
                console.log('后端返回的数据:', data);
                showResultDiv(true); // 不显示 div
                document.getElementById("leftFileName").textContent = oldFileInput.files[0].name;
                document.getElementById("rightFileName").textContent = newFileInput.files[0].name;
                // 在这里处理返回的数据
                getTargetHtml(data.fileLeftStr, data.fileRightStr);
            }).catch(error => {
                console.error('请求失败:', error);
                showResultDiv(false); // 不显示 div
            }).finally(() => {

            });
        } else {
            alert('请选择文档');
        }
    }

    function showResultDiv(show) {
        document.getElementById('resultDiv').style.display= show ? "" : "none";
    }
    let leftFile = {};
    let rightFile = {};
    let sourceTextDiv = document.getElementById('sourceText');
    let targetHtmlDiv = document.getElementById('targetHtml');
    function getTargetHtml(leftText, rightText) {
        sourceTextDiv.innerHTML = '';
        targetHtmlDiv.innerHTML = '';

        const diff = Diff.diffChars(leftText, rightText);
        let updateLength = 0;

        for(let i = 0; i < diff.length; i++) {
            let item = diff[i];
            if (item.added || item.removed) {
                updateLength += item.value.length;
            } else {
                targetHtmlDiv.innerHTML += `<span>${item.value}</span>`;
                sourceTextDiv.innerHTML += `<span>${item.value}</span>`;
                continue;
            }

            if (item.removed && diff[i + 1] && diff[i + 1].added) {
                item.value = setItemValue(item.value, 'rgba(184,62,255,.4)');
                sourceTextDiv.innerHTML += `<span style='background:rgba(184,62,255,.4);'>${item.value}</span>`;
                continue;
            }

            if (item.added && diff[i - 1] && diff[i - 1].removed) {
                item.value = setItemValue(item.value, 'rgba(184,62,255,.4)');
                targetHtmlDiv.innerHTML += `<span style='background:rgba(184,62,255,.4);'>${item.value}</span>`;
                continue;
            }

            if (item.added) {
                item.value = setItemValue(item.value, 'rgba(103,194,58,.4)');
                targetHtmlDiv.innerHTML +=`<span style='background :rgba(103,194,58,.4);'>${item.value}</span>`;
            }

            if (item.removed) {
                item.value = setItemValue(item.value, 'rgba(255,71,109,.4)');
                sourceTextDiv.innerHTML += `<span style='background:rgba(255,71,109,.4);'>${item.value}</span>`;
            }
        }
    }

    function setItemValue(value, color) {
        value = value || '';
        return value;
    }
</script>
</html>

        文中引用了一个diff.min.js文件,是一个通用的工具文件,在网上可以轻易搜到,这里就不补充了。

        4、测试环节,打开页面,输入:http://127.0.0.1:19200/leixi/compare.html,选择上篇博客里转换的文件,对文档略作修改,对比的效果还是蛮准确的,通过这个功能,也可以检验上篇博客中WORD转PDF功能的准确度:

     后记

        这只是一个很简单的尝试,雷袭旨在通过这次实践来对之前的文件转换功能进行融汇贯通。从实用性来说,这其实也是个业务无关的小组件,如果有同行正巧需要实现类似的功能,可以直接把代码拷过去使用,人人为我,我为人人!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2101578.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

RocketMQ消息回溯实践与解析

文章目录 1 问题背景2 验证2.1 生产者启动2.2 消费者启动2.3 执行回溯2.4 结果验证2.5 验证小结2.5.1 分析参数2.5.2 思考 3 分析3.1 策略模式&#xff0c;解析命令行3.2 创建客户端&#xff0c;与服务端交互3.3 获取topic对应的broker地址&#xff0c;提交重置请求3.4 与 name…

TCP 拥塞控制

概念详解 TCP拥塞控制是网络通信中的一个关键机制&#xff0c;它通过动态调整发送数据的速率来避免网络拥塞。以下是TCP拥塞控制的详细概念解释&#xff1a; 拥塞窗口&#xff08;CWND, Congestion Window&#xff09;: 定义&#xff1a;发送方在收到接收方的确认&#xff08;…

华为网络工程师证书等级有哪些?怎么备考?

华为网络工程师是由华为技术厂商推出的一系列网络工程师认证&#xff0c;其主要目的就是为了培养了验证网络工程师在华为技术以及解决方案方面的拥有一定的专业知识及技能&#xff0c;该证书分为多个等级&#xff0c;涵盖了不同网络领域及技术&#xff0c;也为众多的网络工程师…

SqlServer: 安装或升级到SqlServer2022

一、下载安装包。 https://info.microsoft.com/ww-landing-sql-server-2022.html?lcidzh-CN 简单注册一下之后&#xff0c;就可以下载安装包了。 或者在我的资源中下载&#xff1a; https://download.csdn.net/download/yenange/89709660 系统要求&#xff1a; https://…

暴力破解和撞库攻击有什么区别,怎么防御暴力破解和撞库攻击

在网络世界中&#xff0c;我们的账户安全时刻面临着各种威胁。其中&#xff0c;暴力破解和撞库攻击就是常见的两种危险手段。今天&#xff0c;就让我们深入了解这两种攻击方式的含义&#xff0c;并学习如何有效地进行防护。 暴力破解的含义 暴力破解&#xff0c;就如同一个不…

java【day03】---(Vue-Element)

1 Ajax 1.1 Ajax介绍 1.1.1 Ajax概述 我们前端页面中的数据&#xff0c;如下图所示的表格中的学生信息&#xff0c;应该来自于后台&#xff0c;那么我们的后台和前端是互不影响的2个程序&#xff0c;那么我们前端应该如何从后台获取数据呢&#xff1f;因为是2个程序&#xf…

星闪NearLink短距无线连接技术

星闪NearLink短距无线连接技术&#xff0c;作为华为主导的新一代无线短距通信标准技术&#xff0c;自2020年起由中国工信部牵头制定标准&#xff0c;旨在为万物互联时代提供更高效、更稳定的连接方式。 类似技术介绍 AirDrop&#xff08;苹果&#xff09; AirDrop是苹果公司开发…

Apifox使用学习

Apifox是API文档、API调试、API Mock、API自动测试一体化协作平台&#xff0c;定位SwaggerPostmanMockJMeter。 只需要定义好API文档&#xff0c;API调试、API数据Mock、API自动化测试就可以直接使用。 API文档和API开发测试使用同一个工具&#xff0c;API调试完成后即可保证…

PPT图表制作不再难!这款在线PPT软件让办公更简单!

ppt图表怎么制作&#xff1f; 在当下注重视觉呈现的数字化时代&#xff0c;有效的信息传递和数据可视化变得越来越重要。不管是商业演示、学术报告还是项目汇报&#xff0c;一份精心制作的PPT演示文稿&#xff0c;往往能够起到事半功倍的效果。其中&#xff0c;图表作为PPT中不…

n*n矩阵,输出矩阵中任意两点之间所有路径

题目1&#xff1a;给你一个正整数n&#xff0c; 构造一个n*n的四项链表矩阵。 要求&#xff1a; 1.使用四项链表 2.矩阵从左到右&#xff0c;从上到下值依次为1,2,3,4,......n*n 题目2&#xff1a;基于题目1&#xff0c; 在n*n链表矩阵中&#xff0c;输出矩阵中任意两点之间所有…

5款文案自动生成器,高质量创意文案一键为你生成

在当今竞争激烈的内容创作领域&#xff0c;每一个字、每一句话都承载着巨大的价值。对于创作者而言&#xff0c;文案自动生成器的出现&#xff0c;犹如在茫茫大海中点亮了一座指引方向的灯塔。它们不仅为创作者节省了宝贵的时间和精力&#xff0c;更像是一把神奇的钥匙&#xf…

5.4树,森林

5.4.1树的存储结构 可采用顺序存储结构or链式存储结构 要求能唯一的反映树中各节点之间的逻辑 1.双亲表示法 采用一端连续的空间来存储,同时在每个节点中增设一个伪指针,指示双亲节点在数组中的下标 优点:找双亲节点方便,找孩子不方便 attention:由于根节点无双亲节点,所以…

C++:关于反向迭代器的学习分享

前言&#xff1a; 小编仅是一位初学者&#xff0c;所以对于C的理解有限&#xff0c;文章大概率会出现表达不清楚可能也只是因为小编不知道如何更好表达&#xff0c;本文章仅作为一个学习的总结分享。 反向迭代器的概念 反向迭代器故名思意解释反向的迭代器&#xff0c;与正向迭…

地铁X光危险品检测数据集

地铁X光危险品检测数据集介绍 数据集概览 本数据集旨在为地铁X光安检系统提供高质量的危险品检测训练素材。数据集包含18类常见危险品&#xff0c;总共6265张图像&#xff0c;每张图像均经过精心标注&#xff0c;确保了数据的质量和一致性。数据集适用于多种格式&#xff08;Y…

Spring优缺点和SpringBoot基础和搭建

前言 Spring框架是一个流行的Java企业级开发框架&#xff0c;旨在简化应用程序开发。它的核心特性包括依赖注入和面向切面编程&#xff0c;提供了灵活性和强大的社区支持。然而&#xff0c;Spring也存在学习曲线陡峭和配置复杂等缺点。 Spring Boot是基于Spring的项目&#x…

2024年高教社杯数学建模竞赛须知——三大注意事项

为了让大家在最后一天更好的备注国赛&#xff0c;我们今日将结合2024年国赛的新规给大家讲解国赛中三大主要的事项&#xff1a; 论文模版问题——国赛乃至大部分数模竞赛从来没有给出任何的论文模版&#xff0c;大部分的模版均为一次又一次学生、老师内部传播形成。资料使用问…

文件包含PHP伪协议利用方法

首先我们需要把配置文件php.ini 在 php.ini ⾥有两个重要的参数 allow_url_fopen 、allow_url_include&#xff1b; allow_url_fopen:默认值是 ON。允许 url ⾥的封装协议访问⽂件&#xff1b; allow_url_include:默认值是 OFF。不允许包含 url ⾥的封装协议包含⽂件&#x…

深度学习-VGG16原理和代码详解

VGG16 原理和代码详解 VGG16 是由牛津大学的 Visual Geometry Group (VGG) 提出的深度卷积神经网络&#xff0c;发表于 2014 年的论文 “Very Deep Convolutional Networks for Large-Scale Image Recognition”。VGG16 是其中的一种结构&#xff0c;由 16 层网络组成&#xf…

win系统安装mysql,使用mysqldump,pycharm使用mysqldump,避坑

文章目录 下载mysql的win客户端设置系统环境变量验证是否可用pycharm使用mysqldump异常问题排查 下载mysql的win客户端 官网下载地址如果下载旧版本&#xff0c;需自行到Archives里面找 本人使用的是mysql5.7&#xff0c;找到相应版本后&#xff0c;点击Download下载 设置系统…

Docker入门笔记

Docker 文章目录 Docker1. 下载 &#xff08;centos&#xff09;2. 部署 MySQL3. 常用命令4. 数据卷5. 自定义镜像6. Java 项目部署 1. 下载 &#xff08;centos&#xff09; 卸载旧版 yum remove docker \docker-client \docker-client-latest \docker-common \docker-lates…