之前在工作中做了一个法院文书模板的文首文尾合并工具,这里把部分功能代码片段记录下来,方便后续使用

1. 通过swagger接口上传文件报错,设置servlet支持上传文件大小。(不加单位是Byte,可用单位Mb)

# 单个文件的最大值
spring.servlet.multipart.max-file-size=1000000000
# 上传文件总的最大值
spring.servlet.multipart.max-request-size=1000000000

2. 遍历文件夹下的所有文件,如果是文件夹,就在另一个目录下新建一个相同的文件夹(递归复制文件夹使用,因为后面复制文件时并不会自动创建文件夹),并且递归遍历,如果是文件就把文件名和文件路径存储到map中,因为不同目录中可能会有相同文件名的文件,所以只能用允许key值重复的map → IdentityHashMap。

IdentityHashMap hbFiles = new IdentityHashMap();
Map<String, String> hbMap = dealFile(hbFiles, path + File.separator + dirNameSource, dirNameSource);
public static Map<String, String> dealFile(Map files, String filePath, String dirNameSource) {
    File file = new File(filePath);
    String[] fileNameLists = file.list();
    File[] filePathLists = file.listFiles();
    for (int i = 0; i < filePathLists.length; i++) {
        if (filePathLists[i].isFile()) {
            String path = filePathLists[i].getPath();
            //把文件名作为key,文件路径为value 存储在map中
            files.put(fileNameLists[i], path);
        } else {
            (new File(filePathLists[i].getPath().replace("mergeTpl" + File.separator + dirNameSource, "ext"))).mkdirs();
            dealFile(files, filePathLists[i].getPath(), dirNameSource);
        }
    }
    return files;
}

3. POI删除word中从书签A到书签B中间的所有内容

public static int delete(XWPFDocument document, String startBookmark, String endBookmark) {
    List<XWPFParagraph> paragraphs = document.getParagraphs();
    int start = 0;
    int end = 0;
    for (XWPFParagraph paragraph : paragraphs) {
        List<CTBookmark> bookmarkStartList = paragraph.getCTP().getBookmarkStartList();
        for (CTBookmark bookmark : bookmarkStartList) {
            if (bookmark.getName().equals(startBookmark)) {
                start = paragraphs.indexOf(paragraph);
            }
            if (bookmark.getName().equals(endBookmark)) {
                end = paragraphs.indexOf(paragraph);
            }
        }
    }

    for (int i = end; i > start; i--) {
        document.removeBodyElement(i);
    }
    return start;
}

4. POI复制段落,从paragraphA到paragraphB

/**
 * 功能描述:复制段落,从source到target
 */
public static void copyParagraph(XWPFParagraph target,
                                 XWPFParagraph source, Integer index) {
    // 设置段落样式
    target.getCTP().setPPr(source.getCTP().getPPr());
    // 移除所有的run
    for (int pos = target.getRuns().size() - 1; pos >= 0; pos--) {
        target.removeRun(pos);
    }
    // copy 新的run
    for (XWPFRun s : source.getRuns()) {
        XWPFRun targetrun = target.createRun();
        copyRun(targetrun, s, index);
    }
    CTBookmark ctBookmark = target.getCTP().addNewBookmarkStart();
    ctBookmark.setId(BigInteger.valueOf(100000001));
    //书签名称
    ctBookmark.setName("permEnd0");
    target.createRun();
    target.getCTP().addNewBookmarkEnd().setId(BigInteger.valueOf(100000001));
}

/**
 * 功能描述:复制RUN,从source到target
 */
public static void copyRun(XWPFRun target, XWPFRun source, Integer
        index) {
    // 设置run属性
    target.getCTR().setRPr(source.getCTR().getRPr());
    // 设置文本
    String tail = "";
    if (index != null) {
        tail = index.toString();
    }
    target.setText(source.text());
}

5. 上传的文件解压到指定路径

/**
 * 解压zip格式的压缩文件到指定位置
 *
 * @param zipFileName 压缩文件
 * @param extPlace    解压目录
 * @throws Exception
 */
