目录

原理说明:

代码部分说明

文件上传主要方法:

文件删除代码:

主要介绍文件分片下载:

 

记录原因:发现safire浏览器和苹果手机请求数据时,它不是一次性请求完文件流,他会先发送一个类似跨域的预请求,先请求1个字节的数据,后续进行分段获取文件流,所以,要实现这一需求必须做分片下载

git地址,可以参考文件处理代码,具体逻辑根据自己项目需求进行更改:https://github.com/pengxingjia/file-uplaod-download.git

原理说明:

  • 普通的类似安卓、谷歌浏览器下载都是一次性请求完所有数据,但是safire和苹果手机不是,是分片下载(后面统称苹果)
  • 苹果请求时,请求头中会携带range,第一次请求携带的是Range: bytes=0-1,后端需要返回本次请求的字节数

    Content-length:"本次请求的字节数",
    Content-Range:bytes=该次请求字节的起始位置-该次请求字节数的结束位置/文件总字节数;

  • 分片请求返回的状态码固定为206

  • Content-Disposition如果没有设置这个请求头,则Safire浏览器点击下载会直接进入预览模式,设置之后,点击下载后会直接下载图片文件
  • 对应音频视频文件,需要对请求头做特殊处理,如下

    •     //音视频格式,不区分大小写
          private static List<String> extNameVideoList;
          private static List<String> extNameAudioList;
      
          private void initExtNameMap(){
              if (extNameVideoList == null) {
                  extNameVideoList = new ArrayList<>();
                  extNameVideoList.add("mp4");
                  extNameVideoList.add("mov");
              }
              if (extNameAudioList == null){
                  extNameAudioList = new ArrayList<>();
                  extNameAudioList.add("mp3");
              }
          }
      
          /**
           * 判断后缀是否是音频/视频类设置响应的响应头
           *
           * @param extName 后缀名
           */
          private void checkExtNameSetHealer(HttpServletResponse response, String extName) {
              initExtNameMap();
              String lowExtUser = extName.toLowerCase();
      
              if (extNameVideoList.contains(lowExtUser)){
                  response.setContentType("video/mp4");
                  response.setHeader("Content-Type", "video/mp4");
              }else if (extNameAudioList.contains(lowExtUser)){
                  response.setContentType("audio/mp3");
                  response.setHeader("Content-Type", "audio/mp3");
              }
          }

      对应视频文件,请求头需要设置为:
      response.setContentType("video/mp4");
      response.setHeader("Content-Type", "video/mp4");

      对于音频文件,请求头需设置为:
      response.setContentType("audio/mp3");
      response.setHeader("Content-Type", "audio/mp3");

代码部分说明

文件上传主要方法:

1.先解析出文件的名字、后缀

2.fileName用uuid是为了存储到磁盘时文件名唯一

3.具体参考项目代码

 @Override
    public FileInfoDto fileUpload(HttpServletRequest request, MultipartFile file) {
        String fileName = UuidUtils.getUuid();
        String originalName = file.getOriginalFilename();
        String extName = "";
        if (originalName != null && originalName.contains(".")) {
            extName = originalName.substring(originalName.lastIndexOf(".") + 1);
            fileName = fileName + "." + extName;
        }
        //构建空文件对象
        File newFile = FileUtil.getFilePath(fileName);

        //将文件写入到空文件对象中
        try {
            file.transferTo(newFile);
        } catch (IOException e) {
            log.error(e.getMessage());
            throw new FileException(ErrorCodeConstants.FILE_TRANSFER_EXCEPTION);
        }
        //保存文件关联关系
        FileInfo fileInfo = initInfoFileRel(request, file, fileName, extName);
        return this.getFileInfoDto(fileInfo);
    }

文件删除代码:

1.通过地址获取文件对象,若文件对象存在,则调用file.delete()进行删除

    @Override
    public void fileDelete(String url) {
        File file = FileUtil.getFilePath(url);
        if (file.exists()){
            if (file.delete()){
                log.error("文件删除失败");
            }
        }
    }

主要介绍文件分片下载:

  • 此方法设置公共的参数,根据请求方式选择是一次性下载还是分片下载
   //苹果代表分片下载,其他代表一次性下载文件
    public void sendVideo(HttpServletRequest request, HttpServletResponse response, File file, String originalName, String extName) throws IOException {
        //判断后缀是否是音频/视频类
        checkExtNameSetHealer(response, extName);
        response.addHeader("Content-Disposition", "attachment;filename=\"" + new String(originalName.getBytes(StandardCharsets.UTF_8), "iso8859-1") + "\"");
        response.setHeader("ETag", originalName);
        response.setHeader("Last-Modified", new Date().toString());
        response.setHeader("Accept-Ranges", "bytes");

        //设置文件为只读模式
        RandomAccessFile randomFile = new RandomAccessFile(file, "r");
        long contentLength = randomFile.length();
        if (contentLength > fileMaxSize){
            log.error("文件太大,最大支持2g");
            throw new RuntimeException("文件太大");
        }
        response.setHeader("Content-length", "" + contentLength);

        //分片下载时的请求头Range
        String range = request.getHeader("Range");
        try {
            if (range != null && range.startsWith("bytes=")) {
                //分段读取文件流
                pieceDownFileInfo(range, response, randomFile);
            } else {
                //一次性读取所有文件流
                readAllAtOnce(response, randomFile);
            }
        }catch (IOException io){
            io.getStackTrace();
        }
    }
  • 算了   建议看代码吧,文档反而越说越模糊
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