springboot+poi-tl根据模板导出word(含动态表格和图片)
官网:http://deepoove.com/poi-tl/
参考网站:https://blog.csdn.net/M625387195/article/details/124855854
- pom导入的maven依赖
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.12.1</version>
</dependency>
-
准备模板
文本标签用{{ }},动态表格的字段标签用[]。
-
代码实现
3.1 控制器package io.renren.modules.sys.controller; import io.renren.common.utils.R; import io.renren.modules.sys.service.POIService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @Author: Administrator * @Date: 2024/3/14 * @Description: */ @RestController @RequestMapping("/anli") public class AnliController { @Autowired private POIService poiService; @GetMapping("/daochu/{renwuId}") public R daochu(@PathVariable("renwuId") Long renwuId) { String zipUrl = poiService.anlidaochu(renwuId); return new R().put("zipUrl", zipUrl); } }
3.2 实现类
package io.renren.modules.sys.service; /** * @Author: Administrator * @Date: 2024/3/4 * @Description: */ public interface POIService { /** * 案例导出 * @param renwuId */ String anlidaochu(Long renwuId); }
package io.renren.modules.sys.service.impl; import io.renren.common.utils.word.WordUtils; import io.renren.modules.sys.dto.RenwuTemplateDTO; import io.renren.modules.sys.service.POIService; import io.renren.modules.sys.service.SysRenwuService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.zip.ZipOutputStream; /** * @Author: Administrator * @Date: 2024/3/4 * @Description: */ @Service public class POIServiceImpl implements POIService { @Autowired private SysRenwuService renwuService; @Value("${upload.url}") private String UPLOAD_URL; @Value("${upload.path}") private String UPLOAD_SUFFIX_URL; public String getUPLOAD_URL() { return UPLOAD_URL + getUploadSuffixURL(); } public String getUploadSuffixURL() { SimpleDateFormat sdf = new SimpleDateFormat("yyyyMM"); String dateString = sdf.format(new Date()); return UPLOAD_SUFFIX_URL + dateString + "/"; } /** * 案例导出 * @param renwuId */ @Override public String anlidaochu(Long renwuId) { // 将要生成文档的数据查询出来 RenwuTemplateDTO renwuTemplateDTO = renwuService.daochuByRenwuId(renwuId); String url = null; if (renwuTemplateDTO != null) { try { List<String> urlList = WordUtils.piliangDaochu(renwuTemplateDTO); if (urlList != null && urlList.size() > 0) { String name = renwuTemplateDTO.getRenwuName()+"_"+ UUID.randomUUID() +".zip"; url = this.getUploadSuffixURL() + name; FileOutputStream fos = new FileOutputStream(this.getUPLOAD_URL() + name); ZipOutputStream zos = new ZipOutputStream(fos); for (String file : urlList) { WordUtils.addToZipFile(file, zos); } zos.close(); fos.close(); // 使用异步线程删除文件 deleteFilesAsync(urlList); } } catch (Exception e) { throw new RuntimeException(e); } } return url; } @Async public CompletableFuture<Void> deleteFilesAsync(List<String> urlList) { for (String file : urlList) { File fileToDelete = new File(file); if (fileToDelete.exists()) { if (fileToDelete.delete()) { System.out.println("Deleted file: " + file); } else { System.out.println("Failed to delete file: " + file); } } } return CompletableFuture.completedFuture(null); } }
3.3 配置文件
upload: url: H:/GoTionBackends/2023/resources path: /u/cms/www/ outPath: H:/GoTionBackends/2023/resources/doc prefix: http://xxx.xxx.xxx:8087
3.4 工具类
package io.renren.common.utils.word; import com.alibaba.fastjson.JSON; import com.deepoove.poi.XWPFTemplate; import com.deepoove.poi.config.Configure; import com.deepoove.poi.data.*; import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy; import com.deepoove.poi.policy.PictureRenderPolicy; import io.renren.common.utils.word.dto.WordQingdanDetailsDTO; import io.renren.modules.sys.dto.RenwuTemplateDTO; import io.renren.modules.sys.entity.SysQingdanExtEntity; import org.apache.commons.lang.StringUtils; import java.io.*; import java.nio.file.Files; import java.nio.file.Paths; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.UUID; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; /** * @Author: Administrator * @Date: 2024/3/1 * @Description: */ public class WordUtils { public static List<String> piliangDaochu(RenwuTemplateDTO renwuTemplate) throws IOException { List<String> urlList = new ArrayList<>(); if (renwuTemplate.getQingdanDTOList() != null && renwuTemplate.getQingdanDTOList().size() > 0) { for (int i = 0; i < renwuTemplate.getQingdanDTOList().size(); i++) { renwuTemplate.setQingdanDTO(renwuTemplate.getQingdanDTOList().get(i)); String daochuUrl = daochumoban(renwuTemplate); urlList.add(daochuUrl); } } else { String daochuUrl =daochumoban(renwuTemplate); urlList.add(daochuUrl); } return urlList; } public static String daochumoban(RenwuTemplateDTO renwuTemplate) throws IOException { // 为表格的显示绑定行循环 LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy(); // 将bz设置为行循环绑定的数据源的key,即key是bz的value会在模板中的{{bz}}处进行解析 Configure configure = Configure.builder().bind("bz", policy).build(); // 图片标签集合 List<String> pictureTag = new ArrayList<>(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); HashMap<String, Object> dataMap = new HashMap<String, Object>() { { //添加文本 put("xiangmuName", renwuTemplate.getXiangmuName()); put("xiangmuzhouqi", renwuTemplate.getXiangmuzhouqi()); put("renwuName", renwuTemplate.getRenwuName()); put("renwuzhouqi", sdf.format(renwuTemplate.getStartTime()) + " 至 " + sdf.format(renwuTemplate.getEndTime())); put("description", renwuTemplate.getDescription()); String xiangmuLink = ""; if (renwuTemplate.getRenwuResourceUrlList() != null && renwuTemplate.getRenwuResourceUrlList().size() > 0) { for (int i = 0; i < renwuTemplate.getRenwuResourceUrlList().size(); i++) { if (i != renwuTemplate.getRenwuResourceUrlList().size()-1) { xiangmuLink += PeizhiConfig.getUploadPrefix() + renwuTemplate.getRenwuResourceUrlList().get(i) + "\n"; } else { xiangmuLink += PeizhiConfig.getUploadPrefix() + renwuTemplate.getRenwuResourceUrlList().get(i); } } } put("xiangmulink", xiangmuLink ); put("biaoqianName", renwuTemplate.getQingdanDTO() != null ? renwuTemplate.getQingdanDTO().getBiaoqianName() : ""); String diliurk = PeizhiConfig.getUploadUrl() + renwuTemplate.getQingdanDTO().getResourceUrl(); PictureRenderData pictureRenderData = Pictures.ofStream(Files.newInputStream(Paths.get(diliurk)), PictureType.PNG) .size(200, 150).create(); put("dililink", pictureRenderData); put("resourceDescription", renwuTemplate.getQingdanDTO() != null ? renwuTemplate.getQingdanDTO().getDescription() : ""); put("startYear", renwuTemplate.getQingdanDTO() != null ? renwuTemplate.getQingdanDTO().getStartYear() : ""); put("endYear", renwuTemplate.getQingdanDTO() != null ? renwuTemplate.getQingdanDTO().getEndYear(): ""); put("area", renwuTemplate.getQingdanDTO() != null ? renwuTemplate.getQingdanDTO().getArea() : ""); // 其他业务获取到数据源 String testTable = null; if (renwuTemplate.getQingdanDTO() != null && renwuTemplate.getQingdanDTO().getQingdanExtList() != null && renwuTemplate.getQingdanDTO().getQingdanExtList().size() > 0) { String str = ""; for (int i = 0; i < renwuTemplate.getQingdanDTO().getQingdanExtList().size(); i++) { SysQingdanExtEntity ext = renwuTemplate.getQingdanDTO().getQingdanExtList().get(i); String templateType = null, data = PeizhiConfig.getUploadPrefix() + ext.getResourceUrl(); // PictureRenderData pictureRenderData1 = null; if (ext.getTemplateType() == 1) { templateType = "图片"; //String dataUrl = PeizhiConfig.getUploadUrl() + ext.getResourceUrl(); //pictureRenderData1 = Pictures.ofStream(Files.newInputStream(Paths.get(dataUrl)), PictureType.PNG) // .size(200, 150).create(); } else if (ext.getTemplateType() == 2) { templateType = "附件"; } else if (ext.getTemplateType() == 3) { templateType = "音视频"; } else if (ext.getTemplateType() == 4) { templateType = "文本"; data = ext.getExtText(); } else if (ext.getTemplateType() == 5) { templateType = "文档"; } String source = StringUtils.isNotBlank(ext.getSource()) ? ext.getSource() : ""; data = StringUtils.isNotBlank(data) ? data : ""; str += "{\n" + " \"index\": \"" + (i + 1) + "\",\n" + " \"templateName\": \"" + ext.getTemplateName() + "\",\n" + " \"templateType\": \"" + templateType + "\",\n" + " \"source\": \"" + source + "\",\n" + " \"data\": \"" + data + "\",\n" + " },\n"; } testTable = "[" + str + "]"; } // 内容在表格里循环 // JSON使用,需要导入fastjson依赖 List<WordQingdanDetailsDTO> forms = JSON.parseArray(testTable, WordQingdanDetailsDTO.class); if (forms != null && forms.size() > 0) { for (int i = 0; i < forms.size(); i++) { put("index" + i, forms.get(i).getIndex()); put("templateName" + i, forms.get(i).getTemplateName()); put("templateType" + i, forms.get(i).getTemplateType()); put("source" + i, forms.get(i).getSource()); put("data" + i, forms.get(i).getData()); } } put("bz", forms); pictureTag.add("dililink"); } }; for (String tag : pictureTag ) { //设置图片,不然保存的是一串字符 configure.customPolicy(tag, new PictureRenderPolicy()); } if (!new File(PeizhiConfig.getUploadOutPath()).exists()) { new File(PeizhiConfig.getUploadOutPath()).mkdirs(); } String outPath = PeizhiConfig.getUploadOutPath() + "/"+ UUID.randomUUID() +".docx"; // 读取模板、数据并渲染 XWPFTemplate template = XWPFTemplate.compile(new FileInputStream(PeizhiConfig.getUploadOutPath() + "/任务数据.docx"), configure).render(dataMap); // 文件是否已存在,则删除 File file = new File(outPath); if (file.exists()) { file.delete(); } // 生成word保存在指定目录 //template.writeToFile(outPath); template.writeAndClose(Files.newOutputStream(Paths.get(outPath))); template.close(); return outPath; } public static void addToZipFile(String filePath, ZipOutputStream zos) throws IOException { File file = new File(filePath); FileInputStream fis = new FileInputStream(file); ZipEntry zipEntry = new ZipEntry(file.getName()); zos.putNextEntry(zipEntry); byte[] bytes = new byte[1024]; int length; while ((length = fis.read(bytes)) >= 0) { zos.write(bytes, 0, length); } zos.closeEntry(); fis.close(); } public static void createDoc() throws Exception { // 为表格的显示绑定行循环 LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy(); // 将bz设置为行循环绑定的数据源的key,即key是bz的value会在模板中的{{bz}}处进行解析 Configure configure = Configure.builder().bind("bz", policy).build(); List<String> pictureTag = new ArrayList<>(); // 将需要解析的数据放到dataMap中 HashMap<String, Object> dataMap = new HashMap<String, Object>() { { //添加文本 put("xiangmuName", "项目名称"); put("xiangmuzhouqi", "2024-03-01 至 2024-04-02"); put("renwuName", "任务名称"); put("renwuzhouqi", "2024-03-05 至 2024-03-26"); put("description", "项目描述"); put("xiangmulink", "http://www.baidu.com"); put("biaoqianName", "标签名称"); PictureRenderData pictureRenderData = Pictures.ofStream(Files.newInputStream(Paths.get("D:\\template\\picture\\其他\\yiyan-NewYear.png")), PictureType.PNG) .size(200, 150).create(); put("dililink", pictureRenderData); put("resourceDescription", "资源描述"); put("startYear", "1997"); put("endYear", "2018"); put("area", "100.5"); // 其他业务获取到数据源 String testTable = null; { testTable = "[\n" + " {\n" + " \"index\": \"1\",\n" + " \"templateName\": \"模板内容1\",\n" + " \"templateType\": \"模板类型1\",\n" + " \"source\": \"来源1\",\n" + " \"data\": \"http://www.baidu.com\"\n" + " },\n" + " {\n" + " \"index\": \"2\",\n" + " \"templateName\": \"模板内容2\",\n" + " \"templateType\": \"模板类型2\",\n" + " \"source\": \"来源2\",\n" + " \"data\": \"http://www.baidu.com111\"\n" + " },\n" + " {\n" + " \"index\": \"3\",\n" + " \"templateName\": \"模板内容3\",\n" + " \"templateType\": \"模板类型3\",\n" + " \"source\": \"来源3\",\n" + " \"data\": \"http://www.baidu.com222\"\n" + " }\n" + "]"; } // 内容在表格里循环 // JSON使用,需要导入fastjson依赖 List<WordQingdanDetailsDTO> forms = JSON.parseArray(testTable, WordQingdanDetailsDTO.class); for (int i = 0; i < forms.size(); i++) { put("index" + i, forms.get(i).getIndex()); put("templateName" + i, forms.get(i).getTemplateName()); put("templateType" + i, forms.get(i).getTemplateType()); put("source" + i, forms.get(i).getSource()); put("data" + i, forms.get(i).getData()); } put("bz", forms); pictureTag.add("dililink"); } }; for (String tag : pictureTag ) { //设置图片,不然保存的是一串字符 configure.customPolicy(tag, new PictureRenderPolicy()); } String outPath = "D:\\生成数据.docx"; // 读取模板、数据并渲染 XWPFTemplate template = XWPFTemplate.compile(new FileInputStream("D:\\任务数据.docx"), configure).render(dataMap); // 文件是否已存在,则删除 File file = new File(outPath); if (file.exists()) { file.delete(); } // 生成word保存在指定目录 //template.writeToFile(outPath); template.writeAndClose(Files.newOutputStream(Paths.get(outPath))); template.close(); } public static void main(String[] args) throws Exception { createDoc(); } }
3.5 用到的实体类
-
RenwuTemplateDTO
package io.renren.modules.sys.dto; import com.fasterxml.jackson.annotation.JsonFormat; import io.renren.common.validator.group.AddGroup; import io.renren.common.validator.group.UpdateGroup; import io.renren.modules.sys.entity.SysRenwuTemplateEntity; import io.renren.modules.sys.entity.SysResourceEntity; import io.renren.modules.sys.entity.SysXiangmuEntity; import lombok.Data; import org.springframework.format.annotation.DateTimeFormat; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import java.util.ArrayList; import java.util.Date; import java.util.List; /** * @Author: Administrator * @Date: 2023/12/8 * @Description: */ @Data public class RenwuTemplateDTO { private Long renwuId; private Long xiangmuId; private String renwuName; @DateTimeFormat(pattern = "yyyy-MM-dd") @JsonFormat(pattern = "yyyy-MM-dd") private Date startTime; @DateTimeFormat(pattern = "yyyy-MM-dd") @JsonFormat(pattern = "yyyy-MM-dd") private Date endTime; private String description; private String xiangmuName; private String xiangmuzhouqi; private List<SysXiangmuEntity> xiangmuList; private List<SysResourceEntity> fileList = new ArrayList<>(); private QingdanDTO qingdanDTO; /** * 用于导出 */ private List<QingdanDTO> qingdanDTOList; private List<String> renwuResourceUrlList; }
-
QingdanDTO
import com.fasterxml.jackson.annotation.JsonFormat; import io.renren.common.validator.group.AddGroup; import io.renren.common.validator.group.UpdateGroup; import io.renren.modules.sys.entity.SysQingdanExtEntity; import lombok.Data; import org.springframework.format.annotation.DateTimeFormat; import javax.validation.constraints.NotNull; import java.util.Date; import java.util.List; /** * @Author: Administrator * @Date: 2023/12/11 * @Description: */ @Data public class QingdanDTO { private Long qingdanId; private Long renwuId; private Long userId; private String biaoqianName; @DateTimeFormat(pattern = "yyyy") @JsonFormat(timezone = "GMT+8", pattern = "yyyy") private String startYear; @DateTimeFormat(pattern = "yyyy") @JsonFormat(timezone = "GMT+8", pattern = "yyyy") private String endYear; private String area; private String resourceUrl; /** * 资源描述 */ private String description; private String xiangmuName; private String renwuName; /** * 清单详情 */ private List<SysQingdanExtEntity> qingdanExtList; /** * 任务周期 */ private String renwuzhouqi; /** * 项目周期 */ private String xiangmuzhouqi; }
-
SysQingdanExtEntity
import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableLogic; import lombok.Data; import java.io.Serializable; /** * @Author: Administrator * @Date: 2023/12/11 * @Description: */ @Data public class SysQingdanExtEntity implements Serializable { private static final long serialVersionUID = 1L; private Long qingdanExtId; private Long qingdanId; private String templateName; private Long resourceId; private String source; private String extText; private Integer templateType; private String resourceUrl; }
-
WordQingdanDetailsDTO
import lombok.Data; /** * @Author: Administrator * @Date: 2024/3/1 * @Description: */ @Data public class WordQingdanDetailsDTO { /** * 下标序号 */ private Integer index; /** * 名称 */ private String templateName; /** * 类型 */ private String templateType; /** * 来源 */ private String source; /** * 数据 */ private String data; }
-
工具类ConvertUtils
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeanUtils; import java.util.ArrayList; import java.util.Collection; import java.util.List; /** * 转换工具类 * * @author Mark sunlightcs@gmail.com */ public class ConvertUtils { private static Logger logger = LoggerFactory.getLogger(ConvertUtils.class); public static <T> T sourceToTarget(Object source, Class<T> target){ if(source == null){ return null; } T targetObject = null; try { targetObject = target.newInstance(); BeanUtils.copyProperties(source, targetObject); } catch (Exception e) { logger.error("convert error ", e); } return targetObject; } public static <T> List<T> sourceToTarget(Collection<?> sourceList, Class<T> target){ if(sourceList == null){ return null; } List targetList = new ArrayList<>(sourceList.size()); try { for(Object source : sourceList){ T targetObject = target.newInstance(); BeanUtils.copyProperties(source, targetObject); targetList.add(targetObject); } }catch (Exception e){ logger.error("convert error ", e); } return targetList; } }
-
导出效果