SpringBoot integration EasyExcel implementation
The preparatory work
Note: Click on the Demo website
1. Introduce POM dependencies
<! --easyExcel-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
</dependency>
Copy the code
2. Implement functions
- Combined with the Vue front end, the log file can be exported directly from the browser page
- Implement file import
Excel file download
3. Log entity class
There are custom converters in entity classes: for converting Java type data to Excel type data, very useful. With annotations, it is very convenient to export Excel files.
/** * <p> * Operation log information * </p> **@author horse
* @since2020-09-08 * Note: entity class if used@Accessory(chain=true), then the imported data cannot be populated into the instance, and the exported data is not affected */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("tb_operational_log")
@apiModel (value = "OperationalLog object ", description = "operation log information ")
public class OperationalLog implements Serializable {
private static final long serialVersionUID = 1L;
@excelProperty ({" operation log ", "log ID"})
@apiModelProperty (value = "log ID")
@TableId(value = "id", type = IdType.ASSIGN_ID)
private String id;
@excelProperty ({" operation log ", "operation type "})
@apiModelProperty (value = "operation type ")
private String operType;
@excelProperty ({" Operation log ", "operation description "})
@apiModelProperty (value = "operation description ")
private String operDesc;
@excelProperty ({" operation log ", "operator ID"})
@apiModelProperty (value = "operator ID")
private String operUserId;
@excelProperty ({" operation log ", "operator name "})
@apiModelProperty (value = "operator name ")
private String operUserName;
@excelProperty ({" log ", "operation method "})
@apiModelProperty (value = "method ")
private String operMethod;
@excelProperty ({" operation log ", "request method "})
@apiModelProperty (value = "request method ")
private String operRequWay;
@ ExcelProperty (value = {" operation log ", "request time consuming: unit - ms"}, the converter = CustomRequestTimeConverter. Class)
@apiModelProperty (value = "Request time: -ms")
private Long operRequTime;
@excelProperty ({" operation log ", "request parameters "})
@apiModelProperty (value = "request parameter ")
private String operRequParams;
@excelProperty ({" Operation log ", "request Body"})
@apiModelProperty (value = "request Body")
private String operRequBody;
@excelProperty ({" operation log ", "request IP"})
@apiModelProperty (value = "request IP")
private String operRequIp;
@excelProperty ({" operation log ", "request URL"})
@apiModelProperty (value = "request URL")
private String operRequUrl;
@ ExcelProperty (value = {" operation log ", "log logo"}, the converter = CustomLogFlagConverter. Class)
@APIModelProperty (value = "Log ID: 1-admin,0-portal")
private Boolean logFlag;
@excelProperty ({" operation log ", "operation status "})
@apiModelProperty (value = "operation status :1- successful,0- failed ")
@TableField(value = "is_success")
private Boolean success;
@ExcelIgnore
@apiModelProperty (value = "logical delete 1- not deleted, 0- deleted ")
@TableField(value = "is_deleted")
@TableLogic(value = "1", delval = "0")
private Boolean deleted;
@ ExcelProperty (value = {" operation log ", "creation time"}, the converter = CustomTimeFormatConverter. Class)
@apiModelProperty (value = "create time ")
private Date gmtCreate;
}
Copy the code
4. Interface and implementation
4.1 interface
@operatinglog (operType = BlogConstants.EXPORT, operDesc = "EXPORT operation log to the response stream ")
@apiOperation (value = "Export operation log ", hidden = true)
@PostMapping("/oper/export")
public void operLogExport(@RequestBody List<String> logIds, HttpServletResponse response) {
operationalLogService.operLogExport(logIds, response);
}
Copy the code
4.2 Implementation
- Customize export policies
HorizontalCellStyleStrategy
- Custom export interceptor
CellWriteHandler
, more precise custom export policy
/** * Export operation logs (consider paging export) **@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);
// If logIds are not null, query information by ID. Otherwise, query all information
if(! CollectionUtils.isEmpty(logIds)) { operationalLogs =this.listByIds(logIds);
} else {
operationalLogs = this.list(queryWrapper);
}
outputStream = response.getOutputStream();
// Get the cell style
HorizontalCellStyleStrategy strategy = MyCellStyleStrategy.getHorizontalCellStyleStrategy();
// Write response output stream data
EasyExcel.write(outputStream, OperationalLog.class).excelType(ExcelTypeEnum.XLSX).sheet("Operation Information Log")
/ /. RegisterWriteHandler (new LongestMatchColumnWidthStyleStrategy ()) / / adaptive column width (not very adapt to, the effect is not good)
.registerWriteHandler(strategy) // Register the format policy set above
.registerWriteHandler(new CustomCellWriteHandler()) // Set a custom format policy
.doWrite(operationalLogs);
} catch (Exception e) {
log.error(ExceptionUtils.getMessage(e));
throw new BlogException(ResultCodeEnum.EXCEL_DATA_EXPORT_ERROR);
} finally{ IoUtil.close(outputStream); }}Copy the code
The custom export policy is as follows:
/ * * *@author Mr.Horse
* @version 1.0
* @description: Cell style strategy *@date2021/4/30 why do * /
public class MyCellStyleStrategy {
/** * Sets the cell style (for testing only) **@returnStyle strategy */
public static HorizontalCellStyleStrategy getHorizontalCellStyleStrategy(a) {
// Table header policy
WriteCellStyle headerCellStyle = new WriteCellStyle();
// The header is horizontally centered
headerCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
/ / the background color
headerCellStyle.setFillForegroundColor(IndexedColors.SKY_BLUE.getIndex());
WriteFont headerFont = new WriteFont();
headerFont.setFontHeightInPoints((short) 14);
headerCellStyle.setWriteFont(headerFont);
// wrap
headerCellStyle.setWrapped(Boolean.FALSE);
// Content strategy
WriteCellStyle contentCellStyle = new WriteCellStyle();
// Set the format of data allowed. 49 indicates that all data can be allowed
contentCellStyle.setDataFormat((short) 49);
// Set the background color: You need to specify FillPatternType as FillPatternType. Otherwise, the background color cannot be displayed. The header defaults to FillPatternType so you can leave it unspecified
contentCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);
contentCellStyle.setFillForegroundColor(IndexedColors.GREY_40_PERCENT.getIndex());
// Align the content to the left
contentCellStyle.setHorizontalAlignment(HorizontalAlignment.LEFT);
// Set the font
WriteFont contentFont = new WriteFont();
contentFont.setFontHeightInPoints((short) 12);
contentCellStyle.setWriteFont(contentFont);
// Set line wrap
contentCellStyle.setWrapped(Boolean.FALSE);
// Set the border style and color
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());
// Add formatting to the cell style policy
return newHorizontalCellStyleStrategy(headerCellStyle, contentCellStyle); }}Copy the code
Custom export interceptors are simple as follows:
/ * * *@author Mr.Horse
* @version 1.0
* @descriptionImplement CellWriteHandler interface for precise control of cell style *@dateAnd great 2021/4/29 * /
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) {}/** * after the cell is created (no value is written) **@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) {}/** * after cell processing (already written value): set the header hyperlink of the first row and column to the official website of EasyExcel (excel 0,1 is exported by this system, so only set the hyperlink of the first row) *@param writeSheetHolder
* @param writeTableHolder
* @param cellDataList
* @param cell
* @param head
* @param relativeRowIndex
* @param isHead
*/
@Override
public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List
cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead)
{
// Set the hyperlink
if (isHead && cell.getRowIndex() == 0 && cell.getColumnIndex() == 0) {
logger.info("==> Line {}, hyperlink set {} complete", 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);
}
// Format the cell exactly
boolean bool = isHead && cell.getRowIndex() == 1 &&
(cell.getStringCellValue().equals("Request parameters") || cell.getStringCellValue().equals("Request Body"));
if (bool) {
logger.info("Row {}, column {} cell style set.", cell.getRowIndex(), cell.getColumnIndex());
// Get the workbook
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); }}}Copy the code
4.3 Front-end Request
In the front end, based on Vue+Element, click the export button and download it in the browser page.
// Batch export
batchExport() {
// Iterate over the list of id sets
const logIds = []
this.multipleSelection.forEach(item= > {
logIds.push(item.id)
})
// Request the back-end interface
axios({
url: this.BASE_API + '/admin/blog/log/oper/export'.method: 'post'.data: logIds,
responseType: 'arraybuffer'.headers: { 'token': getToken() }
}).then(response= > {
// Type can be set to text type, here is the new Excel type
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 Operation Log ' // Download the file name
// For tags, only Firefox and Chrome (kernel) support the Download attribute
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) // Release the URL object
} else {
// Compatible with Internet Explorer
window.navigator.msSaveBlob(blob, fileName)
}
})
}
Copy the code
Test results: ok, the basic realization of the page download function
Excel file import
5. Configure file reading
This configuration is written based on generics and has strong scalability.
/ * * *@author Mr.Horse
* @version 1.0
* @descriptionEasyExcel file reads configuration (cannot be managed by Spring) *@date 2021/4/27 13:24
*/
public class MyExcelImportConfig<T> extends AnalysisEventListener<T> {
private static Logger logger = LoggerFactory.getLogger(MyExcelImportConfig.class);
/** * The maximum number of bytes read at a time */
private static final int MAX_BATCH_COUNT = 10;
/** * Generic bean properties */
private T dynamicService;
/** * a generic List collection that can accept any argument */
List<T> list = new ArrayList<>();
/** * Constructors inject beans (dynamically injected based on incoming beans) **@param dynamicService
*/
public MyExcelImportConfig(T dynamicService) {
this.dynamicService = dynamicService;
}
/** * calls ** to parse each piece of data@param data
* @param context
*/
@Override
public void invoke(T data, AnalysisContext context) {
logger.info("==> Parse a piece of data: {}", JacksonUtils.objToString(data));
list.add(data);
if (list.size() > MAX_BATCH_COUNT) {
// Save data
saveData();
/ / to empty the listlist.clear(); }}/** * if MAX_BATCH_COUNT is less than MAX_BATCH_COUNT in the final set, it will be called **@param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
saveData();
logger.info("==> Data parsing completed <==");
}
/** * Save data: it should be formally inserted into the database, which is used for testing */
private void saveData(a) {
logger.info("==> Data save begins: {}", list.size());
list.forEach(System.out::println);
logger.info("==> End of data saving <==");
}
/** * this interface is called when the conversion exception gets another exception. We stop reading if we catch and manually throw an exception. Continue reading the next line if no exception is thrown here. * *@param exception
* @param context
* @throws Exception
*/
@Override
public void onException(Exception exception, AnalysisContext context) throws Exception {
logger.error("==> Data parsing failed, but continue reading the next line :{}", exception.getMessage());
// If it is a conversion exception of a cell, the specific line number can be obtained
if (exception instanceof ExcelDataConvertException) {
ExcelDataConvertException convertException = (ExcelDataConvertException) exception;
logger.error("{} line, {} column data parsing exception", convertException.getRowIndex(), convertException.getColumnIndex()); }}}Copy the code
6. Read test
@apiOperation (value = "data import test ", notes =" Operation log import test [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("File imported successfully");
}
Copy the code
7. Attach custom property converter
The conversion of the property content of the converter depends on your actual business needs, and this is just a simple example
/ * * *@author Mr.Horse
* @version 1.0
* @description: Custom Excel converter: Add the operation log request time in units of "ms" *@date2021/4/27 assembling of * /
public class CustomRequestTimeConverter implements Converter<Long> {
/** * When reading data: the property corresponds to the Java data type **@return* /
@Override
public Class<Long> supportJavaTypeKey(a) {
return Long.class;
}
/** * Write data: data type inside Excel, because the request time is long, corresponding to excel is NUMBER, but "ms "corresponds to STRING" **@return* /
@Override
public CellDataTypeEnum supportExcelTypeKey(a) {
return CellDataTypeEnum.STRING;
}
/** * read the fetch call **@param cellData
* @param contentProperty
* @param globalConfiguration
* @return
* @throws Exception
*/
@Override
public Long convertToJavaData(CellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
// Take the string "ms" and convert it to 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 {
// Add string: "ms"
return new CellData<>(String.valueOf(value).concat("ms")); }}Copy the code
Formatting time
/ * * *@author Mr.Horse
* @version 1.0
* @description: {description}
* @date2021/4/27 14:01 * /
public class CustomTimeFormatConverter implements Converter<Date> {
@Override
public Class<Date> supportJavaTypeKey(a) {
return Date.class;
}
@Override
public CellDataTypeEnum supportExcelTypeKey(a) {
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 newCellData<>(DateUtil.format(value, DatePattern.NORM_DATETIME_PATTERN)); }}Copy the code
EasyExcel simple use, this is the end, finished work.