当前位置: 首页 > article >正文

手摸手系列之 Java 通过 PDF 模板生成 PDF 功能

集团 SaaS 平台目前需要实现导出 PDF 格式的电子委托协议功能。业务方已经提供了一个现成的 PDF 文件作为参考。针对这一需求,我们有两个可行的方案:

  1. 完全代码生成:根据 PDF 文件的外观,完全通过代码动态生成 PDF 文件。
  2. 模板填充:将现有的 PDF 文件作为模板,仅需在代码中填充真实数据即可生成最终的 PDF 文件。

从实现效率和开发速度的角度来看,方案二(模板填充)无疑是更优的选择。它不仅能够大幅减少开发工作量,还能确保生成的 PDF 文件与业务方提供的模板完全一致,避免样式偏差。接下来,我们将重点探讨如何通过模板填充的方式实现这一功能。

一、PDF 模板制作

首先通过 PDF 编辑器制作 PDF 模板,这里我选用 Adobe Acrobat Pro 编辑表单来实现,这里我主要用到了表单的文本域和复选框。
工具我放云盘,需要的自取:https://caiyun.139.com/m/i?105CqcMLSgEyR 提取码:6ais
在这里插入图片描述
文本域的 name 对应 Java 中 model 类的属性。

二、前端编码

// html
<a-button v-has="'dec:down'" type="primary" icon="printer" :loading="printBatchLoading" @click="handlePrintBatch">批量打印</a-button>

// JavaScript
/**
 * 批量打印
 */
handlePrintBatch(){
  if (this.selectedRowKeys.length == 0) {
   this.$message.error('请选择至少一票数据!')
   return
  }
  let params = {}
  params.ids = this.selectedRowKeys.join(',')
  this.printBatchLoading = true
  let fileName = ''
  if (this.selectedRowKeys.length > 1) {
   fileName = '电子委托协议批量导出.zip'
  } else {
   fileName = '电子委托协议导出' + (this.selectionRows[0].consignNo ? this.selectionRows[0].consignNo :
    this.selectionRows[0].id) + '.pdf'
  }
  downloadFile(this.url.exportElecProtocolBatch, fileName,
   params).then((res) => {
   if (res.success) {
   } else {
    this.$message.warn(`导出失败!${res.message}`)
   }
  }).finally(() => {
   this.printBatchLoading = false
  })
}

/**
 * 下载文件
 * @param url 文件路径
 * @param fileName 文件名
 * @param parameter
 * @returns {*}
 */
export function downloadFile(url, fileName, parameter) {
  return downFile(url, parameter).then((data) => {
    if (!data || data.size === 0) {
      Vue.prototype['$message'].warning('文件下载失败')
      return
    }
    if (typeof window.navigator.msSaveBlob !== 'undefined') {
      window.navigator.msSaveBlob(new Blob([data]), fileName)
    } else {
      let url = window.URL.createObjectURL(new Blob([data]))
      let link = document.createElement('a')
      link.style.display = 'none'
      link.href = url
      link.setAttribute('download', fileName)
      document.body.appendChild(link)
      link.click()
      document.body.removeChild(link) //下载完成移除元素
      window.URL.revokeObjectURL(url) //释放掉blob对象
    }
  })
}

三、后端编码

/**
 * 批量打印电子委托协议
 *
 * @param ids
 * @return org.jeecg.common.api.vo.Result<?>
 * @author ZHANGCHAO
 * @date 2025/1/16 08:54
 */
