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


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

需求描述:

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

即:

1:加水印;

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

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中打水印圖片的主體功能。希望對大家有所幫助。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM