导出的excel添加水印

给导出的excel添加水印

覆盖图片式添加水印

此方式是根据覆盖图片形式进行的水印的添加,缺点是在可以修改情况下,覆盖的图片会影响修改操作。

1.创建水印图片

 /**
     * 创建水印图片
     * 注意:生成的图片会默认保存到classes目录下,可以根据自己的业务进行更改
     * @param content
     * @param fileName
     * @return
     * @throws IOException
     */
    private static String createWaterMark(String content, String fileName) {
        //生成水印图片的宽度
        Integer width = 700;
        //水印图片的高度
        Integer height = 300;
        // 获取bufferedImage对象
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        String fontType = "宋体";
        Integer fontStyle = java.awt.Font.PLAIN;
        Integer fontSize = 50;
        java.awt.Font font = new java.awt.Font(fontType, fontStyle, fontSize);
        // 获取Graphics2d对象
        Graphics2D g2d = image.createGraphics();
        image = g2d.getDeviceConfiguration().createCompatibleImage(width, height, Transparency.TRANSLUCENT);
        g2d.dispose();
        g2d = image.createGraphics();
        g2d.setColor(new java.awt.Color(0, 0, 0, 80)); //设置字体颜色和透明度
        // 设置字体
        g2d.setStroke(new BasicStroke(1));
        // 设置字体类型  加粗 大小
        g2d.setFont(font);
        //设置倾斜度
        g2d.rotate(Math.toRadians(-10), (double) image.getWidth() / 2, (double) image.getHeight() / 2);
        FontRenderContext context = g2d.getFontRenderContext();
        Rectangle2D bounds = font.getStringBounds(content, context);
        double x = (width - bounds.getWidth()) / 2;
        double y = (height - bounds.getHeight()) / 2;
        double ascent = -bounds.getY();
        double baseY = y + ascent;
        // 写入水印文字原定高度过小,所以累计写水印,增加高度
        g2d.drawString(content, (int) x, (int) baseY);
        // 设置透明度
        g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
        // 释放对象
        g2d.dispose();
        //自定义存放位置
        String targetImagePath ="F:\\data\\erp\\"+fileName;
        //默认存放位置
        // String s = Thread.currentThread().getContextClassLoader().getResource("").getPath() + fileName;

        try {
            ImageIO.write(image, "png", new File(targetImagePath));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return targetImagePath;
    }

2. 给excel加水印

  /**
     * 为Excel打上水印工具函数 请自行确保参数值,以保证水印图片之间不会覆盖。在计算水印的位置的时候,并没有考虑到单元格合并的情况,请注意
     * @param wb Excel Workbook
     * @param sheet 需要打水印的Excel
     * @param waterRemarkPath 水印地址,classPath,目前只支持png格式的图片,
     *                        因为非png格式的图片打到Excel上后可能会有图片变红的问题,且不容易做出透明效果。
     *                        同时请注意传入的地址格式,应该为类似:"\\excelTemplate\\test.png"
     * @param startXCol 水印起始列
     * @param startYRow 水印起始行
     * @param betweenXCol 水印横向之间间隔多少列
     * @param betweenYRow 水印纵向之间间隔多少行
     * @param XCount 横向共有水印多少个
     * @param YCount 纵向共有水印多少个
     * @param waterRemarkWidth 水印图片宽度为多少列
     * @param waterRemarkHeight 水印图片高度为多少行
     * @throws IOException
     */
    private static void putWaterRemarkToExcel(Workbook wb, Sheet sheet, String waterRemarkPath, int startXCol,
                                              int startYRow, int betweenXCol, int betweenYRow, int XCount, int YCount, int waterRemarkWidth,
                                              int waterRemarkHeight) throws IOException {

        // 校验传入的水印图片格式
        if (!waterRemarkPath.endsWith("png") && !waterRemarkPath.endsWith("PNG")) {
            throw new RuntimeException("向Excel上面打印水印,目前支持png格式的图片。");
        }
        // 加载图片
        ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();
        //图片的存放位置
        String path = "F:\\data\\erp\\" + waterRemarkPath;

        InputStream imageIn = new FileInputStream(path);
        //默认位置的图片
        // InputStream imageIn = Thread.currentThread().getContextClassLoader().getResourceAsStream(waterRemarkPath);
        if (null == imageIn || imageIn.available() < 1) {
            throw new RuntimeException("向Excel上面打印水印,读取水印图片失败(1)。");
        }
        BufferedImage bufferImg = ImageIO.read(imageIn);
        if (null == bufferImg) {
            throw new RuntimeException("向Excel上面打印水印,读取水印图片失败(2)。");
        }
        ImageIO.write(bufferImg, "png", byteArrayOut);

        // 开始打水印
        Drawing drawing = sheet.createDrawingPatriarch();

        // 按照共需打印多少行水印进行循环
        for (int yCount = 0; yCount < YCount; yCount++) {
            // 按照每行需要打印多少个水印进行循环
            for (int xCount = 0; xCount < XCount; xCount++) {
                // 创建水印图片位置
                int xIndexInteger = startXCol + (xCount * waterRemarkWidth) + (xCount * betweenXCol);
                int yIndexInteger = startYRow + (yCount * waterRemarkHeight) + (yCount * betweenYRow);

                /** 参数定义:第一个参数是(x轴的开始节点);第二个参数是(是y轴的开始节点);第三个参数是(是x轴的结束节点);
                 * 第四个参数是(是y轴的结束节点);第五个参数是(是从Excel的第几列开始插入图片,从0开始计数);
                 * 第六个参数是(是从excel的第几行开始插入图片,从0开始计数);第七个参数是(图片宽度,共多少列);
                 * 第8个参数是(图片高度,共多少行);*/

                ClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, xIndexInteger,
                        yIndexInteger, xIndexInteger + waterRemarkWidth, yIndexInteger + waterRemarkHeight);

                Picture pic = drawing.createPicture(anchor, wb.addPicture(byteArrayOut.toByteArray(), Workbook.PICTURE_TYPE_PNG));
                pic.resize();
            }
        }
    }

 3.使用

  /**
     * 给Excel添加水印
     * @param wb 工作簿
     * @param sheet  sheet页
     * @param content 水印内容
     */
    private static void painWaterMark(Workbook wb,Sheet sheet,String content) {
        String imgFileName = "waterMark_photo_"+content+".png";
        //创建水印图片  (默认保存到classes目录下)
        createWaterMark(content,imgFileName);

        //将图片写入到excel中
        try {

            //也可以动态获取sheet中的行和列,根据行和列适当的放置水印图片
            //获取excel实际所占行
            //int row = sheet.getFirstRowNum() + sheet.getLastRowNum();
            //获取excel实际所占列
            //int cell = sheet.getRow(sheet.getFirstRowNum()).getLastCellNum() + 1;

            putWaterRemarkToExcel(wb, sheet, imgFileName, 0, 0, 5, 5,2, 10, 2, 2);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

此时的导出的excel可能会支持修改,会造成水印图片支持拖动,所以可以对文件加密只读

 //excel加密只读
sheet.protectSheet(UUID.randomUUID().toString());

 加密后的文件输入密码后支持修改

 实现样式:

 此部分代码只需要添加到excel写出之前(下图)就可以实现excel的添加水印

注意:1.此种方法添加的水印,如果在可修改状态会造成图片可拖动问题

           2.图片大小是写死的没有根据水印文字进行自动变化大小

           3.列是写死的,如果不知修改,可能会造成部分数据不能被查看

优化:

动态展示水印(水印看起来规整):

    /**
     * @param startXCol 水印起始列
     * @param startYRow 水印起始行
     * @param betweenXCol 水印横向之间间隔多少列
     * @param betweenYRow 水印纵向之间间隔多少行
     * @param XCount 横向共有水印多少个
     * @param YCount 纵向共有水印多少个
     * @param waterRemarkWidth 水印图片宽度为多少列
     * @param waterRemarkHeight 水印图片高度为多少行
     * @throws IOException
     */

       
        // 也可以动态获取sheet中的行和列,根据行和列适当的放置水印图片
        // 获取excel实际所占行
        int row = sheet.getFirstRowNum() + sheet.getLastRowNum();
        // 获取excel实际所占列
        int cell = sheet.getRow(sheet.getFirstRowNum()).getLastCellNum() + 1;

        // 纵向个数,向上取整
        int YCount = row / (waterRemarkHeight + betweenYRow) + ((row % (waterRemarkHeight + betweenYRow) != 0) ? 1 : 0);
        // 横向个数,向上取整
        int XCount = cell / (waterRemarkWidth + betweenXCol) + ((cell % (waterRemarkWidth + betweenXCol) != 0) ? 1 : 0);

 让列宽随着导出的列长自动适应:

autoSizeColumn(colNum)方法可能会报错,就需要跟踪所有的列,但是跟踪所有的列会使性能变差。
trackAllColumnsForAutoSizing()方法是SXSSFSheet的方法,如果使用的是Sheet类,就需要强转一下

 private void autoColumn() {
        //让列宽随着导出的列长自动适应
        int maxColumnWidth = 30 * 256;
        int columnNum = sheet.getRow(0).getPhysicalNumberOfCells();
        SXSSFSheet sheetAt;
        for (int colNum = 0; colNum < columnNum; colNum++) {
            //自动列宽
            sheetAt = (SXSSFSheet) sheet;
            // 跟踪所有用于自动调整大小的列,性能比较差
            sheetAt.trackAllColumnsForAutoSizing();
            sheet.autoSizeColumn(colNum);
            //like12 add,20220122,设置最大宽度限制
            int columnWidth = sheet.getColumnWidth(colNum);
            if (columnWidth > maxColumnWidth) {
                columnWidth = maxColumnWidth;
            }
            //手动调整列宽,解决中文不能自适应问题
            sheet.setColumnWidth(colNum, columnWidth * 12 / 10);
        }
    }

2.添加背景图片形式的水印

此方法是在excel中添加背景图片,所以不会影响操作修改

结合本地的项目,先进行导包

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

 已经存在的包

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

 1.还是先创建背景图片,使用的还是上面的方法

/**
     * 创建水印图片
     * 注意:生成的图片会默认保存到classes目录下,可以根据自己的业务进行更改
     *
     * @param content
     * @param fileName
     * @return
     * @throws IOException
     */
    private static String createWaterMark(String content, String fileName) {
        //生成水印图片的宽度
        Integer width = 700;
        //水印图片的高度
        Integer height = 300;
        // 获取bufferedImage对象
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        String fontType = "宋体";
        Integer fontStyle = java.awt.Font.PLAIN;
        Integer fontSize = 30;
        java.awt.Font font = new java.awt.Font(fontType, fontStyle, fontSize);
        // 获取Graphics2d对象
        Graphics2D g2d = image.createGraphics();
        image = g2d.getDeviceConfiguration().createCompatibleImage(width, height, Transparency.TRANSLUCENT);
        g2d.dispose();
        g2d = image.createGraphics();
        g2d.setColor(new java.awt.Color(0, 0, 0, 95)); //设置字体颜色和透明度
        // 设置字体
        g2d.setStroke(new BasicStroke(1));
        // 设置字体类型  加粗 大小
        g2d.setFont(font);
        //设置倾斜度
        g2d.rotate(Math.toRadians(-10), (double) image.getWidth() / 2, (double) image.getHeight() / 2);
        FontRenderContext context = g2d.getFontRenderContext();
        Rectangle2D bounds = font.getStringBounds(content, context);
        double x = (width - bounds.getWidth()) / 2;
        double y = (height - bounds.getHeight()) / 2;
        double ascent = -bounds.getY();
        double baseY = y + ascent;
        // 写入水印文字原定高度过小,所以累计写水印,增加高度
        g2d.drawString(content, (int) x, (int) baseY);
        // 设置透明度
        g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
        // 释放对象
        g2d.dispose();
        //自定义存放位置
        String targetImagePath = "F:\\data\\erp\\" + fileName;
        //默认存放位置
        // String s = Thread.currentThread().getContextClassLoader().getResource("").getPath() + fileName;

        try {
            ImageIO.write(image, "png", new File(targetImagePath));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return targetImagePath;
    }

 2.添加背景水印

    /**
     * 添加水印背景图片
     *
     * @param workbook 文件
     * @param content   水印
     */
public static void setWaterMarkToExcel2(XSSFWorkbook workbook, String content) throws Exception {
        // 获取背景图片
        String imgFileName = "waterMark_photo" + StringUtils.trim(content) + ".png";
        String path = "F:\\data\\erp\\" + imgFileName;

        InputStream imageIn = new FileInputStream(path);
        //默认位置的图片
        // InputStream imageIn = Thread.currentThread().getContextClassLoader().getResourceAsStream(waterRemarkPath);
        if (imageIn.available() < 1) {
            throw new RuntimeException("向Excel上面打印水印,读取水印图片失败(1)。");
        }
        BufferedImage bfi = ImageIO.read(imageIn);

        // 添加背景水印
        ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();
        ImageIO.write(bfi, "png", byteArrayOut);
        int pictureIdx = workbook.addPicture(byteArrayOut.toByteArray(), Workbook.PICTURE_TYPE_PNG);
        //add relation from sheet to the picture data
        POIXMLDocumentPart poixmlDocumentPart = workbook.getAllPictures().get(pictureIdx);
        for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
            XSSFSheet xssfSheet = workbook.getSheetAt(i);
            PackagePartName ppn = poixmlDocumentPart.getPackagePart().getPartName();
            String relType = XSSFRelation.IMAGES.getRelation();
            PackageRelationship pr = xssfSheet.getPackagePart().addRelationship(ppn, TargetMode.INTERNAL, relType, null);
            xssfSheet.getCTWorksheet().addNewPicture().setId(pr.getId());
        }
    }

 3.整合的方法

    public void waterRemarkToExcel(XSSFWorkbook wb, String content) {
        String imgFileName = "waterMark_photo" + StringUtils.trim(content) + ".png";
        //创建水印图片
        createWaterMark(content, imgFileName);

        //添加背景水印
        try {
            setWaterMarkToExcel2(wb, content);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

4.写在工具类中 原来的工具类是使用的 Workbook类,所以先使用流转成XSSFWorkbook,然后写出。

工具类中本次使用的代码

       if (ArrayUtils.isNotEmpty(content) && content[0] != null) {

                //让列宽随着导出的列长自动适应
                // this.autoColumn();
                // 添加背景图片水印
                wb.write(bos);
                byte[] barray = bos.toByteArray();
                is = new ByteArrayInputStream(barray);
                XSSFWorkbook ww = new XSSFWorkbook(is);
                this.waterRemarkToExcel(ww, sheet, content[0]);

                out = new FileOutputStream(getAbsoluteFile(fullfolderName));
                ww.write(out);
                return Result.succeed();
            }

方法代码

    /**
     * 对list数据源将其里面的数据导入到excel表单
     *
     * @param content 添加水印和加密不支持修改 (有值 是,并且是水印内容  没有值不添加,不加密)
     * @return 结果
     */
    public Result exportExcel(String... content) {
        OutputStream out = null;
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        InputStream is = null;
        try {
            // 取出一共有多少个sheet.
            double sheetNo = Math.ceil(list.size() / sheetSize);
            for (int index = 0; index <= sheetNo; index++) {
                createSheet(sheetNo, index);

                // 产生一行
                Row row = sheet.createRow(0);
                int column = 0;
                // 写入各个字段的列头名称
                for (Object[] os : fields) {
                    Excel excel = (Excel) os[1];
                    this.createCell(excel, row, column++);
                }
                if (Type.EXPORT.equals(type)) {
                    fillExcelData(index, row);
                    addStatisticsRow();
                }
            }

            // 加水印的excel不支持修改,所以要保证列宽自动调整展示全部内容
            if (ArrayUtils.isNotEmpty(content) && content[0] != null) {

                //让列宽随着导出的列长自动适应
                // this.autoColumn();
                // 添加背景图片水印
                wb.write(bos);
                byte[] barray = bos.toByteArray();
                is = new ByteArrayInputStream(barray);
                XSSFWorkbook ww = new XSSFWorkbook(is);
                this.waterRemarkToExcel(ww, sheet, content[0]);

                out = new FileOutputStream(getAbsoluteFile(fullfolderName));
                ww.write(out);
                return Result.succeed();
            }

            out = new FileOutputStream(getAbsoluteFile(fullfolderName));
            wb.write(out);
            return Result.succeed();
        } catch (Exception e) {
            log.error("导出Excel异常{}", e.getMessage());
            throw new CustomException("导出Excel失败,请联系网站管理员!");
        } finally {
            if (wb != null) {
                try {
                    wb.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
            try {
                bos.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }

 展示

继续优化,图片不做单独处理,直接传输

涉及方法:

    /**
     * 创建水印图片
     *
     * @param content 水印
     * @return
     * @throws IOException
     */
    private BufferedImage createWaterMark(String content) {
        //生成水印图片的宽度
        Integer width = 700;
        //水印图片的高度
        Integer height = 300;
        // 获取bufferedImage对象
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        String fontType = "宋体";
        Integer fontStyle = java.awt.Font.PLAIN;
        Integer fontSize = 30;
        java.awt.Font font = new java.awt.Font(fontType, fontStyle, fontSize);
        // 获取Graphics2d对象
        Graphics2D g2d = image.createGraphics();
        image = g2d.getDeviceConfiguration().createCompatibleImage(width, height, Transparency.TRANSLUCENT);
        g2d.dispose();
        g2d = image.createGraphics();
        g2d.setColor(new java.awt.Color(0, 0, 0, 95)); //设置字体颜色和透明度
        // 设置字体
        g2d.setStroke(new BasicStroke(1));
        // 设置字体类型  加粗 大小
        g2d.setFont(font);
        //设置倾斜度
        g2d.rotate(Math.toRadians(-10), (double) image.getWidth() / 2, (double) image.getHeight() / 2);
        FontRenderContext context = g2d.getFontRenderContext();
        Rectangle2D bounds = font.getStringBounds(content, context);
        double x = (width - bounds.getWidth()) / 2;
        double y = (height - bounds.getHeight()) / 2;
        double ascent = -bounds.getY();
        double baseY = y + ascent;
        // 写入水印文字原定高度过小,所以累计写水印,增加高度
        g2d.drawString(content, (int) x, (int) baseY);
        // 设置透明度
        g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
        // 释放对象
        g2d.dispose();

        return image;
    }

相关方法:

    /**
     * 创建水印图片,并添加进excel
     *
     * @param content 水印
     * @param wb 文件
     */
    private void waterRemarkToExcel(XSSFWorkbook wb, String content) throws Exception {
        //创建水印图片
        BufferedImage waterMark = createWaterMark(content);

        //添加背景水印
        this.setWaterMarkToExcel(wb, waterMark);

    }

 生成的图片直接用于excel的背景图,不用在写入写出图片

遇到的问题:

问题描述:本地测试正常,部署到开发环境后,生成的背景图上的中文乱码

问题原因:经过查询,是因为linux上的中文是需要下载安装的,现在linux上并没有支持中文

指令查询:fc-list

支持的中文字体:fc-list :lang=zh

支持的英文字体:fc-list :lang=en
 

问题:excel有一键删除水印的功能

 一键删除

所以想着锁定水印不支持修改。

经过资料查询没有找到先关的介绍,而且excel就不支持对水印的锁定。退而求其次,对部分不包含数据单元格加密(数据支持修改,支持复制)。在excel上进行操作,发现是可以达到效果的,现在使用java实现,代码如下:

 private void localRow(int rowCount) {
        CellStyle lockStyle = wb.createCellStyle();
        lockStyle.setLocked(true);

        CellStyle unlockStyle = wb.createCellStyle();
        unlockStyle.setLocked(false);//设置未锁定

        for (int i = 0; i < rowCount + 8; i++) {
            Row row = sheet.createRow(i);
            for (int j = 0; j < 4; j++) {
                Cell cell = row.createCell(j);
                cell.setCellStyle(unlockStyle);//默认是锁定状态;将所有单元格设置为:未锁定;然后再对需要上锁的单元格单独锁定
                //这里可以根据需要进行判断;我这就将第4列上锁了
                if (i > rowCount) {
                    cell.setCellStyle(lockStyle);//将需要上锁的单元格进行锁定
                    cell.setCellValue("上锁了");
                }
            }
        }
        //sheet添加保护,这个一定要否则光锁定还是可以编辑的
        sheet.protectSheet("123456");
    }

 rowCount是填充数据行的最大值

int rowCount = sheet.getLastRowNum();

简单实现如图:

 上锁的区域不支持修改

融合进项目中代码: 对部分cell进行设置不加锁

                CellStyle unlockStyle = wb.createCellStyle();
                unlockStyle.setLocked(false);
                cell.setCellStyle(unlockStyle);
                // 加密
                sheet.protectSheet("123456");

 注意:一个CellStyle可以进行多种set,一个cell只能进行一次setCellStyle。

效果:部分(没有数据的表格)加密

 

 

 对背景的操作:

不可删除背景。 

资料:Java实现导出Excel并附带水印

资料:Java语言为excel添加水印

资料:JAVA操作Excel表格部分不可编辑部分可编辑

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值