【Spring】数据导出为Excel的接口报java.io.IOException: UT010029: Stream is closed错误

2023-09-15 15:59:47

数据导出为Excel的接口报java.io.IOException: UT010029: Stream is closed错误

实习时导师让写一个平台信息导出为Excel的功能,写完之后发现文件正常导出,但控制台一直报Stream is closed错误。在网上找了大半天,都说是使用 OutputStream时关闭了流导致的,这也确实是可能导致报错的原因之一,但我并未手动关闭OutputStream,排查半天,问了旁边大佬才知道文件下载接口不能有返回值。。。。。。。。。。因为接口被调用后response会自动关闭ServletOutputStream,而return时会再次自动关闭OutputStream,就会导致 Stream is closed问题。

导出文件正常:

image-20230915155529371

报错信息:

image-20230915155316789

java.io.IOException: UT010029: Stream is closed
	at io.undertow.servlet.spec.ServletOutputStreamImpl.write(ServletOutputStreamImpl.java:138)
	at com.fasterxml.jackson.core.json.UTF8JsonGenerator._flushBuffer(UTF8JsonGenerator.java:2171)
	at com.fasterxml.jackson.core.json.UTF8JsonGenerator.flush(UTF8JsonGenerator.java:1184)
	at com.fasterxml.jackson.databind.ObjectWriter.writeValue(ObjectWriter.java:1009)
	at org.springblade.core.tool.jackson.AbstractReadWriteJackson2HttpMessageConverter.writeInternal(AbstractReadWriteJackson2HttpMessageConverter.java:116)
	at org.springblade.core.tool.jackson.MappingApiJackson2HttpMessageConverter.writeInternal(MappingApiJackson2HttpMessageConverter.java:73)
	at org.springframework.http.converter.AbstractGenericHttpMessageConverter.write(AbstractGenericHttpMessageConverter.java:104)
	at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:290)
	at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:183)
	at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:78)
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:135)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1067)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:497)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:584)
	at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74)
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129)
	at org.springblade.core.log.filter.LogTraceFilter.doFilter(LogTraceFilter.java:39)
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
	at org.springblade.core.boot.request.BladeRequestFilter.doFilter(BladeRequestFilter.java:58)
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
	at com.alibaba.druid.support.http.WebStatFilter.doFilter(WebStatFilter.java:114)
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
	at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:96)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
	at io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84)
	at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)
	at io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68)
	at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
	at io.undertow.servlet.handlers.RedirectDirHandler.handleRequest(RedirectDirHandler.java:68)
	at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:117)
	at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)
	at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
	at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)
	at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)
	at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)
	at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77)
	at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)
	at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
	at io.undertow.servlet.handlers.SendErrorPageHandler.handleRequest(SendErrorPageHandler.java:52)
	at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
	at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:275)
	at io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:79)
	at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:134)
	at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:131)
	at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)
	at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
	at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:255)
	at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:79)
	at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:100)
	at io.undertow.server.Connectors.executeRootHandler(Connectors.java:387)
	at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:852)
	at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
	at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:2019)
	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1558)
	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1423)
	at org.xnio.XnioWorker$WorkerThreadFactory$1$1.run(XnioWorker.java:1282)
	at java.lang.Thread.run(Thread.java:750)

