Java使用POI爲Excel打水印,調整列寬並設置Excel只讀(用戶不可編輯)

本文介紹在Java語言環境下,使用POI爲Excel打水印的解決方案,具體的代碼編寫以及相關的注意事項。java

需求描述:apache

要求經過系統下載的Excel都帶上公司的水印,列寬調整爲合適的寬度,而且設置爲不可編輯,即只讀。json

即:api

1:加水印;安全

2:調整列寬將單元格內容顯示全;dom

3:設置只讀;函數

 

解決方案思路介紹:工具

三點需求比較來講,以第一點比較複雜,同時網上關於POI爲Excel加水印的資料很是少,而這些資料又多數是相互之間Copy得來,乾貨較少。測試

 

一:使用模版Excel的形式設置水印:字體

目前網上關於POI爲Excel打水印,主要是經過模版的形式來實現,即先準備一份打了水印的模版Excel,說白了就是在這個模版Excel中的一個Sheet中添加一些藝術字,調整下透明度還有字體,角度等,模仿水印,而後加載該模版,再將內容輸出到該模版中,以達到爲Excel添加水印的目的,詳細可參考下面這份博客(不追溯該博客是不是原始版本):http://jsonchar.blog.163.com/blog/static/17601614120106135519213/

 

但這種方式有以下問題:

1:經過手動添加水印,水印數量固定:經過人工在一個Sheet中添加水印,個數必然固定,當實際內容行數不定,列數不定,會致使水印不能覆蓋內容;

2:當沒法肯定下載的Excel會有多少個Sheet的時候,沒法保證輸出的多個Sheet都有水印:產生這種狀況是由於,這種實現方式的根本原理是讀取已經存在的模版Excel內的Sheet,並將內容輸出到該Sheet中,也就是說若是模版內的Sheet內有水印則輸出內容有水印,若是該Sheet沒有水印則輸出內容沒有水印。畢竟經過手工添加水印,準備模版的方式,你不能準備無限多個模版Sheet,準備一個兩個也就到頭了。

3:你沒法經過程序拷貝模版Sheet:你可能會問了,那我準備一個模版Sheet,在其中儘可能多的打上水印,麻煩一點橫向縱向多大點,而後再經過程序拷貝該模版Sheet,想其中輸出內容的形式不行嗎?很遺憾,不行,翻查POI的API你就會發現建立Sheet的方式就那麼兩個,即經過workbook進行建立,雖然sheet(不管類型)的構造函數是public的,可是你沒法將你構造好的sheet添加到workbook中。固然,workbook也提供了cloneSheet的api,可是若是你的模版水印是經過藝術字或者圖片的形式打上去的,那麼也將沒法拷貝。

 

因此這種方案,適合於輸出的內容Sheet頁個數固定,行數可控的形式,由於只有這些固定下來了,在提供模版Excel的時候才能作出合適的模版,在固定的sheet頁,大體的行上打上水印。

 

二:使用程序將水印圖片動態打到Sheet上:

由於上面方案的問題,我選擇經過程序將提早準備好的水印圖片打印到Sheet上的方案,這種方案的主要步驟爲:

1:將須要輸出的內容正常輸出到Excel中,輸出的Excel假定將存在多個Sheet,每一個Sheet中內容的行數也不必定;

2:獲取workbook對象,提取其中的多個sheet;

3:調用工具類,傳入workbook和每個sheet,根據提供好的水印圖片將睡衣圖片打到每個sheet上,水印圖片在sheet上的位置,個數等可配置;

 

要求:

1:水印圖片是png格式,無背景,且水印主體顏色要儘量淺,且細,避免遮擋excel上的信息;

2:使用的POI的版本是3.9,pom文件配置以下:

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

 

