共计 5081 个字符,预计需要花费 13 分钟才能阅读完成。
学习了老师写的文件上传和下载,这里学习一下。
文件上传
controller 层
从 controller 层开始,承受前台传的数据。
1. 前台调用后盾接口:
/** | |
* 上传文件 | |
* @param file 文件 | |
*/ | |
upload(file: File): Observable<HttpEvent<Attachment>> {const formData: FormData = new FormData(); | |
formData.append('file', file); | |
return this.httpClient.post<Attachment>(`${this.url}/upload`, | |
formData, {reportProgress: true, observe: 'events'}); | |
} |
2.controller 层接收数据:
/** | |
* 上传文件 | |
* | |
* @param multipartFile 附件 | |
* @return 上传附件后果 | |
*/ | |
@PostMapping("upload") | |
@JsonView(UploadJsonView.class) | |
public Attachment upload(@RequestParam("file") MultipartFile multipartFile) throws Exception {return this.attachmentService.upload(multipartFile); | |
} |
能够看到,后盾承受文件是以 MultipartFile
类型来接管.
MultipartFile 其实是一个接口,其中定义提供了上传文件的各方面信息
MultipartFile 办法阐明:
返回值类型 | 办法阐明 |
---|---|
byte[] getBytes() | 将文件内容转化成一个 byte[] 返回 |
String getContentType() | 返回文件的内容类型 |
Inputstream | getInputStream() 返回 InputStream 读取文件的内容 |
String getOriginalFilename() | 返回文件的名称 |
Long getSize() | 返回文件大小 以字节为单位 |
Boolean | isEmpty() 判断是否为空,或者上传的文件是否有内容 |
Void transferTo(File dest) | 用来把 MultipartFile 转换换成 File |
有了下面提供的函数,咱们就能够很轻松的操作它。
3.service 层
3.1: 保留文件
既然是文件上传,那么咱们必定要在后盾中保留文件。
那么,咱们是抉择保留在数据库中还是后盾的文件夹中?
答案举荐是:保留在文件夹中,或者说是保留在硬盘上。
数据库间接存储文件的话,比方一些小的图片,如几十 K 的头像图片等,是能够间接转换成二进制,寄存到数据库中的。然而个别我的项目开发都不会在数据库中存文件,否则拜访文件可能会对数据库造成很高的负载。
所以咱们能够,在创立一个 attachment 文件夹,将文件保留在上面。
那么,如何将 MultipartFile 类型的文件,保留到文件夹中呢?
咱们能够利用 Files 提供的动态的 copy 办法,将文件复制到指标门路中。
举个例子,能够如下:
Path saveFilePath = Paths.get("/attachment" + this.getYearMonthDay()); | |
String saveName = multipartFile.getOriginalFilename(); | |
logger.debug("将文件挪动至贮存文件的门路下"); | |
Files.copy(multipartFile.getInputStream(), saveFilePath.resolve(saveName), | |
StandardCopyOption.REPLACE_EXISTING); |
copy 函数用了 Java 的输入输出流类型,将文件复制到指标门路。
3.2 保留实体数据
咱们还须要做两件事:
- 保留文件门路。
- 对文件进行 MD5 加密
为了在保留文件之后,获取这个文件,这就须要咱们 保留这个门路。通过门路获取文件。
同时,咱们还须要进行 对文件进行 MD5 加密,起因有二:
确保完整性: 咱们将文件生成了 MD5 摘要, 传输文件和 MD5 码给接收端, 接收端接管文件后能够对文件生成 MD5 码而后与接管到的 MD5 码比照,校验确保文件是残缺的而且中途没有被批改.
确保有效性: 发送文件过来后, 能够要求前台返回文件的 MD5 码, 你能够将收到的 MD5 码和本人文件的 MD5 码校验, 确保通信为无效; 还有就是能够将文件存储起来,MD5 码也存储在数据库, 以便复查的时候确保文件是传输胜利了的。
所以,当初当初咱们须要创立一个实体,保留文件门路,以及 md5 加密值等属性。
@Entity | |
public class MyFile { | |
@Id | |
@GeneratedValue(strategy = GenerationType.IDENTITY) | |
protected Long id; | |
private String sha1; | |
private String md5; | |
/** | |
* 文件贮存门路 | |
*/ | |
private String path; | |
/** | |
* 文件贮存名称 | |
*/ | |
private String name; | |
/** | |
* 文件 MIME,对应文件类型 | |
*/ | |
private String mime; |
保留实体:
Path saveFilePath = Paths.get(CONFIG_PATH + this.getYearMonthDay()); | |
String sha1ToMultipartFile = CommonService.encrypt(multipartFile, "SHA-1"); | |
String md5ToMultipartFile = CommonService.encrypt(multipartFile, "MD5"); | |
MyFile file = new MyFile(); | |
file.setName(saveName); | |
file.setMime(multipartFile.getContentType()); | |
file.setPath(saveFilePath.toString()); | |
file.setSha1(sha1ToMultipartFile); | |
file.setMd5(md5ToMultipartFile); | |
oldFile = this.myFileRepository.save(file); |
保留好门路之后,咱们当前就能够查问该实体,获取门路之后,进行下载文件。
这里讲了大略的思路,相干函数代码放在文末。
文件下载
controller 层:
/** | |
* 下载 | |
* | |
* @param id id | |
* @param md5 md5 | |
* @param filename 保留的文件名 | |
* @param response 响应 | |
* @throws UnsupportedEncodingException 传入的文件名转码失败时异样 | |
*/ | |
@GetMapping({"download/{id}/{md5}",}) | |
1 public void download(@PathVariable Long id, | |
2 @PathVariable String md5, | |
3 @RequestParam(required = false) String filename, | |
4 HttpServletResponse response) | |
5 throws UnsupportedEncodingException {6 if (filename != null) { | |
7 response.setHeader("Content-disposition", "attachment; filename=" + | |
8 URLEncoder.encode(filename, "UTF-8")); | |
9 } | |
10 this.attachmentService.download(id, md5, response); | |
} |
下载文件须要 3 个参数,附件 id, md5 值,用来比对文件。filename 这里能够本人设置文件的名称。
以后台给 Web 服务器发送一个 http 申请,web 服务器就会针对每一次申请,别离创立一个用于代表申请的 request 对象、和代表响应的 response 对象。
第四行,这里咱们通过获取 HttpServletResponse
对象,设置返回的响应。
第七行的Content-disposition
,Content-disposition 是 MIME 协定的扩大,MIME 协定批示 MIME 用户代理如何显示附加的文件。
能够管制用户申请所得的内容,文件间接在浏览器上显示或者在拜访时弹出文件下载对话框。
简略来说就是,咱们设置了响应类型为,弹出文件下载。
Service
1. 判断 md5 值是否雷同
如果下载的文件和数据库中的 md5 值雷同,再进行文件下载。
@Override | |
public void download(Long id, String md5, HttpServletResponse response) {Attachment attachment = this.getById(id); | |
if (!attachment.getFile().getMd5().equals(md5)) {throw new EntityNotFoundException(); | |
} | |
this.myFileService.download(attachment.getName(), attachment.getFile(), response); | |
} |
2. 文件下载
咱们进行如下的步骤
1. 依据文件门路获取硬盘文件
Path path = Paths.get(myFile.getPath()) | |
.resolve(myFile.getName()); | |
java.io.File file = path.toFile(); |
2. 设置响应的类型,长度
response.setHeader("Content-Type", myFile.getMime()); | |
logger.debug("输入文件长度"); | |
response.setContentLength((int) file.length()); |
3. 用输入输出流,将文件 copy 写入响应的输入流中。
org.apache.commons.io.IOUtils.copy(inputStream, response.getOutputStream());
4. 用 flushBuffer,提交该响应,返回前台。
response.flushBuffer();
flush 蕴含两个步骤:先将缓冲区内容发送至客户端,而后将缓冲区清空)。这就标记着该次响应曾经 committed(提交)。对于以后页面中曾经 committed(提交)的 response,就不能再应用这个 response 向缓冲区写任何货色
public void download(String filename, MyFile myFile, HttpServletResponse response) {Path path = Paths.get(myFile.getPath()) | |
.resolve(myFile.getName()); | |
java.io.File file = path.toFile(); | |
logger.debug("输入文件类型"); | |
FileInputStream inputStream; | |
try {inputStream = new FileInputStream(file); | |
} catch (FileNotFoundException e) {logger.error("读取文件出错" + myFile.getId() + file.getAbsolutePath()); | |
e.printStackTrace(); | |
throw new RuntimeException("读取文件产生谬误"); | |
} | |
response.setHeader("Content-Type", myFile.getMime()); | |
logger.debug("输入文件长度"); | |
response.setContentLength((int) file.length()); | |
logger.debug("写入数据流"); | |
try {org.apache.commons.io.IOUtils.copy(inputStream, response.getOutputStream()); | |
response.flushBuffer();} catch (IOException e) {logger.error("下发数据时产生了谬误"); | |
e.printStackTrace(); | |
throw new RuntimeException("下发数据时产生了谬误"); | |
} | |
} |
至此,文件就被复制到响应的数据流中,前台就会跳出文件下载。