报错接口如下:

	@GetMapping("/exportExcel")
	@ApiOperationSupport(order = 10)
	@ApiOperation(value = "导出数据为Excel表格", notes = "导出数据为Excel表格")
	public R exportExcel(@ApiParam(value = "表单id", required = true) @RequestParam Long formId,
							@ApiParam(value = "数据列表ids", required = true) @RequestParam String ids,
							HttpServletResponse response) {

		List<Map<String, Object>> data;
		List<Long> longIds = Func.toLongList(ids);

		//通过表单id,获取表单配置
		FormEntity formEntity = formService.getById(formId);
		if (null == formEntity) {
			return R.fail("未查询到表单");
//			throw new ServiceException("未查询到表单");
		}

		// 标题字段name
		Set<String> title;
		// 导出的字段列表
		List<FormFieldDTO> formLabels;
		try {
			Long modelId = formEntity.getModelId();
			ModelBase model = modelBaseService.getById(modelId);
			String tableName = model.getName();
			Integer type = model.getType();

			// 获取当前表单的字段信息
			List<FormFieldDTO> formFields = formService.getFormFieldsInfoByFormId(formId);
			Set<String> fieldSet = formFields.stream()
				.filter(item -> item.getModelFieldId() != null)
				.map(FormFieldDTO::getName)
				.collect(Collectors.toSet());


			// 更换数据源
			Long dataSourceId = model.getSourceId();
			if (dataSourceId != null) {
				DynamicDataSourceContextHolder.push(dataSourceId.toString());
			}

			// 获取当前表单的所有字段
			List<Map<String, Object>> fieldsList = baseSqlService.getAllFields(tableName, type);
			Set<String> allFields = fieldsList.get(0).keySet();
			// 求公共字段
			fieldSet.retainAll(allFields);
			// 获取公共字段对应的name
			formLabels = formFields.stream().filter(item -> fieldSet.contains(item.getName())).collect(Collectors.toList());
			// 判断数据库表中是否存在status字段
			if (allFields.contains("status")){
				fieldSet.add("status");
			}

			// 提取字段名
			// 通过IFNULL函数解决mybatis映射Map时忽略控制字段问题
			title = fieldSet;
			String fields = String.join(",", fieldSet);

			data = baseSqlService.queryFieldsByIds(tableName, fields, longIds);

		} catch (Exception e) {
			return R.fail(e.toString());
//			e.printStackTrace();
//			return;
		} finally {
			// 将数据源切换回来
			DynamicDataSourceContextHolder.clear();
		}

		// 结果数据匹配中文信息
		List<Map<String, Object>> recordsWrapper = BaseListRecordsWrapper.build().wrapper(data, formEntity.getStyleJson());

		// 获取表单列表中不存在的字段名
		// **此处需要创建新的HashSet对象,否则removeAll会删除原信息值**
		Set<String> recordFields = new HashSet<>(recordsWrapper.get(0).keySet());
		recordFields.removeAll(title);
		recordFields.forEach(item -> {
			FormFieldDTO formFieldDTO = new FormFieldDTO();
			formFieldDTO.setName(item);
			if ("statusName".equals(item)) {
				formFieldDTO.setLabel("状态");
			} else if ("create_user_name".equals(item)){
				formFieldDTO.setLabel("创建人");
			} else {
				formFieldDTO.setLabel(item);
			}
			formLabels.add(formFieldDTO);
		});
		// 剔除原有的status,仅导出statusName即可
		if (recordFields.contains("statusName")){
			recordsWrapper.forEach(item -> item.remove("status"));
			title.remove("status");
		}
		if (recordFields.contains("create_user_name")){
			recordsWrapper.forEach(item -> item.remove("create_user"));
			title.remove("create_user");
		}
		// 将多出的字段添加至标题(如statusName)
		title.addAll(recordFields);

		Date date = new Date();
		SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
		try {
			ExportUtil.writeExcel(response, recordsWrapper, dateFormat.format(date), "默认页", title, formLabels);
		} catch (IOException e) {
			log.error(e.toString());
		}

		return R.success("下载成功!");
	}
}