步驟1和步驟2不提供代碼,網上有不少,直接貼步驟三的代碼,以下:

 1 import java.awt.image.BufferedImage;
 2 import java.io.ByteArrayOutputStream;
 3 import java.io.IOException;
 4 import java.io.InputStream;
 5 
 6 import javax.imageio.ImageIO;
 7 
 8 import org.apache.poi.ss.usermodel.ClientAnchor;
 9 import org.apache.poi.ss.usermodel.Drawing;
10 import org.apache.poi.ss.usermodel.Picture;
11 import org.apache.poi.ss.usermodel.Sheet;
12 import org.apache.poi.ss.usermodel.Workbook;
13 
14 /**
15  * Excel水印工具類
16  * @author gang.wang
17  * 2017年4月23日
18  */
19 public class ExcelWaterRemarkUtils {
20     
21     /**
22      * 爲Excel打上水印工具函數
23      * 請自行確保參數值,以保證水印圖片之間不會覆蓋。
24      * 在計算水印的位置的時候,並無考慮到單元格合併的狀況,請注意
25      * @param wb       Excel Workbook
26      * @param sheet    須要打水印的Excel
27      * @param waterRemarkPath  水印地址,classPath,目前只支持png格式的圖片,
28      *                         由於非png格式的圖片打到Excel上後可能會有圖片變紅的問題,且不容易作出透明效果。
29      *                         同時請注意傳入的地址格式,應該爲相似:"\\excelTemplate\\test.png"
30      * @param startXCol  水印起始列
31      * @param startYRow  水印起始行
32      * @param betweenXCol 水印橫向之間間隔多少列
33      * @param betweenYRow 水印縱向之間間隔多少行
34      * @param XCount 橫向共有水印多少個
35      * @param YCount 縱向共有水印多少個
36      * @param waterRemarkWidth 水印圖片寬度爲多少列
37      * @param waterRemarkHeight 水印圖片高度爲多少行
38      * @throws IOException 
39      */
40     public static void putWaterRemarkToExcel(Workbook wb, Sheet sheet, String waterRemarkPath, int startXCol, int startYRow,  
41             int betweenXCol, int betweenYRow, int XCount, int YCount,
42             int waterRemarkWidth, int waterRemarkHeight) throws IOException{
43         
44         //校驗傳入的水印圖片格式
45         if(!waterRemarkPath.endsWith("png") && !waterRemarkPath.endsWith("PNG")){
46             throw new RuntimeException("向Excel上面打印水印,目前支持png格式的圖片。");
47         }
48         
49         //加載圖片
50         ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();
51         InputStream imageIn = Thread.currentThread().getContextClassLoader().getResourceAsStream(waterRemarkPath);
52         if(null == imageIn || imageIn.available() < 1){
53             throw new RuntimeException("向Excel上面打印水印,讀取水印圖片失敗(1)。");
54         }
55         BufferedImage bufferImg = ImageIO.read(imageIn);
56         if(null == bufferImg) {
57             throw new RuntimeException("向Excel上面打印水印,讀取水印圖片失敗(2)。");
58         }
59         ImageIO.write(bufferImg,"png",byteArrayOut);
60         
61         //開始打水印
62         Drawing drawing = sheet.createDrawingPatriarch();
63         
64         //按照共需打印多少行水印進行循環
65         for(int yCount=0; yCount<YCount; yCount++){
66             //按照每行須要打印多少個水印進行循環
67             for(int xCount=0; xCount<XCount; xCount++){
68                 //建立水印圖片位置
69                 int xIndexInteger = startXCol + (xCount * waterRemarkWidth) + (xCount * betweenXCol);
70                 int yIndexInteger = startYRow + (yCount * waterRemarkHeight) + (yCount * betweenYRow);
71                 
72                 /*
73                  * 參數定義:
74                  * 第一個參數是(x軸的開始節點);
75                  * 第二個參數是(是y軸的開始節點);
76                  * 第三個參數是(是x軸的結束節點);
77                  * 第四個參數是(是y軸的結束節點);
78                  * 第五個參數是(是從Excel的第幾列開始插入圖片,從0開始計數);
79                  * 第六個參數是(是從excel的第幾行開始插入圖片,從0開始計數);
80                  * 第七個參數是(圖片寬度,共多少列);
81                  * 第8個參數是(圖片高度,共多少行);
82                  */
83                 ClientAnchor anchor = drawing.createAnchor(0, 0, Short.MAX_VALUE, Integer.MAX_VALUE, xIndexInteger, yIndexInteger, waterRemarkWidth, waterRemarkHeight);
84                 Picture pic = drawing.createPicture(anchor, wb.addPicture(byteArrayOut.toByteArray(), Workbook.PICTURE_TYPE_PNG));
85                 pic.resize();
86             }
87         }
88     }
89 }

 