@Override
public void exportElecProtocolBatch(String ids, HttpServletResponse response) {
    try {
        // 获取协议列表
        List<ElecProtocol> elecProtocolList = fetchProtocolsByIds(ids);
        if (isEmpty(elecProtocolList)) {
            throw new RuntimeException("未获取到电子委托协议数据");
        }

        if (elecProtocolList.size() == 1) {
            // 单个文件导出
            ElecProtocol protocol = elecProtocolList.get(0);
            Map<String, Object> data = prepareDataMap(protocol);
            byte[] pdfBytes = generatePdf(data);

            // 设置响应头
            String pdfFileName = URLEncoder.encode(
                    "电子委托协议_" + (isNotBlank(protocol.getConsignNo()) ? protocol.getConsignNo() : protocol.getId()) + ".pdf",
                    "UTF-8");
            response.setContentType("application/pdf");
            response.setHeader("Content-Disposition", "attachment; filename=" + pdfFileName);

            try (ServletOutputStream outputStream = response.getOutputStream()) {
                outputStream.write(pdfBytes);
                outputStream.flush();
            }
        } else {
            // 多个文件压缩成 ZIP 导出
            response.setContentType("application/zip");
            String zipFileName = URLEncoder.encode("电子委托协议导出.zip", "UTF-8");
            response.setHeader("Content-Disposition", "attachment; filename=" + zipFileName);

            try (ZipOutputStream zipOut = new ZipOutputStream(response.getOutputStream())) {
                for (ElecProtocol protocol : elecProtocolList) {
                    Map<String, Object> data = prepareDataMap(protocol);
                    byte[] pdfBytes = generatePdf(data);

                    String pdfFileName = "电子委托协议_" +
                            (isNotBlank(protocol.getConsignNo()) ? protocol.getConsignNo() : protocol.getId()) + ".pdf";
                    ZipEntry zipEntry = new ZipEntry(pdfFileName);
                    zipOut.putNextEntry(zipEntry);
                    zipOut.write(pdfBytes);
                    zipOut.closeEntry();
                }
            }
        }
    } catch (Exception e) {
        log.error("导出电子委托协议失败: {}", e.getMessage(), e);
        throw new RuntimeException("导出失败,请稍后再试");
    }
}

/**
 * 获取数据
 *
 * @param ids
 * @return java.util.List<org.jeecg.modules.business.entity.ElecProtocol>
 * @author ZHANGCHAO
 * @date 2025/1/16 16:24
 */
private List<ElecProtocol> fetchProtocolsByIds(String ids) {
    return baseMapper.selectBatchIds(Arrays.asList(ids.split(",")));
}

/**
 * 处理数据
 *
 * @param elecProtocol
 * @return java.util.Map<java.lang.String, java.lang.Object>
 * @author ZHANGCHAO
 * @date 2025/1/16 16:23
 */
private Map<String, Object> prepareDataMap(ElecProtocol elecProtocol) {
    Map<String, Object> map = new HashMap<>();
    map.put("consignorName", elecProtocol.getConsignorName());
    map.put("trusteeName", elecProtocol.getTrusteeName());
    map.put("gName", elecProtocol.getGName());
    if (isNotBlank(elecProtocol.getEntryId())) {
        map.put("entryId", "No." + elecProtocol.getEntryId());
    }
    map.put("codeTs", elecProtocol.getCodeTs());
    map.put("receiveDate", elecProtocol.getReceiveDate());
    map.put("ieDate", elecProtocol.getIeDate());
    map.put("billCode", elecProtocol.getBillCode());
    if (isNotBlank(elecProtocol.getTradeMode())) {
        List<DictModelVO> jgfs = decListMapper.getDictItemByCode("JGFS");
        List<DictModelVO> dictModelVO1=jgfs.stream().filter(i->i.getValue()
                        .equals(elecProtocol.getTradeMode()))
                .collect(Collectors.toList());
        map.put("tradeMode", isNotEmpty(dictModelVO1) ? dictModelVO1.get(0).getText() : "");
    }
    map.put("qtyOrWeight", elecProtocol.getQtyOrWeight());
    map.put("packingCondition", elecProtocol.getPackingCondition());
    map.put("paperinfo", elecProtocol.getPaperinfo());
    if (isNotBlank(elecProtocol.getOriCountry())) {
        List<DictModel> dictModels2 = sysBaseApi.getDictItems("erp_countries,name,code");
        Map<String, String> dictMap2 = new HashMap<>();
        if (isNotEmpty(dictModels2)) {
            dictModels2.forEach(dictModel -> {
                dictMap2.put(dictModel.getValue(), dictModel.getText());
            });
        }
        if(dictMap2.containsKey(elecProtocol.getOriCountry())) {
            map.put("oriCountry", dictMap2.get(elecProtocol.getOriCountry()));
        }
    }
    if (isNotBlank(elecProtocol.getDeclarePrice())) {
        if (isNotBlank(elecProtocol.getCurr())) {
            // 币制
            List<DictModel> dictModels3 = sysBaseApi.getDictItems("erp_currencies,name,code,1=1");
            Map<String, String> dictMap3 = new HashMap<>();
            if (isNotEmpty(dictModels3)) {
                dictModels3.forEach(dictModel -> {
                    dictMap3.put(dictModel.getValue(), dictModel.getText());
                });
            }
            if(dictMap3.containsKey(elecProtocol.getCurr())) {
                map.put("declarePrice", dictMap3.get(elecProtocol.getCurr()) + ": " + elecProtocol.getDeclarePrice() + "元");
            } else {
                map.put("declarePrice", elecProtocol.getDeclarePrice());
            }
        } else {
            map.put("declarePrice", elecProtocol.getDeclarePrice());
        }
    }
    map.put("otherNote", elecProtocol.getOtherNote());
    map.put("promiseNote", elecProtocol.getPromiseNote());
    String dateStr = DateUtil.format(new Date(), DatePattern.CHINESE_DATE_PATTERN);
    map.put("dateStr", dateStr);
    map.put("printTime", dateStr + " " + DateUtil.format(new Date(), DatePattern.NORM_TIME_PATTERN));

    // 处理选框逻辑
    String paperinfo = elecProtocol.getPaperinfo();
    if (isNotBlank(paperinfo) && paperinfo.length() == 6) {
        for (int i = 0; i < paperinfo.length(); i++) {
            if (paperinfo.charAt(i) == '1') {
                map.put("gou" + (i + 1), "On");
            }
        }
    }
    return map;
}

