通过java将数据导出为PDF,包扣合并单元格操作

最近项目中需要将查询出来的表格数据以PDF形式导出,并且表格的形式包含横向行与纵向列的单元格合并操作,导出的最终效果如图所示:

首先引入操作依赖

java 复制代码
<!--导出pdf所需包-->
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itextpdf</artifactId>
            <version>5.5.10</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itext-asian</artifactId>
            <version>5.2.0</version>
        </dependency>

最上面的基本信息是固定死的就是4*4的表格,这个创建起来 就比较简单,主要是下面这个表格,需要从数据库查出数据并循环进行展示,并且内容相同的列要进行合并。直接展示代码:

主类对外调用方法:

复制代码
@Operation(summary = "导出PDF")
    @PostMapping("/download")
    @SneakyThrows(Exception.class)
    public void download(Long id,HttpServletResponse response, HttpServletRequest request) {
        // 防止日志记录获取session异常
        request.getSession();
        // 设置编码格式
        response.setContentType("application/pdf;charset=UTF-8");
        response.setCharacterEncoding("utf-8");
        String fileName = URLEncoder.encode("调试PDF", "UTF-8");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".pdf");
        contractDemandThreeRequestBaseService.download(id,response);
    }

具体实现类:

java 复制代码
@Override
    public void download(Long id, HttpServletResponse response) {
        //要下载的数据查询数据部分我去掉了有需要自己根据业务取
        ContractDemandThreeRequestBaseDO baseDO = contractDemandThreeRequestBaseMapper.selectById(id);
        ContractDemandThreeRequestBaseRespVO base = ContractDemandThreeRequestBaseConvert.INSTANCE.convert(baseDO);
        base.setDeptName(ObjectUtil.isEmpty(deptService.getDept(base.getDeptId())) ? null : deptService.getDept(base.getDeptId()).getName());
        base.setProjectLeaderName(ObjectUtil.isEmpty(userService.getUser(base.getProjectLeaderId())) ? null : userService.getUser(base.getProjectLeaderId()).getNickname());
        //子表数据
        List<ContractDemandThreeQuestionDO> details = contractDemandThreeQuestionMapper.
                selectList(Wrappers.lambdaQuery(ContractDemandThreeQuestionDO.class).eq(ContractDemandThreeQuestionDO::getDemandId, id));
        //下面进行表格的创建、字体设置、合并单元格
        // 定义全局的字体静态变量
        Font titlefont;
        Font headfont;
        Font keyfont = null;
        Font textfont = null;
        Font content = null;
        BaseFont bfChinese = null;
        // 最大宽度
        try {
            // 不同字体(这里定义为同一种字体:包含不同字号、不同style)这里我用的最后一个                content字体
            bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
            titlefont = new Font(bfChinese, 16, Font.BOLD);
            headfont = new Font(bfChinese, 14, Font.BOLD);
            keyfont = new Font(bfChinese, 10, Font.BOLD);
            textfont = new Font(bfChinese, 15, Font.NORMAL);
            content = new Font(bfChinese, 10, Font.NORMAL);
        } catch (Exception e) {
            e.printStackTrace();
        }
        BaseFont bf;
        Font font = null;
        try {
            //创建字体
            bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H",
                    BaseFont.NOT_EMBEDDED);
            //使用字体并给出颜色
            font = new Font(bf, 20, Font.BOLD, BaseColor.BLACK);
        } catch (Exception e) {
            e.printStackTrace();
        }
        Document document = new Document();
        try {
            PdfWriter pdfWriter = PdfWriter.getInstance(document, response.getOutputStream());
            //打开生成的pdf文件
            document.open();
            //设置内容
            Paragraph paragraph = new Paragraph("采购需求三问", font);
            paragraph.setAlignment(1);
            //引用字体
            document.add(paragraph);
            // 设置表格的列宽和列数
            float[] widths = {25f, 25f, 25f, 25f};
            PdfPTable table = new PdfPTable(widths);
            table.setSpacingBefore(20f);
            // 设置表格宽度为100%
            table.setWidthPercentage(100.0F);
            table.setHeaderRows(1);
            table.getDefaultCell().setHorizontalAlignment(1);
            PdfPCell cell = null;
            //第一行:表格的名字
            cell = new PdfPCell(new Paragraph("采购项目名称", content));
            cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
            cell.setHorizontalAlignment(Element.ALIGN_CENTER);
            cell.setFixedHeight(30);
            table.addCell(cell);
            //表格里面的值(下面都是同样的操作)
            cell = new PdfPCell(new Paragraph(base.getProjectName(), content));
            cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
            cell.setHorizontalAlignment(Element.ALIGN_CENTER);
            table.addCell(cell);

            //第二行
            cell = new PdfPCell(new Paragraph("项目编号", content));
            cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
            cell.setHorizontalAlignment(Element.ALIGN_CENTER);
            cell.setFixedHeight(30);
            table.addCell(cell);
            cell = new PdfPCell(new Paragraph(base.getProjectCode(), content));
            cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
            cell.setHorizontalAlignment(Element.ALIGN_CENTER);
            table.addCell(cell);
            cell = new PdfPCell(new Paragraph("资金类型", content));
            cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
            cell.setHorizontalAlignment(Element.ALIGN_CENTER);
            table.addCell(cell);
            cell = new PdfPCell(new Paragraph(getDitcValue("FundType", base.getFundType()), content));
            cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
            cell.setHorizontalAlignment(Element.ALIGN_CENTER);
            table.addCell(cell);

            //第三行
            cell = new PdfPCell(new Paragraph("支出类别", content));
            cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
            cell.setHorizontalAlignment(Element.ALIGN_CENTER);
            cell.setFixedHeight(30);
            table.addCell(cell);
            cell = new PdfPCell(new Paragraph(getDitcValue("ExpenditureCategory", base.getExpenditureCategory()), content));
            cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
            cell.setHorizontalAlignment(Element.ALIGN_CENTER);
            table.addCell(cell);
            cell = new PdfPCell(new Paragraph("预算含税总金额(万元)", content));
            cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
            cell.setHorizontalAlignment(Element.ALIGN_CENTER);
            table.addCell(cell);
            cell = new PdfPCell(new Paragraph(String.valueOf(base.getBudgetIncludingTaxTotalMoney()), content));
            cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
            cell.setHorizontalAlignment(Element.ALIGN_CENTER);
            table.addCell(cell);
            //第三行
            cell = new PdfPCell(new Paragraph("采购内容", content));
            cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
            cell.setHorizontalAlignment(Element.ALIGN_CENTER);
            cell.setFixedHeight(30);
            table.addCell(cell);
            cell = new PdfPCell(new Paragraph(base.getProcureContent(), content));
            cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
            cell.setHorizontalAlignment(Element.ALIGN_CENTER);
            table.addCell(cell);
            //第四行
            cell = new PdfPCell(new Paragraph("需求部门", content));
            cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
            cell.setHorizontalAlignment(Element.ALIGN_CENTER);
            cell.setFixedHeight(30);
            table.addCell(cell);
            cell = new PdfPCell(new Paragraph(base.getDeptName(), content));
            cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
            cell.setHorizontalAlignment(Element.ALIGN_CENTER);
            table.addCell(cell);
            cell = new PdfPCell(new Paragraph("项目负责人", content));
            cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
            cell.setHorizontalAlignment(Element.ALIGN_CENTER);
            cell.setFixedHeight(30);
            table.addCell(cell);
            cell = new PdfPCell(new Paragraph(base.getProjectLeaderName(), content));
            cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
            cell.setHorizontalAlignment(Element.ALIGN_CENTER);
            table.addCell(cell);

            // 设置表格的列宽和列数
            float[] widths2 = {25f, 25f, 25f};
            PdfPTable table2 = new PdfPTable(widths2);
            table2.setSpacingBefore(20f);
            // 设置表格宽度为100%
            table2.setWidthPercentage(100.0F);
            table2.setHeaderRows(1);
            table2.getDefaultCell().setHorizontalAlignment(1);
            //需求三问详情信息标题栏
            cell = new PdfPCell(new Paragraph("需求三问", content));
            cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
            cell.setHorizontalAlignment(Element.ALIGN_CENTER);
            cell.setFixedHeight(20);
            table2.addCell(cell);
            cell = new PdfPCell(new Paragraph("选择项", content));
            cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
            cell.setHorizontalAlignment(Element.ALIGN_CENTER);
            table2.addCell(cell);
            cell = new PdfPCell(new Paragraph("内容项", content));
            cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
            cell.setHorizontalAlignment(Element.ALIGN_CENTER);
            table2.addCell(cell);

            //下面的代码就是样图中底下的表格,需要动态构建数据
            //人员列表数据-第五行
            List<List<String>> doList = new ArrayList<>();
            if (CollectionUtils.isNotEmpty(details)) {
                //组装数据
                for (ContractDemandThreeQuestionDO detail : details) {
                    String natureType = "项目" + getDitcValue("QuestionType", detail.getDemandNatureType());
                    String deviceGoodsType = getDitcValue(baseDO.getModelType(), detail.getItemValue());
                    doList.add(Arrays.asList(natureType, deviceGoodsType, detail.getItemContent()));
                }
                makeData(doList, content, table2);
            //为两个表格添加标题
            document.add(new Paragraph("\n"));
            document.add(new Paragraph("▋ 基本信息", content));
            document.add(new Paragraph("\n"));
            document.add(table);
            document.add(new Paragraph("\n"));
            document.add(new Paragraph("▋ 采购需求三问内容", content));
            document.add(new Paragraph("\n"));
            //将table2中数据相同的列合并单元格(横向合并)
            document.add(table2);

            // 加水印(水印组成:下载人姓名-手机号-部门)
            PdfContentByte waterMar = pdfWriter.getDirectContentUnder();
            AtomicReference<String> text = new AtomicReference<>("");
            Long loginUserId = getLoginUserId();
            AdminUserRedisVO adminUserRedisVO = adminUserRedisDAO.get(loginUserId);
            Optional.ofNullable(adminUserRedisVO).ifPresent(vo -> {
                DeptDO dept = deptService.getDept(adminUserRedisVO.getDeptId());
                String deptName = Objects.nonNull(dept) ? dept.getName() : "";
                text.set(vo.getNickname() + "-" + vo.getMobile() + "-" + deptName);
            });
            addTextFullWaterMark(waterMar, text.get(), bfChinese);
            //关闭文档
            document.close();
        } catch (DocumentException | IOException e) {
            e.printStackTrace();
            log.error("导出pdf失败:{}", e);
        }
    }

