加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > Java > 正文

Java利用Freemark生成word文档

发布时间:2020-12-15 05:33:17 所属栏目:Java 来源:网络整理
导读:记录项目中通过freemark生成word文档。 freemark生成word文档一个不好的地方就是需要手动将带有占位符的.doc模板转成xml文件(另存为2003xml),不好就不好在一些占位符被分隔开,需要手动取处理(可以用notepad++格式化下并处理,比较美观:开启xml支持插件

记录项目中通过freemark生成word文档。

freemark生成word文档一个不好的地方就是需要手动将带有占位符的.doc模板转成xml文件(另存为2003xml),不好就不好在一些占位符被分隔开,需要手动取处理(可以用notepad++格式化下并处理,比较美观:开启xml支持插件);

? ? ? ? ? ? ? ? ? ?


要吐槽的是什么先转xml再填充占位符,或者是先把占位符写在记事本里面再复制到.doc里面...全是扯淡,不符合word里面单词拼写的还是照样会被分开;

关于freemark的官方资料,自行去看官网,这边仅记录下自己项目中使用的;

网上大部分博客都是直接一个demo,扔几个占位符,然后从本地磁盘或是指定路径读取模板,再将生成word输出到指定路径,有个卵用,实际项目有多少是这样的...

?

下面记录下自己在java中利用freemark生成报告并下载:

1)数据库配置xml模板路径(存于oss)动态生成word文档,并下载到本地

2)当批量下载的时候,需打成zip包,并提供处理进度查询

3)已下载的文件支持可重复下载(文件放到oss服务器)

?

项目使用技术栈(前后端分离):vue+springBoot+mybatisPlus

项目第一版实现的是将xml模板放在resources下面,但考虑到模板的灵活性及可配置,改用上传oss;

?

直接上代码,不废话

控制层(判空啥都略过,因为业务操作部分每个项目不一样,只记录重要步骤):

package com.xxxx.modules.api.controller;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.xxxx.common.utils.FreemarkerUtil;
import com.xxxx.common.utils.PoiUtil;
import com.xxxx.common.utils.ZipUtil;
import com.xxxx.modules.constant.ApiConsts;
import com.xxxx.modules.framework.PendingJobPool;
import com.xxxx.modules.framework.vo.TaskResult;
import com.xxxx.modules.framework.vo.TaskResultType;
import com.xxxx.modules.heath.dto.TCPatientsDTO;
import com.xxxx.modules.heath.dto.TCPhsUserDTO;
import com.xxxx.modules.heath.dto.TCTransportLogDTO;
import com.xxxx.modules.heath.entity.TCTransportLogEntity;
import com.xxxx.modules.heath.service.*;
import com.xxxx.modules.jt.service.SingleTablePolicy;
import com.xxxx.modules.jt.service.WordService;
import com.xxxx.modules.oss.cloud.OSSFactory;
import freemarker.template.Template;
import io.renren.common.annotation.LogOperation;
import io.renren.common.constant.Constant;
import io.renren.common.utils.ConvertUtils;
import io.renren.common.utils.Result;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;
import java.util.zip.Adler32;
import java.util.zip.CheckedOutputStream;
import java.util.zip.ZipOutputStream;


@Slf4j
public class ClientController {//缓存批量下载的jobName
    public static Map<String,Object> batchJobNameCache = new HashMap<>();

    // 取得机器的cpu数量
    public static final int THREAD_COUNTS = Runtime.getRuntime().availableProcessors();

    public static ExecutorService docMakePool = Executors.newFixedThreadPool(THREAD_COUNTS*2);

