前端时间公司有个项目,需求大致是这样的——根据word模版,生成带学生照片的信息表格。如果是批量打印,则生成一个word文档,每个学生占用一页。

在实现时,参考了两位老哥的代码:

使用poi根据模版生成word文档,支持插入数据和图片;

poi替换word模板内容 并且合并生成多页的word 实现分页。

先上工具类的代码:

import org.apache.poi.xwpf.usermodel.*;importjavax.servlet.http.HttpServletResponse;import java.io.*;importjava.util.Iterator;importjava.util.List;importjava.util.Map;importjava.util.regex.Matcher;importjava.util.regex.Pattern;public classWordUtils {/*** 根据模板生成word**@parampath      模板的路径*@paramparams    需要替换的参数*@paramtableList 需要插入的参数*@paramfileName  生成word文件的文件名*@paramresponse*/public void getWord(String path, Map<String, Object> params, List<String[]> tableList, String fileName, HttpServletResponse response) throwsException {File file= newFile(path);InputStream is= newFileInputStream(file);CustomXWPFDocument doc= newCustomXWPFDocument(is);this.replaceInPara(doc, doc, params);    //替换文本里面的变量this.replaceInTable(doc, doc, params, tableList); //替换表格里面的变量OutputStream os =response.getOutputStream();response.setHeader("Content-disposition", "attachment; filename=" +fileName);doc.write(os);this.close(os);this.close(is);}/*** 替换段落里面的变量**@paramdoc    要替换的文档*@paramparams 参数*/public void replaceInPara(CustomXWPFDocument firstDoc, CustomXWPFDocument doc, Map<String, Object>params) {Iterator<XWPFParagraph> iterator =doc.getParagraphsIterator();XWPFParagraph para;while(iterator.hasNext()) {para=iterator.next();this.replaceInPara(firstDoc, para, params, doc);}}/*** 替换段落里面的变量**@parampara   要替换的段落*@paramparams 参数*/private void replaceInPara(CustomXWPFDocument firstDoc, XWPFParagraph para, Map<String, Object>params, CustomXWPFDocument doc) {List<XWPFRun>runs;Matcher matcher;if (this.matcher(para.getParagraphText()).find()) {runs=para.getRuns();int start = -1;int end = -1;String str= "";for (int i = 0; i < runs.size(); i++) {XWPFRun run=runs.get(i);String runText=run.toString();if ('$' == runText.charAt(0) && '{' == runText.charAt(1)) {start=i;}if ((start != -1)) {str+=runText;}if ('}' == runText.charAt(runText.length() - 1)) {if (start != -1) {end=i;break;}}}for (int i = start; i <= end; i++) {para.removeRun(i);i--;end--;}for (Map.Entry<String, Object>entry : params.entrySet()) {String key=entry.getKey();if (str.indexOf(key) != -1) {Object value=entry.getValue();if (value instanceofString) {str=str.replace(key, value.toString());para.createRun().setText(str,0);break;}else if (value instanceofMap) {str= str.replace(key, "");Map pic=(Map) value;int width = Integer.parseInt(pic.get("width").toString());int height = Integer.parseInt(pic.get("height").toString());int picType = getPictureType(pic.get("type").toString());byte[] byteArray = (byte[]) pic.get("content");ByteArrayInputStream byteInputStream= newByteArrayInputStream(byteArray);try{String ind=firstDoc.addPictureData(byteInputStream, picType);int id =firstDoc.getNextPicNameNumber(picType);firstDoc.createPicture(ind, id, width, height, para);//由于图片重复则显示错误,重写create方法//firstDoc.addPictureData(byteInputStream, picType);//firstDoc.createPicture(firstDoc.getAllPictures().size() - 1, width, height, para, firstDoc);para.createRun().setText(str, 0);break;}catch(Exception e) {e.printStackTrace();}}}}}}/*** 为表格插入数据,行数不够添加新行**@paramtable     需要插入数据的表格*@paramtableList 插入数据集合*/private static void insertTable(XWPFTable table, List<String[]>tableList) {//创建行,根据需要插入的数据添加新行,不处理表头for (int i = 0; i < tableList.size(); i++) {XWPFTableRow row=table.createRow();}//遍历表格插入数据List<XWPFTableRow> rows =table.getRows();int length =table.getRows().size();for (int i = 1; i < length - 1; i++) {XWPFTableRow newRow=table.getRow(i);List<XWPFTableCell> cells =newRow.getTableCells();for (int j = 0; j < cells.size(); j++) {XWPFTableCell cell=cells.get(j);String s= tableList.get(i - 1)[j];cell.setText(s);}}}/*** 替换表格里面的变量**@paramdoc    要替换的文档*@paramparams 参数*/public void replaceInTable(CustomXWPFDocument firstDoc, CustomXWPFDocument doc, Map<String, Object> params, List<String[]>tableList) {Iterator<XWPFTable> iterator =doc.getTablesIterator();XWPFTable table;List<XWPFTableRow>rows;List<XWPFTableCell>cells;List<XWPFParagraph>paras;while(iterator.hasNext()) {table=iterator.next();if (table.getRows().size() > 1) {//判断表格是需要替换还是需要插入,判断逻辑有$为替换,表格无$为插入if (this.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) {this.replaceInPara(firstDoc, para, params, doc);}}}}else{insertTable(table, tableList);//插入数据
}}}}/*** 正则匹配字符串**@paramstr*@return*/privateMatcher matcher(String str) {Pattern pattern= Pattern.compile("\\$\\{(.+?)\\}", Pattern.CASE_INSENSITIVE);Matcher matcher=pattern.matcher(str);returnmatcher;}/*** 根据图片类型,取得对应的图片类型代码**@parampicType*@returnint*/public intgetPictureType(String picType) {int res =CustomXWPFDocument.PICTURE_TYPE_PICT;if (picType != null) {if (picType.equalsIgnoreCase("png")) {res=CustomXWPFDocument.PICTURE_TYPE_PNG;}else if (picType.equalsIgnoreCase("dib")) {res=CustomXWPFDocument.PICTURE_TYPE_DIB;}else if (picType.equalsIgnoreCase("emf")) {res=CustomXWPFDocument.PICTURE_TYPE_EMF;}else if (picType.equalsIgnoreCase("jpg") || picType.equalsIgnoreCase("jpeg")) {res=CustomXWPFDocument.PICTURE_TYPE_JPEG;}else if (picType.equalsIgnoreCase("wmf")) {res=CustomXWPFDocument.PICTURE_TYPE_WMF;}}returnres;}/*** 将输入流中的数据写入字节数组**@paramin*@return*/public static byte[] inputStream2ByteArray(InputStream in, booleanisClose) {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();}}}returnbyteArray;}/*** 关闭输入流**@paramis*/public voidclose(InputStream is) {if (is != null) {try{is.close();}catch(IOException e) {e.printStackTrace();}}}/*** 关闭输出流**@paramos*/public voidclose(OutputStream os) {if (os != null) {try{os.close();}catch(IOException e) {e.printStackTrace();}}}/*** @Description: 保存图片(用来插入)* @Param:*@return:* @Author: lgc* @Date: 2019/4/24*/public byte[] readInputStream(InputStream inStream) throwsException {ByteArrayOutputStream outStream= newByteArrayOutputStream();//创建一个Buffer字符串byte[] buffer = new byte[1024];//每次读取的字符串长度,如果为-1,代表全部读取完毕int len = 0;//使用一个输入流从buffer里把数据读取出来while ((len = inStream.read(buffer)) != -1) {//用输出流往buffer里写入数据,中间参数代表从哪个位置开始读,len代表读取的长度outStream.write(buffer, 0, len);}//关闭输入流
inStream.close();//把outStream里的数据写入内存returnoutStream.toByteArray();}
}