补充说明:List<List<String>> doList = new ArrayList<>();

这个是我构建底下表格数据的格式,举个例子,类似于:

复制代码
List<List<String>> headList = new ArrayList<>();
headList.add(Arrays.asList(new String[]{"1", "2", "3"}));
headList.add(Arrays.asList(new String[]{"2", "6", "10"}));
headList.add(Arrays.asList(new String[]{"1", "12", "13"}));
headList.add(Arrays.asList(new String[]{"2", "9", "11"}));
headList.add(Arrays.asList(new String[]{"1", "8", "12"}));

根据自己的实际业务构建,我底下的表格是一个n*3的表格,只有三列,所以数据结构就相当于上面的两层List结构,最外层的集合代表有多少行,嵌套的结合相当于多少列(这里就是三列),我将对象集合查询出来后使用循环,将我所用到的数据组装成这个示例的样子。

重点来了,以下是合并单元格的方法:

复制代码
makeData(doList, content, table2);
java 复制代码
/**
     * 合并单元格方法
     *
     * @param list        表头数据  list中相连下标位置内容如果相同自动合并 上下位置内容相同自动合并
     * @param fontChinese 支持转换中文的Font对象
     * @return
     */
    private void makeData(List<List<String>> list, Font fontChinese, PdfPTable table2) {
        List<List<PdfPCell>> aa = new ArrayList<>();
        int length = list.get(0).size();
        //循环在外层,这里代表的是有几列(其实是固定的,因为上面构建的时候,列数就是三列)
        //下面的循环不用管,是将你组装的数据设置到单元格里
        for (int i = 0; i < list.size(); i++) {
            List<String> strings = list.get(i);
            int colNum = 1;
            List<PdfPCell> bb = new ArrayList<>();
            for (int j = 0; j < strings.size(); j++) {
                if (j + 1 < strings.size()) {
                    if (strings.get(j).equals(strings.get(j + 1))) {
                        colNum++;
                    } else {
                        PdfPCell cell = new PdfPCell();
                        //合并列
                        cell.setColspan(colNum);
                        Paragraph elements = new Paragraph(strings.get(j), fontChinese);
                        elements.setAlignment(1);
                        cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
                        cell.setHorizontalAlignment(Element.ALIGN_CENTER);
                        cell.setFixedHeight(30);
                        cell.addElement(elements);
                        bb.add(cell);

                        for (int a = 1; a < colNum; a++) {
                            bb.add(null);
                        }
                        colNum = 1;
                    }
                } else {
                    PdfPCell cell = new PdfPCell();
                    cell.setColspan(colNum);
                    Paragraph elements = new Paragraph(strings.get(j), fontChinese);
                    cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
                    cell.setHorizontalAlignment(Element.ALIGN_CENTER);
                    elements.setAlignment(1);
                    cell.setFixedHeight(30);
                    cell.addElement(elements);
                    bb.add(cell);
                    for (int a = 1; a < colNum; a++) {
                        bb.add(null);
                    }
                    colNum = 1;
                }
            }
            aa.add(bb);
        }
        //合并算法

        for (int i = 0; i < length; i++) {

            int rowSpan = 1;

            for (int j = 0; j < aa.size(); j++) {

                if (aa.get(j).get(i) == null) {
                    continue;
                }

                if (j + 1 < aa.size()) {

                    if (aa.get(j + 1).get(i) != null
                            && aa.get(j).get(i).getCompositeElements().get(0).toString()
                            .equals(aa.get(j + 1).get(i).getCompositeElements().get(0).toString())
                    ) {
                        rowSpan++;
                    } else {
                        aa.get(j - rowSpan + 1).get(i).setRowspan(rowSpan);

                        for (int a = 1; a < rowSpan; a++) {
                            aa.get(j - rowSpan + 1 + a).set(i, null);
                        }
                        rowSpan = 1;
                    }
                } else {
                    aa.get(j - rowSpan + 1).get(i).setRowspan(rowSpan);

                    for (int a = 1; a < rowSpan; a++) {
                        aa.get(j - rowSpan + 1 + a).set(i, null);
                    }
                    rowSpan = 1;
                }
            }
            break;
        }

        for (List<PdfPCell> a : aa) {
            for (PdfPCell pCell : a) {
                if (pCell != null) {
                    table2.addCell(pCell);
                }
            }
        }

    }

