序言
笔者最近在查看作业评阅系统的时候,对于其中的附件上传和下载不太了解,通过写这篇文章来记录自己的收获。
文件传到哪里
1.传到工程目录下
在一些文件存储量很小的工程中,有一些上传文件放置在工程本身的目录下,但是随着文件上传的量越来越大,工程本身所在的文件夹容量会越来越大,不仅打包和部署的效率会降低,工程的启动和运行也会变慢,所以一般不会采用这做法。
2.上传到工程所在服务器
将文件专门上传到Web应用工程所在容器(如Tomcat
)位于的服务器中,单独开辟一个盘符或文件夹用于存储上传的图片,这种做法让上传 文件与工程本身分离,工程的打包和启动效率不受到任何影响。但是如果以后出现了海量图片,Web应用工程所在的服务器的效率会降低,这样也会间接地降低应用的执行效率,所以在上传图片量不大的情况下,可以采用该做法。
3.搭建文件服务器
一般大型的互联网项目,都会为自己的文件上传单独架设一个文件服务器(有集群的应用,可能会有多台文件服务器),也有独立处理文件上传、文件访问的服务器。这种方案就是太烧钱。
总结:上面分析了三种方案的特点和优缺点。第一种一般不采取,第二种可能会采取,最常用的就是第三种方案。笔者所用系统是使用的第一种方案。
上传
MultipartFile工具类
MultipartFile
是SpringMVC
提供简化上传操作的工具类。
在不使用框架之前,都是使用原生的HttpServletRequest
来接收上传的数据,文件是以二进制流传递到后端的,然后需要我们自己转换为File类,非常麻烦。使用了MultipartFile
工具类之后,我们对文件上传的操作就简便许多了。
以下是MultipartFile
工具类全部的接口方法:
方法名 | 返回值 | 作用 |
---|---|---|
getContentType() | String | 在取文件MIME类型 |
getlnputStream() | InputStream | 获取文件流 |
getName() | String | 获取 form 表单中文件组件的名字 |
getOriginalFilename() | String | 获取上传文件件的原名 |
getSize() | long | 获取文件的大小,单位为byte |
isEmpty() | boolean | 是否为空 |
transferTo(File dest) | void | 将数据保存到一个目标文件中 |
上传实现:
1.空文件不执行上传操作。
2.给文件按照一定的命名规则进行重命名。此举即可以防止命名冲突,也可以从一定程度上防止病毒木马等入侵,可以采取其它命名策略(如SHA、MD5等),但是要保证命名的合理性和安全性,此处示例代码并没有进行处理。
3.若文件命名策略可能生成同名文件(如两个用户上传了同一个文件),可采用后上传的文件不执行保存操作。示例没有此操作,因为查询文件是否存在方式不同,所以并未展示。
public void saveFile(MultipartFile multipartFile, Path saveFilePath) throws Exception {
logger.debug("获取文件名");
String fileName = multipartFile.getOriginalFilename();
logger.debug("从文件名中截取拓展名");
// 从"."最后一次出现的位置的下一位开始截取,获取扩展名
assert fileName != null;
String ext = fileName.substring(fileName.lastIndexOf(".") + 1);
logger.debug("设置保存文件名");
saveName = fileName;
logger.debug("判断上传的文件是否为空");
if (multipartFile.isEmpty()) {
throw new RuntimeException("上传的附件不能为空" + fileName);
}
logger.debug("如果目录不存在,则创建目录。如果目录存在,则不创建");
if (!Files.exists(saveFilePath)) {
Files.createDirectories(saveFilePath);
new File(saveFilePath.resolve("index.html").toString()).createNewFile();
}
logger.debug("将文件移动至储存文件的路径下");
Files.copy(multipartFile.getInputStream(), saveFilePath.resolve(saveName),
StandardCopyOption.REPLACE_EXISTING);
}
笔者直接用文件名来将文件存储在工程中,这种行为是很不安全的,所以建议用自己定义的加密解密办法来处理此问题,存入服务器的文件就会变成二进制文件,当别人直接冲服务器拿到文件时,也无法查看,这就保证了安全性。
下载
实现过程:获取文件存储地址->读取文件到输入流->将输入流复制到输出流
下载部分核心代码
public void download(File f, OutputStream outputStream) {
// File为自定义实体,其中存储了文件位置和存储名等信息
Path path = Paths.get(f.getPath())
.resolve(f.getName());
java.io.File file = path.toFile();
// 输出文件类型
FileInputStream inputStream;
try {
inputStream = new FileInputStream(file);
} catch (FileNotFoundException e) {
logger.error("读取文件出错" + f.getId() + file.getAbsolutePath());
e.printStackTrace();
throw new RuntimeException("读取文件发生错误");
}
try {
org.apache.commons.io.IOUtils.copy(inputStream, outputStream);
} catch (IOException e) {
logger.error("下发数据时发生了错误");
e.printStackTrace();
throw new RuntimeException("下发数据时发生了错误");
}
}
由于本周事情较多,没有实现一个demo来更好的介绍,接下来会尽快实现
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。