Rest简单导出csv格式数据文件
- 前言
- 一、引入HttpServletResponse
- 依赖注入
- 工具类
- 方法参数
- 二、导出实现
- 三、基于切面赋能接口
- 四、效果展示
- 五、基于HandlerMethod扩展点实现
前言
由于最近开发组件与数据相关,在数据规模越来越大后,传统swagger界面针对大批量的数据查询越来越慢(接口本身性能没问题),为了解决浏览器和swagger界面本身JSON渲染慢的问题,考虑可以通过指定的Rest接口,导出数据文件,方便用于核对数据。
一、引入HttpServletResponse
依赖注入
@Autowired
private HttpServletRequest request;
@Autowired
private HttpServletResponse response;
工具类
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
HttpServletRequest request = servletRequestAttributes.getRequest();
HttpServletResponse response = servletRequestAttributes.getResponse();
方法参数
@GetMapping(value = "")
public String download(HttpServletRequest request,HttpServletResponse response) {
//...
在方法上加上参数,Springboot就会帮你绑定,你可以直接使用。如果你的方法有其他参数,把这两个参数加到后面即可。
二、导出实现
导出的实现,其实也十分简单,主要就是操作response进行数据写入即可。下面提供最原生的代码示例。
@RequestMapping("/download")
public void download(@RequestParam("data") String data) {
response.reset();
response.addHeader("content-type", "application/octet-stream");
response.setContentType("application/octet-stream;charset=UTF-8");
response.addHeader("Content-Disposition", "attachment;filename=" + "output.csv");
response.setStatus(HttpServletResponse.SC_OK);
ServletOutputStream outputStream = null;
try {
outputStream = response.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
}
for (int i = 0; i < 10; i++) {
String info = "field-" + i;
try {
outputStream.write(info.getBytes("UTF-8"));
} catch (IOException e) {
e.printStackTrace();
}
if (i < 9) {
try {
outputStream.write(",".getBytes("UTF-8"));
} catch (IOException e) {
e.printStackTrace();
}
} else {
try {
outputStream.write("\n".getBytes("UTF-8"));
} catch (IOException e) {
e.printStackTrace();
}
}
}
for (int i = 0; i < 10; i++) {
String info = "value-" + i;
try {
outputStream.write(info.getBytes("UTF-8"));
} catch (IOException e) {
e.printStackTrace();
}
if (i < 9) {
try {
outputStream.write(",".getBytes("UTF-8"));
} catch (IOException e) {
e.printStackTrace();
}
} else {
try {
outputStream.write("\n".getBytes("UTF-8"));
} catch (IOException e) {
e.printStackTrace();
}
}
}
try {
if (outputStream != null) {
outputStream.flush();
}
if (outputStream != null) {
outputStream.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
三、基于切面赋能接口
聪明如你,可能已经想到了,每个数据查询接口,都要写一个对应的导出文件接口啊,也太low了吧。确实,很明显可以考虑借助AOP实现批量接口的数据文件导出功能。
增加切面相关的JAR包依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
定义注解:
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NeedCsvExport {
}
切面类:
@Aspect
@Component
public class CsvExport {
@Autowired
private HttpServletRequest request;
@Autowired
private HttpServletResponse response;
@Around("@annotation(NeedCsvExport)))")
public Object export(ProceedingJoinPoint joinPoint) {
String header = request.getHeader("csv-export");
System.out.println(header);
if (!Boolean.parseBoolean(header)) {
try {
return joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
try {
ServletOutputStream outputStream = response.getOutputStream();
response.reset();
response.addHeader("content-type", "application/octet-stream");
response.setContentType("application/octet-stream;charset=UTF-8");
response.addHeader("Content-Disposition", "attachment;filename=" + "out.csv");
response.setStatus(HttpServletResponse.SC_OK);
Object proceed = joinPoint.proceed();
//导入
StringBuffer head = new StringBuffer();
Field[] declaredFields = proceed.getClass().getDeclaredFields();
for (int i = 0; i < declaredFields.length; i++) {
String name = declaredFields[i].getName();
if (i < declaredFields.length - 1) {
head.append(name).append(",");
} else {
head.append(name);
}
}
writeString(head.toString(), outputStream);
close(outputStream);
return null;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return null;
}
private void writeString(String content, ServletOutputStream outputStream) throws IOException {
outputStream.write(content.getBytes(Charset.forName("UTF-8")));
}
private void close(ServletOutputStream outputStream) {
try {
if (outputStream != null) {
outputStream.flush();
}
if (outputStream != null) {
outputStream.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
从具体的实现中可以看到,如果检测到http请求头中有变化csv-export且为true,则将查询结果导出到csv文件中,并原接口返回null。
使用:
@NeedCsvExport
@RequestMapping("/push")
public Result push(@RequestParam("data") String data) {
String result = loopLongPollingService.push(data);
return new Result(result);
}
四、效果展示
第二节实现后,在浏览器输出对应url:http://localhost:18081/loop/download,则直接自动下载csv文件
内容:
第三节实现后,请求地址:http://localhost:18081/loop/push?data=1234,不设置head请求头的csv-export,方法正常执行和返回,如图:
设置csv-export:true后,执行:
点击下载,则会下载结果数据文件。
五、基于HandlerMethod扩展点实现
如下所示:
1. HandlerMethodArgumentResolver
2. HandlerMethodArgumentResolverComposite
3. HandlerMethodReturnValueHandler
4. HandlerMethodReturnValueHandlerComposite
在接口org.springframework.web.servlet.config.annotation.WebMvcConfigurer的方法addReturnValueHandlers和addArgumentResolvers可以加入上面具体Resolver的实现,通过该处扩展点则可以叠加对于方法执行的控制。当然WebMvcConfigurer还有很多扩展点可以使用,如:
public interface WebMvcConfigurer {
default void configurePathMatch(PathMatchConfigurer configurer) {
}
default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
}
default void configureAsyncSupport(AsyncSupportConfigurer configurer) {
}
default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
}
default void addFormatters(FormatterRegistry registry) {
}
default void addInterceptors(InterceptorRegistry registry) {
}
default void addResourceHandlers(ResourceHandlerRegistry registry) {
}
default void addCorsMappings(CorsRegistry registry) {
}
default void addViewControllers(ViewControllerRegistry registry) {
}
default void configureViewResolvers(ViewResolverRegistry registry) {
}
default void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
}
default void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {
}
default void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
}
default void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
}
default void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
}
default void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
}
@Nullable
default Validator getValidator() {
return null;
}
@Nullable
default MessageCodesResolver getMessageCodesResolver() {
return null;
}
}
代码示例:
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.charset.Charset;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import org.springframework.core.MethodParameter;
import org.springframework.util.Assert;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
import com.example.demo.control.NeedCsvExport;
public class NeedCsvReturnValueHandler implements HandlerMethodReturnValueHandler {
@Override
public boolean supportsReturnType(MethodParameter methodParameter) {
return methodParameter.getMethodAnnotation(NeedCsvExport.class) != null;
}
@Override
public void handleReturnValue(Object returnValue, MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest) throws Exception {
HttpServletResponse response = nativeWebRequest.getNativeResponse(HttpServletResponse.class);
Assert.state(response != null, "No HttpServletResponse");
NeedCsvExport responseExcel = methodParameter.getMethodAnnotation(NeedCsvExport.class);
Assert.state(responseExcel != null, "No @NeedCsvExport");
modelAndViewContainer.setRequestHandled(true);
response.reset();
response.addHeader("content-type", "application/octet-stream");
response.setContentType("application/octet-stream;charset=UTF-8");
response.addHeader("Content-Disposition", "attachment;filename=" + "out.csv");
response.setStatus(HttpServletResponse.SC_OK);
//导入
StringBuffer head = new StringBuffer();
Field[] declaredFields = returnValue.getClass().getDeclaredFields();
for (int i = 0; i < declaredFields.length; i++) {
String name = declaredFields[i].getName();
if (i < declaredFields.length - 1) {
head.append(name).append(",");
} else {
head.append(name);
}
}
ServletOutputStream outputStream = response.getOutputStream();
writeString(head.toString(), outputStream);
close(outputStream);
}
private void writeString(String content, ServletOutputStream outputStream) throws IOException {
outputStream.write(content.getBytes(Charset.forName("UTF-8")));
}
private void close(ServletOutputStream outputStream) {
try {
if (outputStream != null) {
outputStream.flush();
}
if (outputStream != null) {
outputStream.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
通过配置注入上述Handler
import java.util.List;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
public class NeedCsvWebMvcConfigurer implements WebMvcConfigurer {
@Override
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {
handlers.add(new NeedCsvReturnValueHandler());
}
}
@Configuration
public class ThreadPoolConfig {
@Bean
public WebMvcConfigurer getWebMvcConfigurer() {
NeedCsvWebMvcConfigurer configurer = new NeedCsvWebMvcConfigurer();
return configurer;
}
}
通过直接定义成Component注入,只需要在NeedCsvWebMvcConfigurer增加Component注解即可。
@Component
public class NeedCsvWebMvcConfigurer implements WebMvcConfigurer {
而CSV格式或xls格式文件的导出,可以借助开源项目做到更优雅导入导出或直接实现自定义的starter
ps.需要注意的是HandlerMethodReturnValueHandler处理器,针对@RestController注解类中方法是无效的