函數參數的定義介紹的很詳細了,再也不過多介紹,說說須要注意的地方:

1:必定要先向Excel中寫內容,而後再打日誌,要否則圖片有可能會拉伸致使失真;

2:最好使用Png格式的圖片,避免形成打上的圖片背景變紅的問題,網上有這種問題的解決方案,這裏再也不贅述;

3:該函數默認是將圖片放在了項目的classpath目錄下;

 

至此,使用poi向Excel上面打水印的功能就完成了!!!可能會有寫遮擋,但也實在沒法避免了,目前尚未發現相似於設置圖片透明度,文字環繞效果,圖片置底的api~~

 

 

接下來再說兩個小點:

1:怎樣實現調整列寬:

Sheet提供了默認的自動設置列寬的api,即根據cell中內容的寬度,自動調整列寬(sheet.autoSizeColumnt),可是通過測試,在SXSSFWorkbook和SXSSFSheet中,會遇到個別列特別窄的問題,就是計算的行寬不太準,並且遇到了公式,數字也有可能計算的不太準,所以我是本身計算的列寬,並設置的。

 

首先在程序循環向Excel中寫內容的時候,自行設置一個Map存儲每一列的最大列寬,具體的函數很簡單,就是一個比較大小的函數,舉例以下:

 1     private void getMaxColLength(Map<Integer,Integer> colMaxLength, Integer colIndex, Integer length){
 2         Integer oriLength = 0;
 3         if(colMaxLength.containsKey(colIndex)){
 4             oriLength = colMaxLength.get(colIndex);
 5         }
 6         
 7         if(length > oriLength){
 8             colMaxLength.put(colIndex, length);
 9         } else {
10             colMaxLength.put(colIndex, oriLength);
11         }
12     }

獲得的每一列的最大寬度(調用字符串的length()函數),須要和Excel中的寬度進行換算,換算的公式是寬度*256,即程序求得的每一列的寬度乘以256,在實際使用中,大多數狀況須要多乘以一些,由於實測中,若是隻乘以256,會使得列正正好好的匹配內容寬度,遇到漢字還有可能遮擋一部分,所以最好將這個基數擴大些:

sheet.setColumnWidth(colIndex, length * 480);

 

2:如何設置Excel只讀,或者說是不可編輯:

當Excel中的數據敏感,但願客戶不能修改內容時,除了使用水印的形式,還須要配合不可修改功能,個人實現是爲Excel設置一個密碼,保證該Excel不可修改,試圖修改將會被要求輸入密碼,只要密碼夠複雜應該沒有破解的可能,好比你設置一個隨機的UUID,而且不記錄該UUID,這樣就沒有人知道該密碼了,保證安全性。在設置了密碼以後用戶依然能夠經過拷貝的方法,將Excel中的內容拷貝到其餘sheet或者Excel中去,這點須要注意,具體的代碼很簡單,以下:

sheet.protectSheet(UUID.randomUUID().toString());

 

到這,本文要介紹的主體內容就介紹完了,實現了一個經過POI,向Excel中打水印圖片的主體功能。但願對你們有所幫助。

相關文章
相關標籤/搜索