下面是很多人都提到的poi无法显示bug的解决:

importorg.apache.poi.openxml4j.opc.OPCPackage;importorg.apache.poi.xwpf.usermodel.XWPFDocument;importorg.apache.poi.xwpf.usermodel.XWPFParagraph;importorg.apache.xmlbeans.XmlException;importorg.apache.xmlbeans.XmlToken;importorg.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualDrawingProps;importorg.openxmlformats.schemas.drawingml.x2006.main.CTPositiveSize2D;importorg.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.CTInline;importjava.io.IOException;importjava.io.InputStream;public class CustomXWPFDocument extendsXWPFDocument {public CustomXWPFDocument(InputStream in) throwsIOException {super(in);}publicCustomXWPFDocument() {super();}public CustomXWPFDocument(OPCPackage pkg) throwsIOException {super(pkg);}/***@paramid*@paramwidth     宽*@paramheight    高*@paramparagraph 段落*/public void createPicture(int id, int width, intheight, XWPFParagraph paragraph, CustomXWPFDocument doc) {final int EMU = 9525;width*=EMU;height*=EMU;String blipId=doc.getAllPictures().get(id).getPackageRelationship().getId();CTInline inline=paragraph.createRun().getCTR().addNewDrawing().addNewInline();String picXml= ""+ "<a:graphic xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\">"+ "   <a:graphicData uri=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">"+ "      <pic:pic xmlns:pic=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">"+ "         <pic:nvPicPr>" + "            <pic:cNvPr id=\""+id+ "\" name=\"Generated\"/>"+ "            <pic:cNvPicPr/>"+ "         </pic:nvPicPr>"+ "         <pic:blipFill>"+ "            <a:blip r:embed=\""+blipId+ "\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\"/>"+ "            <a:stretch>"+ "               <a:fillRect/>"+ "            </a:stretch>"+ "         </pic:blipFill>"+ "         <pic:spPr>"+ "            <a:xfrm>"+ "               <a:off x=\"0\" y=\"0\"/>"+ "               <a:ext cx=\""+width+ "\" cy=\""+height+ "\"/>"+ "            </a:xfrm>"+ "            <a:prstGeom prst=\"rect\">"+ "               <a:avLst/>"+ "            </a:prstGeom>"+ "         </pic:spPr>"+ "      </pic:pic>"+ "   </a:graphicData>" + "</a:graphic>";inline.addNewGraphic().addNewGraphicData();XmlToken xmlToken= null;try{xmlToken=XmlToken.Factory.parse(picXml);}catch(XmlException xe) {xe.printStackTrace();}inline.set(xmlToken);CTPositiveSize2D extent=inline.addNewExtent();extent.setCx(width);extent.setCy(height);CTNonVisualDrawingProps docPr=inline.addNewDocPr();docPr.setId(id);docPr.setName("图片" +blipId);docPr.setDescr("头像");}public void createPicture(String blipId, int id, int width, intheight, XWPFParagraph paragraph) {final int EMU = 9525;width*=EMU;height*=EMU;//String blipId = getAllPictures().get(id).getPackageRelationship().getId();CTInline inline =paragraph.createRun().getCTR().addNewDrawing().addNewInline();String picXml= "" +"<a:graphic xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\">" +"   <a:graphicData uri=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">" +"      <pic:pic xmlns:pic=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">" +"         <pic:nvPicPr>" +"            <pic:cNvPr id=\"" + id + "\" name=\"Generated\"/>" +"            <pic:cNvPicPr/>" +"         </pic:nvPicPr>" +"         <pic:blipFill>" +"            <a:blip r:embed=\"" + blipId + "\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\"/>" +"            <a:stretch>" +"               <a:fillRect/>" +"            </a:stretch>" +"         </pic:blipFill>" +"         <pic:spPr>" +"            <a:xfrm>" +"               <a:off x=\"0\" y=\"0\"/>" +"               <a:ext cx=\"" + width + "\" cy=\"" + height + "\"/>" +"            </a:xfrm>" +"            <a:prstGeom prst=\"rect\">" +"               <a:avLst/>" +"            </a:prstGeom>" +"         </pic:spPr>" +"      </pic:pic>" +"   </a:graphicData>" +"</a:graphic>";inline.addNewGraphic().addNewGraphicData();XmlToken xmlToken= null;try{xmlToken=XmlToken.Factory.parse(picXml);}catch(XmlException xe) {xe.printStackTrace();}inline.set(xmlToken);CTPositiveSize2D extent=inline.addNewExtent();extent.setCx(width);extent.setCy(height);CTNonVisualDrawingProps docPr=inline.addNewDocPr();docPr.setId(id);docPr.setName("图片" +id);docPr.setDescr("头像");}
}

最后是我自己的实现类代码:

importcom.edu.model.InterviewRoomPlay;importcom.edu.model.InterviewRoomUser;importcom.edu.service.EduInterviewService;importcom.yz.controller.BaseController;importcom.yz.service.annotation.LoginRequired;importorg.apache.commons.lang.StringUtils;importorg.apache.poi.xwpf.usermodel.XWPFParagraph;importorg.apache.xmlbeans.XmlOptions;importorg.openxmlformats.schemas.wordprocessingml.x2006.main.CTBody;importorg.springframework.stereotype.Controller;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RequestParam;importorg.springframework.web.bind.annotation.ResponseBody;importjavax.annotation.Resource;importjavax.imageio.ImageIO;importjavax.servlet.http.HttpServletResponse;importjavax.servlet.http.HttpSession;importjava.awt.image.BufferedImage;import java.io.*;importjava.net.HttpURLConnection;importjava.net.URL;importjava.text.SimpleDateFormat;import java.util.*;importjava.util.zip.ZipEntry;importjava.util.zip.ZipOutputStream;@Controller
@RequestMapping("/exportWord")
@LoginRequiredpublic class ExportWordController extendsBaseController {@ResourceprivateEduInterviewService service;private String ImageHandle = "?imageView2/2/w/120/h/141/q/80";@RequestMapping("/batch")@ResponseBodypublic voidbatch(@RequestParam Integer playId, @RequestParam Integer schoolId,HttpServletResponse response, HttpSession session)throwsException {//创建压缩包位置File fileZip = new File("/wordZip");if (!fileZip.exists()) {fileZip.mkdirs();}//获得面试间和场次名称InterviewRoomPlay play =service.getPlayById(playId);String wordName= play.getConfigName() + "-" + play.getName() + "的学生打印表";String filesPath= "/wordZip/" + newDate().getTime();//合并模板的个数就是每次要解析多少个当前的模板 ,那么就需要首先定义一个集合SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");List<InterviewRoomUser> userList =service.getUserByPlayId(playId);//我用的是2007版本以后poi对word进行解析的docx//首先定义一个XWPFDocument 集合 这个对象可以进行word 解析 合并 还有下载都离不开这个对象List<CustomXWPFDocument> xwpfDocuments = new ArrayList<>();//由于合并时将所有图片都存在第一个doc里面,所以定义方便图片传值CustomXWPFDocument firstDoc = null;//判断需要的参数
Integer sex;Date playStartTime;String filePath= session.getServletContext().getRealPath("/") + "doc/interviewDoc/" + schoolId + ".docx";//期次我们先解析word模板 替换其中的占位符int index = 0;Integer handleTime= 1;for(InterviewRoomUser user : userList) {WordUtils wordUtils= newWordUtils();Map<String, Object> params = new HashMap<>();playStartTime=user.getPlayStartTime();sex=user.getSex();String headImage=user.getHeadImage();if(StringUtils.isNotEmpty(headImage)) {//替换的图片String headimg = session.getServletContext().getRealPath("/") + "doc/interviewDoc/head.png";//new一个URL对象URL url = new URL(headImage +ImageHandle);HttpURLConnection conn=(HttpURLConnection) url.openConnection();conn.setRequestMethod("GET");conn.setConnectTimeout(5 * 1000);InputStream inStream=conn.getInputStream();byte[] data =wordUtils.readInputStream(inStream);File imageFile= newFile(headimg);FileOutputStream outStream= newFileOutputStream(imageFile);outStream.write(data);outStream.close();//图片InputStream murl = new URL(headImage +ImageHandle).openStream();BufferedImage sourceImg=ImageIO.read(murl);Map<String, Object> header = new HashMap<>();header.put("width", sourceImg.getWidth());header.put("height", sourceImg.getHeight());header.put("type", wordUtils.getPictureType(headimg));header.put("content", wordUtils.inputStream2ByteArray(new FileInputStream(headimg), true));params.put("${headImage}", header);}else{params.put("${headImage}", "");}params.put("${name}", user.getName());String idcard=user.getIdcard();params.put("${idcard}", idcard);if (sex != null) {if (sex == 1) {params.put("${sex}", "男");}else if (sex == 2) {params.put("${sex}", "女");}else{params.put("${sex}", "");}}else{if (!StringUtils.isEmpty(idcard)) {params.put("${sex}", checkSex(idcard));}else{params.put("${sex}", "");}}params.put("${phone}", user.getPhone());params.put("${fromSchool}", user.getFromSchool());params.put("${fromSpecialty}", user.getFromSpecialty());params.put("${specialtyName}", user.getSpecialtyName());if (playStartTime != null) {params.put("${playStartTime}", sdf.format(playStartTime));}else{params.put("${playStartTime}", "");}params.put("${playName}", user.getPlayName());//返回一个新的xwpfDocument对象File file = newFile(filePath);InputStream is= newFileInputStream(file);CustomXWPFDocument doc= newCustomXWPFDocument(is);if (index == 0 || (index != 1 && index % 49 == 1)) {firstDoc=doc;}wordUtils.replaceInPara(firstDoc, doc, params);wordUtils.replaceInTable(firstDoc, doc, params,null);xwpfDocuments.add(doc);is.close();//每50条输出一次if (userList.size() > 50 && index != 0 && (index == userList.size() - 1 || index % 49 == 0)) {File files= newFile(filesPath);if (!files.exists()) {files.mkdirs();}//这样第一步将所有word内容替换之后生成多个   xwpfDocument//现在将多个xwpfDocument 进行合并 追加 生成word文件CustomXWPFDocument xwpfDocument = xwpfDocuments.get(0);for (int i = 0; i < xwpfDocuments.size(); i++) {//每次的追加为了避免样式和格式混乱 加上分页符//当是只有一条数据的时候 直接输出if (i == 0) {xwpfDocument= xwpfDocuments.get(0);continue;}else{//当存在多条时候xwpfDocument =mergeWord(xwpfDocument, xwpfDocuments.get(i));}}//合并之后返回XWPFDocument对象 写出就可以了String fileName = wordName + handleTime + ".docx";FileOutputStream fout= new FileOutputStream(filesPath + "/" + newFile(fileName));xwpfDocument.write(fout);fout.close();handleTime++;xwpfDocuments.clear();}index++;}if (userList.size() > 50) {createZipPath(filesPath, response, play.getConfigName()+ "-" + play.getName() + "的学生打印表");File files= newFile(filesPath);if(files.exists()) {delFolder(files.getPath());}}else{if (xwpfDocuments.size() > 0) {//这样第一步将所有word内容替换之后生成多个   xwpfDocument//现在将多个xwpfDocument 进行合并 追加 生成word文件CustomXWPFDocument xwpfDocument = xwpfDocuments.get(0);for (int i = 0; i < xwpfDocuments.size(); i++) {//每次的追加为了避免样式和格式混乱 加上分页符//当是只有一条数据的时候 直接输出if (i == 0) {xwpfDocument= xwpfDocuments.get(0);continue;}else{//当存在多条时候xwpfDocument =mergeWord(xwpfDocument, xwpfDocuments.get(i));}}//合并之后返回XWPFDocument对象 写出就可以了OutputStream os =response.getOutputStream();String fileName= new String((wordName + ".docx").getBytes("UTF-8"), "iso-8859-1");response.setHeader("Content-disposition", "attachment; filename=" +fileName);xwpfDocument.write(os);os.close();}}}//两个对象进行追加public CustomXWPFDocument mergeWord(CustomXWPFDocument document, CustomXWPFDocument doucDocument2) throwsException {CustomXWPFDocument src1Document=document;XWPFParagraph p=src1Document.createParagraph();//设置分页符p.setPageBreak(true);CTBody src1Body=src1Document.getDocument().getBody();CustomXWPFDocument src2Document=doucDocument2;CTBody src2Body=src2Document.getDocument().getBody();XWPFParagraph p2=src2Document.createParagraph();XmlOptions optionsOuter= newXmlOptions();optionsOuter.setSaveOuter();String appendString=src2Body.xmlText(optionsOuter);String srcString=src1Body.xmlText();String prefix= srcString.substring(0, srcString.indexOf(">") + 1);String mainPart= srcString.substring(srcString.indexOf(">") + 1, srcString.lastIndexOf("<"));String sufix= srcString.substring(srcString.lastIndexOf("<"));String addPart= appendString.substring(appendString.indexOf(">") + 1, appendString.lastIndexOf("<"));CTBody makeBody= CTBody.Factory.parse(prefix + mainPart + addPart +sufix);src1Body.set(makeBody);returnsrc1Document;}//压缩输出public static void createZipPath(String path, HttpServletResponse response, String zipName) throwsIOException {ZipOutputStream zipOutputStream= null;OutputStream output=response.getOutputStream();response.reset();String fileNameZip= new String((zipName + ".zip").getBytes("UTF-8"), "iso-8859-1");response.setHeader("Content-disposition", "attachment; filename=" +fileNameZip);response.setContentType("application/msword");zipOutputStream= newZipOutputStream(output);File[] files= newFile(path).listFiles();FileInputStream fileInputStream= null;byte[] buf = new byte[1024];int len = 0;if (files != null && files.length > 0) {for(File wordFile : files) {String fileName=wordFile.getName();fileInputStream= newFileInputStream(wordFile);//放入压缩zip包中;zipOutputStream.putNextEntry(newZipEntry(fileName));//读取文件;while ((len = fileInputStream.read(buf)) > 0) {zipOutputStream.write(buf,0, len);}//关闭;
zipOutputStream.closeEntry();if (fileInputStream != null) {fileInputStream.close();}}}if (zipOutputStream != null) {zipOutputStream.close();}}/**** 删除文件夹**@paramfolderPath 文件夹完整绝对路径*/public static voiddelFolder(String folderPath) {try{delAllFile(folderPath);//删除完里面所有内容String filePath =folderPath;filePath=filePath.toString();java.io.File myFilePath= newjava.io.File(filePath);myFilePath.delete();//删除空文件夹} catch(Exception e) {e.printStackTrace();}}/**** 删除指定文件夹下所有文件**@parampath 文件夹完整绝对路径*@return*/public static booleandelAllFile(String path) {boolean flag = false;File file= newFile(path);if (!file.exists()) {returnflag;}if (!file.isDirectory()) {returnflag;}String[] tempList=file.list();File temp= null;for (int i = 0; i < tempList.length; i++) {if(path.endsWith(File.separator)) {temp= new File(path +tempList[i]);}else{temp= new File(path + File.separator +tempList[i]);}if(temp.isFile()) {temp.delete();}if(temp.isDirectory()) {delAllFile(path+ "/" + tempList[i]);//先删除文件夹里面的文件delFolder(path + "/" + tempList[i]);//再删除空文件夹flag = true;}}returnflag;}publicString checkSex(String idcard) {Integer sex= 0;if (idcard.length() == 15) {//15位身份证最后一位奇男偶女sex = oddOrEven(Integer.valueOf(idcard.substring(idcard.length() - 1, idcard.length())));}else if (idcard.length() == 18) {//18位身份证第17位奇男偶女sex = oddOrEven(Integer.valueOf(idcard.substring(idcard.length() - 2, idcard.length() - 1)));}else{return "";}if (sex == 1) {return "男";}else if (sex == 2) {return "女";}return "";}//判断奇偶数public Integer oddOrEven(inta) {if ((a & 1) == 1) {return 1;}else{return 2;}}
}

还有就是poi的版本(版本不重要,只要不是太老的就行):

<dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>3.9</version>
</dependency>

下面是一些详细个人的代码说明:

  由于我在多次生成时发现,poi的图片处理是根据模版存放的。也就是说,当CustomXWPFDocument的对象不一样时,每个对象的第一张图片的rid都是rid8(我也没有细究为啥是从8开始),但是由于后期我需要将多个CustomXWPFDocument整合成为一个输出,就造成明明图片的对象不一样,但是由于除了第一个CustomXWPFDocument的图片对象外,其他的图片并没有将数据代入第一个CustomXWPFDocument,但是图片对应的rid又能找到对应对象,所以模版的图片就成了同一张。所有后续拼接的图片根据id只能在第一个中找,一旦没找到,就会造成图片不显示,这也就是我为什么对每次输出循环的第一个CustomXWPFDocument进行重复添加图片的操作。

  而两个createPicture的区别在于,当poi遇到完全一样的照片时,将不会执行重复写入图片数据的操作,而是使用相同的rid以作对应。我所使用的那个,可以将图片对应的rid返回,这样就不会造成图片错乱。而注释掉的那个,当发现图片重复,返回的只有CustomXWPFDocument中最后一个图片的信息,这样当重复图片不是连续时,图片的显示就出错了。

  在实际操作中,我将50个及以下的操作选择用单个word直接输出,而50个以上则分开用zip输出。原因在于,在实际使用时,我发现当将多个CustomXWPFDocument合并形成一个时,数据量过大会导致卡死,影响后续的输出。在我的项目中,70个以上处理速度开始变慢翻倍,大概在76个时直接卡死。每个人可能因模版的大小及复杂程度有所不同。

  当模版中有图片时,本地图片可以直接获取文件信息。链接图片则需要访问链接,并将其写入本地的一个文件中,以便直接操作使用。

   以上皆为个人理解和查询结果,欢迎批评指正~~

转载于:https://www.cnblogs.com/ssbfs/p/11251564.html

poi根据模版生成多页word,并压缩下载相关推荐

  1. 使用poi根据模版生成word文档并转换成PDF文件(可处理doc文件与docx文件版)

    该篇文章是<使用poi根据模版生成word文档并转换成PDF文件>后续解决传入文件为doc文档或docx的处理方法 /*** 根据模板生成word** @param path 模板的路径* ...

  2. poi根据模版生成word(包括导出)

    根据word模版生成新的word直接把流输出 下载 进行改造的作为笔记 记录一下 大佬勿喷!!! 下面展示完整的代码 1.controller层 @RequestMapping(value = &qu ...

  3. 使用poi根据模版生成word文档,支持插入图片,复制表格,插入、循环插入数据,继承模板大部分格式及样式(优化版)

    一.制作word模版,${xxxx}是一会要替换的内容,最下面的表格是要插入数据,根据是否以$开头来判断是需要替换还是插入数据, 注意如果是需要循环插入数据,制作的表格模版需要一行全部输入井号#,格式 ...

  4. 使用poi根据模版生成word文档并转换成PDF文件

    一.首先制作word模版(这里需要注意的是文件后缀是docx不能是doc),${xxxx}是一会要替换的内容 关于为何必须是docx后缀可以看这篇文章https://www.cnblogs.com/c ...

  5. dedesmc 手机端生成静态页

    dedesmc 手机端生成静态页 1.首先下载插件,下载地址:https://pan.baidu.com/s/1Nfx_KBYuxRkZ7VzoPxy28g 密码:83x7 2.进入 dedecms ...

  6. POI生成Web版Word文件

    POI生成Web版Word文件 1       通过URL的输入流实现 2       直接把Html文本写入到Word文件 所谓的使用POI生成Web版Word文件是指利用POI将Html代码插入到 ...

  7. Java使用POI通过模板生成Word

    Java使用POI通过模板生成Word 前言 最近工作需要用到,所以记录下来以便查找. 一.概述 POI读写word使用的核心类是XWPFDocument.一个XWPFDocument代表一个docx ...

  8. Java通过POI或Freemarker生成word文档,使用Jfreechart创建统计图表

    最近做了一个使用Java生成统计分析报告word文档的功能,有提前制作好的word文档,其中共包含了普通文本变量,普通表格,动态表格.统计图表(柱状图.饼状图.折线图等),在此记录下POI和freem ...

  9. Word邮件合并功能详解:合并后生成多个word文档,删除空白页

    Word邮件合并功能详解:合并后生成多个word文档,删除空白页 最近在实习,干了很多打杂得工作,所以office软件用的很多很多,瞬间觉得自己可以去裸考计算机二级了哈哈哈哈哈哈.今天因为工作用到了邮 ...

  10. c语言 自动生成word文件,C#根据Word模版生成Word文件

    本文实例为大家分享了C#根据Word模版生成Word文的具体代码,供大家参考,具体内容如下 1.指定的word模版 2.生成word类 添加com Microsoft word 11.0 Object ...

最新文章

  1. 提升代码内外部质量的22条经验
  2. windows平台搭建Mongo数据库复制集(类似集群)(三)
  3. Python GUI漫谈
  4. STM32F103xC、STM32F103xD和STM32F103xE增强型模块框图 与 时钟树
  5. 最高201万!华为高薪招应届生,8位获聘者大有来头
  6. linux内核编译感想,Linux内核编译小结
  7. 目标检测特殊层:PSROIPooling详解
  8. pytorch中resnet_ResNet代码详解
  9. 2018牛客网暑假ACM多校训练赛(第三场)I Expected Size of Random Convex Hull 计算几何,凸包,其他...
  10. 【蓝桥杯】历届试题 错误票据
  11. 90-70-010-源码-CUBE查询-源码
  12. StickyListHeaders的使用
  13. 不添加外键能关联查询_SpringDataJPA关联关系
  14. LeetCode 1026. 节点与其祖先之间的最大差值
  15. 20161212 输出1到n之间所有的奇(单)数(n30000) 。
  16. thinkphp LoginAction.class.php 登录模块
  17. 最简单的的树莓派安装opencv教程(一键安装)
  18. 工程项目提成标准方案_工程项目提成实施分配方案
  19. NPDP知识推送-第一章新产品开发战略(1)
  20. Android开发艺术探索读书笔记(二)

热门文章

  1. 解决 Kotlin 换页符提示错误 Illegal escape f 无法使用问题
  2. PostgreSQL获得去、今、明年份、今年的第一天、去年的第一天转换时区、最后一天等
  3. 使用Python Snap7读取西门子触摸板 Dint LReal(int double)数据
  4. POJ 3537 Crosses and Crosses 博弈论 SG函数 记忆化搜索
  5. Java实现百度贴吧自动签到器
  6. linux之kubuntu挂载硬盘
  7. Building your Deep Neural Network - Step by Step v5 作业 - Neural Networks and Deep Learning
  8. IntelliJ IDEA优化内存配置提高启动和运行速度
  9. android 视频裁剪
  10. IDEA 顶部导航栏(Main Menu)不见了怎么办