SpringBoot 打包为Jar读取Excel模板 写入数据并下载 填坑之旅

版权声明:来至蜀山雪松 https://blog.csdn.net/jianxia801/article/details/85167826

1、应用场景
由于以前我们开发的应用都是在一个Application之中,并且使用的war方式打包发版(SpringBoot版本)的。在最近的项目之中我们改造了成为微服务(SpringCloud版),于是为了方便发版,各个服务之间直接使用jar方式打包。说明一下 war打包和jar打包各有优缺点。
war打包可以在更新一个java class类的时候,直接复制一个class类即可,操作方便。如果不前后端分离,相关的静态文件可以可以直接复制更新。可以使得每次复制的代码量及其少。
jar打包方式可以其他不用管,每次都直接复制覆盖即可不用管其他内容。
参考文章:https://www.cnblogs.com/crushlinux/p/6738070.html
Jar/War/Ear等包的作用与区别详解

jar war EAR
英文 Java Archive file Web Archive file Enterprise Archive file
包含内容 class、properties文件,是文件封装的最小单元;包含Java类的普通库、资源(resources)、辅助文件(auxiliary files)等 Servlet、JSP页面、JSP标记库、JAR库文件、HTML/XML文档和其他公用资源文件,如图片、音频文件等 除了包含JAR、WAR以外,还包括EJB组件
部署文件 application-client.xml web.xml application.xml
容器 应用服务器(application servers) 小型服务程序容器(servlet containers) EJB容器(EJB containers)
级别

2、中途过程记录
由于本次使用微服务(SpringCloud)方式,于是发布jar方式来使用。但是我们的业务使用使用到了Excel模板。我们在导出数据的时候,需要先从jar之中读取到Excel模板,然后把导出结果集List一条一条的写入到Excel模板之中。由于以前我们使用的是War方式打包,以及在IDE开发环境之中,没有打包为Jar包的时候是可以通过常规的Servlet方式获得文件路径,如下的代码所示:

  String realPath = request.getServletContext().getRealPath("/app/exportTemplate/");
String templatePath = realPath + File.separator + templateNames[0]+".xlsx";;

此种方式在打包为War包和本地IDE之中未打包为Jar时候是可以正常运行,并导出数据。
当使用Jar方式打包的时候,此种方式就不起作用了,就导出不了数据,因为打包后的Jar是一个文件,里面包含了你的Excel文件和其他文件。此时需要如下两方面的注意事项:
2.1、打包jar时候包含你的Excel模板文件,以及其他资源文件

<build>
		<finalName>project_module_8811</finalName>
		<resources>
			<resource>
				<directory>src/main/java</directory>
				<filtering>true</filtering>
				<includes>
					<include>**/*.xml</include>
					<include>**/*.properties</include>
				</includes>
			</resource>
			<resource>
				<directory>src/main/resources</directory>
				<filtering>true</filtering>
				<includes>
					<include>**/*.xml</include>
					<include>**/*.properties</include>
					<include>**/*.json</include>
				</includes>
			</resource>	
			<!-- Excel 获得其他 OLE文件 假如如下使用方式-->		
			<resource>
				<directory>src/main/webapp</directory>
				<filtering>true</filtering>
				<include>**/**/*.xlsx</include>
			</resource>
		</resources> 
		</bulid>

2.2、在Jar文件之中通过Stream方式获得其中包含的Excel模板或者其他文件

// 导出模板路径
String templatePath = "app/exportTemplate" + File.separator + templateNames[0]+ ".xlsx";
ClassPathResource resource = new ClassPathResource(templatePath);
resource.getInputStream();

如果要读取Jar之中的文件都需要使用此种流的方式获得文件。
可以参考如下文章:
https://blog.csdn.net/WYA1993/article/details/84873011
https://blog.csdn.net/qq_36223142/article/details/82660411
2.3、胜利前的黑暗
本人也是通过此种Stream方式把Excel模板以InputStream 传递给导出表的。并且还改造了一起的方法。如下代码所示

long millionSeconds = sdf.parse(str).getTime();// 毫秒
String fileName = templateNames[1] + millionSeconds + ".xlsx";
setExportResponseHeader(response, getValue(fileName));
ServletOutputStream ouputStream = response.getOutputStream();
export2ExcelByInputStream(excelInputStream, benmap, ouputStream);

@SuppressWarnings("rawtypes")
public static void export2ExcelByInputStream(InputStream excelInputStream, Map beans, OutputStream os) {
	// 导出模板解析器
	XLSTransformer transformer = new XLSTransformer();
try {
	Workbook workbook = transformer.transformXLS(excelInputStream, beans);
	// JxlsOutTag.localThd.remove();
	workbook.write(os);
} catch (Exception e) {
	e.printStackTrace();
}
}

如果使用通用方式 来包含Excel模板和Word模板或者使用OLE类型文件,会导致你崩溃的事情发生。
在这里插入图片描述

在发现此问题后,本人没有关注这个报错的原因,于是后面各种试验都不能导出数据,各种崩溃很无语的,就要在放弃了。最后差点让其他同事准备把这个Excel文件写死固定位置,以期待获得此模板信息。准备把读取模板的文件位置写死在配置文件之中,本打算通过,

配置文件的代码
#文件上传位置
cbs.imagesPath=file://83_mntdir/excelTemplate/
@Value("${cbs.imagesPath}")
private String webUploadPath;

但是此同事所写点的导出工具类之中所写代码都是static修饰的。代码如下:

@SuppressWarnings("rawtypes")
public static void export2Excel(String templatePath, Map beans, OutputStream os) {
// 导出模板解析器
XLSTransformer transformer = new XLSTransformer();
InputStream is = null;
/*try {
	is = new FileInputStream(templatePath);
} catch (FileNotFoundException e1) {
   e1.printStackTrace();
}*/
try {
      ClassPathResource cpr = new ClassPathResource("/" + templatePath);
	// is=new FileInputStream(file);
	is = cpr.getInputStream();
} catch(Exception e1) {
	e1.printStackTrace();
}
try {
    Workbook workbook = transformer.transformXLS(is, beans);
    // JxlsOutTag.localThd.remove();
	workbook.write(os);
} catch (Exception e) {
	e.printStackTrace();
}
}

