• java大文件(百M以上)的上传下载思路


    这里只写后端的代码,基本的思想就是,前端将文件分片,然后每次访问上传接口的时候,向后端传入参数:当前为第几块文件,和分片总数

    下面直接贴代码吧,一些难懂的我大部分都加上注释了:

    上传文件实体类:

    看得出来,实体类中已经有很多我们需要的功能了,还有实用的属性。如MD5秒传的信息。

    public class FileInf {

         public FileInf(){}

         public String id="";

         public String pid="";

        public String pidRoot="";   

         /**  * 表示当前项是否是一个文件夹项。    */

         public boolean fdTask=false;        

         //   /// 是否是文件夹中的子文件  /// </summary>

         public boolean fdChild=false;

         /**  * 用户ID。与第三方系统整合使用。    */

         public int uid=0;

         /**  * 文件在本地电脑中的名称   */

         public String nameLoc="";

         /**  * 文件在服务器中的名称。   */

         public String nameSvr="";

         /**  * 文件在本地电脑中的完整路径。示例:D:SoftQQ2012.exe */

         public String pathLoc="";  

         /**  * 文件在服务器中的完整路径。示例:F:\ftp\uer\md5.exe     */

         public String pathSvr="";

         /**  * 文件在服务器中的相对路径。示例:/www/web/upload/md5.exe   */

         public String pathRel="";

         /**  * 文件MD5    */

         public String md5="";

         /**  * 数字化的文件长度。以字节为单位,示例:120125    */

         public long lenLoc=0;

         /**  * 格式化的文件尺寸。示例:10.03MB   */

         public String sizeLoc="";

         /**  * 文件续传位置。  */

         public long offset=0;

         /**  * 已上传大小。以字节为单位 */

         public long lenSvr=0;

         /**  * 已上传百分比。示例:10%  */

         public String perSvr="0%";

         public boolean complete=false;

         public Date PostedTime = new Date();

         public boolean deleted=false;

         /**  * 是否已经扫描完毕,提供给大型文件夹使用,大型文件夹上传完毕后开始扫描。  */

         public boolean scaned=false;

    }

    首先是文件数据接收逻辑,负责接收控件上传的文件块数据,然后写到服务器的文件中。控件已经提供了块的索引,大小,MD5和长度信息,我们可以根据需要来灵活进行处理,也可以将文件块的数据保存到分布式存储系统中。

    <%

    out.clear();

    String uid             = request.getHeader("uid");//

    String id              = request.getHeader("id");

    String lenSvr      = request.getHeader("lenSvr");

    String lenLoc      = request.getHeader("lenLoc");

    String blockOffset = request.getHeader("blockOffset");

    String blockSize   = request.getHeader("blockSize");

    String blockIndex  = request.getHeader("blockIndex");

    String blockMd5        = request.getHeader("blockMd5");

    String complete        = request.getHeader("complete");

    String pathSvr         = "";

    //参数为空

    if(  StringUtils.isBlank( uid )

         || StringUtils.isBlank( id )

         || StringUtils.isBlank( blockOffset ))

    {

         XDebug.Output("param is null");

         return;

    }

    // Check that we have a file upload request

    boolean isMultipart = ServletFileUpload.isMultipartContent(request);

    FileItemFactory factory = new DiskFileItemFactory();  

    ServletFileUpload upload = new ServletFileUpload(factory);

    List files = null;

    try

    {

         files = upload.parseRequest(request);

    }

    catch (FileUploadException e)

    {// 解析文件数据错误 

        out.println("read file data error:" + e.toString());

        return;

      

    }

    FileItem rangeFile = null;

    // 得到所有上传的文件

    Iterator fileItr = files.iterator();

    // 循环处理所有文件

    while (fileItr.hasNext())

    {

         // 得到当前文件

         rangeFile = (FileItem) fileItr.next();

         if(StringUtils.equals( rangeFile.getFieldName(),"pathSvr"))

         {

             pathSvr = rangeFile.getString();

             pathSvr = PathTool.url_decode(pathSvr);

         }

    }

    boolean verify = false;

    String msg = "";

    String md5Svr = "";

    long blockSizeSvr = rangeFile.getSize();

    if(!StringUtils.isBlank(blockMd5))

    {

         md5Svr = Md5Tool.fileToMD5(rangeFile.getInputStream());

    }

    verify = Integer.parseInt(blockSize) == blockSizeSvr;

    if(!verify)

    {

         msg = "block size error sizeSvr:" + blockSizeSvr + "sizeLoc:" + blockSize;

    }

    if(verify && !StringUtils.isBlank(blockMd5))

    {

         verify = md5Svr.equals(blockMd5);

         if(!verify) msg = "block md5 error";

    }

    if(verify)

    {

         //保存文件块数据

         FileBlockWriter res = new FileBlockWriter();

         //仅第一块创建

         if( Integer.parseInt(blockIndex)==1) res.CreateFile(pathSvr,Long.parseLong(lenLoc));

         res.write( Long.parseLong(blockOffset),pathSvr,rangeFile);

         up6_biz_event.file_post_block(id,Integer.parseInt(blockIndex));

        

         JSONObject o = new JSONObject();

         o.put("msg", "ok");

         o.put("md5", md5Svr); 

         o.put("offset", blockOffset);//基于文件的块偏移位置

         msg = o.toString();

    }

    rangeFile.delete();

    out.write(msg);

    %>

    文件初始化部分

    <%

    out.clear();

    WebBase web = new WebBase(pageContext);

    String id     = web.queryString("id");

    String md5         = web.queryString("md5");

    String uid         = web.queryString("uid");

    String lenLoc      = web.queryString("lenLoc");//数字化的文件大小。12021

    String sizeLoc     = web.queryString("sizeLoc");//格式化的文件大小。10MB

    String callback = web.queryString("callback");

    String pathLoc     = web.queryString("pathLoc");

    pathLoc            = PathTool.url_decode(pathLoc);

    //参数为空

    if ( StringUtils.isBlank(md5)

         && StringUtils.isBlank(uid)

         && StringUtils.isBlank(sizeLoc))

    {

         out.write(callback + "({"value":null})");

         return;

    }

    FileInf fileSvr= new FileInf();

    fileSvr.id = id;

    fileSvr.fdChild = false;

    fileSvr.uid = Integer.parseInt(uid);

    fileSvr.nameLoc = PathTool.getName(pathLoc);

    fileSvr.pathLoc = pathLoc;

    fileSvr.lenLoc = Long.parseLong(lenLoc);

    fileSvr.sizeLoc = sizeLoc;

    fileSvr.deleted = false;

    fileSvr.md5 = md5;

    fileSvr.nameSvr = fileSvr.nameLoc;

    //所有单个文件均以uuid/file方式存储

    PathBuilderUuid pb = new PathBuilderUuid();

    fileSvr.pathSvr = pb.genFile(fileSvr.uid,fileSvr);

    fileSvr.pathSvr = fileSvr.pathSvr.replace("\","/");

    DBConfig cfg = new DBConfig();

    DBFile db = cfg.db();

    FileInf fileExist = new FileInf();

        

    boolean exist = db.exist_file(md5,fileExist);

    //数据库已存在相同文件,且有上传进度,则直接使用此信息

    if(exist && fileExist.lenSvr > 1)

    {

         fileSvr.nameSvr             = fileExist.nameSvr;

         fileSvr.pathSvr        = fileExist.pathSvr;

         fileSvr.perSvr              = fileExist.perSvr;

         fileSvr.lenSvr              = fileExist.lenSvr;

         fileSvr.complete       = fileExist.complete;

         db.Add(fileSvr);

        

         //触发事件

        up6_biz_event.file_create_same(fileSvr);

    }//此文件不存在

    else

    {

         db.Add(fileSvr);

         //触发事件

        up6_biz_event.file_create(fileSvr);

        

         FileBlockWriter fr = new FileBlockWriter();

         fr.CreateFile(fileSvr.pathSvr,fileSvr.lenLoc);

    }

    Gson gson = new Gson();

    String json = gson.toJson(fileSvr);

    json = URLEncoder.encode(json,"UTF-8");//编码,防止中文乱码

    json = json.replace("+","%20");

    json = callback + "({"value":"" + json + ""})";//返回jsonp格式数据。

    out.write(json);%>

    第一步:获取RandomAccessFile,随机访问文件类的对象

    第二步:调用RandomAccessFile的getChannel()方法,打开文件通道 FileChannel,这块逻辑可以优化,如果以后有分布式存储需求,可以改为分布式存储,减轻单台服务器的压力。

    public class FileBlockWriter {  

         public FileBlockWriter(){} 

         public void CreateFile(String pathSvr,long lenLoc)

         {

             try

             {

                  File ps = new File(pathSvr);

                  PathTool.createDirectory(ps.getParent());

                 

                 RandomAccessFile raf = new RandomAccessFile(pathSvr, "rw");

                 raf.setLength(lenLoc);//fix:以原始大小创建文件

                 raf.close();

             } catch (IOException e) {

                  // TODO Auto-generated catch block

                  e.printStackTrace();

             }       

         }

        

         public void write(long offset,String pathSvr,FileItem block)

         {       

             try

             {

                  InputStream stream = block.getInputStream();           

                  byte[] data = new byte[(int)block.getSize()];

                  stream.read(data);

                  stream.close();            

                 

                  RandomAccessFile raf = new RandomAccessFile(pathSvr,"rw");

                  raf.seek(offset);

                  raf.write(data);

                  raf.close();

                 

             } catch (IOException e) {

                  // TODO Auto-generated catch block

                  e.printStackTrace();

             }

         }

    }

    第三步:获取当前是第几个分块,计算文件的最后偏移量

    第四步:获取当前文件分块的字节数组,用于获取文件字节长度

    第五步:使用文件通道FileChannel类的 map()方法创建直接字节缓冲器  MappedByteBuffer

    第六步:将分块的字节数组放入到当前位置的缓冲区内  mappedByteBuffer.put(byte[] b);

    第七步:释放缓冲区

    第八步:检查文件是否全部完成上传

     

    文件夹扫描类

     

    存储路径生成类

     

    好了,到此就全部结束了,如果有疑问或批评,欢迎评论和私信,我们一起成长一起学习。

    最后放一张实现的效果图

     

    后端代码逻辑大部分是相同的,目前能够支持MySQL,Oracle,SQL。在使用前需要配置一下数据库,可以参考我写的这篇文章:http://blog.ncmem.com/wordpress/2019/08/07/java超大文件上传与下载/

    欢迎入群一起讨论:374992201

  • 相关阅读:
    MIne FirstBlog
    P6563 [SBCOI2020]一直在你身旁
    P6563 [SBCOI2020]一直在你身旁
    T122085 [SBCOI2020]时光的流逝
    LC 918. Maximum Sum Circular Subarray
    1026 Table Tennis
    LC 1442. Count Triplets That Can Form Two Arrays of Equal XOR
    LC 1316. Distinct Echo Substrings
    LC 493. Reverse Pairs
    1029 Median (二分)
  • 原文地址:https://www.cnblogs.com/songsu/p/13685244.html
Copyright © 2020-2023  润新知