    @GetMapping("xxxx")
    @ApiOperation("批量下载")
    @LogOperation("批量下载")
    @ResponseBody
    public Result batchDownloadPlanscode(@RequestParam Map<String,Object> params,HttpServletRequest request) throws Exception {
       //参数判断

     //
判断任务是否已经存在(防重复) String jobName = "xxxx"; String jobNameExist = MapUtils.getString(batchJobNameCache,jobName,""); if(StringUtils.isNotEmpty(jobNameExist)){ log.info(tipStr + "下载正在处理中,请耐心等待~"); return new Result().error(201,tipStr + "下载正在处理中,请耐心等待~"); } //1、数据库取模板路径,获得模板实例 String url = xxxxService.getTemplateUrl(planCode,downType); if(StringUtils.isEmpty(url)){ log.info("模板url为空"); return new Result().error(202,"请先指定模板~"); } //本次任务显示的中文名称(作为客户端显示)——根据实际需求,看是否需要,可以是客户端传参 String downName = "xxxx"; // 2、这个根据项目实际需求 List<TCPatientsDTO> list = xxxxxService.getByPlansCode(planCode); if(list.isEmpty()){ log.info("没有可下载的数据"); return new Result().error(201,"暂无该场次报告数据!"); } //本批次任务记录的主键 Long id = IdWorker.getId(); batchJobNameCache.put(jobName,0); //3、另起一个线程处理下载任务(重要) new Thread(new AsynMakeDoc(id,list,downType,url)).start(); //4、记录下载痕迹 TCTransportLogDTO dto = new TCTransportLogDTO(); dto.setId(id); dto.setUserName(phsUser.getUserName());//当前用户 dto.setBusinessType(ApiConsts.TRANSMISSION_UPLOAD);//上传(生成报告-打zip包-上传oss) dto.setStatus(1);//有效 dto.setJobName(jobName);//批次任务唯一标识 dto.setResultName(downName);//批次任务中文名称,作为下载记录的显示在客户端 dto.setJobType(0);//0-批次,1-子任务 dto.setCreateDate(new Date()); tcTransportLogService.insert(ConvertUtils.sourceToTarget(dto,TCTransportLogEntity.class)); return new Result().success(200,"添加下载新任务成功~",jobName + "," + id); } /** * 异步处理word生成 */ class AsynMakeDoc implements Runnable{ private Long id;//主键 private String jobName;//场次号_档案_用户 private List<TCPatientsDTO> list;private String templateUrl; public AsynMakeDoc(Long id,String jobName,List<TCPatientsDTO> list,String templateUrl) { this.jobName = jobName; this.list = list; this.downType = downType; this.templateUrl = templateUrl; this.id = id; } @Override public void run() { Object template; //取oss模板的后缀 String templateType = wordService.getType(templateUrl); if(ApiConsts.TEMPLATE_TYPE_XML.equals(templateType)){ templateType = ApiConsts.TEMPLATE_TYPE_XML; template = FreemarkerUtil.getTemplate(templateUrl); }else{ templateType = ApiConsts.TEMPLATE_TYPE_POI; //多个自定义渲染策略 Configure configures = Configure.createDefault(); configures.customPolicy("urines",new SingleTablePolicy(1,5)); configures.customPolicy("bloods",5)); configures.customPolicy("examines",5)); template = PoiUtil.getTemplate(templateUrl,configures); } String fileName = "_报告.doc"; String zipName = "_报告.zip"; String plansCode = list.get(0).getPlansCode(); //报告、压缩包临时路径 String outTempPath = ""; String zipPath = ""; //更新批次下载记录状态 TCTransportLogEntity dto = new TCTransportLogEntity(); dto.setId(id); dto.setJobType(1);//不管成功失败,批次任务变更为子任务 dto.setBusinessType(ApiConsts.TRANSMISSION_DOWNLOAD);//变更为下载 File zipFile = null; ZipOutputStream zos = null; //生成目标文件对象的输出流 OutputStream outputStream = null; try { //临时压缩包目录:本地磁盘/jobName.zip(jobName需保证多用户并发时候不会相互干扰) outTempPath = getTempPath(); zipPath = outTempPath + jobName + ".zip"; zipFile = new File(zipPath); log.info("temporary zip :" + zipPath); outputStream = new FileOutputStream(zipPath); CheckedOutputStream cos = new CheckedOutputStream(outputStream,new Adler32()); // 生成ZipOutputStream,用于写入要压缩的文件 zos = new ZipOutputStream(cos); //1、往线程池添加任务 log.info(" start generating words..."); CompletionService<String> docCompletionService = new ExecutorCompletionService<String>(docMakePool); for (int i = 0; i < list.size(); i++) { docCompletionService.submit(new DocMakeTask(list.get(i),fileName,template,outTempPath + jobName,templateType)); } //计算已打成完成数量 int zipCount = 0; //2、从线程池取执行结果进行压缩 for (int j = 0; j < list.size(); j++) { // 阻塞取结果 Future<String> future = docCompletionService.take(); // 判断要压缩的源文件是否存在 String path = future.get(); if (!StringUtils.isEmpty(path)) { File sourceFile = new File(path); if (!sourceFile.exists()) { throw new RuntimeException("[" + sourceFile + "] is not exists ..."); } ZipUtil.compressFile(sourceFile,zos,sourceFile.getName(),true); if (sourceFile.exists()) { sourceFile.delete(); } zipCount++; //通过应用缓存更新处理进度 log.info("压缩进度:" + zipCount + "/" + list.size() + " : " + zipCount*100/list.size()); batchJobNameCache.put(jobName,zipCount*100/list.size()); } } //关闭压缩流(不然上传的文件是不完整的) zos.finish(); zos.close(); outputStream.close(); long s1 = System.currentTimeMillis(); log.info(jobName + ".zip completed,耗时:" + (s1 - start)); //删除临时文件夹 File docTempDir = new File(outTempPath + jobName); if(docTempDir.exists()){ docTempDir.delete(); log.info(" temporary folder " + jobName + " has been deleted "); } log.info(" ready to upload "); //3、压缩包上传oss,路径自定义:场次号/场次号_healthy.zip FileInputStream inputStream = null; String ossPathName = downType + "/" + plansCode + "/"+ System.currentTimeMillis() + "/" + plansCode + zipName; String ossPath = ""; try{ inputStream = new FileInputStream(zipPath); ossPath = OSSFactory.build().upload(inputStream,ossPathName); batchJobNameCache.put(jobName + "_ossPath",ossPath); log.info("upload complete,ossPath:" + ossPath); dto.setResultReturn(ossPath); dto.setResultType(String.valueOf(TaskResultType.Success)); }catch (Exception e){ dto.setResultType(String.valueOf(TaskResultType.Failure)); batchJobNameCache.put(jobName + "_ossPath",String.valueOf(TaskResultType.Failure)); // batchJobNameCache.remove(jobName); dto.setResultReason("上传压缩包失败"); log.info(" upload zip failure "); }finally { if(inputStream != null){ inputStream.close(); } } } catch (Exception e) { dto.setResultReason("打包失败"); batchJobNameCache.put(jobName + "_ossPath",String.valueOf(TaskResultType.Failure)); // batchJobNameCache.remove(jobName); dto.setResultType(String.valueOf(TaskResultType.Failure)); log.info(" zip failure "); }finally { //删除压缩包 if (zipFile.exists()) { zipFile.delete(); log.info(jobName + ".zip has been deleted ! "); } //更新批次任务为子任务状态(0->1) tcTransportLogService.updateById(dto); } } } /** * 生成wor并返回相应path */ class DocMakeTask implements Callable<String> { private TCPatientsDTO tcPatientsDTO; private Object template;private String fileName; private String outPath; //生成报告的临时根目录 private String templateType; public DocMakeTask(TCPatientsDTO tcPatientsDTO,String fileName,Object template,String outPath,String templateType) { this.tcPatientsDTO = tcPatientsDTO; this.fileName = fileName; this.template = template; this.outPath = outPath;this.templateType = templateType; } @Override public String call() throws Exception { //生成的报告的临时目录 String docTempPath = ""; // 取模板填充数据 Map<String,Object> dataMap = ""; docTempPath = outPath + File.separator + "xxx_xxx" + fileName; // 生成报告 if(ApiConsts.TEMPLATE_TYPE_XML.equals(templateType)){ FreemarkerUtil.createWordByTemplate((Template) template,docTempPath,dataMap); }else{ PoiUtil.writeToFileByTemplate((XWPFTemplate) template,dataMap); } log.info("word :" + docTempPath); return docTempPath; } } @GetMapping("archives") @ApiOperation("单份报告下载") @LogOperation("单份报告下载") @ResponseBody public Result archives(@RequestParam Map<String,HttpServletRequest request) throws Exception {//参数判空处理等都略过。。。

     //根据业务类型取取模板实例(通过oss链接取模板)
String url = wordService.getTemplateUrl(plansCode,downType); if(StringUtils.isEmpty(url)){ log.info("模板链接为空"); return new Result().error(202,"请先指定报告模板"); } //取oss模板的后缀(项目支持POI和xml) String templateType = wordService.getType(url); if(ApiConsts.TEMPLATE_TYPE_XML.equals(templateType)){ //"xml" templateType = ApiConsts.TEMPLATE_TYPE_XML; }else{ //"poi" templateType = ApiConsts.TEMPLATE_TYPE_POI; } //2、取业务类型对应数据 Map<String,Object> dataMap = new HashMap<>(); if(ApiConsts.RESIDENT_HEALTHY.equals(downType)){ //健康档案 dataMap = wordService.getDataMapPoi(plansCode,sn,ApiConsts.RESIDENT_HEALTHY,templateType); }else if(ApiConsts.RESIDENT_REPORT.equals(downType)){ //体检报告 dataMap = wordService.getDataMapPoi(plansCode,ApiConsts.RESIDENT_REPORT,templateType); } String name = MapUtils.getString(dataMap,"name"); //oos名称:{downType}/{planscoe}/时间戳/{sn}_{name}_xxx.doc String ossPathName = downType + "/" + plansCode + "/" + System.currentTimeMillis() + "/" + sn + "_" + name + "_" + fileName; //记录下载痕迹 TCTransportLogDTO dto = null; String userName = phsUser.getUserName(); //上传oss返回的链接 String ossPath = ""; try{ if(ApiConsts.TEMPLATE_TYPE_POI.equals(templateType)){ //临时目录 String outTempPath = wordService.getTempPath() + File.separator + sn + "_" + name + fileName; //多个自定义渲染策略 Configure configures = Configure.createDefault(); configures.customPolicy("urines",5)); XWPFTemplate template = PoiUtil.getTemplate(url,configures); template.render(dataMap); template.writeToFile(outTempPath); template.close(); ossPath = OSSFactory.build().upload(new FileInputStream(outTempPath),ossPathName); //删除本地临时文件 ZipUtil.delFile(new File(outTempPath)); }else{ StringWriter out = new StringWriter(); Template template = FreemarkerUtil.getTemplate(url); template.process(dataMap,out); ossPath = OSSFactory.build().upload(out.toString().getBytes(StandardCharsets.UTF_8),ossPathName); } log.info(sn + "_" + name + fileName + "生成! doc link:" + ossPath); dto = new TCTransportLogDTO(); dto.setUserName(userName); dto.setBusinessType(ApiConsts.TRANSMISSION_DOWNLOAD);//下载 dto.setStatus(1);//有效 dto.setJobType(1);//子任务类型 dto.setResultType(String.valueOf(TaskResultType.Success));//下载成功 dto.setResultReturn(ossPath);//下载存储路径 dto.setResultName(sn + "_" + name + fileName);//下载后文件名称 dto.setCreateDate(new Date()); return new Result().success(200,tipStr + "下载完成",ossPath); }catch (Exception e){ dto.setResultReturn("");//下载存储路径 dto.setResultType(String.valueOf(TaskResultType.Failure)); dto.setResultReason("下载失败"); log.info(sn + "_" + name + fileName + " 下载失败!"); return new Result().success(201,tipStr + "下载失败",""); }finally { tcTransportLogService.insert(ConvertUtils.sourceToTarget(dto,TCTransportLogEntity.class)); } } @GetMapping("progressList") @ApiOperation("下载完成记录列表") @LogOperation("下载完成记录列表") public Result getProgressList(@RequestParam Map<String,Object> params,HttpServletRequest request){//取当前用户下的所有子任务(businessType:==1表示上传完毕(待下载),==2表示已下载) List<TCTransportLogDTO> list = DB.getxxxx(xxx); if(list.isEmpty()){ return new Result().success(201,"暂无下载记录~",list); } return new Result().success(200,"获取下载记录成功",list); } @GetMapping("getInTransit") @ApiOperation("获取打包中列表") @LogOperation("获取打包中列表") public Result getInTransit(@RequestParam Map<String,HttpServletRequest request){ Integer businessType = MapUtils.getInteger(params,"businessType",ApiConsts.TRANSMISSION_UPLOAD); //获取所有批量任务 List<TCTransportLogDTO> list = tcTransportLogService.getInTransit(phsUser.getUserName(),0,businessType); if(list.isEmpty()){ return new Result().success(201,"暂无下载中任务~",list); } //实际正在进行的列表 List<TCTransportLogDTO> returnList = new ArrayList<>(); //异常任务+已完成任务 List<TCTransportLogEntity> completeList = new ArrayList<>(); //下载任务异常列表 List<TCTransportLogEntity> updateList = new ArrayList<>(); for(TCTransportLogDTO dto : list){ String jobName = dto.getJobName(); //1、缓存中不存在(已完成或者任务没有正常结束两种) String existJobName = MapUtils.getString(batchJobNameCache,""); if(StringUtils.isEmpty(existJobName)){ if(StringUtils.isEmpty(dto.getResultType())){ dto.setResultType(String.valueOf(TaskResultType.Exception)); dto.setResultReason("任务没有正常结束"); dto.setJobType(1);//任务改为子任务 updateList.add(ConvertUtils.sourceToTarget(dto,TCTransportLogEntity.class)); } completeList.add(ConvertUtils.sourceToTarget(dto,TCTransportLogEntity.class)); continue; } try{ //2、正在进行的工作 int percent = Integer.parseInt(existJobName); dto.setPercertage(percent); }catch (Exception e){ e.printStackTrace(); } returnList.add(dto); } //处理打包异常 if(!updateList.isEmpty()){ log.info("任务没有正常结束:" + updateList.size()); //2、更新数据库状态为异常 tcTransportLogService.updateBatchById(updateList); //检查已完成的列表,删除临时文件 handlerAbnormalTask(completeList); } if(returnList.isEmpty()){ return new Result().success(201,returnList); } return new Result().success(200,"获取下载任务成功",returnList); } /** * 处理批量下载[下载失败/打包异常]任务 * @param completeList 已完成的列表 */ private void handlerAbnormalTask(List<TCTransportLogEntity> completeList) { String outTempPath = wordService.getTempPath(); for(TCTransportLogEntity entry : completeList){ File zipFile = new File(outTempPath + File.separator + entry.getJobName() + ".zip"); ZipUtil.delFile(zipFile); File temFileDir = new File(outTempPath + File.separator + entry.getJobName()); ZipUtil.delFile(temFileDir); } log.info("delete complete or exception task..."); } /** * 以服务器的最后一个磁盘作为临时目录(返回字符串带文件分隔符) * @return */ private String getTempPath() { //本地磁盘的根路径 File[] paths = File.listRoots(); return paths[paths.length-1].getAbsolutePath(); } @PostMapping("queryProcess") @ApiOperation("查询打包进度") @LogOperation("查询打包进度") public Result queryProcess(@RequestBody Map<String,Object> params){ String taskList = MapUtils.getString(params,"jobNames",""); if(StringUtils.isEmpty(taskList)){ return new Result().success(201,"下载完成",null); } List<TCTransportLogDTO> jobNameList = JSONObject.parseArray(taskList,TCTransportLogDTO.class); List<TCTransportLogDTO> returnList = new ArrayList<>(); //遍历列表,分开已经完成并过期的工作( for(TCTransportLogDTO dto : jobNameList){ String jobName = dto.getJobName(); //1)、缓存中不存在的 String existJobName = MapUtils.getString(batchJobNameCache,""); if(StringUtils.isEmpty(existJobName)){ continue; } //2)、刷新进度条显示 if(dto.getPercertage() == 100 ){ //打包完成,下载中 dto.setRemark("下载中..."); }else{ // 进度 < 100,刷新打包中任务进度 int percent = MapUtils.getIntValue(batchJobNameCache,0); dto.setPercertage(percent); dto.setRemark("打包中..."); } returnList.add(dto); } if(returnList.isEmpty()){ return new Result().success(201,"下载完成","刷新打包进度条",returnList); } @GetMapping("monitorPackage") @ApiOperation("监听打包") @LogOperation("监听打包") public Result monitorPackage(@RequestParam Map<String,HttpServletResponse response){ String jobName = MapUtils.getString(params,"jobName",""); if(StringUtils.isEmpty(jobName)){ log.info("[ monitorPackage ] jobName parameter is missing"); return new Result().error(202,"jobName parameter is missing"); } String jobNameExist = MapUtils.getString(batchJobNameCache,""); if(StringUtils.isEmpty(jobNameExist)){ log.info("[ monitorPackage ] [" + jobName + "] is not found"); return new Result().error(202,"[" + jobName + "] is not found"); } //进度 int percentage = MapUtils.getIntValue(batchJobNameCache,jobName); //打包完成后,判断oss链接 String ossPath = MapUtils.getString(batchJobNameCache,jobName + "_ossPath"); if(StringUtils.isEmpty(ossPath)){ log.info("zip being packaged"); return new Result().success(201,"zip being packaged",percentage); }else{ //2、从缓存中剔除 batchJobNameCache.remove(jobName); batchJobNameCache.remove(jobName + "_ossPath"); log.info(" remove from batchJobNameCache cache "); if(String.valueOf(TaskResultType.Failure).equals(ossPath)){ //打包失败/上传失败==下载失败 log.info(percentage==100 ? "上传失败" : "打包失败"); return new Result().success(202,percentage==100 ? "上传失败" : "打包失败",ossPath); } //下载成功 log.info("package is complete,ready to download "); return new Result().success(200,"package is complete,ready to download ",ossPath); } } @GetMapping("queryDetail") @ApiOperation("查询详情") @LogOperation("查询详情") public String queryDetail(@RequestParam("jobName") String jobName){ List<TaskResult<String>> taskDetail = pendingJobPool.getTaskDetail(jobName); if(!taskDetail.isEmpty()){ return taskDetail.toString(); } return null; } @GetMapping("clearMark") @ApiOperation("清除下载记录") @LogOperation("清除下载记录") public Result clearMark(@RequestParam Map<String,HttpServletRequest request){ String clearIds = MapUtils.getString(params,"clearIds",""); if (StringUtils.isEmpty(clearIds)) { log.info("传输完成记录主键为空"); return new Result().error(201,"丢失需要清除的记录主键信息"); } List<Long> listIds = Arrays.asList(clearIds.split(",")).stream().map(s -> Long.parseLong(s.trim())).collect(Collectors.toList()); int row = tcTransportLogService.clearByIds(listIds); if(row == listIds.size()){ log.info("清除传输记录成功"); return new Result().success(200,"清除成功",row); } log.info("清除传输记录失败"); return new Result().success(202,"清除失败",row); } }

涉及工具类:

package com.xxxx.common.utils;

import com.xxxx.modules.ftl.RemoteTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.springframework.util.ResourceUtils;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Map;

public class FreemarkerUtil {
  public static Template getTemplate(String url) {
    try {
      // 通过Freemarker的Configuration读取相应的ftl,这里是对应的你使用jar包的版本号:<version>2.3.28</version>
      Configuration configuration = new Configuration(Configuration.VERSION_2_3_28);
      // 处理空值
      configuration.setClassicCompatible(true);
      configuration.setDefaultEncoding("UTF-8");
      RemoteTemplateLoader remoteTemplateLoader = new RemoteTemplateLoader(url);
      configuration.setTemplateLoader(remoteTemplateLoader);
      Template template = configuration.getTemplate(url);
      return template;
    } catch (IOException e) {
      e.printStackTrace();
    }
    return null;
  }

  public void print(String name,Map<String,Object> root) {
    // 通过Template可以将模版文件输出到相应的文件流
    Template template = this.getTemplate(name);
    try {
      template.process(root,new PrintWriter(System.out)); // 在控制台输出内容
    } catch (TemplateException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  /**
   * 输出HTML文件
   *
   * @param name
   * @param root
   * @param outFile
   */
  public void fprint(String name,Object> root,String outFile) {
    FileWriter out = null;
    try {
      // 通过一个文件输出流,就可以写到相应的文件中,此处用的是绝对路径
      File file = new File(outFile);
      if (!file.getParentFile().exists()) {
        file.getParentFile().mkdirs();
      }
      out = new FileWriter(file);
      Template temp = this.getTemplate(name);
      temp.process(root,out);
    } catch (IOException e) {
      e.printStackTrace();
    } catch (TemplateException e) {
      e.printStackTrace();
    } finally {
      try {
        if (out != null) out.close();
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }

  public static void createWorldByMode(String modeName,String outFile,Object params) {
    Configuration cfg = new Configuration(Configuration.VERSION_2_3_28);
    Writer out = null;
    try {
      // 设置模板路径
      cfg.setDirectoryForTemplateLoading(ResourceUtils.getFile("classpath:templates"));
      cfg.setDefaultEncoding("UTF-8");
      // 处理空值
      cfg.setClassicCompatible(true);
      File file = new File(outFile);
      if (!file.getParentFile().exists()) {
        file.getParentFile().mkdirs();
      }
      if (!file.exists()) {
        file.createNewFile();
      }
      out = new OutputStreamWriter(new FileOutputStream(file),"UTF-8"); // 设置编码 UTF-8
      Template template = cfg.getTemplate(modeName);
      template.process(params,out);
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      if (null != out) {
        try {
          out.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
  }

  /**
   * 根据模板创建word文档
   *
   * @param template 模板
   * @param outFile 生成的word文档字符串
   * @param params 模板填充需要的Map数据
   */
  public static void createWordByTemplate(Template template,Object params) {
    Writer out = null;
    FileOutputStream fos = null;
    try {
      // 2、输出word
      File wordFile = new File(outFile);
      if (!wordFile.getParentFile().exists()) {
        wordFile.getParentFile().mkdirs();
      }
      if (!wordFile.exists()) {
        wordFile.createNewFile();
      }
      fos = new FileOutputStream(wordFile);
      out = new OutputStreamWriter(fos,StandardCharsets.UTF_8);
      template.process(params,out);
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      try {
      if (null != out) {
          out.close();
      }
      if(fos!=null){
        fos.close();
      }
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }
}
package com.quiknos.modules.ftl;

import freemarker.cache.URLTemplateLoader;

import java.net.MalformedURLException;
import java.net.URL;

public class RemoteTemplateLoader extends URLTemplateLoader {

    private String urlPath;

    public RemoteTemplateLoader(String urlPath) {
        this.urlPath = urlPath;
    }

    @Override
    protected URL getURL(String path) {
        URL url = null;
        try {
            url = new URL(urlPath);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        return url;
    }
}

poi:

package com.xxxx.common.utils;

import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import io.renren.common.exception.RenException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;


/**
 * Created by hzm on 2019/6/26
 *
 */
@Slf4j
public final class PoiUtil {

    /**
     * 根据url取poi模板
     * @param urlPath 模板url
     * @return
     */
    public static XWPFTemplate getTemplate(String urlPath,Configure configure){
        if(StringUtils.isEmpty(urlPath)){
            throw new RenException(" url is empty ");
        }

        XWPFTemplate template = null;
        InputStream inputStream = null;
        try {
            inputStream = getInputStream(urlPath);
            if(null == configure){
                template = XWPFTemplate.compile(inputStream);
            }else{
                template = XWPFTemplate.compile(inputStream,configure);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if(inputStream != null){
                    inputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return template;
    }

    /**
     * 根据url从服务器获取一个输入流
     * @param urlPath
     * @return
     */
    private static InputStream getInputStream(String urlPath) {
        HttpURLConnection httpURLConnection = null;
        InputStream inputStream = null;
        try {
            URL url = new URL(urlPath);
            httpURLConnection = (HttpURLConnection) url.openConnection();
            httpURLConnection.setConnectTimeout(3000);//设置连接超时
            httpURLConnection.setDoInput(true);//设置应用程序要从网络连接读取数据
            httpURLConnection.setRequestMethod("GET");
            int responseCode = httpURLConnection.getResponseCode();
            if(responseCode == 200){
                //接收服务器返回的流
                inputStream = httpURLConnection.getInputStream();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return inputStream;
    }

    /**
     * 根据doc模板和数据输出到文件流生成新文档
     * @param template doc模板
     * @param outFile
     * @param dataMap   数据源
     */
    public static void writeByTemplate(XWPFTemplate template,Object> dataMap){
        //输出流
        File wordFile = new File(outFile);
        if (!wordFile.getParentFile().exists()) {
            wordFile.getParentFile().mkdirs();
        }
        FileOutputStream fos = null;
        try {
            if (!wordFile.exists()) {
                wordFile.createNewFile();
            }
            fos = new FileOutputStream(wordFile);
            //输出到文件流
            template.render(dataMap).write(fos);
            fos.flush();
        } catch (Exception e) {

        } finally {
            try {
                if(fos!=null){
                    fos.close();
                }
                if(null != template){
                    template.close();
                }
            } catch (IOException e) {
                log.info("报告生成异常:" + e.getStackTrace());
            }
        }
    }

    /**
     * 根据doc模板和数据输出到文件
     * @param template doc模板
     * @param outFile 输出文件
     * @param dataMap 数据源
     */
    public static void writeToFileByTemplate(XWPFTemplate template,Object> dataMap){
        try {
            //输出到文件
            template.render(dataMap).writeToFile(outFile);
            template.close();
        } catch (Exception e) {
            log.info("报告生成异常:" + e.getStackTrace());
        }
    }
}

压缩工具类:

package com.xxxx.common.utils;

import java.io.*;
import java.util.zip.CRC32;
import java.util.zip.CheckedOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;



public final class ZipUtil {

    /**
     * 功能描述: 压缩成Zip格式
     *
     * @author: hongzm
     * @param: srcFilePath
     *             要压缩的源文件路径
     * @param: destFilePath
     *             压缩后文件存放路径
     * @param: KeepFileStructure
     *             是否保留原来的目录结构,true:保留目录结构;
     *             false:所有文件跑到压缩包根目录下(注意:不保留目录结构可能会出现同名文件,会压缩失败)
     */
    public static void toZip(String srcFilePath,String destFilePath,boolean KeepFileStructure) {
        // 判断要压缩的源文件是否存在
        File sourceFile = new File(srcFilePath);
        if(!sourceFile.exists()) {
            throw new RuntimeException(sourceFile + "不存在...");
        }

        long start = System.currentTimeMillis();

        // 如果压缩文件已经存在,增加序号
        String zipName = destFilePath + sourceFile.getName();

        // 创建存放压缩文件的文件对象
        File zipFile = new File(zipName + ".zip");
        ZipOutputStream zos = null;
        try {
            // 生成目标文件对象的输出流
            FileOutputStream fos = new FileOutputStream(zipFile);
            CheckedOutputStream cos = new CheckedOutputStream(fos,new CRC32());
            // 生成ZipOutputStream,用于写入要压缩的文件
            zos = new ZipOutputStream(cos);
            compressbyType(sourceFile,KeepFileStructure);
            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();
                }
            }
        }
    }

    public static void compressbyType(File sourceFile,ZipOutputStream zos,String zipName,boolean KeepDirStructure) throws Exception {
        if(!sourceFile.exists())
            return;
        System.out.println("压缩" + sourceFile.getName());

        if(sourceFile.isFile()) {
            // if(!"myDir3.txt".equals(sourceFile.getName())) {
            // 文件
            compressFile(sourceFile,zipName,KeepDirStructure);
            // }

        } else {
            // 文件夹
            compressDir(sourceFile,KeepDirStructure);
        }
    }

    public static void compressFile(File file,boolean keepDirStructure)
            throws IOException {

        // 1、向zip输出流中添加一个zip实体(压缩文件的目录),构造器中name为zip实体的文件的名字
        ZipEntry entry = new ZipEntry(zipName);
        zos.putNextEntry(entry);
        FileInputStream fis = null;
        BufferedInputStream bis = null;

        // 2、 copy文件到zip输出流中
        int len;
        byte[] buf = new byte[1024];
        try{
            // 要压缩的文件对象写入文件流中
            fis = new FileInputStream(file);
            bis = new BufferedInputStream(fis);
            while((len = bis.read(buf)) != -1) {
                zos.write(buf,0,len);
                zos.flush();
            }
        }catch (Exception e){

        }finally {
// Complete the entry
            if(fis != null){
                fis.close();
            }
//            zos.closeEntry();
            if(bis != null){
                bis.close();
            }
        }


    }

    public static void compressDir(File dir,boolean KeepDirStructure)
            throws IOException,Exception {

        if(!dir.exists())
            return;

        File[] files = dir.listFiles();
        if(files.length == 0) { // 空文件夹
            // 需要保留原来的文件结构时,需要对空文件夹进行处理
            if(KeepDirStructure) {
                // 空文件夹的处理
                zos.putNextEntry(new ZipEntry(zipName + File.separator));
                // 没有文件,不需要文件的copy
                zos.closeEntry();
            }
        } else {
            for(File file : files) {
                // 判断是否需要保留原来的文件结构
                if(KeepDirStructure) {
                    // 注意:file.getName()前面需要带上父文件夹的名字加一斜杠,// 不然最后压缩包中就不能保留原来的文件结构,即:所有文件都跑到压缩包根目录下了
                    compressbyType(file,zipName + File.separator + file.getName(),KeepDirStructure);
                } else {
                    compressbyType(file,file.getName(),KeepDirStructure);
                }
            }
        }
    }

    /**
     * 功能描述: outputStream转inputStream
     *
     * @author: hongzm
     * @param: out 输出流
     * @return: byte[]
     */
    public static ByteArrayInputStream outPareIn(OutputStream out){
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        baos = (ByteArrayOutputStream) out;
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        return bais;
    }

    /**
     * 功能描述: inputStream转byte[]
     *
     * @author: hongzm
     * @param: in 输入流
     * @return: byte[]
     */
    public static byte[] outPareIn(InputStream in) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buff = new byte[1024];
        int n = 0;
        while ((n = in.read(buff)) != -1){
            baos.write(buff,n);
        }

        byte[] buff2 = baos.toByteArray();
        return buff2;
    }

    /**
     * 删除文件或者目录
     * @param file
     * @return
     */
    public static boolean delFile(File file){
        if(!file.exists()){
            return false;
        }
        if(file.isDirectory()){
            File[] files = file.listFiles();
            for(File f : files){
                delFile(f);
            }
        }
        return file.delete();
    }
}

?

下面是自己开发中的笔记

下载记录表设计:

补充自己的数据推演:

?

开发中遇到的坑:

1)批量下载网上给出的大都是随便整几个几kb文件(压缩还不快吗),压缩成文件流,响应到浏览器,即可下载,要我说没卵用,实际项目会是这么几kb的文件吗,如若是几百份文件,每份生成的word好几兆呢,像我项目中每份word生成后是4-5兆,而且批量最大300-400份,要考虑客户端一个请求的超时问题,最终我选择采用了异步打包的方案;

2)有人可能会想在客户端点击下载时候,先拿到保存路径,后台将生成word放到这个路径下——告诉你:行不通,首先服务端没有这个权限,换句话说就是服务端怎么知道客户端要下载的,所以即使你拿到路径传到后台,服务器只会解析成服务器的本地路径,当然,本地项目在跑的时候,是可以实现功能的,因为项目就在你本机上;

3)本地下载报告中文不会乱码,但是服务器就不好说,所以还是要在生成word时候设置字符编码,这是开发时候遇到的问题之一;

4)还有一个要注意的,如果项目中是将模板放在resources下面,又是打成jar包,部到服务器上,ResourceUtils.getFile("classpath:templates")是取不到模板的,换句话说,项目打成jar包,而你若想把临时文件夹放到这个路径下,是行不通的;

5)异步处理任务中有几点需要注意:

  ①CompletionService可以了解下,一句话,先完成的先处理,并不会按先进先出的套路;(并发编程知识)

  ②压缩完的时候,要先关闭相关文件流,再上传,不然会就算上传了,下载下来也是不能用的zip包

    文件流关闭顺序一般是:

      一般情况是:先打开后关闭,后打开先关闭(可以想象成打开家门顺序);

      另一种情况是:看依赖关系,如果a流依赖b流,应该是先关闭a流,再关闭b流(可以想象成删主从表顺序,先删从表,再删主表);

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读