SpringBoot整合EasyExcel實現(xiàn)文件導(dǎo)入導(dǎo)出
準備工作
1. 引入pom依賴
<!--easyExcel--> <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> </dependency>
2. 實現(xiàn)功能
- 結(jié)合Vue前端,實現(xiàn)瀏覽器頁面直接導(dǎo)出日志文件
- 實現(xiàn)文件的導(dǎo)入
Excel文件下載
3. 日志實體類
實體類里有自定義轉(zhuǎn)換器:用于Java類型數(shù)據(jù)和Excel類型數(shù)據(jù)的轉(zhuǎn)換,非常使用。結(jié)合注解,可以非常方便的進行Excel文件導(dǎo)出。
/** * <p> * 操作日志信息 * </p> * * @author horse * @since 2020-09-08 * 注意: 實體類中如果使用@Accessory(chain=true),那么導(dǎo)入的數(shù)據(jù)無法填充到實例中,導(dǎo)出數(shù)據(jù)不受影響 */ @Data @EqualsAndHashCode(callSuper = false) @TableName("tb_operational_log") @ApiModel(value = "OperationalLog對象", description = "操作日志信息") public class OperationalLog implements Serializable { private static final long serialVersionUID = 1L; @ExcelProperty({"操作日志", "日志ID"}) @ApiModelProperty(value = "日志ID") @TableId(value = "id", type = IdType.ASSIGN_ID) private String id; @ExcelProperty({"操作日志", "操作類型"}) @ApiModelProperty(value = "操作類型") private String operType; @ExcelProperty({"操作日志", "操作描述"}) @ApiModelProperty(value = "操作描述") private String operDesc; @ExcelProperty({"操作日志", "操作員ID"}) @ApiModelProperty(value = "操作員ID") private String operUserId; @ExcelProperty({"操作日志", "操作員名稱"}) @ApiModelProperty(value = "操作員名稱") private String operUserName; @ExcelProperty({"操作日志", "操作方法"}) @ApiModelProperty(value = "操作方法") private String operMethod; @ExcelProperty({"操作日志", "請求方法"}) @ApiModelProperty(value = "請求方法") private String operRequWay; @ExcelProperty(value = {"操作日志", "請求耗時:單位-ms"}, converter = CustomRequestTimeConverter.class) @ApiModelProperty(value = "請求耗時:單位-ms") private Long operRequTime; @ExcelProperty({"操作日志", "請求參數(shù)"}) @ApiModelProperty(value = "請求參數(shù)") private String operRequParams; @ExcelProperty({"操作日志", "請求Body"}) @ApiModelProperty(value = "請求Body") private String operRequBody; @ExcelProperty({"操作日志", "請求IP"}) @ApiModelProperty(value = "請求IP") private String operRequIp; @ExcelProperty({"操作日志", "請求URL"}) @ApiModelProperty(value = "請求URL") private String operRequUrl; @ExcelProperty(value = {"操作日志", "日志標識"}, converter = CustomLogFlagConverter.class) @ApiModelProperty(value = "日志標識: 1-admin,0-portal") private Boolean logFlag; @ExcelProperty({"操作日志", "操作狀態(tài)"}) @ApiModelProperty(value = "操作狀態(tài):1-成功,0-失敗") @TableField(value = "is_success") private Boolean success; @ExcelIgnore @ApiModelProperty(value = "邏輯刪除 1-未刪除, 0-刪除") @TableField(value = "is_deleted") @TableLogic(value = "1", delval = "0") private Boolean deleted; @ExcelProperty(value = {"操作日志", "創(chuàng)建時間"}, converter = CustomTimeFormatConverter.class) @ApiModelProperty(value = "創(chuàng)建時間") private Date gmtCreate; }
4. 接口和具體實現(xiàn)
4.1 接口
@OperatingLog(operType = BlogConstants.EXPORT, operDesc = "導(dǎo)出操作日志,寫出到響應(yīng)流中") @ApiOperation(value = "導(dǎo)出操作日志", hidden = true) @PostMapping("/oper/export") public void operLogExport(@RequestBody List<String> logIds, HttpServletResponse response) { operationalLogService.operLogExport(logIds, response); }
4.2 具體實現(xiàn)
- 自定義導(dǎo)出策略HorizontalCellStyleStrategy
- 自定義導(dǎo)出攔截器CellWriteHandler,更加精確的自定義導(dǎo)出策略
/** * 導(dǎo)出操作日志(可以考慮分頁導(dǎo)出) * * @param logIds * @param response */ @Override public void operLogExport(List<String> logIds, HttpServletResponse response) { OutputStream outputStream = null; try { List<OperationalLog> operationalLogs; LambdaQueryWrapper<OperationalLog> queryWrapper = new LambdaQueryWrapper<OperationalLog>() .orderByDesc(OperationalLog::getGmtCreate); // 如果logIds不為null,按照id查詢信息,否則查詢?nèi)? if (!CollectionUtils.isEmpty(logIds)) { operationalLogs = this.listByIds(logIds); } else { operationalLogs = this.list(queryWrapper); } outputStream = response.getOutputStream(); // 獲取單元格樣式 HorizontalCellStyleStrategy strategy = MyCellStyleStrategy.getHorizontalCellStyleStrategy(); // 寫入響應(yīng)輸出流數(shù)據(jù) EasyExcel.write(outputStream, OperationalLog.class).excelType(ExcelTypeEnum.XLSX).sheet("操作信息日志") // .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 自適應(yīng)列寬(不是很適應(yīng),效果并不佳) .registerWriteHandler(strategy) // 注冊上面設(shè)置的格式策略 .registerWriteHandler(new CustomCellWriteHandler()) // 設(shè)置自定義格式策略 .doWrite(operationalLogs); } catch (Exception e) { log.error(ExceptionUtils.getMessage(e)); throw new BlogException(ResultCodeEnum.EXCEL_DATA_EXPORT_ERROR); } finally { IoUtil.close(outputStream); } }
自定義導(dǎo)出策略簡單如下:
/** * @author Mr.Horse * @version 1.0 * @description: 單元格樣式策略 * @date 2021/4/30 8:43 */ public class MyCellStyleStrategy { /** * 設(shè)置單元格樣式(僅用于測試) * * @return 樣式策略 */ public static HorizontalCellStyleStrategy getHorizontalCellStyleStrategy() { // 表頭策略 WriteCellStyle headerCellStyle = new WriteCellStyle(); // 表頭水平對齊居中 headerCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER); // 背景色 headerCellStyle.setFillForegroundColor(IndexedColors.SKY_BLUE.getIndex()); WriteFont headerFont = new WriteFont(); headerFont.setFontHeightInPoints((short) 14); headerCellStyle.setWriteFont(headerFont); // 自動換行 headerCellStyle.setWrapped(Boolean.FALSE); // 內(nèi)容策略 WriteCellStyle contentCellStyle = new WriteCellStyle(); // 設(shè)置數(shù)據(jù)允許的數(shù)據(jù)格式,這里49代表所有可以都允許設(shè)置 contentCellStyle.setDataFormat((short) 49); // 設(shè)置背景色: 需要指定 FillPatternType 為FillPatternType.SOLID_FOREGROUND 不然無法顯示背景顏色.頭默認了 FillPatternType所以可以不指定 contentCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND); contentCellStyle.setFillForegroundColor(IndexedColors.GREY_40_PERCENT.getIndex()); // 設(shè)置內(nèi)容靠左對齊 contentCellStyle.setHorizontalAlignment(HorizontalAlignment.LEFT); // 設(shè)置字體 WriteFont contentFont = new WriteFont(); contentFont.setFontHeightInPoints((short) 12); contentCellStyle.setWriteFont(contentFont); // 設(shè)置自動換行 contentCellStyle.setWrapped(Boolean.FALSE); // 設(shè)置邊框樣式和顏色 contentCellStyle.setBorderLeft(MEDIUM); contentCellStyle.setBorderTop(MEDIUM); contentCellStyle.setBorderRight(MEDIUM); contentCellStyle.setBorderBottom(MEDIUM); contentCellStyle.setTopBorderColor(IndexedColors.RED.getIndex()); contentCellStyle.setBottomBorderColor(IndexedColors.GREEN.getIndex()); contentCellStyle.setLeftBorderColor(IndexedColors.YELLOW.getIndex()); contentCellStyle.setRightBorderColor(IndexedColors.ORANGE.getIndex()); // 將格式加入單元格樣式策略 return new HorizontalCellStyleStrategy(headerCellStyle, contentCellStyle); } }
自定義導(dǎo)出攔截器簡單如下:
/** * @author Mr.Horse * @version 1.0 * @description 實現(xiàn)CellWriteHandler接口, 實現(xiàn)對單元格樣式的精確控制 * @date 2021/4/29 21:11 */ public class CustomCellWriteHandler implements CellWriteHandler { private static Logger logger = LoggerFactory.getLogger(CustomCellWriteHandler.class); @Override public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) { } /** * 單元格創(chuàng)建之后(沒有寫入值) * * @param writeSheetHolder * @param writeTableHolder * @param cell * @param head * @param relativeRowIndex * @param isHead */ @Override public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) { } @Override public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, CellData cellData, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) { } /** * 單元格處理后(已寫入值): 設(shè)置第一行第一列的頭超鏈接到EasyExcel的官網(wǎng)(本系統(tǒng)的導(dǎo)出的excel 0,1兩行都是頭,所以只設(shè)置第一行的超鏈接) * 這里再進行攔截的單元格樣式設(shè)置的話,前面該樣式將全部失效 * * @param writeSheetHolder * @param writeTableHolder * @param cellDataList * @param cell * @param head * @param relativeRowIndex * @param isHead */ @Override public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) { // 設(shè)置超鏈接 if (isHead && cell.getRowIndex() == 0 && cell.getColumnIndex() == 0) { logger.info(" ==> 第{}行,第{}列超鏈接設(shè)置完成", cell.getRowIndex(), cell.getColumnIndex()); CreationHelper helper = writeSheetHolder.getSheet().getWorkbook().getCreationHelper(); Hyperlink hyperlink = helper.createHyperlink(HyperlinkType.URL); hyperlink.setAddress("https://github.com/alibaba/easyexcel"); cell.setHyperlink(hyperlink); } // 精確設(shè)置單元格格式 boolean bool = isHead && cell.getRowIndex() == 1 && (cell.getStringCellValue().equals("請求參數(shù)") || cell.getStringCellValue().equals("請求Body")); if (bool) { logger.info("第{}行,第{}列單元格樣式設(shè)置完成。", cell.getRowIndex(), cell.getColumnIndex()); // 獲取工作簿 Workbook workbook = writeSheetHolder.getSheet().getWorkbook(); CellStyle cellStyle = workbook.createCellStyle(); Font cellFont = workbook.createFont(); cellFont.setBold(Boolean.TRUE); cellFont.setFontHeightInPoints((short) 14); cellFont.setColor(IndexedColors.SEA_GREEN.getIndex()); cellStyle.setFont(cellFont); cell.setCellStyle(cellStyle); } } }
4.3 前端請求
前端在基于Vue+Element的基礎(chǔ)上實現(xiàn)了點擊導(dǎo)出按鈕,在瀏覽器頁面進行下載。
// 批量導(dǎo)出 batchExport() { // 遍歷獲取id集合列表 const logIds = [] this.multipleSelection.forEach(item => { logIds.push(item.id) }) // 請求后端接口 axios({ url: this.BASE_API + '/admin/blog/log/oper/export', method: 'post', data: logIds, responseType: 'arraybuffer', headers: { 'token': getToken() } }).then(response => { // type類型可以設(shè)置為文本類型,這里是新版excel類型 const blob = new Blob([response.data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8' }) const pdfUrl = window.URL.createObjectURL(blob) const fileName = 'HorseBlog操作日志' // 下載文件的名字 // 對于<a>標簽,只有 Firefox 和 Chrome(內(nèi)核)支持 download 屬性 if ('download' in document.createElement('a')) { const link = document.createElement('a') link.href = pdfUrl link.setAttribute('download', fileName) document.body.appendChild(link) link.click() window.URL.revokeObjectURL(pdfUrl) // 釋放URL 對象 } else { // IE 瀏覽器兼容方法 window.navigator.msSaveBlob(blob, fileName) } }) }
測試結(jié)果:還行,基本實現(xiàn)了頁面下載的功能
Excel文件導(dǎo)入
5. 文件讀取配置
本配置基于泛型的方式編寫,可擴展性較強。
/** * @author Mr.Horse * @version 1.0 * @description: EasyExcel文件讀取配置(不能讓spring管理) * @date 2021/4/27 13:24 */ public class MyExcelImportConfig<T> extends AnalysisEventListener<T> { private static Logger logger = LoggerFactory.getLogger(MyExcelImportConfig.class); /** * 每次讀取的最大數(shù)據(jù)條數(shù) */ private static final int MAX_BATCH_COUNT = 10; /** * 泛型bean屬性 */ private T dynamicService; /** * 可接收任何參數(shù)的泛型List集合 */ List<T> list = new ArrayList<>(); /** * 構(gòu)造函數(shù)注入bean(根據(jù)傳入的bean動態(tài)注入) * * @param dynamicService */ public MyExcelImportConfig(T dynamicService) { this.dynamicService = dynamicService; } /** * 解析每條數(shù)據(jù)都進行調(diào)用 * * @param data * @param context */ @Override public void invoke(T data, AnalysisContext context) { logger.info(" ==> 解析一條數(shù)據(jù): {}", JacksonUtils.objToString(data)); list.add(data); if (list.size() > MAX_BATCH_COUNT) { // 保存數(shù)據(jù) saveData(); // 清空list list.clear(); } } /** * 所有數(shù)據(jù)解析完成后,會來調(diào)用一次 * 作用: 避免最后集合中小于 MAX_BATCH_COUNT 條的數(shù)據(jù)沒有被保存 * * @param context */ @Override public void doAfterAllAnalysed(AnalysisContext context) { saveData(); logger.info(" ==> 數(shù)據(jù)解析完成 <=="); } /** * 保存數(shù)據(jù): 正式應(yīng)該插入數(shù)據(jù)庫,這里用于測試 */ private void saveData() { logger.info(" ==> 數(shù)據(jù)保存開始: {}", list.size()); list.forEach(System.out::println); logger.info(" ==> 數(shù)據(jù)保存結(jié)束 <=="); } /** * 在轉(zhuǎn)換異常 獲取其他異常下會調(diào)用本接口。我們?nèi)绻蹲讲⑹謩訏伋霎惓t停止讀取。如果這里不拋出異常則 繼續(xù)讀取下一行。 * * @param exception * @param context * @throws Exception */ @Override public void onException(Exception exception, AnalysisContext context) throws Exception { logger.error(" ==> 數(shù)據(jù)解析失敗,但是繼續(xù)讀取下一行:{}", exception.getMessage()); // 如果是某一個單元格的轉(zhuǎn)換異常 能獲取到具體行號 if (exception instanceof ExcelDataConvertException) { ExcelDataConvertException convertException = (ExcelDataConvertException) exception; logger.error("第{}行,第{}列數(shù)據(jù)解析異常", convertException.getRowIndex(), convertException.getColumnIndex()); } } }
6. 讀取測試
@ApiOperation(value = "數(shù)據(jù)導(dǎo)入測試", notes = "操作日志導(dǎo)入測試[OperationalLog]", hidden = true) @PostMapping("/import") public R excelImport(@RequestParam("file") MultipartFile file) throws IOException { EasyExcel.read(file.getInputStream(), OperationalLog.class, new MyExcelImportConfig<>(operationalLogService)) .sheet().doRead(); return R.ok().message("文件導(dǎo)入成功"); }
7. 附上自定義屬性轉(zhuǎn)換器
轉(zhuǎn)換器的屬性內(nèi)容轉(zhuǎn)換,需要根據(jù)自己的實際業(yè)務(wù)需求而定,這里僅作為簡單示例
/** * @author Mr.Horse * @version 1.0 * @description: 自定義excel轉(zhuǎn)換器: 將操作日志的請求耗時加上單位 "ms" * @date 2021/4/27 10:25 */ public class CustomRequestTimeConverter implements Converter<Long> { /** * 讀取數(shù)據(jù)時: 屬性對應(yīng)的java數(shù)據(jù)類型 * * @return */ @Override public Class<Long> supportJavaTypeKey() { return Long.class; } /** * 寫入數(shù)據(jù)時: excel內(nèi)部的數(shù)據(jù)類型,因為請求耗時是long類型,對應(yīng)excel是NUMBER類型,但是加上"ms后對應(yīng)的是STRING類型" * * @return */ @Override public CellDataTypeEnum supportExcelTypeKey() { return CellDataTypeEnum.STRING; } /** * 讀取回調(diào) * * @param cellData * @param contentProperty * @param globalConfiguration * @return * @throws Exception */ @Override public Long convertToJavaData(CellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception { // 截取字符串: "ms",轉(zhuǎn)換為long類型 String value = cellData.getStringValue(); return Long.valueOf(value.substring(0, value.length() - 2)); } @Override public CellData<Long> convertToExcelData(Long value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception { // 添加字符串: "ms" return new CellData<>(String.valueOf(value).concat("ms")); } }
格式化時間
/** * @author Mr.Horse * @version 1.0 * @description: {description} * @date 2021/4/27 14:01 */ public class CustomTimeFormatConverter implements Converter<Date> { @Override public Class<Date> supportJavaTypeKey() { return Date.class; } @Override public CellDataTypeEnum supportExcelTypeKey() { return CellDataTypeEnum.STRING; } @Override public Date convertToJavaData(CellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception { String value = cellData.getStringValue(); return DateUtil.parse(value, DatePattern.NORM_DATETIME_PATTERN); } @Override public CellData<Date> convertToExcelData(Date value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception { return new CellData<>(DateUtil.format(value, DatePattern.NORM_DATETIME_PATTERN)); } }
EasyExcel簡單使用,到此結(jié)束,打完收功。
以上就是SpringBoot整合EasyExcel實現(xiàn)文件導(dǎo)入導(dǎo)出的詳細內(nèi)容,更多關(guān)于SpringBoot整合EasyExcel的資料請關(guān)注腳本之家其它相關(guān)文章!
- SpringBoot?整合?EasyExcel?實現(xiàn)自由導(dǎo)入導(dǎo)出功能
- SpringBoot整合EasyExcel實現(xiàn)批量導(dǎo)入導(dǎo)出
- SpringBoot整合easyExcel實現(xiàn)CSV格式文件的導(dǎo)入導(dǎo)出
- SpringBoot整合EasyExcel實現(xiàn)復(fù)雜Excel表格的導(dǎo)入導(dǎo)出
- 使用SpringBoot與EasyExcel實現(xiàn)復(fù)雜的導(dǎo)入導(dǎo)出
- SpringBoot整合EasyExcel實現(xiàn)導(dǎo)入導(dǎo)出功能
- SpringBoot中EasyExcel實現(xiàn)execl導(dǎo)入導(dǎo)出
- SpringBoot整合EasyExcel實現(xiàn)導(dǎo)入導(dǎo)出數(shù)據(jù)
- Springboot整合easyexcel實現(xiàn)一個接口任意表的Excel導(dǎo)入導(dǎo)出
相關(guān)文章
java 多線程Thread與runnable的區(qū)別
這篇文章主要介紹了java 多線程Thread與runnable的區(qū)別的相關(guān)資料,java線程有兩種方法繼承thread類與實現(xiàn)runnable接口,下面就提供實例幫助大家理解,需要的朋友可以參考下2017-08-08JAVA 16位ID生成工具類含16位不重復(fù)的隨機數(shù)數(shù)字+大小寫
這篇文章主要介紹了JAVA 16位ID生成工具類含16位不重復(fù)的隨機數(shù)數(shù)字+大小寫,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02MybatisPlus3.3.0沒有MybatisPlusInterceptor類問題的解決方法
項目使用的是mybatis-plus-extension3.3.0依賴,然后在我使用分頁插件的時候,發(fā)現(xiàn)無法導(dǎo)入MybatisPlusInterceptor類所以本文給大家介紹了MybatisPlus3.3.0沒有MybatisPlusInterceptor類問題的解決方法,需要的朋友可以參考下2023-12-12詳解Spring系列之@ComponentScan自動掃描組件
這篇文章主要介紹了Spring @ComponentScan自動掃描組件使用,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-06-06Java實現(xiàn)Word/Pdf/TXT轉(zhuǎn)html的實例代碼
本文主要介紹了Java實現(xiàn)Word/Pdf/TXT轉(zhuǎn)html的實例代碼,代碼簡單易懂,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2020-02-02idea編輯XML文件出現(xiàn):Tag name expected報錯的解決
在XML中,一些特殊字符不能直接使用,因為它們被保留用于XML文檔的結(jié)構(gòu)和語法,如果直接使用這些保留字符,會導(dǎo)致解析錯誤,正確的做法是使用實體引用或字符引用,或者使用CDATA標記將這些字符包裹起來2025-01-01SpringSecurit鹽值加密的密碼驗證以及強密碼驗證過程
在密碼加密過程中,鹽值的使用可以增強密碼的安全性,如果忘記存儲鹽值,將無法驗證密碼,強密碼應(yīng)包含數(shù)字、字母和特殊字符,長度應(yīng)在8到30位之間,以提高賬戶安全2023-03-03