基於註解的Excel匯出
阿新 • • 發佈:2018-12-19
資料庫Excel匯出操作程式碼過於冗長慘不忍睹,無法複用。
註解配合工具類做了個小工具如下:
自定義註解:
package com.ruoyi.framework.aspectj.lang.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 自定義匯出Excel資料註解 * * @author ruoyi */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Excel { /** * 匯出到Excel中的名字. */ public String name(); /** * 日期格式, 如: yyyy-MM-dd */ public String dateFormat() default ""; /** * 讀取內容轉表示式 (如: 0=男,1=女,2=未知) */ public String readConverterExp() default ""; /** * 匯出時在excel中每個列的高度 單位為字元 */ public double height() default 14; /** * 匯出時在excel中每個列的寬 單位為字元 */ public double width() default 20; /** * 文字字尾,如% 90 變成90% */ public String suffix() default ""; /** * 當值為空時,欄位的預設值 */ public String defaultValue() default ""; /** * 提示資訊 */ public String prompt() default ""; /** * 設定只能選擇不能輸入的列內容. */ public String[] combo() default {}; /** * 是否匯出資料,應對需求:有時我們需要匯出一份模板,這是標題需要但內容需要使用者手工填寫. */ public boolean isExport() default true; }
實體類:
package com.ruoyi.project.system.dict.domain; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import com.ruoyi.framework.aspectj.lang.annotation.Excel; import com.ruoyi.framework.web.domain.BaseEntity; /** * 字典資料表 sys_dict_data * * @author ruoyi */ public class DictData extends BaseEntity { private static final long serialVersionUID = 1L; /** 字典編碼 */ @Excel(name = "字典編碼") private Long dictCode; /** 字典排序 */ @Excel(name = "字典排序") private Long dictSort; /** 字典標籤 */ @Excel(name = "字典標籤") private String dictLabel; /** 字典鍵值 */ @Excel(name = "字典鍵值") private String dictValue; /** 字典型別 */ @Excel(name = "字典型別") private String dictType; /** 字典樣式 */ @Excel(name = "字典樣式") private String cssClass; /** 表格字典樣式 */ private String listClass; /** 是否預設(Y是 N否) */ @Excel(name = "是否預設", readConverterExp = "Y=是,N=否") private String isDefault; /** 狀態(0正常 1停用) */ @Excel(name = "狀態", readConverterExp = "0=正常,1=停用") private String status; public Long getDictCode() { return dictCode; } public void setDictCode(Long dictCode) { this.dictCode = dictCode; } public Long getDictSort() { return dictSort; } public void setDictSort(Long dictSort) { this.dictSort = dictSort; } public String getDictLabel() { return dictLabel; } public void setDictLabel(String dictLabel) { this.dictLabel = dictLabel; } public String getDictValue() { return dictValue; } public void setDictValue(String dictValue) { this.dictValue = dictValue; } public String getDictType() { return dictType; } public void setDictType(String dictType) { this.dictType = dictType; } public String getCssClass() { return cssClass; } public void setCssClass(String cssClass) { this.cssClass = cssClass; } public String getListClass() { return listClass; } public void setListClass(String listClass) { this.listClass = listClass; } public String getIsDefault() { return isDefault; } public void setIsDefault(String isDefault) { this.isDefault = isDefault; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) .append("dictCode", getDictCode()) .append("dictSort", getDictSort()) .append("dictLabel", getDictLabel()) .append("dictValue", getDictValue()) .append("dictType", getDictType()) .append("cssClass", getCssClass()) .append("listClass", getListClass()) .append("isDefault", getIsDefault()) .append("status", getStatus()) .append("createBy", getCreateBy()) .append("createTime", getCreateTime()) .append("updateBy", getUpdateBy()) .append("updateTime", getUpdateTime()) .append("remark", getRemark()) .toString(); } }
解析工具類:
package com.ruoyi.common.utils.poi; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Field; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import org.apache.poi.hssf.usermodel.DVConstraint; import org.apache.poi.hssf.usermodel.HSSFCell; import org.apache.poi.hssf.usermodel.HSSFCellStyle; import org.apache.poi.hssf.usermodel.HSSFDataValidation; import org.apache.poi.hssf.usermodel.HSSFFont; import org.apache.poi.hssf.usermodel.HSSFRow; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.hssf.util.HSSFColor.HSSFColorPredefined; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.FillPatternType; import org.apache.poi.ss.usermodel.HorizontalAlignment; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.VerticalAlignment; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.usermodel.WorkbookFactory; import org.apache.poi.ss.util.CellRangeAddressList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.framework.aspectj.lang.annotation.Excel; import com.ruoyi.framework.config.RuoYiConfig; import com.ruoyi.framework.web.domain.AjaxResult; /** * Excel相關處理 * * @author ruoyi */ public class ExcelUtil<T> { private static final Logger log = LoggerFactory.getLogger(ExcelUtil.class); public Class<T> clazz; public ExcelUtil(Class<T> clazz) { this.clazz = clazz; } /** * 對excel表單預設第一個索引名轉換成list * * @param input 輸入流 * @return 轉換後集合 */ public List<T> importExcel(InputStream input) throws Exception { return importExcel(StringUtils.EMPTY, input); } /** * 對excel表單指定表格索引名轉換成list * * @param sheetName 表格索引名 * @param input 輸入流 * @return 轉換後集合 */ public List<T> importExcel(String sheetName, InputStream input) throws Exception { List<T> list = new ArrayList<T>(); Workbook workbook = WorkbookFactory.create(input); Sheet sheet = null; if (StringUtils.isNotEmpty(sheetName)) { // 如果指定sheet名,則取指定sheet中的內容. sheet = workbook.getSheet(sheetName); } else { // 如果傳入的sheet名不存在則預設指向第1個sheet. sheet = workbook.getSheetAt(0); } if (sheet == null) { throw new IOException("檔案sheet不存在"); } int rows = sheet.getPhysicalNumberOfRows(); if (rows > 0) { // 預設序號 int serialNum = 0; // 有資料時才處理 得到類的所有field. Field[] allFields = clazz.getDeclaredFields(); // 定義一個map用於存放列的序號和field. Map<Integer, Field> fieldsMap = new HashMap<Integer, Field>(); for (int col = 0; col < allFields.length; col++) { Field field = allFields[col]; // 將有註解的field存放到map中. if (field.isAnnotationPresent(Excel.class)) { // 設定類的私有欄位屬性可訪問. field.setAccessible(true); fieldsMap.put(++serialNum, field); } } for (int i = 1; i < rows; i++) { // 從第2行開始取資料,預設第一行是表頭. Row row = sheet.getRow(i); int cellNum = serialNum; T entity = null; for (int j = 0; j < cellNum; j++) { Cell cell = row.getCell(j); if (cell == null) { continue; } else { // 先設定Cell的型別,然後就可以把純數字作為String型別讀進來了 row.getCell(j).setCellType(CellType.STRING); cell = row.getCell(j); } String c = cell.getStringCellValue(); if (StringUtils.isEmpty(c)) { continue; } // 如果不存在例項則新建. entity = (entity == null ? clazz.newInstance() : entity); // 從map中得到對應列的field. Field field = fieldsMap.get(j + 1); // 取得型別,並根據物件型別設定值. Class<?> fieldType = field.getType(); if (String.class == fieldType) { field.set(entity, String.valueOf(c)); } else if ((Integer.TYPE == fieldType) || (Integer.class == fieldType)) { field.set(entity, Integer.parseInt(c)); } else if ((Long.TYPE == fieldType) || (Long.class == fieldType)) { field.set(entity, Long.valueOf(c)); } else if ((Float.TYPE == fieldType) || (Float.class == fieldType)) { field.set(entity, Float.valueOf(c)); } else if ((Short.TYPE == fieldType) || (Short.class == fieldType)) { field.set(entity, Short.valueOf(c)); } else if ((Double.TYPE == fieldType) || (Double.class == fieldType)) { field.set(entity, Double.valueOf(c)); } else if (Character.TYPE == fieldType) { if ((c != null) && (c.length() > 0)) { field.set(entity, Character.valueOf(c.charAt(0))); } } else if (java.util.Date.class == fieldType) { if (cell.getCellTypeEnum() == CellType.NUMERIC) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); cell.setCellValue(sdf.format(cell.getNumericCellValue())); c = sdf.format(cell.getNumericCellValue()); } else { c = cell.getStringCellValue(); } } else if (java.math.BigDecimal.class == fieldType) { c = cell.getStringCellValue(); } } if (entity != null) { list.add(entity); } } } return list; } /** * 對list資料來源將其裡面的資料匯入到excel表單 * * @param list 匯出資料集合 * @param sheetName 工作表的名稱 * @return 結果 */ public AjaxResult exportExcel(List<T> list, String sheetName) { OutputStream out = null; HSSFWorkbook workbook = null; try { // 得到所有定義欄位 Field[] allFields = clazz.getDeclaredFields(); List<Field> fields = new ArrayList<Field>(); // 得到所有field並存放到一個list中. for (Field field : allFields) { if (field.isAnnotationPresent(Excel.class)) { fields.add(field); } } // 產生工作薄物件 workbook = new HSSFWorkbook(); // excel2003中每個sheet中最多有65536行 int sheetSize = 65536; // 取出一共有多少個sheet. double sheetNo = Math.ceil(list.size() / sheetSize); for (int index = 0; index <= sheetNo; index++) { // 產生工作表物件 HSSFSheet sheet = workbook.createSheet(); if (sheetNo == 0) { workbook.setSheetName(index, sheetName); } else { // 設定工作表的名稱. workbook.setSheetName(index, sheetName + index); } HSSFRow row; HSSFCell cell; // 產生單元格 // 產生一行 row = sheet.createRow(0); // 寫入各個欄位的列頭名稱 for (int i = 0; i < fields.size(); i++) { Field field = fields.get(i); Excel attr = field.getAnnotation(Excel.class); // 建立列 cell = row.createCell(i); // 設定列中寫入內容為String型別 cell.setCellType(CellType.STRING); HSSFCellStyle cellStyle = workbook.createCellStyle(); cellStyle.setAlignment(HorizontalAlignment.CENTER); cellStyle.setVerticalAlignment(VerticalAlignment.CENTER); if (attr.name().indexOf("注:") >= 0) { HSSFFont font = workbook.createFont(); font.setColor(HSSFFont.COLOR_RED); cellStyle.setFont(font); cellStyle.setFillForegroundColor(HSSFColorPredefined.YELLOW.getIndex()); sheet.setColumnWidth(i, 6000); } else { HSSFFont font = workbook.createFont(); // 粗體顯示 font.setBold(true); // 選擇需要用到的字型格式 cellStyle.setFont(font); cellStyle.setFillForegroundColor(HSSFColorPredefined.LIGHT_YELLOW.getIndex()); // 設定列寬 sheet.setColumnWidth(i, (int) ((attr.width() + 0.72) * 256)); row.setHeight((short) (attr.height() * 20)); } cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); cellStyle.setWrapText(true); cell.setCellStyle(cellStyle); // 寫入列名 cell.setCellValue(attr.name()); // 如果設定了提示資訊則滑鼠放上去提示. if (StringUtils.isNotEmpty(attr.prompt())) { // 這裡預設設了2-101列提示. setHSSFPrompt(sheet, "", attr.prompt(), 1, 100, i, i); } // 如果設定了combo屬性則本列只能選擇不能輸入 if (attr.combo().length > 0) { // 這裡預設設了2-101列只能選擇不能輸入. setHSSFValidation(sheet, attr.combo(), 1, 100, i, i); } } int startNo = index * sheetSize; int endNo = Math.min(startNo + sheetSize, list.size()); // 寫入各條記錄,每條記錄對應excel表中的一行 HSSFCellStyle cs = workbook.createCellStyle(); cs.setAlignment(HorizontalAlignment.CENTER); cs.setVerticalAlignment(VerticalAlignment.CENTER); for (int i = startNo; i < endNo; i++) { row = sheet.createRow(i + 1 - startNo); // 得到匯出物件. T vo = (T) list.get(i); for (int j = 0; j < fields.size(); j++) { // 獲得field. Field field = fields.get(j); // 設定實體類私有屬性可訪問 field.setAccessible(true); Excel attr = field.getAnnotation(Excel.class); try { // 設定行高 row.setHeight((short) (attr.height() * 20)); // 根據Excel中設定情況決定是否匯出,有些情況需要保持為空,希望使用者填寫這一列. if (attr.isExport()) { // 建立cell cell = row.createCell(j); cell.setCellStyle(cs); if (vo == null) { // 如果資料存在就填入,不存在填入空格. cell.setCellValue(""); continue; } String dateFormat = attr.dateFormat(); String readConverterExp = attr.readConverterExp(); if (StringUtils.isNotEmpty(dateFormat)) { cell.setCellValue(DateUtils.parseDateToStr(dateFormat, (Date) field.get(vo))); } else if (StringUtils.isNotEmpty(readConverterExp)) { cell.setCellValue(convertByExp(String.valueOf(field.get(vo)), readConverterExp)); } else { cell.setCellType(CellType.STRING); // 如果資料存在就填入,不存在填入空格. cell.setCellValue(StringUtils.isNull(field.get(vo)) ? attr.defaultValue() : field.get(vo) + attr.suffix()); } } } catch (Exception e) { log.error("匯出Excel失敗{}", e.getMessage()); } } } } String filename = encodingFilename(sheetName); out = new FileOutputStream(getAbsoluteFile(filename)); workbook.write(out); return AjaxResult.success(filename); } catch (Exception e) { log.error("匯出Excel異常{}", e.getMessage()); return AjaxResult.error("匯出Excel失敗,請聯絡網站管理員!"); } finally { if (workbook != null) { try { workbook.close(); } catch (IOException e1) { e1.printStackTrace(); } } if (out != null) { try { out.close(); } catch (IOException e1) { e1.printStackTrace(); } } } } /** * 設定單元格上提示 * * @param sheet 要設定的sheet. * @param promptTitle 標題 * @param promptContent 內容 * @param firstRow 開始行 * @param endRow 結束行 * @param firstCol 開始列 * @param endCol 結束列 * @return 設定好的sheet. */ public static HSSFSheet setHSSFPrompt(HSSFSheet sheet, String promptTitle, String promptContent, int firstRow, int endRow, int firstCol, int endCol) { // 構造constraint物件 DVConstraint constraint = DVConstraint.createCustomFormulaConstraint("DD1"); // 四個引數分別是:起始行、終止行、起始列、終止列 CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol); // 資料有效性物件 HSSFDataValidation dataValidationView = new HSSFDataValidation(regions, constraint); dataValidationView.createPromptBox(promptTitle, promptContent); sheet.addValidationData(dataValidationView); return sheet; } /** * 設定某些列的值只能輸入預製的資料,顯示下拉框. * * @param sheet 要設定的sheet. * @param textlist 下拉框顯示的內容 * @param firstRow 開始行 * @param endRow 結束行 * @param firstCol 開始列 * @param endCol 結束列 * @return 設定好的sheet. */ public static HSSFSheet setHSSFValidation(HSSFSheet sheet, String[] textlist, int firstRow, int endRow, int firstCol, int endCol) { // 載入下拉列表內容 DVConstraint constraint = DVConstraint.createExplicitListConstraint(textlist); // 設定資料有效性載入在哪個單元格上,四個引數分別是:起始行、終止行、起始列、終止列 CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol); // 資料有效性物件 HSSFDataValidation dataValidationList = new HSSFDataValidation(regions, constraint); sheet.addValidationData(dataValidationList); return sheet; } /** * 解析匯出值 0=男,1=女,2=未知 * * @param propertyValue 引數值 * @param converterExp 翻譯註解 * @return 解析後值 * @throws Exception */ public static String convertByExp(String propertyValue, String converterExp) throws Exception { try { String[] convertSource = converterExp.split(","); for (String item : convertSource) { String[] itemArray = item.split("="); if (itemArray[0].equals(propertyValue)) { return itemArray[1]; } } } catch (Exception e) { throw e; } return propertyValue; } /** * 編碼檔名 */ public String encodingFilename(String filename) { filename = UUID.randomUUID().toString() + "_" + filename + ".xls"; return filename; } /** * 獲取下載路徑 * * @param filename 檔名稱 */ public String getAbsoluteFile(String filename) { String downloadPath = RuoYiConfig.getDownloadPath() + filename; File desc = new File(downloadPath); if (!desc.getParentFile().exists()) { desc.getParentFile().mkdirs(); } return downloadPath; } }
使用:
@Log(title = "字典資料", businessType = BusinessType.EXPORT)
@RequiresPermissions("system:dict:export")
@PostMapping("/export")
@ResponseBody
public AjaxResult export(DictData dictData)
{
List<DictData> list = dictDataService.selectDictDataList(dictData);
ExcelUtil<DictData> util = new ExcelUtil<DictData>(DictData.class);
return util.exportExcel(list, "dictData");
}