一、背景
Apache POI 是創建和維護操作各種符合Office Open XML(OOXML)標準和微軟的OLE 2複合文檔格式(OLE2)的Java API。用它可以使用Java讀取和創建,修改MS Excel文件.而且,還可以使用Java讀取和創建MS Word和MSPowerPoint文件。 Apache POI 提供Java操作Excel解決方案(適用於Excel97-2008)。
根據指定格式的JSON文件生成對應的excel文件,需求如下
二、效果預覽
三、數據格式
由於是生成Excel文件,這裡值考慮生成xlsx格式的Excel文件,數據多表頭默認考慮使用| 表示,不在使用colspan rowspan作為。如需要表示兩列兩行,第一列合併表頭格式為: A|B,A|C生成的表格為
| A | |
| B | C |
前端通過post的方式將需要生成的數據構造成符合要求的JSON文件提交跟後台。根據以上需求定義JSON格式如下
{"saveName": "生成Excel的文件名.xlsx","userStyles": [{"id": "1", //不能出現重複,在需要設置單元樣式的地方,可以直接將style賦值為此值"style": {"font": { //設置字體基本格式"blod": true,//是否加粗"italic": true, //是否傾斜"color": "#FF0000",//字體顏色"name": "微軟雅黑", //字體名稱"height": 20 //大小},"fmtStr": "", //單元格格式,#,##0.00_);#,##0.00;0 千分位"align": "",//水平對齊方式left right center"valign": "",//垂直對齊方式top center bottom"borderColor": "", //設置邊框顏色如#FF0000"bgColor": "" //設置單元格填充顏色}}],"sheets": [{"sheetName": "", //sheet名稱"title": [], // 對應Sheet標題區域數據"titleMerge": [], //對應Sheet標題區域合併信息"head": [{}], //表頭信息"data": [], //數據信息"dataMerge": [], //數據合併信息"foot": [], //表尾信息"footMerge": [], //表尾合併信息"img": [] //圖片信息,需要將圖片轉換base64}]}簡要說明
head 數組中為JSON對象格式為
{"name": "A|B", //表頭名稱,多表頭用|分割"type": "str", //此列數據類型str num ,在excel中日期也是數字類型,通過fmtStr,顯示為日期格式"field": "F_FIELD1", //備用字段,可不用"style": { //此列數據為列默認樣式,可以是Style對象,也可以是在userStyles中定義的id值"align": "center"}}在數組title data foot 中,列表中的數據,可以是一個單獨的值如1,”a”,也可以是一個對象,當為對象時,格式為
{"value": "", //單元格具體的值"type": "", //單元格類型,默認str"style": {} //單元格樣式可以是Style對象,也可以是在userStyles中定義的id值,如果沒設置,默認取head總此列對應的style} titleMerge、dataMerge、footMerge數組值為逗號分隔的字符串,其含義為"開始行,結束行,開始列,結束列",索引從0開始。如在title中有兩行三列數據,現在需要合併一行兩列數據對應的值為"0,0,0,1"
img數組中值為對象,格式
{"col": 1, //圖片開始列"row": 0, //開始行"colSpan": 1,//列跨度,最小值1"rowSpan": 2, //行跨度,最小值1"data": "" //base64圖片數據如: "data:image/png;base64,iVBO...ggg=="}四、關鍵實現
07以後的Excle文件,其實是一個壓縮包,裡邊是一個個的xml文件,其中每一個sheet是一個xml文件,樣式是一個xml文件,圖片是對應的圖片文件,放在media文件夾中,所以,代碼思路依次為
功能入口如下
@Overridepublic void buildOutputStream() throws FileProducerException {// 處理傳入的JSON數據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("無數據可生成");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());}}生成單元格樣式對象,包括字體邊框背景對齊方式
private XSSFCellStyle createCellStyle(JSONObject style) { XSSFCellStyle cellStyle = wb.createCellStyle();// 設置字體JSONObject font = style.getJSONObject(this.STYLE_FONT);Font excelFont = this.createFont(font);if (excelFont != null) {cellStyle.setFont(excelFont);}// border統一黑色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);}// 對齊方式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()));// 自動換行TRUEcellStyle.setWrapText(true); // 格式String fmt = style.getString(this.FMTSTRING);if (StringUtils.isNotBlank(fmt))cellStyle.setDataFormat(wb.createDataFormat().getFormat(fmt));return cellStyle;}創建字體樣式
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;}處理表頭,表過多表頭處理,採用| 分割的方式,傳入head長度為列數據,name中有幾個| 就知道表頭有幾行。所以針對錶頭處理有以下幾個步驟
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();// 處理默認樣式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;}}// 處理默認列寬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;}添加圖片,默認採用單元格描點方式,將圖片固定指定的單元格區域內
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);}五、總結
這次通過傳入JSON對像生成樣式豐富的excel文件,對於POI操作office文檔又更加熟悉一些。相對於解析excel文檔,生成就不用考慮文件格式,如:兼容2003格式,考慮大文件sax方式解析。相對於js前端生成excel文件,增加了對生成後文件二次加工的可能性,所以在功能入口中,採用了生成二進制流的方式。文件生成好後,可以繼續發送郵件,上傳ftp等操作。
重點說明
好了,以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對武林網的支持。