public static boolean unZipFiles(MultipartFile zipFileName, String extPlace) {
    System.setProperty("sun.zip.encoding",
            System.getProperty("sun.jnu.encoding"));
    try {
        (new File(extPlace)).mkdirs();
        File f = new File(zipFileName.getOriginalFilename());
        FileUtils.copyInputStreamToFile(zipFileName.getInputStream(), f);
        ZipFile zipFile = new ZipFile(f, "GBK"); // 处理中文文件名乱码的问题
        if ((!f.exists()) && (f.length() <= 0)) {
            throw new Exception("要解压的文件不存在!");
        }
        String strPath, gbkPath, strtemp;
        File tempFile = new File(extPlace);
        strPath = tempFile.getAbsolutePath();
        Enumeration<?> e = zipFile.getEntries();
        while (e.hasMoreElements()) {
            org.apache.tools.zip.ZipEntry zipEnt = (org.apache.tools.zip.ZipEntry) e.nextElement();
            gbkPath = zipEnt.getName();
            if (zipEnt.isDirectory()) {
                strtemp = strPath + File.separator + gbkPath;
                File dir = new File(strtemp);
                dir.mkdirs();
                continue;
            } else { // 读写文件
                InputStream is = zipFile.getInputStream(zipEnt);
                BufferedInputStream bis = new BufferedInputStream(is);
                gbkPath = zipEnt.getName();
                strtemp = strPath + File.separator + gbkPath;// 建目录
                String strsubdir = gbkPath;
                for (int i = 0; i < strsubdir.length(); i++) {
                    if (strsubdir.substring(i, i + 1).equalsIgnoreCase("/")) {
                        String temp = strPath + File.separator
                                + strsubdir.substring(0, i);
                        File subdir = new File(temp);
                        if (!subdir.exists()) {
                            subdir.mkdir();
                        }
                    }
                }
                FileOutputStream fos = new FileOutputStream(strtemp);
                BufferedOutputStream bos = new BufferedOutputStream(fos);
                int c;
                while ((c = bis.read()) != -1) {
                    bos.write((byte) c);
                }
                bos.close();
                fos.close();
                is.close();
                bis.close();
            }
        }
        return true;
    } catch (Exception e) {
        e.printStackTrace();
        return false;
    }
}

6. 递归压缩文件夹


private static final int BUFFER_SIZE = 2 * 1024;

/**
 * 压缩成ZIP 方法
 *
 * @param srcDir           压缩文件夹路径
 * @param out              压缩文件输出流
 * @param KeepDirStructure 是否保留原来的目录结构,true:保留目录结构;
 *                         <p>
 *                         false:所有文件跑到压缩包根目录下(注意:不保留目录结构可能会出现同名文件,会压缩失败)
 * @throws RuntimeException 压缩失败会抛出运行时异常
 */
