目标
通过注解形式完成对一个方法返回值的通用导出功能
工程搭建
pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.13</version>
</parent>
<groupId>com.example</groupId>
<artifactId>export</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>export</name>
<description>export</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.7.6</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- EasyExcel -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>4.0.3</version>
</dependency>
<!-- aspect -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<!-- util -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.15.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>com.example.export.ExportApplication</mainClass>
<skip>true</skip>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
核心代码
CusExport
package com.example.export.annotation;
import java.lang.annotation.*;
/**
* @author PC
* 基于EasyExcel实现动态列导出
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CusExport {
/**
* 导出数据对象
*/
Class<?> dataClass() default Void.class;
/**
* 是否是动态的
*
* @return true 是 false 否
*/
boolean dynamicFlag() default false;
/**
* 文件名,未指定取实体类名,无实体类取 getDefaultFileName()
*
* @return 文件名
*/
String fileName() default "";
}
CusExportAspect
package com.example.export.aspect;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.example.export.annotation.CusExport;
import com.example.export.config.ExportProperties;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.*;
/**
* @author PC
* 导出切面
*/
@Aspect
@Component
public class CusExportAspect {
private final static Logger logger = LoggerFactory.getLogger(CusExportAspect.class);
private final ExportProperties exportProperties;
@Autowired
public CusExportAspect(ExportProperties exportProperties) {
this.exportProperties = exportProperties;
}
@AfterReturning(pointcut = "@annotation(com.example.export.annotation.CusExport)", returning = "result")
public void afterReturningAdvice(JoinPoint joinPoint, Object result) throws IOException {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
CusExport cusExport = methodSignature.getMethod().getAnnotation(CusExport.class);
HttpServletResponse response = getHttpServletResponse();
// 这里URLEncoder.encode可以防止中文乱码
String generatorFileName = StringUtils.isEmpty(cusExport.fileName()) ? exportProperties.getDefaultFileName() : cusExport.fileName();
String fileName = URLEncoder.encode(generatorFileName, "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
if (!cusExport.dynamicFlag()) {
EasyExcel.write(response.getOutputStream(), cusExport.dataClass())
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).sheet(exportProperties.getDefaultSheetName())
.doWrite((Collection<?>) result);
} else {
this.dealSpecified(cusExport, result, fileName, response);
}
}
private static HttpServletResponse getHttpServletResponse() {
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
assert attr != null;
HttpServletResponse response = attr.getResponse();
assert response != null;
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
return response;
}
@SuppressWarnings("unchecked")
private void dealSpecified(CusExport cusExport, Object result, String fileName, HttpServletResponse response) throws IOException {
if (Objects.isNull(cusExport) || Objects.isNull(result) || StringUtils.isEmpty(fileName)) {
logger.error("the required field is missing");
}
logger.debug("the data type is not specified");
List<Map<String, Object>> resultList = new ArrayList<>();
if (result instanceof List) {
resultList = (List<Map<String, Object>>) result;
}
if (CollectionUtils.isEmpty(resultList)) {
logger.info("data is empty");
}
List<List<String>> headList = new ArrayList<>();
boolean fillHeadFlag = false;
for (Map<String, Object> resultItem : resultList) {
if (!fillHeadFlag) {
resultItem.keySet().forEach(keyCode -> headList.add(Collections.singletonList(keyCode)));
fillHeadFlag = true;
}
}
EasyExcel.write(response.getOutputStream())
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
// 放入动态头
.head(headList).sheet(exportProperties.getDefaultSheetName())
.doWrite(convertMapToList(resultList, headList));
}
private List<List<Object>> convertMapToList(List<Map<String, Object>> mapList, List<List<String>> headList) {
List<List<Object>> list = new ArrayList<>();
for (Map<String, Object> map : mapList) {
List<Object> info = new ArrayList<>();
for (List<String> itemHeadList : headList) {
info.add(map.get(itemHeadList.get(0)));
}
list.add(info);
}
return list;
}
}
测试
测试代码
在对应方法添加@CusExport注解即可,导出实体类需指定dataClass,导出动态数据需指定dynamicFlag = true,如需调整生成的文件名,还可以指定fileName
TestExportServiceImpl
package com.example.export.app.service.impl;
import com.example.export.annotation.CusExport;
import com.example.export.app.service.TestExportService;
import com.example.export.domain.entity.ExcelTest;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author PC
*/
@Component
public class TestExportServiceImpl implements TestExportService {
@Override
@CusExport(dataClass = ExcelTest.class)
public List<ExcelTest> generatorDataForClass() {
List<ExcelTest> excelTestList = new ArrayList<>();
for (int j = 0; j < 100; j++) {
ExcelTest excelTest = new ExcelTest();
excelTest.setCode("testCode" + j);
excelTest.setName("testName" + j);
excelTest.setEnabledFlag(j % 2);
excelTestList.add(excelTest);
}
return excelTestList;
}
@Override
@CusExport(dynamicFlag = true)
public List<Map<String, Object>> generatorDataForMap() {
List<Map<String, Object>> excelTestList = new ArrayList<>();
for (int j = 0; j < 100; j++) {
Map<String, Object> item = new HashMap<>(3);
item.put("code", "testCodeMap" + j);
item.put("name", "testNameMap" + j);
item.put("enabledFlag", j % 2);
excelTestList.add(item);
}
return excelTestList;
}
//
}
ExportTestController
package com.example.export.api.controller;
import com.example.export.app.service.TestExportService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author PC
*/
@RestController("v1.exportTestController")
@RequestMapping("/export")
public class ExportTestController {
private final TestExportService testExportService;
@Autowired
public ExportTestController(TestExportService testExportService) {
this.testExportService = testExportService;
}
@GetMapping("/by-class")
public void exportTest(){
testExportService.generatorDataForClass();
}
@GetMapping("/by-map")
public void exportTestMap(){
testExportService.generatorDataForMap();
}
}
测试
访问127.0.0.1:18081/export/by-class
访问127.0.0.1:18081/export/by-map
参考资料
[1].EasyExcel官网文档
[2].demo