利用POI生成EXCEL文件的方法實(shí)例
一、背景
Apache POI 是創(chuàng)建和維護(hù)操作各種符合Office Open XML(OOXML)標(biāo)準(zhǔn)和微軟的OLE 2復(fù)合文檔格式(OLE2)的Java API。用它可以使用Java讀取和創(chuàng)建,修改MS Excel文件.而且,還可以使用Java讀取和創(chuàng)建MS Word和MSPowerPoint文件。Apache POI 提供Java操作Excel解決方案(適用于Excel97-2008)。
根據(jù)指定格式的JSON文件生成對(duì)應(yīng)的excel文件,需求如下
- 支持多sheet
- 支持單元格合并
- 支持插入圖片
- 支持單元格樣式可定制
- 需要 標(biāo)題(title),表頭(head),數(shù)據(jù)(data) ,表尾(foot) 明確區(qū)分
二、效果預(yù)覽

三、數(shù)據(jù)格式
由于是生成Excel文件,這里值考慮生成xlsx格式的Excel文件,數(shù)據(jù)多表頭默認(rèn)考慮使用 | 表示,不在使用colspan rowspan作為。如需要表示兩列兩行,第一列合并表頭格式為: A|B,A|C生成的表格為
| A | |
| B | C |
前端通過(guò)post的方式將需要生成的數(shù)據(jù)構(gòu)造成符合要求的JSON文件提交跟后臺(tái)。根據(jù)以上需求定義JSON格式如下
{
"saveName": "生成Excel的文件名.xlsx",
"userStyles": [{
"id": "1", //不能出現(xiàn)重復(fù),在需要設(shè)置單元樣式的地方,可以直接將style賦值為此值
"style": {
"font": { //設(shè)置字體基本格式
"blod": true,//是否加粗
"italic": true, //是否傾斜
"color": "#FF0000",//字體顏色
"name": "微軟雅黑", //字體名稱
"height": 20 //大小
},
"fmtStr": "", //單元格格式,#,##0.00_);#,##0.00;0 千分位
"align": "",//水平對(duì)齊方式 left right center
"valign": "",//垂直對(duì)齊方式 top center bottom
"borderColor": "", //設(shè)置邊框顏色 如 #FF0000
"bgColor": "" //設(shè)置單元格填充顏色
}
}],
"sheets": [{
"sheetName": "", //sheet名稱
"title": [], // 對(duì)應(yīng)Sheet標(biāo)題區(qū)域數(shù)據(jù)
"titleMerge": [], //對(duì)應(yīng)Sheet標(biāo)題區(qū)域合并信息
"head": [{}], //表頭信息
"data": [], //數(shù)據(jù)信息
"dataMerge": [], //數(shù)據(jù)合并信息
"foot": [], //表尾信息
"footMerge": [], //表尾合并信息
"img": [] //圖片信息,需要將圖片轉(zhuǎn)換base64
}]
}
簡(jiǎn)要說(shuō)明
head 數(shù)組中為JSON對(duì)象格式為
{
"name": "A|B", //表頭名稱,多表頭用|分割
"type": "str", //此列數(shù)據(jù)類型 str num ,在excel中日期也是數(shù)字類型,通過(guò)fmtStr,顯示為日期格式
"field": "F_FIELD1", //備用字段,可不用
"style": { //此列數(shù)據(jù)為列默認(rèn)樣式,可以是Style對(duì)象,也可以是在userStyles中定義的id值
"align": "center"
}
}
在數(shù)組 title data foot 中,列表中的數(shù)據(jù),可以是一個(gè)單獨(dú)的值如 1,”a”,也可以是一個(gè)對(duì)象,當(dāng)為對(duì)象時(shí),格式為
{
"value": "", //單元格具體的值
"type": "", //單元格類型,默認(rèn)str
"style": {} //單元格樣式 可以是Style對(duì)象,也可以是在userStyles中定義的id值,如果沒(méi)設(shè)置,默認(rèn)取head總此列對(duì)應(yīng)的style
}
titleMerge、dataMerge、footMerge數(shù)組值為逗號(hào)分隔的字符串,其含義為 "開(kāi)始行,結(jié)束行,開(kāi)始列,結(jié)束列",索引從0開(kāi)始。如在title中有兩行三列數(shù)據(jù),現(xiàn)在需要合并一行兩列數(shù)據(jù)對(duì)應(yīng)的值為"0,0,0,1"
img數(shù)組中值為對(duì)象,格式
{
"col": 1, //圖片開(kāi)始列
"row": 0, //開(kāi)始行
"colSpan": 1,//列跨度,最小值1
"rowSpan": 2, //行跨度,最小值1
"data": "" //base64圖片數(shù)據(jù)如: "data:image/png;base64,iVBO...ggg=="
}
四、關(guān)鍵實(shí)現(xiàn)
07以后的Excle文件,其實(shí)是一個(gè)壓縮包,里邊是一個(gè)個(gè)的xml文件,其中每一個(gè)sheet是一個(gè)xml文件,樣式是一個(gè)xml文件,圖片是對(duì)應(yīng)的圖片文件,放在media文件夾中,所以,代碼思路依次為
- 構(gòu)建 XSSFWorkbook 對(duì)象
- 生成樣式
- 依次生成,title head data foot 行數(shù)據(jù)
- 依次處理合并信息 titlemerge datamerge footmerge
- 添加圖片信息
- 輸出文件流
功能入口如下
@Override
public void buildOutputStream() throws FileProducerException {
// 處理傳入的JSON數(shù)據(jù)
sheets = this.jsonData.getJSONArray(this.SHEETS);
Iterator<Object> sheetIter = sheets.iterator();
if (sheets.isEmpty()) {
this.responseData.setErrcode(1001);
this.responseData.setSuccess(false);
this.responseData.setErrmsg("無(wú)數(shù)據(jù)可生成");
throw new FileProducerException();
}
wb = new XSSFWorkbook();
// 建立全局格式
JSONArray userStyles = this.jsonData.getJSONArray(this.USERSTYLES);
this.initUserStyles(userStyles);
this.initDefaultHeadStyle();
XSSFSheet ws;
JSONObject sheet;
JSONArray sheetData;
JSONArray sheetTitle;
JSONArray sheetHead;
JSONArray sheetFoot;
JSONArray sheetImgs;
String sheetName;
int sheetIndex = 0;
while (sheetIter.hasNext()) {
sheet = (JSONObject) sheetIter.next();
// 獲取sheet名稱
sheetName = sheet.getString(this.SHEET_NAME);
ws = wb.createSheet();
if (StringUtils.isNotBlank(sheetName)) {
wb.setSheetName(sheetIndex, sheetName);
}
int sheetRowIndex = 0;
sheetTitle = sheet.getJSONArray(this.SHEET_TITLE);
this.setMergeCells(ws, sheet.getJSONArray(this.SHEET_TITLE_MERGE),
sheetRowIndex);
sheetRowIndex = this.createRandom(ws, sheetTitle, sheetRowIndex);
sheetHead = sheet.getJSONArray(this.SHEET_HEAD);
sheetRowIndex = this.createHeadColumn(ws, sheetHead, sheetRowIndex);
this.setMergeCells(ws, sheet.getJSONArray(this.SHEET_DATA_MERGE),
sheetRowIndex);
sheetData = sheet.getJSONArray(this.SHEET_DATA);
sheetRowIndex = this.createData(ws, sheetData, sheetRowIndex);
sheetFoot = sheet.getJSONArray(this.SHEET_FOOT);
this.setMergeCells(ws, sheet.getJSONArray(this.SHEET_FOOT_MERGE),
sheetRowIndex);
sheetRowIndex = this.createRandom(ws, sheetFoot, sheetRowIndex);
sheetImgs = sheet.getJSONArray(this.SHEET_IMG);
this.setSheetImages(ws, sheetImgs);
}
// 返回輸出流
try {
ByteArrayOutputStream os = new ByteArrayOutputStream();
wb.write(os);
this.outStreams.add(os);
} catch (IOException e) {
throw new FileProducerException(e.getMessage(), e.getCause());
}
}
生成單元格樣式對(duì)象,包括字體 邊框 背景 對(duì)齊方式
private XSSFCellStyle createCellStyle(JSONObject style) {
XSSFCellStyle cellStyle = wb.createCellStyle();
// 設(shè)置字體
JSONObject font = style.getJSONObject(this.STYLE_FONT);
Font excelFont = this.createFont(font);
if (excelFont != null) {
cellStyle.setFont(excelFont);
}
// border統(tǒng)一黑色
cellStyle.setBorderBottom(BorderStyle.THIN);
cellStyle.setBorderTop(BorderStyle.THIN);
cellStyle.setBorderLeft(BorderStyle.THIN);
cellStyle.setBorderRight(BorderStyle.THIN);
String borderColor = style.getString(this.BORDER_COLOR);
if (StringUtils.isNotBlank(borderColor)) {
XSSFColor xfBorderColor = new XSSFColor(new Color(Integer.parseInt(
borderColor.substring(1), 16)));
cellStyle.setBorderColor(BorderSide.BOTTOM, xfBorderColor);
cellStyle.setBorderColor(BorderSide.TOP, xfBorderColor);
cellStyle.setBorderColor(BorderSide.LEFT, xfBorderColor);
cellStyle.setBorderColor(BorderSide.RIGHT, xfBorderColor);
}
// 背景色
String bgColor = style.getString(this.BACKGROUND_COLOR);
if (StringUtils.isNotBlank(bgColor)) {
XSSFColor cellBgColor = new XSSFColor(new Color(Integer.parseInt(
bgColor.substring(1), 16)));
cellStyle.setFillForegroundColor(cellBgColor);
cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
}
// 對(duì)齊方式
String hAlignment = style.getString(this.HALIGNMENT);
if (StringUtils.isNotBlank(hAlignment))
cellStyle.setAlignment(HorizontalAlignment.valueOf(hAlignment
.toUpperCase()));
String vAlignment = style.getString(this.VALIGNMENT);
if (StringUtils.isNotBlank(vAlignment))
cellStyle.setVerticalAlignment(VerticalAlignment.valueOf(vAlignment
.toUpperCase()));
// 自動(dòng)換行TRUE
cellStyle.setWrapText(true);
// 格式
String fmt = style.getString(this.FMTSTRING);
if (StringUtils.isNotBlank(fmt))
cellStyle.setDataFormat(wb.createDataFormat().getFormat(fmt));
return cellStyle;
}
創(chuàng)建字體樣式
private Font createFont(JSONObject fontCfg) {
if (fontCfg == null)
return null;
XSSFFont font = wb.createFont();
font.setFontName(fontCfg.getString(this.FONT_NAME));
Boolean fontBoole = fontCfg.getBoolean(FONT_BLOD);
if (fontBoole != null)
font.setBold(fontBoole.booleanValue());
fontBoole = fontCfg.getBoolean(this.FONT_ITALIC);
if (fontBoole != null)
font.setItalic(fontBoole.booleanValue());
fontBoole = fontCfg.getBoolean(this.FONT_UNDERLINE);
if (fontBoole != null && fontBoole.booleanValue() == true)
font.setUnderline(FontUnderline.SINGLE.getByteValue());
Short fontHeight = fontCfg.getShort(this.FONT_HEIGHT);
if (fontHeight != null)
font.setFontHeightInPoints(fontHeight);
String colorStr = fontCfg.getString(this.FONT_COLOR);
if (colorStr != null) {
font.setColor(new XSSFColor(new Color(Integer.parseInt(
colorStr.substring(1), 16))));
}
return font;
}
處理表頭,表過(guò)多表頭處理,采用 | 分割的方式,傳入head長(zhǎng)度為列數(shù)據(jù),name中有幾個(gè) | 就知道表頭有幾行。所以針對(duì)表頭處理有以下幾個(gè)步驟
- 生成默認(rèn)列樣式
- 填充所有列數(shù)據(jù),求出最大行數(shù)
- 橫向合并內(nèi)容相同的單元
- 縱向合并空白的單元格
private int createHeadColumn(XSSFSheet ws, JSONArray sheetHead,
int sheetRowIndex) {
if (sheetHead == null)
return sheetRowIndex;
Iterator<Object> headIter = sheetHead.iterator();
JSONObject curHead = null;
int colIndex = 0;
Object colStyle = null;
int colSize = sheetHead.size();
headTypes = new String[colSize];
headCellStyleKeys = new String[colSize];
int[] headColLevel = new int[colSize];
String colName = null;
String[] colNameAry = null;
int maxLevel = 0;
int colLevel = 0;
XSSFCell headCell = null;
ArrayList<ArrayList<String>> headValueList = new ArrayList<ArrayList<String>>();
while (headIter.hasNext()) {
curHead = (JSONObject) headIter.next();
// 處理默認(rèn)樣式
if (curHead.containsKey(this.COLUMN_STYLE)) {
colStyle = curHead.get(this.COLUMN_STYLE);
if (colStyle instanceof JSONObject) {
headCellStyleKeys[colIndex] = this.COLUMNSTYLE_PREV
+ colIndex;
this.userStyles.put(headCellStyleKeys[colIndex],
this.createCellStyle((JSONObject) colStyle));
} else if (this.userStyles.containsKey(colStyle)) {
headCellStyleKeys[colIndex] = (String) colStyle;
}
}
// 處理默認(rèn)列寬
if (curHead.containsKey(this.COLUMN_WIDTH)) {
ws.setDefaultColumnWidth(pixToExcelWdith(curHead
.getIntValue(this.COLUMN_WIDTH)));
}
// 保存列樣式
if (curHead.containsKey(this.COLUMN_TYPE)) {
headTypes[colIndex] = curHead.getString(this.COLUMN_TYPE);
} else {
headTypes[colIndex] = this.CELLTYPESTRING;
}
// 處理多表頭
colName = curHead.getString(this.COLUMN_NAME);
colNameAry = colName.split("\\|");
colLevel = colNameAry.length;
headColLevel[colIndex] = colLevel;
if (colLevel > maxLevel) {
maxLevel = colLevel;
}
for (int i = 0; i < colLevel; i++) {
if (headValueList.size() <= i) {
headValueList.add(new ArrayList<String>());
}
headValueList.get(i).add(colIndex, colNameAry[i]);
XSSFRow row = ws.getRow(sheetRowIndex + i);
if (row == null) {
row = ws.createRow(sheetRowIndex + i);
}
headCell = row.createCell(colIndex);
headCell.setCellValue(colNameAry[i]);
headCell.setCellStyle(this.userStyles.get(this.HEADSTYLE_KEY));
}
colIndex++;
}
// 橫向合并
Iterator<ArrayList<String>> a = headValueList.iterator();
JSONArray headMerge = new JSONArray();
String prev = "";
String curent = null;
int lRowIndex = 0;
int startCol = 0;
int mergeCol = 0;
ArrayList<String> columnInfo = null;
while (a.hasNext()) {
startCol = 0;
mergeCol = 0;
prev = "";
columnInfo = a.next();
// 第三列才能知道,第一列和第二列是否合并
columnInfo.add("");
Iterator<String> b = columnInfo.iterator();
XSSFCell lastRowCell = null;
while (b.hasNext()) {
curent = b.next();
if (lRowIndex > 0) {
lastRowCell = ws.getRow(sheetRowIndex + lRowIndex - 1)
.getCell(startCol);
}
if (prev.equalsIgnoreCase(curent) && lRowIndex == 0) {
ws.getRow(sheetRowIndex + lRowIndex).getCell(startCol)
.setCellType(Cell.CELL_TYPE_BLANK);
mergeCol++;
} else if (prev.equalsIgnoreCase(curent)
&& lRowIndex > 0
&& StringUtils
.isBlank(lastRowCell.getStringCellValue())) {
ws.getRow(sheetRowIndex + lRowIndex).getCell(startCol)
.setCellType(Cell.CELL_TYPE_BLANK);
mergeCol++;
} else {
if (mergeCol > 0 && startCol > 0) {
headMerge.add(String.format("%d,%d,%d,%d", lRowIndex,
lRowIndex, startCol - mergeCol - 1,
startCol - 1));
mergeCol = 0;
}
}
startCol++;
prev = curent;
}
lRowIndex++;
}
for (int i = 0; i < colSize; i++) {
if (headColLevel[i] < maxLevel) { // 存在列合并
headMerge.add(String.format("%d,%d,%d,%d", headColLevel[i] - 1,
maxLevel - 1, i, i));
for (int r = headColLevel[i]; r < maxLevel; r++) {
ws.getRow(sheetRowIndex + r)
.createCell(i)
.setCellStyle(
this.userStyles.get(this.HEADSTYLE_KEY));
}
}
}
this.setMergeCells(ws, headMerge, sheetRowIndex);
return sheetRowIndex + maxLevel;
}
添加圖片,默認(rèn)采用單元格描點(diǎn)方式,將圖片固定指定的單元格區(qū)域內(nèi)
private void addImg(XSSFSheet ws, JSONObject img, XSSFCreationHelper cHelper) {
String imgBase64 = img.getString(this.SHEET_IMG_DATA);
if (StringUtils.isBlank(imgBase64))
return;
String[] imgary = imgBase64.split(",");
System.out.println(imgary[0]);
byte[] imgByte = Base64.decodeBase64(imgary[1]);
int imgIdx = wb.addPicture(imgByte, Workbook.PICTURE_TYPE_JPEG);
XSSFDrawing drawImg = ws.createDrawingPatriarch();
XSSFClientAnchor anchor = cHelper.createClientAnchor();
int col = img.getIntValue(this.SHEET_IMG_COL);
int row = img.getIntValue(this.SHEET_IMG_ROW);
anchor.setCol1(col);
anchor.setRow1(row);
XSSFPicture pict = drawImg.createPicture(anchor, imgIdx);
Integer colSpan = img.getInteger(this.SHEET_IMG_COLSPAN);
if (colSpan == null)
colSpan = 1;
Integer rowSpan = img.getInteger(this.SHEET_IMG_ROWSPAN);
if (rowSpan == null)
rowSpan = 1;
pict.resize(colSpan, rowSpan);
}
五、總結(jié)
這次通過(guò)傳入JSON對(duì)象生成樣式豐富的excel文件,對(duì)于POI操作office文檔又更加熟悉一些。相對(duì)于解析excel文檔,生成就不用考慮文件格式,如:兼容2003格式,考慮大文件sax方式解析。相對(duì)于js前端生成excel文件,增加了對(duì)生成后文件二次加工的可能性,所以在功能入口中,采用了生成二進(jìn)制流的方式。文件生成好后,可以繼續(xù)發(fā)送郵件,上傳ftp等操作。
重點(diǎn)說(shuō)明
- 對(duì)于各數(shù)據(jù)區(qū)域數(shù)據(jù),保持區(qū)域數(shù)據(jù)獨(dú)立性(數(shù)據(jù)索引值)
- 對(duì)于圖片開(kāi)始行和開(kāi)始列,索引值是針對(duì)一個(gè)完整的sheet
- 對(duì)于表頭區(qū)域,多表頭采用 | 分割,減少部分傳輸數(shù)據(jù)
- excel中style為所有sheet共享樣式。
好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
- java后臺(tái)利用Apache poi 生成excel文檔提供前臺(tái)下載示例
- Java 使用POI生成帶聯(lián)動(dòng)下拉框的excel表格實(shí)例代碼
- Java通過(guò)apache poi生成excel實(shí)例代碼
- java使用poi讀取ppt文件和poi讀取excel、word示例
- java使用poi讀取excel內(nèi)容方法實(shí)例
- JAVA使用POI獲取Excel的列數(shù)與行數(shù)
- Java 使用poi把數(shù)據(jù)庫(kù)中數(shù)據(jù)導(dǎo)入Excel的解決方法
- java poi讀取excel操作示例(2個(gè)代碼)
- Java利用POI實(shí)現(xiàn)導(dǎo)入導(dǎo)出Excel表格示例代碼
- java的poi技術(shù)讀取和導(dǎo)入Excel實(shí)例
相關(guān)文章
使用StringRedisTemplate操作Redis方法詳解
這篇文章主要為大家介紹了使用StringRedisTemplate操作Redis方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08
springboot如何開(kāi)啟和關(guān)閉kafka消費(fèi)
在Kafka消費(fèi)者中,通過(guò)關(guān)閉自動(dòng)消費(fèi)配置,使用自定義容器工廠,并在消費(fèi)監(jiān)聽(tīng)器上設(shè)置id,可以手動(dòng)控制消費(fèi)的開(kāi)啟和關(guān)閉,這是根據(jù)個(gè)人經(jīng)驗(yàn)總結(jié)的方法,旨在幫助其他開(kāi)發(fā)者2024-12-12
Java中Json字符串直接轉(zhuǎn)換為對(duì)象的方法(包括多層List集合)
下面小編就為大家?guī)?lái)一篇Java中Json字符串直接轉(zhuǎn)換為對(duì)象的方法(包括多層List集合)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-08-08
MyBatis中example.createCriteria()方法的具體使用
本文詳細(xì)介紹了MyBatis的Example工具的使用方法,包括鏈?zhǔn)秸{(diào)用指定字段、設(shè)置查詢條件、支持多種查詢方式等,還介紹了mapper的crud方法、and/or方法的使用,以及如何進(jìn)行多條件和多重條件查詢,感興趣的可以了解一下2024-10-10
Java通過(guò)Scanner了解if...else if語(yǔ)句
這篇文章主要介紹了Java通過(guò)Scanner了解if...else if語(yǔ)句,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-01-01
Java實(shí)現(xiàn)AES算法的實(shí)例代碼
高級(jí)加密標(biāo)準(zhǔn)(AES,Advanced?Encryption?Standard)為最常見(jiàn)的對(duì)稱加密算法(微信小程序加密傳輸就是用這個(gè)加密算法的),本文重點(diǎn)給大家介紹Java實(shí)現(xiàn)AES算法的實(shí)例代碼,感興趣的朋友一起看看吧2022-02-02
java中l(wèi)ist和數(shù)組互相轉(zhuǎn)換的一些方法總結(jié)
在日常的Java開(kāi)發(fā)中經(jīng)常會(huì)遇到需要在數(shù)組和List之間進(jìn)行轉(zhuǎn)換的情況,這篇文章主要給大家介紹了關(guān)于java中l(wèi)ist和數(shù)組互相轉(zhuǎn)換的一些方法,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-12-12
解決程序包org.springframework.test.context不存在
這篇文章主要介紹了解決程序包org.springframework.test.context不存在的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09