/**
 * 生成PDF
 *
 * @param data
 * @return byte[]
 * @author ZHANGCHAO
 * @date 2025/1/16 16:24
 */
private byte[] generatePdf(Map<String, Object> data) throws Exception {
    try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
        PdfReader reader = new PdfReader(this.getClass().getResourceAsStream("/templates/pdf/电子委托协议模板.pdf"));
        PdfStamper stamper = new PdfStamper(reader, bos);
        AcroFields form = stamper.getAcroFields();

        BaseFont bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.EMBEDDED);
        ArrayList<BaseFont> fontList = new ArrayList<>();
        fontList.add(bf);
        form.setSubstitutionFonts(fontList);

        for (Map.Entry<String, Object> entry : data.entrySet()) {
            if (entry.getKey().contains("gou")) {
                form.setField(entry.getKey(), isNotEmpty(entry.getValue()) ? entry.getValue().toString() : "", true);
            } else {
                form.setField(entry.getKey(), isNotEmpty(entry.getValue()) ? entry.getValue().toString() : "");
            }
        }

        stamper.setFormFlattening(true);
        stamper.close();
        return bos.toByteArray();
    }
}

同时支持导出单个和批量,单个是直接生成PDF文件,批量是打成压缩包。

四、效果展示

页面数据:
[图片]
生成的 PDF:
在这里插入图片描述

总结

总得来说,Java通过itext PDF模板生成PDF文件功能很简单,主要是数据的填充而已。还可以继续丰富下,比如多行文本、自动换行功能等。


http://www.kler.cn/a/507119.html

相关文章:

  • 【C++】构造函数与析构函数
  • HCIP-VLAN-hybrid接口+DHCP实验
  • 【Linux】Mysql部署步骤
  • 半导体数据分析: 玩转WM-811K Wafermap 数据集(三) AI 机器学习
  • C++ 并发编程之std::find的并发版本
  • Jmeter进行http接口并发测试
  • 专业140+总分410+宁波大学829信号与系统考研经验宁大电子信息与通信工程,真题,大纲,参考书。
  • 卷积神经网络的底层是傅里叶变换
  • CSS中的accent-color如何使用
  • SQL Server查询计划操作符——查询计划相关操作符(4)
  • Swift 专题二 语法速查
  • 如何调用短信服务接口给自己的网站设置短信验证码功能
  • IntelliJ IDEA:享受高效的 JAVA 开发
  • 苹果电脑怎么清理后台,提升苹果电脑运行速度
  • 如何在浏览器前端运行Python程序
  • C#与Vue2上传下载Excel文件
  • 《Keras 3 神经网络紧凑型卷积转换器(Transformers)》
  • 【python小知识】安装ta-lib遇到的问题
  • RabbitMQ中有哪几种交换机类型?
  • 网络安全 | 什么是威胁情报?
  • Oracle分析工具-Logminer
  • 面试的问题
  • HTML中link的用法
  • Hive中没有超级管理员,如何进行权限控制
  • VS Code--常用的插件
  • 两种方法,加密excel打开密码