修改后:

	@GetMapping("/exportExcel")
	@ApiOperationSupport(order = 10)
	@ApiOperation(value = "导出数据为Excel表格", notes = "导出数据为Excel表格")
	public void exportExcel(@ApiParam(value = "表单id", required = true) @RequestParam Long formId,
							@ApiParam(value = "数据列表ids", required = true) @RequestParam String ids,
							HttpServletResponse response) {

		List<Map<String, Object>> data;
		List<Long> longIds = Func.toLongList(ids);

		//通过表单id,获取表单配置
		FormEntity formEntity = formService.getById(formId);
		if (null == formEntity) {
			throw new ServiceException("未查询到表单");
		}

		// 标题字段name
		Set<String> title;
		// 导出的字段列表
		List<FormFieldDTO> formLabels;
		try {
			Long modelId = formEntity.getModelId();
			ModelBase model = modelBaseService.getById(modelId);
			String tableName = model.getName();
			Integer type = model.getType();

			// 获取当前表单的字段信息
			List<FormFieldDTO> formFields = formService.getFormFieldsInfoByFormId(formId);
			Set<String> fieldSet = formFields.stream()
				.filter(item -> item.getModelFieldId() != null)
				.map(FormFieldDTO::getName)
				.collect(Collectors.toSet());


			// 更换数据源
			Long dataSourceId = model.getSourceId();
			if (dataSourceId != null) {
				DynamicDataSourceContextHolder.push(dataSourceId.toString());
			}

			// 获取当前表单的所有字段
			List<Map<String, Object>> fieldsList = baseSqlService.getAllFields(tableName, type);
			Set<String> allFields = fieldsList.get(0).keySet();
			// 求公共字段
			fieldSet.retainAll(allFields);
			// 获取公共字段对应的name
			formLabels = formFields.stream().filter(item -> fieldSet.contains(item.getName())).collect(Collectors.toList());
			// 判断数据库表中是否存在status字段
			if (allFields.contains("status")){
				fieldSet.add("status");
			}

			// 提取字段名
			// 通过IFNULL函数解决mybatis映射Map时忽略控制字段问题
			title = fieldSet;
			String fields = String.join(",", fieldSet);

			data = baseSqlService.queryFieldsByIds(tableName, fields, longIds);

		} catch (Exception e) {
			e.printStackTrace();
			return;
		} finally {
			// 将数据源切换回来
			DynamicDataSourceContextHolder.clear();
		}

		// 结果数据匹配中文信息
		List<Map<String, Object>> recordsWrapper = BaseListRecordsWrapper.build().wrapper(data, formEntity.getStyleJson());

		// 获取表单列表中不存在的字段名
		// **此处需要创建新的HashSet对象,否则removeAll会删除原信息值**
		Set<String> recordFields = new HashSet<>(recordsWrapper.get(0).keySet());
		recordFields.removeAll(title);
		recordFields.forEach(item -> {
			FormFieldDTO formFieldDTO = new FormFieldDTO();
			formFieldDTO.setName(item);
			if ("statusName".equals(item)) {
				formFieldDTO.setLabel("状态");
			} else if ("create_user_name".equals(item)){
				formFieldDTO.setLabel("创建人");
			} else {
				formFieldDTO.setLabel(item);
			}
			formLabels.add(formFieldDTO);
		});
		// 剔除原有的status,仅导出statusName即可
		if (recordFields.contains("statusName")){
			recordsWrapper.forEach(item -> item.remove("status"));
			title.remove("status");
		}
		if (recordFields.contains("create_user_name")){
			recordsWrapper.forEach(item -> item.remove("create_user"));
			title.remove("create_user");
		}
		// 将多出的字段添加至标题(如statusName)
		title.addAll(recordFields);

		Date date = new Date();
		SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
		try {
			ExportUtil.writeExcel(response, recordsWrapper, dateFormat.format(date), "默认页", title, formLabels);
		} catch (IOException e) {
			log.error(e.toString());
		}
	}

再次调用接口已无报错:

image-20230915155607055

更多推荐

HTTP代理反爬虫技术详解

HTTP代理是一种网络技术,它可以将客户端的请求转发到目标服务器,并将服务器的响应返回给客户端。在网络安全领域中,HTTP代理经常被用来反爬虫,以保护网站的正常运营。HTTP代理反爬虫的原理是通过限制访问者的IP地址、访问频率、User-Agent和验证码验证等方式,来限制恶意爬虫的访问。下面我们来具体分析一下这几种方

拓世AIGC | 大语言模型螺旋上升式进化,人文、技术与未来