这里将合并算法进行一个说明:外层循环其实还就是表示列,我这里是三列,他会依次循环三列,并将横向与纵向的表格中有相同数据的都进行合并,也就是值相同的行进行合并,值相同的列也进行合并。但我的业务要求就是只合并第一列,所以我这里也没有改算法,偷了个懒,直接在第一次循环后加了break,直接终止后面的循环。这个自己看自己的业务要求。代码里的getDictValue方法是我自己获取字典值的方法,可自行定义

加水印:

java 复制代码
public static void addTextFullWaterMark(PdfContentByte waterMar, String text, BaseFont bf) {
        waterMar.beginText();

        PdfGState gs = new PdfGState();
        // 设置填充字体不透明度为0.2f
        gs.setFillOpacity(0.2f);
        waterMar.setFontAndSize(bf, 20);
        // 设置透明度
        waterMar.setGState(gs);
        // 设置水印对齐方式 水印内容 X坐标 Y坐标 旋转角度
        for (int x = 0; x <= 900; x += 200) {
            for (int y = -50; y <= 800; y += 200) {
                waterMar.showTextAligned(Element.ALIGN_RIGHT, text, x, y, 35);
            }
        }
        // 设置水印颜色
        waterMar.setColorFill(BaseColor.GRAY);
        //结束设置
        waterMar.endText();
        waterMar.stroke();
    }

这个操作PDF的类很强大,基本上你想怎么导出,都可以进行调整

相关推荐
飞翔的佩奇几秒前
Java项目:基于SSM框架实现的劳务外包管理系统【ssm+B/S架构+源码+数据库+毕业论文】
java·mysql·spring·毕业设计·ssm·毕业论文·劳务外包
luckywuxn16 分钟前
EurekaServer 工作原理
java·eureka
壹米饭18 分钟前
Java程序员学Python学习笔记一:学习python的动机与思考
java·后端·python
java金融21 分钟前
Java 锁升级机制详解
java
Young556624 分钟前
还不了解工作流吗(基础篇)?
java·workflow·工作流引擎
让我上个超影吧25 分钟前
黑马点评【缓存】
java·redis·缓存
ajassi200034 分钟前
开源 java android app 开发(十一)调试、发布
android·java·linux·开源
YuTaoShao1 小时前
Java八股文——MySQL「存储引擎篇」
java·开发语言·mysql
crud1 小时前
Java 中的 synchronized 与 Lock:深度对比、使用场景及高级用法
java
王德博客1 小时前
【Java课堂笔记】Java 入门基础语法与面向对象三大特性详解
java·开发语言