前言:
之前开发过程中碰到本地运行无问题,测试环境运行找不到资源文件的问题。已解决,记录一下
线上环境使用jar包部署至docker。
这里使用的是相对路径获取,获取不到。
原理解析:(重点)
打成jar包后项目本身就是一个文件,不能再用(File)获取文件的方式来读取,只能用流的方式来读取文件内容,本地之所以能运行,是因为IDE中的资源文件在target/classes目录下,是正常的文件系统结构。所以本质都是需要使用流来获取文件。
解决方案
方法一:(本人使用方法)
原理:首先通过读取文件inputStream流,通过input流将文件写入docker容器中,通过绝对路径获取文件。
优点:可以获取file文件,在一些工具类需要传入filePath可以使用
缺点:需要将文件从jar包中读取出来,写入到docker容器中
package org.jeecg.modules.lst.util;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@Slf4j
@Component
public class DocUtil {
//此路径是其余方法进行调用,且只需要加载一次
private static String sourceTemplatePath;
// 读取文件名称
private static String[] ftlArray = {"shopPDF.ftl","yahei.ttf"};
// resources下模板文件夹名称
private static String temPath = "templates/shopsummary/";
//模块名,针对多模块项目。单模块项目需删除
private static String modelPath = String.format("jeecg-module-system%sjeecg-system-biz", File.separator);
static {
//静态方法调用一次
sourceTemplatePath = createFtlFileByFtlArray();
}
// 获取临时文件路径
public static String getSourceTemplatePath(){
return sourceTemplatePath;
}
//获取临时文件模板路径
public static String getRentalAgreementPath(){
return sourceTemplatePath+ ftlArray[0];
}
private static String createFtlFileByFtlArray() {
String path = "";
for (int i = 0; i < ftlArray.length; i++) {
path = createFtlFile(temPath, ftlArray[i]);
if (null == path) {
log.info("not copy success:" + ftlArray[i]);
}
}
return path;
}
private static String createFtlFile(String ftlPath, String ftlName) {
try {
//获取当前项目所在的绝对路径
String proFilePath = System.getProperty("user.dir");
log.info("project run path:" + proFilePath);
//获取模板下的路径
String newFilePath = proFilePath + File.separator + modelPath + File.separator + "src" + File.separator + "main" + File.separator + "resources" + File.separator + ftlPath;
newFilePath = newFilePath.replace("/", File.separator);
log.info("newFilePath:" + newFilePath);
//检查项目运行时的src下的对应路径
File newFile = new File(newFilePath + ftlName);
if (newFile.isFile() && newFile.exists()) {
return newFilePath;
}
//当项目打成jar包会运行下面的代码,而且复制一份到src路径下(具体结构看下面图片)
InputStream certStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(ftlPath + ftlName);
byte[] certData = IOUtils.toByteArray(certStream);
FileUtils.writeByteArrayToFile(newFile, certData);
return newFilePath;
} catch (IOException e) {
log.error("复制文件失败--> 异常信息:" + e);
}
return "";
}
}
写一个静态文件,在项目启动时,首先读取文件是否存在,如不存在,使用Thread.currentThread().getContextClassLoader().getResourceAsStream(ftlPath + ftlName);读取文件input流,写入到docker容器中。
因为我是多模块项目,所以代码中添加了modelPath,如果是单体项目,将modelPath去掉,本质上就是取相对路径,只要能通过相对路径取到文件即可。
代码解析:
为方便理解,我使用gpt解析了这段代码。大家可以参考下。
该类使用了以下注解:
@Slf4j:来自 lombok 库,自动为该类提供一个名为"log"的日志记录器对象,用于记录日志。
@Component:来自 Spring 框架,将这个类标记为 Spring 管理的组件,使得 Spring 框架可以初始化并使用这个实例。
DocUtil类中定义了以下静态变量:
sourceTemplatePath:作为其他方法调用的路径变量,只需加载一次。ftlArray:包含两个文件名称的字符串数组。
temPath:定义在 resources 下的模板文件夹名称。
modelPath:针对多模块项目的模块名称。单模块项目需要删除该变量。
类中包含以下静态方法:static代码块:只执行一次(类加载时)。调用createFtlFileByFtlArray()方法来加载并获取模板文件路径。
getSourceTemplatePath():返回临时文件路径。 getRentalAgreementPath():返回rental
agreement模板文件路径。
createFtlFileByFtlArray():遍历ftlArray数组中的所有文件名,并为每个文件名调用createFtlFile()方法。返回文件路径。
createFtlFile(String ftlPath, String ftlName):使用输入的ftlPath和文件名ftlName创建文件,返回新文件的路径。 首先,获取项目当前运行路径并输出到日志中。再获取所需文件的目标路径。 检查文件是否存在。如果存在,则返回新文件路径。 对于以jar包形式运行的项目,使用类加载器从jar文件中读取文件内容,然后复制到具有相同文件结构的src路径下,并返回新文件路径。 如果复制过程发生错误,捕获并记录异常,并返回空字符串。这段代码主要处理了从项目resources文件夹中读取模板文件,复制到合适的位置,返回这些模板文件的文件路径。同时处理了项目以jar包形式运行的情况。
获取文件方式:
//获取资源文件夹目录
DocUtil.getSourceTemplatePath();
//获取资源文件
DocUtil.getSourceTemplatePath() + fileName;
docker容器内路径展示,更新在项目路径下
方法二:
如果只需要读取文件。可以直接使用inputStream流获取。
用第二种方式读取jar中的文件流
ClassPathResource resource = new ClassPathResource("static/xxx.docx");
File sourceFile = resource.getFile();
InputStream fis = resource.getInputStream();
还要在项目pom.xml中配置resources
<build>
<!-- 定义包含这些资源文件,能在jar包中获取这些文件 -->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
<include>**/*.yml</include>
</includes>
<!--是否替换资源中的属性-->
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.*</include>
</includes>
<!--是否替换资源中的属性-->
<filtering>false</filtering>
</resource>
</resources>
</build>