学习了老师写的文件上传和下载,这里学习一下。

文件上传

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()返回文件的内容类型
InputstreamgetInputStream() 返回InputStream读取文件的内容
String getOriginalFilename()返回文件的名称
Long getSize()返回文件大小 以字节为单位
BooleanisEmpty() 判断是否为空,或者上传的文件是否有内容
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加密值等属性。

@Entitypublic 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("下发数据时产生了谬误");    }  }

至此,文件就被复制到响应的数据流中, 前台就会跳出文件下载。