本月初,上海世博园举办外滩大会见解论坛中,众多学者和企业家共同探讨了大语言模型时代的人机关系、硅基生命和碳基生命未来之争等议题。面对全新的局面,论坛释放出积极信号和值得持续关注的论点。从黄浦江的波涛翻涌,我们捕捉到了人工智能未来科技的波澜壮阔。(汇集近20位两院院士、诺贝尔奖和图灵奖得主,超500位科技企业家和专家学者

Websocket集群解决方案以及实战(附图文源码)

最近在项目中在做一个消息推送的功能,比如客户下单之后通知给给对应的客户发送系统通知,这种消息推送需要使用到全双工的websocket推送消息。所谓的全双工表示客户端和服务端都能向对方发送消息。不使用同样是全双工的http是因为http只能由客户端主动发起请求,服务接收后返回消息。websocket建立起连接之后,客户端

WebMvcConfigurerAdapter、WebMvcConfigurer、WebMvcConfigurationSupport

WebMvcConfigurerAdapter、WebMvcConfigurer、WebMvcConfigurationSupport的关系?WebMvcConfigurerAdapter、WebMvcConfigurer和WebMvcConfigurationSupport是SpringMVC框架中用于配置Web应用

android系统目录结构

文章目录android系统目录结构问答偏好设置保存在哪里在应用设置中点击清除数据,清除的是什么在应用设置中点击清除缓存,清除的是什么参考android系统目录结构/-system(一般只有root权限才能访问)-data-app(存放应用程序的APK文件)-data(内部存储)-<安装的应用包名>-app_textur

lock和synchronized的区别

lock和synchronized都是在多线程环境下用于保护共享资源的机制,但它们有一些重要的区别:实现方式:synchronized是Java语言内置的关键字,可以用于方法或代码块级别的同步。Lock是一个接口,位于java.util.concurrent.locks包下,提供了更灵活的锁定机制。灵活性:Lock提供

SQL-4大板块(存储过程、函数、视图、触发器)

一、存储过程(做复杂运算)1.做复杂运算,是对变量做运算;2.可以对多个表进行update、insert、delete、select、query;3.可以在最终结果返回多个表,但是对对接环境有苛刻要求,比如:VB不支持接收返回多个表4.当一个查询语句无法实现,或者语句查询速度很慢时,想提高效率就会用到存储过程。先把需要

MongoDB——将时间戳转换为日期

在MongoDB中将时间戳转换为日期从timestamp转换为日期取决于我们保存时间戳的类型。它是对象、数字还是字符串类型?我们可以在mongoshell上使用以下命令检查字段的类型。在本教程中,我们将学习如何将时间戳转换为数字、字符串或对象类型的日期。检查字段类型://MongoDB5.0.8>typeofdb.co

滑动时间窗口的思想和实现,环形数组,golang

固定时间窗口在开发限流组件的时候,我们需要统计一个时间区间内的请求数,比如以分钟为单位。所谓固定时间窗口,就是根据时间函数得到当前请求落在哪个分钟之内,我们在统计的时候只关注当前分钟之内的数量,即[0s,60s],因为流量并不是均匀的,所以就会出现,在两个分钟之间超过阈值,1分50秒时来了150个请求,在2分10秒时来

图像语义分割概述

图像语义分割概述一、图像语义分割概念图像语义分割(ImageSemanticSegmentation)是一项计算机视觉任务,其目标是将输入的图像分割成多个区域,并为每个像素分配一个语义类别标签,以表示该像素属于图像中的哪个物体或区域。与其他图像分割任务不同,图像语义分割不仅关注于分割图像,还要理解图像中不同部分的语义含

【C++】面向对象编程示例 ( 案例需求 | Visual Studio 创建类 | 类的声明 | 类的实现 | 类的调用 )

文章目录一、案例需求二、VisualStudio创建类三、类的声明四、类的实现五、类的调用一、案例需求使用C++面向对象,抽象出一个立方体类;立方体有长/宽/高/面积/体积私有成员变量,以及访问这些成员变量的公共成员方法;还提供立方体的对比函数,对比2个立方体对象是否相等;二、VisualStudio创建类在Visua

热文推荐