public static void toZip(String srcDir, OutputStream out, boolean KeepDirStructure) throws RuntimeException {

    long start = System.currentTimeMillis();
    ZipOutputStream zos = null;
    try {
        zos = new ZipOutputStream(out);
        File sourceFile = new File(srcDir);
        compress(sourceFile, zos, sourceFile.getName(), KeepDirStructure);
        long end = System.currentTimeMillis();
        System.out.println("压缩完成,耗时:" + (end - start) + " ms");
    } catch (Exception e) {
        throw new RuntimeException("zip error from ZipUtils", e);
    } finally {
        if (zos != null) {
            try {
                zos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

/**
 * 递归压缩方法
 *
 * @param sourceFile       源文件
 * @param zos              zip输出流
 * @param name             压缩后的名称
 * @param KeepDirStructure 是否保留原来的目录结构, true:保留目录结构;
 *                         false:所有文件跑到压缩包根目录下(注意:不保留目录结构可能会出现同名文件,会压缩失败)
 * @throws Exception
 */
private static void compress(File sourceFile, ZipOutputStream zos, String name, boolean KeepDirStructure)
        throws Exception {

    byte[] buf = new byte[BUFFER_SIZE];
    if (sourceFile.isFile()) {
        // 向zip输出流中添加一个zip实体,构造器中name为zip实体的文件的名字
        zos.putNextEntry(new ZipEntry(name));
        // copy文件到zip输出流中
        int len;
        FileInputStream in = new FileInputStream(sourceFile);
        while ((len = in.read(buf)) != -1) {
            zos.write(buf, 0, len);
        }
        // Complete the entry
        zos.closeEntry();
        in.close();
    } else {
        File[] listFiles = sourceFile.listFiles();
        if (listFiles == null || listFiles.length == 0) {
            // 需要保留原来的文件结构时,需要对空文件夹进行处理
            if (KeepDirStructure) {
                // 空文件夹的处理
                zos.putNextEntry(new ZipEntry(name + "/"));
                // 没有文件,不需要文件的copy
                zos.closeEntry();
            }
        } else {
            for (File file : listFiles) {
                // 判断是否需要保留原来的文件结构
                if (KeepDirStructure) {
                    // 注意:file.getName()前面需要带上父文件夹的名字加一斜杠,
                    // 不然最后压缩包中就不能保留原来的文件结构,即:所有文件都跑到压缩包根目录下了
                    compress(file, zos, name + "/" + file.getName(), KeepDirStructure);
                } else {
                    compress(file, zos, file.getName(), KeepDirStructure);
                }
            }
        }
    }
}

7. 递归删除文件夹方法

public static void delete(File file) {
    //判断文件不为null或文件目录存在
    if (file == null || !file.exists()) {
        return;
    }
    //取得这个目录下的所有子文件对象
    File[] files = file.listFiles();
    //遍历该目录下的文件对象
    for (File f : files) {
        //判断子目录是否存在子目录,如果是文件则删除
        if (f.isDirectory()) {
            delete(f);
        } else {
            f.delete();
        }
    }
    //删除空文件夹  for循环已经把上一层节点的目录清空。
    file.delete();
}

8. 主功能

public static void dealTplFile(String path, String startTag, String endTag, String dirNameSource, String dirNameTarget) {
    (new File(path.replace("mergeTpl", "ext"))).mkdirs();
    IdentityHashMap hbFiles = new IdentityHashMap();
    Map<String, String> hbMap = dealFile(hbFiles, path + File.separator + dirNameSource, dirNameSource);
    for (Map.Entry<String, String> entry : hbMap.entrySet()) {
        String TplName = entry.getKey();
        String hbTplPath = entry.getValue();
        String hnTplPath = hbTplPath.replace(dirNameSource, dirNameTarget);
        if (!new File(hnTplPath).exists()) {
            logger.error("未找到【" + dirNameTarget + "】目录下的文件:" + TplName);
            continue;
        }
        OutputStream os = null;
        try {
            XWPFDocument source = new XWPFDocument(new FileInputStream(hbTplPath));
            XWPFDocument target = new XWPFDocument(new FileInputStream(hnTplPath));
            XWPFDocument document = mergeWord(source, target, startTag, endTag);
            os = new FileOutputStream(hbTplPath.replace("mergeTpl" + File.separator + dirNameSource, "ext"));
            document.write(os);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
public static XWPFDocument mergeWord(XWPFDocument source, XWPFDocument target, String startTag, String endTag) {
    int delete = delete(target, "permStart0", "permEnd0");
    boolean startCopy = false;
    for (XWPFParagraph s_paragraph : source.getParagraphs()) {
        if (s_paragraph.getText().contains(endTag)) {
            break;
        }
        if (startCopy == true) {
            XmlCursor xmlCursor = target.getParagraphs().get(delete).getCTP().newCursor();
            xmlCursor.toNextSibling();
            target.insertNewParagraph(xmlCursor);
            delete++;
            copyParagraph(target.getParagraphArray(delete), s_paragraph, 0);
        }
        if (s_paragraph.getText().contains(startTag)) {
            while (target.getParagraphs().get(delete).getRuns().size() != 0) {
                target.getParagraphs().get(delete).removeRun(0);
            }
            for (XWPFRun run : s_paragraph.getRuns()) {
                XWPFRun run1 = target.getParagraphs().get(delete).createRun();
                copyRun(run1, run, 0);
            }
            startCopy = true;
        }
    }
    return target;
}

9. Controller使用swagger页面上传下载文件

@RestController
@RequestMapping("/mergeTpl")
@Api(value = "文件controller", tags = {"A - 模板合并工具"})
public class MergeTplController {

    @ResponseBody
    @PostMapping(value = "/upload", consumes = "multipart/*", headers = "content-type=multipart/form-data")
    @ApiOperation(value = "上传模板包")
    @ApiResponses(value = {
            @ApiResponse(code = 200, message = "上传成功!"),
            @ApiResponse(code = 500, message = "上传失败!")
    })
    public ResponseEntity mergeTpl(@ApiParam(value = "模板包", required = true) MultipartFile file,
                                   @RequestParam(required = false, defaultValue = "【案号】") String startTag,
                                   @RequestParam(required = false, defaultValue = "【审判人员】") String endTag,
                                   @RequestParam(required = false, defaultValue = "海南") String dirNameTarget,
                                   @RequestParam(required = false, defaultValue = "行业标准") String dirNameSource) throws IOException {
        String jypath = System.getProperty("user.dir") + File.separator + "merge" + File.separator + "mergeTpl";
        String outputpath = System.getProperty("user.dir") + File.separator + "merge" + File.separator + "ext";
        String mainpath = System.getProperty("user.dir") + File.separator + "merge";
        MergeTplUtil.unZipFiles(file, jypath);
        MergeTplUtil.dealTplFile(jypath, startTag, endTag, dirNameSource, dirNameTarget);
        OutputStream ops = new FileOutputStream(new File(mainpath + File.separator + "ext.zip"));
        MergeTplUtil.toZip(outputpath, ops, true);
        InputStream is = new FileInputStream(mainpath + File.separator + "ext.zip");
        String fileName = "合并后文件.zip";
        MergeTplUtil.delete(new File(mainpath));
        File copyfile = new File(System.getProperty("user.dir") + file.getName());
        if (copyfile.exists()) {
            copyfile.delete();
        }
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
        return new ResponseEntity(IOUtils.toByteArray(is), headers, HttpStatus.OK);
    }
}