Java 文件分块上传客户端源代码

2024-04-23 08:38

本文主要是介绍Java 文件分块上传客户端源代码,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

MIME协议(中文版).doc

        本博客介绍如何进行文件的分块上传。本文侧重介绍客户端,服务器端请参考博客《Java 文件分块上传服务器端源代码》。建议读者朋友在阅读本文代码前先了解一下 MIME 协议。

        所谓分块上传并非把大文件进行物理分块,然后挨个上传,而是依次读取大文件的一部分文件流进行上传。分块,倒不如说分流比较切实。本文通过一个项目中的示例,说明使用 Apache 的 HttpComponents/HttpClient 对大文件进行分块上传的过程。示例使用的版本是 HttpComponents Client 4.2.1。
        本文仅以一小 demo 功能性地解释 HttpComponents/HttpClient 分块上传,没有考虑 I/O 关闭、多线程等资源因素,读者可以根据自己的项目酌情处理。
        本文核心思想及流程:以 100 MB 大小为例,大于 100 MB 的进行分块上传,否则整块上传。对于大于 100 MB 的文件,又以 100 MB 为单位进行分割,保证每次以不大于 100 MB 的大小进行上传。比如 304 MB 的一个文件会分为 100 MB、100 MB、100 MB、4 MB 等四块依次上传。第一次读取 0 字节开始的 100 MB 个字节,上传;第二次读取第 100 MB 字节开始的 100 MB 个字节,上传;第三次读取第 200 MB 字节开始的 100 MB 个字节,上传;第四次读取最后剩下的 4 MB 个字节进行上传。

        自定义的 ContentBody 源码如下,其中定义了流的读取和输出:

[html]  view plain copy print ?
  1. package com.defonds.rtupload.common.util.block;  
  2.   
  3. import java.io.File;  
  4. import java.io.IOException;  
  5. import java.io.OutputStream;  
  6. import java.io.RandomAccessFile;  
  7.   
  8. import org.apache.http.entity.mime.content.AbstractContentBody;  
  9.   
  10. import com.defonds.rtupload.GlobalConstant;  
  11.   
  12. public class BlockStreamBody extends AbstractContentBody {  
  13.       
  14.     //给MultipartEntity看的2个参数  
  15.     private long blockSize = 0;//本次分块上传的大小  
  16.     private String fileName = null;//上传文件名  
  17.     //writeTo需要的3个参数  
  18.     private int blockNumber = 0blockIndex = 0;//blockNumber分块数;blockIndex当前第几块  
  19.     private File targetFile = null;//要上传的文件  
  20.   
  21.     private BlockStreamBody(String mimeType) {  
  22.         super(mimeType);  
  23.         // TODO Auto-generated constructor stub  
  24.     }  
  25.       
  26.     /**  
  27.      * 自定义的ContentBody构造子  
  28.      * @param blockNumber分块数  
  29.      * @param blockIndex当前第几块  
  30.      * @param targetFile要上传的文件  
  31.      */  
  32.     public BlockStreamBody(int blockNumber, int blockIndex, File targetFile) {  
  33.         this("application/octet-stream");  
  34.         this.blockNumber = blockNumber;//blockNumber初始化  
  35.         this.blockIndex = blockIndex;//blockIndex初始化  
  36.         this.targetFile = targetFile;//targetFile初始化  
  37.         this.fileName = targetFile.getName();//fileName初始化  
  38.         //blockSize初始化  
  39.         if (blockIndex < blockNumber) {//不是最后一块,那就是固定大小了  
  40.             this.blockSize = GlobalConstant.CLOUD_API_LOGON_SIZE;  
  41.         } else {//最后一块  
  42.             this.blockSize = targetFile.length() - GlobalConstant.CLOUD_API_LOGON_SIZE * (blockNumber - 1);  
  43.         }  
  44.     }  
  45.   
  46.     @Override  
  47.     public void writeTo(OutputStream out) throws IOException {  
  48.         byte b[] = new byte[1024];//暂存容器  
  49.         RandomAccessFile raf  = new RandomAccessFile(targetFile, "r");//负责读取数据  
  50.         if (blockIndex == 1) {//第一块  
  51.             int n = 0;  
  52.             long readLength = 0;//记录已读字节数  
  53.             while (readLength <= blockSize - 1024) {//大部分字节在这里读取  
  54.                 n = raf.read(b, 0, 1024);  
  55.                 readLength += 1024;  
  56.                 out.write(b, 0, n);  
  57.             }  
  58.             if (readLength <= blockSize) {//余下的不足 1024 个字节在这里读取  
  59.                 n = raf.read(b, 0, (int)(blockSize - readLength));  
  60.                 out.write(b, 0, n);  
  61.             }  
  62.         } else if (blockIndex < blockNumber) {//既不是第一块,也不是最后一块  
  63.             raf.seek(GlobalConstant.CLOUD_API_LOGON_SIZE * (blockIndex - 1));//跳过前[块数*固定大小 ]个字节  
  64.             int n = 0;  
  65.             long readLength = 0;//记录已读字节数  
  66.             while (readLength <= blockSize - 1024) {//大部分字节在这里读取  
  67.                 n = raf.read(b, 0, 1024);  
  68.                 readLength += 1024;  
  69.                 out.write(b, 0, n);  
  70.             }  
  71.             if (readLength <= blockSize) {//余下的不足 1024 个字节在这里读取  
  72.                 n = raf.read(b, 0, (int)(blockSize - readLength));  
  73.                 out.write(b, 0, n);  
  74.             }  
  75.         } else {//最后一块  
  76.             raf.seek(GlobalConstant.CLOUD_API_LOGON_SIZE * (blockIndex - 1));//跳过前[块数*固定大小 ]个字节  
  77.             int n = 0;  
  78.             while ((n = raf.read(b, 0, 1024)) != -1) {  
  79.                 out.write(b, 0, n);  
  80.             }  
  81.         }  
  82.           
  83.         //TODO 最后不要忘掉关闭out/raf  
  84.     }  
  85.       
  86.     @Override  
  87.     public String getCharset() {  
  88.         // TODO Auto-generated method stub  
  89.         return null;  
  90.     }  
  91.       
  92.     @Override  
  93.     public String getTransferEncoding() {  
  94.         // TODO Auto-generated method stub  
  95.         return "binary";  
  96.     }  
  97.       
  98.     @Override  
  99.     public String getFilename() {  
  100.         // TODO Auto-generated method stub  
  101.         return fileName;  
  102.     }  
  103.   
  104.     @Override  
  105.     public long getContentLength() {  
  106.         // TODO Auto-generated method stub  
  107.         return blockSize;  
  108.     }  
  109.   
  110. }  


        在自定义的 HttpComponents/HttpClient 工具类 HttpClient4Util 里进行分块上传的封装:

[java]  view plain copy print ?
  1. public static String restPost(String serverURL, File targetFile,Map<String, String> mediaInfoMap){  
  2.   
  3.     String content ="";  
  4.     try {  
  5.         DefaultHttpClient httpClient = new DefaultHttpClient();  
  6.         HttpPost post = new HttpPost(serverURL +"?");  
  7.         httpClient.getParams().setParameter("http.socket.timeout",60*60*1000);  
  8.         MultipartEntity mpEntity = new MultipartEntity();  
  9.         List<String> keys = new ArrayList<String>(mediaInfoMap.keySet());  
  10.         Collections.sort(keys, String.CASE_INSENSITIVE_ORDER);  
  11.         for (Iterator<String> iterator = keys.iterator(); iterator.hasNext();) {  
  12.             String key = iterator.next();  
  13.             if (StringUtils.isNotBlank(mediaInfoMap.get(key))) {  
  14.                 mpEntity.addPart(key, new StringBody(mediaInfoMap.get(key)));  
  15.             }  
  16.         }  
  17.           
  18.         if(targetFile!=null&&targetFile.exists()){  
  19.             ContentBody contentBody = new FileBody(targetFile);  
  20.             mpEntity.addPart("file", contentBody);  
  21.         }  
  22.         post.setEntity(mpEntity);  
  23.   
  24.           
  25.         HttpResponse response = httpClient.execute(post);  
  26.         content = EntityUtils.toString(response.getEntity());  
  27.         httpClient.getConnectionManager().shutdown();  
  28.     } catch (Exception e) {  
  29.         e.printStackTrace();  
  30.     }  
  31.     System.out.println("=====RequestUrl==========================\n"  
  32.             +getRequestUrlStrRest(serverURL, mediaInfoMap).replaceAll("&fmt=json"""));  
  33.     System.out.println("=====content==========================\n"+content);  
  34.     return content.trim();  
  35. }  

        其中 "file" 是分块上传服务器对分块文件参数定义的名字。细心的读者会发现,整块文件上传直接使用 Apache 官方的 InputStreamBody,而分块才使用自定义的 BlockStreamBody。

        最后调用 HttpClient4Util 进行上传:

[java]  view plain copy print ?
  1. public static Map<String, String> uploadToDrive(  
  2.         Map<String, String> params, String domain) {  
  3.   
  4.     File targetFile = new File(params.get("filePath"));  
  5.     long targetFileSize = targetFile.length();  
  6.     int mBlockNumber = 0;  
  7.     if (targetFileSize < GlobalConstant.CLOUD_API_LOGON_SIZE) {  
  8.         mBlockNumber = 1;  
  9.     } else {  
  10.         mBlockNumber = (int) (targetFileSize / GlobalConstant.CLOUD_API_LOGON_SIZE);  
  11.         long someExtra = targetFileSize  
  12.                 % GlobalConstant.CLOUD_API_LOGON_SIZE;  
  13.         if (someExtra > 0) {  
  14.             mBlockNumber++;  
  15.         }  
  16.     }  
  17.     params.put("blockNumber", Integer.toString(mBlockNumber));  
  18.   
  19.     if (domain != null) {  
  20.         LOG.debug("Drive---domain=" + domain);  
  21.         LOG.debug("drive---url=" + "http://" + domain + "/sync"  
  22.                 + GlobalConstant.CLOUD_API_PRE_UPLOAD_PATH);  
  23.     } else {  
  24.         LOG.debug("Drive---domain=null");  
  25.     }  
  26.     String responseBodyStr = HttpClient4Util.getRest("http://" + domain  
  27.             + "/sync" + GlobalConstant.CLOUD_API_PRE_UPLOAD_PATH, params);  
  28.   
  29.     ObjectMapper mapper = new ObjectMapper();  
  30.     DrivePreInfo result;  
  31.     try {  
  32.         result = mapper.readValue(responseBodyStr, ArcDrivePreInfo.class);  
  33.     } catch (IOException e) {  
  34.         LOG.error("Drive.preUploadToArcDrive error.", e);  
  35.         throw new RtuploadException(GlobalConstant.ERROR_CODE_13001);// TODO  
  36.     }  
  37.     // JSONObject jsonObject = JSONObject.fromObject(responseBodyStr);  
  38.     if (Integer.valueOf(result.getRc()) == 0) {  
  39.         int uuid = result.getUuid();  
  40.         String upsServerUrl = result.getUploadServerUrl().replace("https",  
  41.                 "http");  
  42.         if (uuid != -1) {  
  43.             upsServerUrl = upsServerUrl  
  44.                     + GlobalConstant.CLOUD_API_UPLOAD_PATH;  
  45.             params.put("uuid", String.valueOf(uuid));  
  46.   
  47.             for (int i = 1; i <= mBlockNumber; i++) {  
  48.                 params.put("blockIndex""" + i);  
  49.                 HttpClient4Util.restPostBlock(upsServerUrl, targetFile,  
  50.                         params);//  
  51.             }  
  52.         }  
  53.     } else {  
  54.         throw new RtuploadException(GlobalConstant.ERROR_CODE_13001);// TODO  
  55.     }  
  56.     return null;  
  57. }  

        其中 params 这个 Map 里封装的是服务器分块上传所需要的一些参数,而上传块数也在这里进行确定。
        本文中的示例经本人测试能够上传大文件成功,诸如 *.mp4 的文件上传成功没有出现任何问题。如果读者朋友测试时遇到问题无法上传成功,请在博客后跟帖留言,大家共同交流下。本文示例肯定还存在很多不足之处,如果读者朋友发现还请留言指出,笔者先行谢过了。

这篇关于Java 文件分块上传客户端源代码的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/928306

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

在cscode中通过maven创建java项目

在cscode中创建java项目 可以通过博客完成maven的导入 建立maven项目 使用快捷键 Ctrl + Shift + P 建立一个 Maven 项目 1 Ctrl + Shift + P 打开输入框2 输入 "> java create"3 选择 maven4 选择 No Archetype5 输入 域名6 输入项目名称7 建立一个文件目录存放项目,文件名一般为项目名8 确定