SpringBoot根据word模板导出PDF

811 阅读1分钟

这是我的第一篇掘金博客,开启掘金写作之路 maven相关依赖

<poi.version>3.17</poi.version>
<fr-pdf.version>2.0.1</fr-pdf.version>

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>${poi.version}</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>${poi.version}</version>
</dependency>
<dependency>
    <groupId>fr.opensagres.xdocreport</groupId>
    <artifactId>fr.opensagres.poi.xwpf.converter.pdf</artifactId>
    <version>${fr-pdf.version}</version>
</dependency>
<dependency>
    <groupId>fr.opensagres.xdocreport</groupId>
    <artifactId>fr.opensagres.xdocreport.document</artifactId>
    <version>${fr-pdf.version}</version>
</dependency>

<dependency>
    <groupId>fr.opensagres.xdocreport</groupId>
    <artifactId>fr.opensagres.xdocreport.itext.extension</artifactId>
    <version>${fr-pdf.version}</version>
</dependency>

工具类

import com.quantaeye.app.common.util.DateUtil;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.RichTextString;
import org.apache.poi.util.Units;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
import org.springframework.util.StringUtils;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class WordToPdfUtil {
    /**
     * @param params
     * @param tableIndex word的表格索引
     * @param rownum 从第几行开始
     * @param createrowindex 以第几行作为创建行
     * @return
     * @throws Exception
     */
    public static boolean export(XWPFDocument xwpfDocument,List<Map<String,String>> params, int tableIndex,int rownum,int
            createrowindex) throws Exception{
        insertValueToTable(xwpfDocument,params,tableIndex,rownum,createrowindex);
        return true;
    }
    /**
     * 循环填充表格内容
     * @param xwpfDocument
     * @param params
     * @param tableIndex
     * @throws Exception
     */
    public static void insertValueToTable(XWPFDocument xwpfDocument, List<Map<String,String>>
            params, int tableIndex,int rownum,int createrowindex) throws Exception {
        List<XWPFTable> tableList = xwpfDocument.getTables();
        if(tableList.size()<=tableIndex){
            throw new Exception("tableIndex对应的表格不存在");
        }
        XWPFTable table = tableList.get(tableIndex);
        List<XWPFTableRow> rows = table.getRows();
        if(rows.size()<2){
            throw new Exception("tableIndex对应表格应该为2行");
        }
        //模板的那一行
        XWPFTableRow tmpRow = rows.get(rownum);
        List<XWPFTableCell> tmpCells = null;
        List<XWPFTableCell> cells = null;
        XWPFTableCell tmpCell = null;
        tmpCells = tmpRow.getTableCells();
        table.getCTTbl();
        String cellText = null;
        String cellTextKey = null;
        Map<String,Object> totalMap = null;
        for (int i = 0, len = params.size(); i < len; i++) {
            Map<String,String> map = params.get(i);
            // 创建新的一行
            XWPFTableRow row = table.createRow();
            //XWPFTableRow row = createRow(table.getCTTbl(),table,createrowindex);
            // 获取模板的行高 设置为新一行的行高
            row.setHeight(tmpRow.getHeight());
            cells = row.getTableCells();
            for (int k = 0, klen = cells.size(); k < klen; k++) {
                tmpCell = tmpCells.get(k);
                XWPFTableCell cell = cells.get(k);
                cellText = tmpCell.getText();
                if (org.apache.commons.lang.StringUtils.isNotBlank(cellText)) {
                    //转换为mapkey对应的字段
                    cellTextKey = cellText.replace("$", "").replace("{", "").replace("}",
                            "");
                    if (map.containsKey(cellTextKey)) {
                        // 填充内容 并且复制模板行的属性
                        setCellText(tmpCell,cell,map.get(cellTextKey)==null?"":map.get(cellTextKey).toString());
                    }
                }
            }
        }
        // 删除模版行
        table.removeRow(rownum);
    }

    public static XWPFTableRow createRow(CTTbl ctTbl, XWPFTable table, int i){
        int sizeCol = ctTbl.sizeOfTrArray() > 0 ? ctTbl.getTrArray(i)
                .sizeOfTcArray() : 0;
        XWPFTableRow tabRow = new XWPFTableRow(ctTbl.addNewTr(), table);
        addColumn(tabRow, sizeCol);
        //tableRows.add(tabRow);
        return tabRow;
    }
    public static void addColumn(XWPFTableRow tabRow, int sizeCol) {
        if (sizeCol > 0) {
            for (int i = 0; i < sizeCol; i++) {
                tabRow.createCell();
            }
        }
    }
    /**
     * 复制模板行的属性
     * @param tmpCell
     * @param cell
     * @param text
     * @throws Exception
     */
    public static void setCellText(XWPFTableCell tmpCell, XWPFTableCell cell,String text)
            throws Exception {
        CTTc cttc2 = tmpCell.getCTTc();
        CTTcPr ctPr2 = cttc2.getTcPr();
        CTTc cttc = cell.getCTTc();
        CTTcPr ctPr = cttc.addNewTcPr();
        if (ctPr2.getTcW() != null) {
            ctPr.addNewTcW().setW(ctPr2.getTcW().getW());
        }
        if (ctPr2.getVAlign() != null) {
            ctPr.addNewVAlign().setVal(ctPr2.getVAlign().getVal());
        }
        if (cttc2.getPList().size() > 0) {
            CTP ctp = cttc2.getPList().get(0);
            if (ctp.getPPr() != null) {
                if (ctp.getPPr().getJc() != null) {
                    cttc.getPList().get(0).addNewPPr().addNewJc()
                            .setVal(ctp.getPPr().getJc().getVal());
                }
            }
        }
        if (ctPr2.getTcBorders() != null) {
            ctPr.setTcBorders(ctPr2.getTcBorders());
        }
        XWPFParagraph tmpP = tmpCell.getParagraphs().get(0);
        XWPFParagraph cellP = cell.getParagraphs().get(0);
        XWPFRun tmpR = null;
        if (tmpP.getRuns() != null && tmpP.getRuns().size() > 0) {
            tmpR = tmpP.getRuns().get(0);
        }
        XWPFRun cellR = cellP.createRun();
        cellR.setText(text);
        // 复制字体信息
        if (tmpR != null) {
            if(!cellR.isBold()){
                cellR.setBold(tmpR.isBold());
            }
            cellR.setItalic(tmpR.isItalic());
            cellR.setUnderline(tmpR.getUnderline());
            cellR.setColor(tmpR.getColor());
            cellR.setTextPosition(tmpR.getTextPosition());
            if (tmpR.getFontSize() != 1)
            {
                cellR.setFontSize(tmpR.getFontSize());
            }
            if (tmpR.getFontFamily() != null) {
                cellR.setFontFamily(tmpR.getFontFamily());
            }
            if (tmpR.getCTR() != null) {
                if (tmpR.getCTR().isSetRPr()) {
                    CTRPr tmpRPr = tmpR.getCTR().getRPr();
                    if (tmpRPr.isSetRFonts()) {
                        CTFonts tmpFonts = tmpRPr.getRFonts();
                        CTRPr cellRPr = cellR.getCTR().isSetRPr() ? cellR
                                .getCTR().getRPr() : cellR.getCTR().addNewRPr();
                        CTFonts cellFonts = cellRPr.isSetRFonts() ? cellRPr
                                .getRFonts() : cellRPr.addNewRFonts();
                        cellFonts.setAscii(tmpFonts.getAscii());
                        cellFonts.setAsciiTheme(tmpFonts.getAsciiTheme());
                        cellFonts.setCs(tmpFonts.getCs());
                        cellFonts.setCstheme(tmpFonts.getCstheme());
                        cellFonts.setEastAsia(tmpFonts.getEastAsia());
                        cellFonts.setEastAsiaTheme(tmpFonts.getEastAsiaTheme());
                        cellFonts.setHAnsi(tmpFonts.getHAnsi());
                        cellFonts.setHAnsiTheme(tmpFonts.getHAnsiTheme());
                    }
                }
            }
        }
        // 复制段落信息
        cellP.setAlignment(tmpP.getAlignment());
        cellP.setVerticalAlignment(tmpP.getVerticalAlignment());
        cellP.setBorderBetween(tmpP.getBorderBetween());
        cellP.setBorderBottom(tmpP.getBorderBottom());
        cellP.setBorderLeft(tmpP.getBorderLeft());
        cellP.setBorderRight(tmpP.getBorderRight());
        cellP.setBorderTop(tmpP.getBorderTop());
        cellP.setPageBreak(tmpP.isPageBreak());
        if (tmpP.getCTP() != null) {
            if (tmpP.getCTP().getPPr() != null) {
                CTPPr tmpPPr = tmpP.getCTP().getPPr();
                CTPPr cellPPr = cellP.getCTP().getPPr() != null ? cellP
                        .getCTP().getPPr() : cellP.getCTP().addNewPPr();
                // 复制段落间距信息
                CTSpacing tmpSpacing = tmpPPr.getSpacing();
                if (tmpSpacing != null) {
                    CTSpacing cellSpacing = cellPPr.getSpacing() != null ? cellPPr
                            .getSpacing() : cellPPr.addNewSpacing();
                    if (tmpSpacing.getAfter() != null) {
                        cellSpacing.setAfter(tmpSpacing.getAfter());
                    }
                    if (tmpSpacing.getAfterAutospacing() != null) {
                        cellSpacing.setAfterAutospacing(tmpSpacing
                                .getAfterAutospacing());
                    }
                    if (tmpSpacing.getAfterLines() != null) {
                        cellSpacing.setAfterLines(tmpSpacing.getAfterLines());
                    }
                    if (tmpSpacing.getBefore() != null) {
                        cellSpacing.setBefore(tmpSpacing.getBefore());
                    }
                    if (tmpSpacing.getBeforeAutospacing() != null) {
                        cellSpacing.setBeforeAutospacing(tmpSpacing
                                .getBeforeAutospacing());
                    }
                    if (tmpSpacing.getBeforeLines() != null) {
                        cellSpacing.setBeforeLines(tmpSpacing.getBeforeLines());
                    }
                    if (tmpSpacing.getLine() != null) {
                        cellSpacing.setLine(tmpSpacing.getLine());
                    }
                    if (tmpSpacing.getLineRule() != null) {
                        cellSpacing.setLineRule(tmpSpacing.getLineRule());
                    }
                }
                // 复制段落缩进信息
                CTInd tmpInd = tmpPPr.getInd();
                if (tmpInd != null) {
                    CTInd cellInd = cellPPr.getInd() != null ? cellPPr.getInd()
                            : cellPPr.addNewInd();
                    if (tmpInd.getFirstLine() != null) {
                        cellInd.setFirstLine(tmpInd.getFirstLine());
                    }
                    if (tmpInd.getFirstLineChars() != null) {
                        cellInd.setFirstLineChars(tmpInd.getFirstLineChars());
                    }
                    if (tmpInd.getHanging() != null) {
                        cellInd.setHanging(tmpInd.getHanging());
                    }
                    if (tmpInd.getHangingChars() != null) {
                        cellInd.setHangingChars(tmpInd.getHangingChars());
                    }
                    if (tmpInd.getLeft() != null) {
                        cellInd.setLeft(tmpInd.getLeft());
                    }
                    if (tmpInd.getLeftChars() != null) {
                        cellInd.setLeftChars(tmpInd.getLeftChars());
                    }
                    if (tmpInd.getRight() != null) {
                        cellInd.setRight(tmpInd.getRight());
                    }
                    if (tmpInd.getRightChars() != null) {
                        cellInd.setRightChars(tmpInd.getRightChars());
                    }
                }
            }
        }
    }

    /**
     * 替换段落里面的变量
     *
     * @param doc    要替换的文档
     * @param params 参数
     */
    public static void replaceParams(XWPFDocument doc, Map<String, String> params) {
        Iterator<XWPFParagraph> iterator = doc.getParagraphsIterator();
        XWPFParagraph paragraph;
        while (iterator.hasNext()) {
            paragraph = iterator.next();
            replaceParam(paragraph, params);
        }
    }
    /**
     * 正则匹配字符串
     *
     * @param str
     * @return
     */
    public static Matcher matcher(String str) {
        Pattern pattern = Pattern.compile("\$\{(.+?)\}", Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(str);
        return matcher;
    }

    /**
     * 替换表格里面的变量
     *
     * @param params 要替换的文档
     * @param params 参数
     */
    public static void replaceTableParams(XWPFDocument xwpfDocument, Map<String, String> params,int tableIndex) {
        List<XWPFTable> tableList = xwpfDocument.getTables();
        XWPFTable table =tableList.get(tableIndex);
        List<XWPFTableRow> rows;
        List<XWPFTableCell> cells;
        List<XWPFParagraph> paras;
        //判断表格是需要替换还是需要插入,判断逻辑有$为替换,表格无$为插入
        if (matcher(table.getText()).find()) {
            rows = table.getRows();
            for (XWPFTableRow row : rows) {
                cells = row.getTableCells();
                for (XWPFTableCell cell : cells) {
                    paras = cell.getParagraphs();
                    for (XWPFParagraph para : paras) {
                        replaceParam(para, params,cell);
                    }
                }
            }
        }
    }

    /**
     * 替换表格里面的图片变量
     *
     * @param params 要替换的文档
     * @param params 参数
     */
    public static void replaceTableImgParams(XWPFDocument xwpfDocument, Map<String, Object> params,int tableIndex) {
        List<XWPFTable> tableList = xwpfDocument.getTables();
        XWPFTable table =tableList.get(tableIndex);
        List<XWPFTableRow> rows;
        List<XWPFTableCell> cells;
        List<XWPFParagraph> paras;
        //判断表格是需要替换还是需要插入,判断逻辑有$为替换,表格无$为插入
        if (matcher(table.getText()).find()) {
            rows = table.getRows();
            for (XWPFTableRow row : rows) {
                cells = row.getTableCells();
                for (XWPFTableCell cell : cells) {
                    paras = cell.getParagraphs();
                    for (XWPFParagraph para : paras) {
                        replaceImgParam(para, params);
                    }
                }
            }
        }
    }

    /**
     * 替换表格里面的图片变量
     *
     * @param paragraph 要替换的段落
     * @param params    参数
     */
    public static void replaceImgParam(XWPFParagraph paragraph, Map<String, Object> params) {
        List<XWPFRun> runs;
        Matcher matcher;
        String runText = "";
        if (matcher(paragraph.getParagraphText()).find()) {
            runs = paragraph.getRuns();
            int j = runs.size();
            for (int i = 0; i < j; i++) {
                runText += runs.get(0).toString();
                //保留最后一个段落,在这段落中替换值,保留段落样式
                if (!((j - 1) == i)) {
                    paragraph.removeRun(0);
                }
            }
            matcher = matcher(runText);
            if (matcher.find()) {
                while ((matcher = matcher(runText)).find()) {
                    String key = matcher.group(1);
                    Object obj = params.get(key);
                    if(obj instanceof Map){
                        Map<String,Object> map = (Map<String,Object>)params.get(key);
                        byte[] byteArray = (byte[])map.get("byteArray");
                        String imgFilePath = String.valueOf(map.get("imgFilePath"));
                        InputStream in = null;
                        try{
                            in = new ByteArrayInputStream(byteArray);
                            runs.get(0).addBreak();
                            runs.get(0).addPicture(in, getImgFormat(imgFilePath), imgFilePath, Units.toEMU((int)map.get("width")), Units.toEMU((int)map.get("height"))); // 200x200 pixels
                            runs.get(0).addBreak(BreakType.PAGE);
                            runText = matcher.replaceFirst("");
                        }catch(Exception ex){
                            ex.printStackTrace();
                        }finally {
                            try {
                                if(in != null){
                                    in.close();
                                }
                            }catch (Exception e){
                                e.printStackTrace();
                            }
                        }
                    }else if(obj instanceof String || obj instanceof Integer){
                        runText = matcher.replaceFirst(String.valueOf(obj));
                    }else if(obj instanceof Date){
                        runText = matcher.replaceFirst(DateUtil.formatDate((Date)obj,DateUtil.DATETIME));
                    }else{
                        runText = matcher.replaceFirst("");
                    }
                }
                runs.get(0).setText(runText, 0);
            }
        }
    }

    public static final InputStream byte2Input(byte[] buf) {
        return new ByteArrayInputStream(buf);
    }

    public static int getImgFormat(String imgFile){
        int format;
        if(imgFile.endsWith(".emf")){
            format = XWPFDocument.PICTURE_TYPE_EMF;
        }
        else if(imgFile.endsWith(".wmf")){
            format = XWPFDocument.PICTURE_TYPE_WMF;
        }
        else if(imgFile.endsWith(".pict")){
            format = XWPFDocument.PICTURE_TYPE_PICT;
        }
        else if(imgFile.endsWith(".jpeg") || imgFile.endsWith(".jpg")){
            format = XWPFDocument.PICTURE_TYPE_JPEG;
        }
        else if(imgFile.endsWith(".png")) {
            format = XWPFDocument.PICTURE_TYPE_PNG;
        }
        else if(imgFile.endsWith(".dib")){
            format = XWPFDocument.PICTURE_TYPE_DIB;
        }
        else if(imgFile.endsWith(".gif")){
            format = XWPFDocument.PICTURE_TYPE_GIF;
        }
        else if(imgFile.endsWith(".tiff")){
            format = XWPFDocument.PICTURE_TYPE_TIFF;
        }
        else if(imgFile.endsWith(".eps")){
            format = XWPFDocument.PICTURE_TYPE_EPS;
        }
        else if(imgFile.endsWith(".bmp")){
            format = XWPFDocument.PICTURE_TYPE_BMP;
        }
        else{
            format = XWPFDocument.PICTURE_TYPE_WPG;
        }
        return format;
    }

    /**
     * 替换页眉里面的变量
     *
     * @param params 要替换的文档
     * @param params 参数
     */
    public static void replaceXWPFHeaderParams(XWPFDocument xwpfDocument, Map<String, String> params) {
        List<XWPFHeader> xwpfHeaderList = xwpfDocument.getHeaderList();
        Iterator iterator = xwpfHeaderList.iterator();
        XWPFParagraph para;
        XWPFHeader xwpfHeader;
        while (iterator.hasNext()) {
            xwpfHeader = (XWPFHeader) iterator.next();
            List<XWPFParagraph> xwpfParagraphList = xwpfHeader.getParagraphs();
            Iterator iteratorPara = xwpfParagraphList.iterator();
            while (iteratorPara.hasNext()){
                para = (XWPFParagraph) iteratorPara.next();
                replaceParam(para, params);
            }
        }
    }

    /**
     * 替换段落里面的变量
     *
     * @param paragraph 要替换的段落
     * @param params    参数
     */
    public static void replaceParam(XWPFParagraph paragraph, Map<String, String> params,XWPFTableCell cell) {
        List<XWPFRun> runs;
        Matcher matcher;
        String runText = "";

        if (matcher(paragraph.getParagraphText()).find()) {
            runs = paragraph.getRuns();
            int j = runs.size();
            for (int i = 0; i < j; i++) {
                runText += runs.get(0).toString();
                //保留最后一个段落,在这段落中替换值,保留段落样式
                if (!((j - 1) == i)) {
                    paragraph.removeRun(0);
                }
            }
            matcher = matcher(runText);
            if (matcher.find()) {
                while ((matcher = matcher(runText)).find()) {
                    runText = matcher.replaceFirst(String.valueOf(params.get(matcher.group(1))));
                }
                runs.get(0).setText(runText, 0);
            }
        }
    }

    /**
     * 替换段落里面的变量
     *
     * @param paragraph 要替换的段落
     * @param params    参数
     */
    public static void replaceParam(XWPFParagraph paragraph, Map<String, String> params) {
        List<XWPFRun> runs;
        Matcher matcher;
        String runText = "";
        if (matcher(paragraph.getParagraphText()).find()) {
            runs = paragraph.getRuns();
            int j = runs.size();
            for (int i = 0; i < j; i++) {
                runText += runs.get(0).toString();
                //保留最后一个段落,在这段落中替换值,保留段落样式
                if (!((j - 1) == i)) {
                    paragraph.removeRun(0);
                }
            }
            matcher = matcher(runText);
            if (matcher.find()) {
                while ((matcher = matcher(runText)).find()) {
                    String key = matcher.group(1);
                    if(key.equals("softwareD") || key.equals("platform") || key.equals("contractSubject")
                            || key.equals("dataD")|| key.equals("report")|| key.equals("operationType")
                            || key.equals("companyS")|| key.equals("pA")|| key.equals("pW")){
                        runs.get(0).setFontFamily("Wingdings 2");
                    }
                    runText = matcher.replaceFirst(String.valueOf(params.get(matcher.group(1))));
                }
                runs.get(0).setText(runText, 0);
            }
        }
    }

    private RichTextString fillTextRight(Font font){
        RichTextString richTextString = new XSSFRichTextString("\\u25A1");
        // 设置字体名称
        font.setFontName("Wingdings 2");
        richTextString.applyFont(font);
        return richTextString;

    }

    /**
     * 将输入流中的数据写入字节数组
     *
     * @param in
     * @return
     */
    public static byte[] inputStream2ByteArray(InputStream in, boolean isClose) {
        byte[] byteArray = null;
        try {
            int total = in.available();
            byteArray = new byte[total];
            in.read(byteArray);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (isClose) {
                try {
                    in.close();
                } catch (Exception e2) {
                    e2.getStackTrace();
                }
            }
        }
        return byteArray;
    }

    /**
     * 将Object对象里面的属性和值转化成Map对象
     *
     * @param obj
     * @return
     * @throws IllegalAccessException
     */
    public static Map<String, String> objectToMap(Object obj) throws IllegalAccessException {
        Map<String, String> map = new HashMap<>();
        Class<?> clazz = obj.getClass();
        for (Field field : clazz.getDeclaredFields()) {
            field.setAccessible(true);
            String fieldName = field.getName();
            Object value =  field.get(obj);
            if(!StringUtils.isEmpty(value)){
                if(value instanceof String || value instanceof Integer){
                    map.put(fieldName, value.toString());
                }else if(value instanceof Date){
                    map.put(fieldName, DateUtil.formatDate((Date)value,DateUtil.DATETIME));
                }
                //其它不转换,这里留着扩充使用
            }else{
                map.put(fieldName, "");
            }
        }
        return map;
    }

   public static Map<String, Object> objectToMapTwo(Object obj) throws IllegalAccessException {
        Map<String, Object> map = new HashMap<>();
        Class<?> clazz = obj.getClass();
        for (Field field : clazz.getDeclaredFields()) {
            field.setAccessible(true);
            String fieldName = field.getName();
            Object value =  field.get(obj);
            map.put(fieldName, value);
        }
        return map;
    }


}

SpringBoot中实现

@ApiOperation("导出PDF")
@RequestMapping(value = "/exportPdf", method = RequestMethod.GET)
public void exportPdfHttpServletResponse response) throws Exception {
    BufferedOutputStream outputStream = null;
    XWPFDocument doc = null;
    try{
        ExtFontFactory extFontFactory = new ExtFontFactory();
        extFontFactory.registerDirectories();
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("docx/project.docx");
        doc = new XWPFDocument(inputStream);
        Map<String, String> params = new HashMap<>();
        ProjectExportVo editvo = new ProjectExportVo();//获取业务数据,需要自己添加

        params = WordToPdfUtil.objectToMap(editvo);
        //(1)页眉替换
        WordToPdfUtil.replaceXWPFHeaderParams(doc,params);
        //(2)替换段落里面的变量
        WordToPdfUtil.replaceParams(doc, params);
        List<XWPFTable> tableList = doc.getTables();
        //(3)表格替换:替换固定表格
        WordToPdfUtil.replaceTableParams(doc,params,0);
        WordToPdfUtil.replaceTableParams(doc,params,1);
        //(4)表格List替换
        List<Map<String,String>> mapList = new ArrayList<>();
        //mapList = 获取业务数据
        WordToPdfUtil.export(doc,mapList,3,1,1);
        mapList.clear();

        PdfOptions options = PdfOptions.create();
        outputStream = new BufferedOutputStream(response.getOutputStream());
        PdfConverter.getInstance().convert(doc, outputStream, options);
        response.setContentType("application/pdf");
        response.setHeader("Content-disposition", "attachment;filename="+UUID.randomUUID()+".pdf");
    }catch (Exception ex){
        ex.printStackTrace();
        log.error("项目pdf文件下载失败. msg={}", ex.getMessage());
    }finally {
        if (outputStream != null) {
            try {
                outputStream.flush();
                outputStream.close();
            } catch (Exception e) {
                log.error(e.getMessage());
            }
        }
        if (doc !=null){
            try {
                doc.close();
            } catch (Exception e) {
                log.error(e.getMessage());
            }
        }
    }
}

word模板 页眉,正文,固定表格中的 ${projectNo}

动态表格

测试1测试2测试3测试4测试5
${ceshi1}${ceshi2}${ceshi3}${ceshi4}${ceshi5}