因为这static修饰的代码在类加载时候已经被加载了,如果你一个非静态类型的变量是无法传递进去的,除非使用参数传递进去
于是这位同事写死位置失败了。
此时我们另外一位同事,发现的问题的根源,依据报错原因找到了为何不能通过InputStream把Excel模板文件通过数据流方式传递原因。
POI之java.io.IOException: ZIP entry size is too large
报错如下所示:

java.io.IOException: Failed to read zip entry source
    at org.apache.poi.openxml4j.opc.ZipPackage.<init>(ZipPackage.java:106) ~[poi-ooxml-3.15.jar:3.15]
    at org.apache.poi.openxml4j.opc.OPCPackage.open(OPCPackage.java:342) ~[poi-ooxml-3.15.jar:3.15]
    at org.apache.poi.util.PackageHelper.open(PackageHelper.java:37) ~[poi-ooxml-3.15.jar:3.15]
    at org.apache.poi.xssf.usermodel.XSSFWorkbook.<init>(XSSFWorkbook.java:285) ~[poi-ooxml-3.15.jar:3.15]
    at com.fish.cdc.data.std.controller.DemoController.importData(DemoController.java:51) ~[classes/:?]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_144]
.....................
Caused by: java.io.IOException: ZIP entry size is too large
    at org.apache.poi.openxml4j.util.ZipInputStreamZipEntrySource$FakeZipEntry.<init>(ZipInputStreamZipEntrySource.java:122) ~[poi-ooxml-3.15.jar:3.15]
    at org.apache.poi.openxml4j.util.ZipInputStreamZipEntrySource.<init>(ZipInputStreamZipEntrySource.java:56) ~[poi-ooxml-3.15.jar:3.15]
    at org.apache.poi.openxml4j.opc.ZipPackage.<init>(ZipPackage.java:99) ~[poi-ooxml-3.15.jar:3.15]
--------------------- 

最后通过此篇文章分析 POI之java.io.IOException: ZIP entry size is too large

找到所有原因
Excel文件被损坏了, 考虑到maven项目中打开了filtering的特性,就是会针对项目的资源文件进行扫描,一般的文件,eot文件等等都会受到影响,从而导致了文件无法被正确的解析。

问题的解决
既然知道了原因,则就可以在filtering中新增exclude/include规则即可:

<resource>
	<directory>src/main/webapp</directory>
	<filtering>true</filtering>
	<excludes>
		<exclude>**/**/*.xlsx</exclude>
	</excludes>
</resource>
<resource>
	<directory>src/main/webapp</directory>
	<filtering>false</filtering>
	<includes>
		<include>**/**/*.xlsx</include>
	</includes>
</resource>

此段配置的意思,在Maven filter的过程之中先 excludes 排除掉eot文件(Excel模板文件)
在进过此种方式配置之后,都可以解决通过InputStream流方式在Jar之中的Excel模板之中写入数据了。
3、针对此次解决问题的反思
此问题花费了大改一天半时间,第一天下午主要发现了模板文件未被打包进入Jar文件,甚至本地IDE环境之中也没有此模板文件,最后通过找到以前项目把模板文件copy过来。第一步已经找到了Excel模板文件。
接着第二天进行通过InpuStream方式获获得Excel模板文件,但是忽略了报出的错误

java.io.IOException: Failed to read zip entry source
    at org.apache.poi.openxml4j.opc.ZipPackage.
    <init>(ZipPackage.java:106) ~[poi-ooxml-3.15.jar:3.15]
    at org.apache.poi.openxml4j.opc.OPCPackage.open
    (OPCPackage.java:342) ~[poi-ooxml-3.15.jar:3.15]
    at org.apache.poi.util.PackageHelper.open
    (PackageHelper.java:37) ~[poi-ooxml-3.15.jar:3.15]
    at org.apache.poi.xssf.usermodel.XSSFWorkbook.<init>(XSSFWorkbook.java:285) ~[poi-ooxml-3.15.jar:3.15]
    at com.fish.cdc.data.std.controller.DemoController.importData
    (DemoController.java:51) ~[classes/:?]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_144]

导致在即将解决问题时候,差点让自己的成果付之东流。
研发和破案以及研究科学一样不能放过任何一个细节,只有细心才能发现问题的蛛丝马迹,通过分析这些关联关系才能够解决问题。
另外同事没有能够通过写死文件位置成功的主要原因是在静态(Static)方法的类里面获得常量信息是不可能的,只能通过传递参数方式还是可以的。
另外作为Java程序多多关注此网站
https://stackoverflow.com

猜你喜欢

转载自blog.csdn.net/jianxia801/article/details/85167826