zoukankan      html  css  js  c++  java
  • Web 大文件上传解决方案(500M以上)

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

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

    上传文件实体类:

    看得出来,实体类中已经有很多我们需要的功能了,还有实用的属性。如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

  • 相关阅读:
    JavaCC 研究与应用( 8000字 心得 源程序)
    推荐UML插件Green UML、AmaterasUML
    Custom PMD Rules
    静态分析工具及使用总结(三)
    静态分析工具及使用总结(二)CheckStyle
    静态分析工具及使用总结(一)
    基于antlr的表达式解析器——函数类型验证
    基于antlr的表达式解析器——函数生成(通过freemarker)
    网络流--最大流--hlpp(预流推进)模板
    网络流--最大流--Dinic模板矩阵版(当前弧优化+非当前弧优化)
  • 原文地址:https://www.cnblogs.com/songsu/p/12808368.html
Copyright © 2011-2022 走看看