本博客将介绍如何进行文件的分块上传。如果读者还想了解文件的“分块”下载相关内容可以去参考博客《Java 服务器端支持断点续传的源代码【支持快车、迅雷】》。
本文侧重介绍服务器端,客户端端请参考本篇博客的姊妹篇《Java 文件分块上传客户端源代码》,关于分块上传的思想及其流程,已在该博客中进行了详细说明,这里不再赘述。
直接上代码。接收客户端 HTTP 分块上传请求的 Spring MVC 控制器源代码如下:
@Controller public class UploadController extends BaseController { private static final Log log = LogFactory.getLog(UploadController.class); private UploadService uploadService; private AuthService authService; /** * 大文件分成小文件块上传,一次传递一块,最后一块上传成功后,将合并所有已经上传的块,保存到File Server * 上相应的位置,并返回已经成功上传的文件的详细属性. 当最后一块上传完毕,返回上传成功的信息。此时用getFileList查询该文件, * 该文件的uploadStatus为2。client请自行处理该状态下文件如何显示。(for UPS Server) * */ @RequestMapping("/core/v1/file/upload") @ResponseBody public Object upload(HttpServletResponse response, @RequestParam(value = "client_id", required = false) String appkey, @RequestParam(value = "sig", required = false) String appsig, @RequestParam(value = "token", required = false) String token, @RequestParam(value = "uuid", required = false) String uuid, @RequestParam(value = "block", required = false) String blockIndex, @RequestParam(value = "file", required = false) MultipartFile multipartFile, @RequestParam Map<String, String> parameters) { checkEmpty(appkey, BaseException.ERROR_CODE_16002); checkEmpty(token, BaseException.ERROR_CODE_16007); checkEmpty(uuid, BaseException.ERROR_CODE_20016); checkEmpty(blockIndex, BaseException.ERROR_CODE_20006); checkEmpty(appsig, BaseException.ERROR_CODE_10010); if (multipartFile == null) { throw new BaseException(BaseException.ERROR_CODE_20020);// 上传文件不存在 } Long uuidL = parseLong(uuid, BaseException.ERROR_CODE_20016); Integer blockIndexI = parseInt(blockIndex, BaseException.ERROR_CODE_20006); Map<String, Object> appMap = getAuthService().validateSigature(parameters); AccessToken accessToken = CasUtil.checkAccessToken(token, appMap); Long uid = accessToken.getUid(); String bucketUrl = accessToken.getBucketUrl(); // 从上传目录拷贝文件到工作目录 String fileAbsulutePath = null; try { fileAbsulutePath = this.copyFile(multipartFile.getInputStream(), multipartFile.getOriginalFilename()); } catch (IOException ioe) { log.error(ioe.getMessage(), ioe); throw new BaseException(BaseException.ERROR_CODE_20020);// 上传文件不存在 } File uploadedFile = new File(Global.UPLOAD_TEMP_DIR + fileAbsulutePath); checkEmptyFile(uploadedFile);// file 非空验证 Object rs = uploadService.upload(uuidL, blockIndexI, uid, uploadedFile, bucketUrl); setHttpStatusOk(response); return rs; } // TODO 查看下这里是否有问题 // 上传文件非空验证 private void checkEmptyFile(File file) { if (file == null || file.getAbsolutePath() == null) { throw new BaseException(BaseException.ERROR_CODE_20020);// 上传文件不存在 } } /** * 写文件到本地文件夹 * * @throws IOException * 返回生成的文件名 */ private String copyFile(InputStream inputStream, String fileName) { OutputStream outputStream = null; String tempFileName = null; int pointPosition = fileName.lastIndexOf("."); if (pointPosition < 0) {// myvedio tempFileName = UUID.randomUUID().toString();// 94d1d2e0-9aad-4dd8-a0f6-494b0099ff26 } else {// myvedio.flv tempFileName = UUID.randomUUID() + fileName.substring(pointPosition);// 94d1d2e0-9aad-4dd8-a0f6-494b0099ff26.flv } try { outputStream = new FileOutputStream(Global.UPLOAD_TEMP_DIR + tempFileName); int readBytes = 0; byte[] buffer = new byte[10000]; while ((readBytes = inputStream.read(buffer, 0, 10000)) != -1) { outputStream.write(buffer, 0, readBytes); } return tempFileName; } catch (IOException ioe) { // log.error(ioe.getMessage(), ioe); throw new BaseException(BaseException.ERROR_CODE_20020);// 上传文件不存在 } finally { if (outputStream != null) { try { outputStream.close(); } catch (IOException e) { } } if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { } } } } /** * 测试此服务是否可用 * * @param response * @return * @author zwq7978 */ @RequestMapping("/core/v1/file/testServer") @ResponseBody public Object testServer(HttpServletResponse response) { setHttpStatusOk(response); return Global.SUCCESS_RESPONSE; } public UploadService getUploadService() { return uploadService; } public void setUploadService(UploadService uploadService) { this.uploadService = uploadService; } public void setAuthService(AuthService authService) { this.authService = authService; } public AuthService getAuthService() { return authService; } }
比如要上传的文件是 test450k.mp4。对照《Java 文件分块上传客户端源代码》中分块上传服务器对分块文件参数定义的名字"file",upload 方法里使用的是 MultipartFile 接收该对象。对于每次的 HTTP 请求,使用 copyFile 方法将文件流输出到服务器本地的一个临时文件夹里,比如作者的是 D:/defonds/syncPath/uploadTemp,该文件下会有 50127019-b63b-4a54-8f53-14efd1e58ada.mp4 临时文件生成用于保存上传文件流。
分块依次上传。当所有块都上传完毕之后,将这些临时文件都转移到服务器指定目录中,比如作者的这个目录是 D:/defonds/syncPath/file,在该文件夹下会有/1/temp_dir_5_1 目录生成,而 uploadTemp 的临时文件则被挨个转移到这个文件夹下,生成形如 5.part0001 的文件。以下是文件转移的源代码:
/** * 把所有块从临时文件目录移到指定本地目录或S2/S3 * * @param preUpload */ private void moveBlockFiles(BlockPreuploadFileInfo preUpload) { @SuppressWarnings("unchecked") String[] s3BlockUrl=new String[preUpload.getBlockNumber()]; String[] localBlockUrl=new String[preUpload.getBlockNumber()];//本地的块文件路径 以便以后删除 List<BlockUploadInfo> blocks = (List<BlockUploadInfo>) getBaseDao().queryForList( "upload.getBlockUploadFileByUuid", preUpload.getUuid()); String tempDirName = SyncUtil.getTempDirName(preUpload.getUuid(), preUpload.getUid()); String parentPath = Global.UPLOAD_ABSOLUTE_PAHT_ + Global.PATH_SEPARATIVE_SIGN + String.valueOf(preUpload.getUid()); String dirPath = parentPath + Global.PATH_SEPARATIVE_SIGN + tempDirName; new File(dirPath).mkdirs();//创建存放块文件的文件夹 (本地) int j=0; for (BlockUploadInfo info : blocks) { try { String strBlockIndex = createStrBlockIndex(info.getBlockIndex()); String suffixPath = preUpload.getUuid() + ".part" + strBlockIndex; String tempFilePath = info.getTempFile(); File tempFile = new File(tempFilePath); File tmpFile = new File(dirPath + suffixPath); if (tmpFile.exists()) { FileUtils.deleteQuietly(tmpFile); } FileUtils.moveFile(tempFile, tmpFile); localBlockUrl[j]=dirPath + suffixPath; j++; info.setStatus(Global.MOVED_TO_NEWDIR); getBaseDao().update("upload.updateBlockUpload", info); if (log.isInfoEnabled()) log.info(preUpload.getUuid() + " " + info.getBuId() + " moveBlockFiles"); } catch (IOException e) { log.error(e.getMessage(), e); throw new BaseException("file not found"); } } preUpload.setLocalBlockUrl(localBlockUrl); preUpload.setDirPath(dirPath); preUpload.setStatus(Global.MOVED_TO_NEWDIR); getBaseDao().update("upload.updatePreUploadInfo", preUpload); } private String createStrBlockIndex(int blockIndex) { String strBlockIndex; if (blockIndex < 10) { strBlockIndex = "000" + blockIndex; } else if (10 <= blockIndex && blockIndex < 100) { strBlockIndex = "00" + blockIndex; } else if (100 <= blockIndex && blockIndex < 1000) { strBlockIndex = "0" + blockIndex; } else { strBlockIndex = "" + blockIndex; } return strBlockIndex; }
最后是文件的组装源代码:
/** * 组装文件 * */ private void assembleFileWithBlock(BlockPreuploadFileInfo preUpload) { String dirPath = preUpload.getDirPath(); // 开始在指定目录组装文件 String uploadedUrl = null; String[] separatedFiles; String[][] separatedFilesAndSize; int fileNum = 0; File file = new File(dirPath); separatedFiles = file.list(); separatedFilesAndSize = new String[separatedFiles.length][2]; Arrays.sort(separatedFiles); fileNum = separatedFiles.length; for (int i = 0; i < fileNum; i++) { separatedFilesAndSize[i][0] = separatedFiles[i]; String fileName = dirPath + separatedFiles[i]; File tmpFile = new File(fileName); long fileSize = tmpFile.length(); separatedFilesAndSize[i][1] = String.valueOf(fileSize); } RandomAccessFile fileReader = null; RandomAccessFile fileWrite = null; long alreadyWrite = 0; int len = 0; byte[] buf = new byte[1024]; try { uploadedUrl = Global.UPLOAD_ABSOLUTE_PAHT_ + Global.PATH_SEPARATIVE_SIGN + preUpload.getUid() + Global.PATH_SEPARATIVE_SIGN + preUpload.getUuid(); fileWrite = new RandomAccessFile(uploadedUrl, "rw"); for (int i = 0; i < fileNum; i++) { fileWrite.seek(alreadyWrite); // 读取 fileReader = new RandomAccessFile((dirPath + separatedFilesAndSize[i][0]), "r"); // 写入 while ((len = fileReader.read(buf)) != -1) { fileWrite.write(buf, 0, len); } fileReader.close(); alreadyWrite += Long.parseLong(separatedFilesAndSize[i][1]); } fileWrite.close(); preUpload.setStatus(Global.ASSEMBLED); preUpload.setServerPath(uploadedUrl); getBaseDao().update("upload.updatePreUploadInfo", preUpload); if(Global.BLOCK_UPLOAD_TO!=Global.BLOCK_UPLOAD_TO_LOCAL) { //组装完毕没有问题 删除掉S2/S3上的block String[] path=preUpload.getS3BlockUrl(); for (String string : path) { try { if(Global.BLOCK_UPLOAD_TO==Global.BLOCK_UPLOAD_TO_S2) { S2Util.deleteFile(preUpload.getBucketUrl(), string); }else { S3Util.deleteFile(preUpload.getBucketUrl(), string); } } catch (Exception e) { log.error(e.getMessage(), e); } } } if (log.isInfoEnabled()) log.info(preUpload.getUuid() + " assembleFileWithBlock"); } catch (IOException e) { log.error(e.getMessage(), e); try { if (fileReader != null) { fileReader.close(); } if (fileWrite != null) { fileWrite.close(); } } catch (IOException ex) { log.error(e.getMessage(), e); } } }
BlockPreuploadFileInfo 是我们自定义的业务文件处理 bean。
OK,分块上传的服务器、客户端源代码及其工作流程至此已全部介绍完毕,以上源代码全部是经过项目实践过的,大部分现在仍运行于一些项目之中。有兴趣的朋友可以自己动手,将以上代码自行改造,看看能否运行成功。如果遇到问题可以在本博客下跟帖留言,大家一起讨论讨论。
相关推荐
4. 服务器端处理:服务器端需要能够接收和合并这些分块,确保按照正确的顺序组合成原始文件,并能识别并处理续传请求。 5. 错误处理与重试机制:当上传失败时,能识别错误并决定是否重试,以及重试的策略。 6. ...
在“JAVA文件传输(论文+源代码).zip”中,我们可能找到一篇关于Java文件传输技术的学术论文以及相关的源代码实现。通过分析这个压缩包内的内容,我们可以深入理解Java文件传输的原理、设计模式以及实际应用。 首先...
在这个示例中,我们将探讨如何使用Java的Socket实现文件传输,包括服务器端和客户端的实现细节。 首先,我们从服务器端开始。服务器端的核心是监听特定端口(在这个例子中是8821)上的连接请求,并在接收到连接后...
2. 源代码:包括了Java编写的服务器端和客户端程序,可能有类、接口、方法等实现文件传输的核心功能,如文件上传、下载、断点续传、进度显示等功能。 3. 测试数据或示例文件:用于验证和演示文件传输功能的文件。 4....
这个"JAVA文件传输(LW+源代码).rar"压缩包可能包含了轻量级(Lightweight)的Java文件传输解决方案以及相关的源代码,使得学习者或开发者能够理解和应用这些代码来构建自己的文件传输系统。 在Java中,文件传输主要...
本资源“JAVA文件传输(论文+源代码).rar”包含了一篇关于该主题的论文以及相应的源代码,旨在帮助学生和开发者深入理解如何在Java环境中实现文件的上传、下载和共享。 论文部分可能探讨了以下几个核心知识点: 1. ...
在这个"Socket编程文件上传源代码案例"中,我们将深入探讨如何利用Socket和IO流技术来实现在TCP网络上的文件上传功能。 首先,我们需要理解Socket的概念。Socket是操作系统提供的一种进程间通信(IPC)机制,它为...
1. **服务器端**:包含监听客户端连接,接收文件上传请求,保存文件到服务器的逻辑。 2. **客户端**:负责选择本地文件,建立连接,发送文件到服务器的代码。 3. **控制台界面或图形用户界面**:提供用户交互,如...
本资源“JAVA文件传输(论文+源代码)”是一个典型的Java项目实例,主要关注文件传输这一核心功能。这个项目不仅提供了完整的源代码,还有配套的论文,为学习者提供了深入理解文件传输机制和Java编程实践的机会。 ...
8. **源代码分析**:压缩包内的源代码可能包含客户端和服务器端的实现,展示了如何使用Java进行文件上传和下载。通过阅读和理解这些代码,可以深入学习文件传输的具体实现。 9. **异常处理**:在文件传输过程中,...
10. **论文与源代码**:`JAVA文件传输(论文+源代码)`可能包含了一份详细的技术报告或论文,解释了项目的实现原理和技术细节,以及源代码,供学习者理解和参考。 通过这个项目,开发者或学习者不仅可以了解文件传输...
标题中的"文件上传下载源代码和linux中tomcat发布包.rar"表明这是一个关于Java Web开发的资源包,其中包含了文件上传和下载的功能实现,并且可以在Linux环境下的Tomcat服务器上运行。这个压缩包可能是一个完整的项目...
这需要在客户端和服务器端保存并同步文件传输状态。 6. **错误处理和重试机制**:在网络通信中,数据包丢失或传输错误是常见情况,因此需要良好的错误处理和自动重试机制。 7. **安全性**:文件传输可能涉及敏感...
2. **文件分块上传**:为了处理大文件,源代码可能采用了文件分块技术。大文件会被分割成小块,每一块单独上传,确保在网络不稳定的情况下也能完成上传。这样可以提高上传的成功率,减少因为网络问题导致的失败。 3...
本资源"文件上传与下载源代码"提供了在MyEclipse环境下实现这一功能的具体示例。MyEclipse是一款强大的Java集成开发环境,常用于构建Web应用。 1. **文件上传**: 文件上传是用户通过网页将本地文件传输到服务器的...