本文介紹在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中打水印圖片的主體功能。但願對你們有所幫助。