大文件续传,文件分享

1. 最近各种文件分享平台,很多都要注册, 对于很多需要临时分享文件下的场景,不想被这种东西烦恼,于是借鉴网上代码,进行了一些修改, 写了一个文件分享项目, 该项目只是自用,数据库都没用, 就是简单的上传,下载, 删除也没有。如果有同样需求的同学可以看一下。

项目完整地址下载: cloudfile: 大文件断点续传, 文件分享,项目案例

下载后配置下路径和下载目录,正常运行即可

package com.zzk.cloudfile.demos.web;import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.zzk.cloudfile.demos.web.common.AjaxResult;
import com.zzk.cloudfile.demos.web.common.SliceBadException;
import com.zzk.cloudfile.demos.web.util.ExecutorUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;
import java.util.stream.Collectors;@Controller
public class BasicController {private final String identification = "-slice-";private final String uploadslicedir = "uploads" + File.separator + "slice" + File.separator;//分片目录private final String uploaddir = "uploads" + File.separator + "real" + File.separator;//实际文件目录
//    private final String physicalPath = "D:\\cloudfile\\";private final String physicalPath = "/cloudfile/";private final String virtualPath = "http://10.123.123.125:8081/uploads/real/";// http://127.0.0.1:8080/hello?name=lisi@RequestMapping("/hello")@ResponseBodypublic String hello(@RequestParam(name = "name", defaultValue = "unknown user") String name) {return "Hello " + name;}//获取分片@GetMapping("/testing/{fileName}/{fileSliceSize}/{fileSize}")@ResponseBodypublic AjaxResult testing(@PathVariable String fileName, @PathVariable long fileSliceSize, @PathVariable long fileSize) {String dir = fileNameMd5Dir(fileName, fileSize);String absolutePath = physicalPath + uploadslicedir + dir;File file = FileUtil.mkdir(absolutePath);if (!file.exists()) {return AjaxResult.error();}List<File> filesAll = FileUtil.loopFiles(file.getAbsolutePath());if (filesAll.size() < 2) {//分片缺少 删除全部分片文件 ,从新上传FileUtil.clean(absolutePath);return AjaxResult.error();}//从小到大文件进行按照序号排序,和判断分片是否损坏List<String> collect = null;try {collect = fileSliceIsbadAndSort(file, fileSliceSize);} catch (Exception e) {if (e instanceof SliceBadException) {FileUtil.clean(absolutePath);return AjaxResult.error();}}//获取最后一个分片String fileSliceLatest = collect.get(collect.size() - 1);int code = fileId(fileSliceLatest);//服务器的分片总大小必须小于或者等于文件的总大小if ((code * fileSliceSize) <= fileSize) {JSONObject obj = JSONUtil.createObj();obj.set("code", String.valueOf(code));obj.set("fileSliceLatest", fileSliceLatest);obj.set("progressNow", collect.size());return AjaxResult.success(obj);} else {//分片异常 ,删除全部分片文件,从新上传FileUtil.clean(absolutePath);return AjaxResult.error();}}@PostMapping(value = "/uploads")@ResponseBodypublic AjaxResult uploads(HttpServletRequest request, @RequestParam("part") MultipartFile part) throws IOException {String fileSliceName = request.getParameter("fileSliceName");long fileSize = Long.parseLong(request.getParameter("fileSize")); //文件大小String dir = fileSliceMd5Dir(fileSliceName, fileSize);File file = FileUtil.mkdir(physicalPath + uploadslicedir + dir + File.separator + fileSliceName);part.transferTo(file);int i = fileId(file.getName());
//        try {
//            //模拟耗时操作,要不前端显示太快,正式时删除
//            Thread.sleep(RandomUtil.randomInt(2000, 5000, true, true));
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }return AjaxResult.success(i);}// 合并分片@GetMapping(value = "/merge-file-slice/{fileSliceName}/{fileSliceSize}/{fileSize}")@ResponseBodypublic AjaxResult mergeFileSlice(@PathVariable String fileSliceName, @PathVariable long fileSliceSize, @PathVariable long fileSize) throws Exception {int l = (int) Math.ceil((double) fileSize / fileSliceSize); //有多少个分片String dir = fileSliceMd5Dir(fileSliceName, fileSize); //分片所在的目录String absolutePath = physicalPath + uploadslicedir + dir;File file = FileUtil.mkdir(absolutePath);String realFileName;String realFileNamePath;if (file.exists()) {List<String> filesAll = FileUtil.listFileNames(absolutePath);//阻塞循环判断是否还在上传  ,解决前端进行ajax异步上传的问题int beforeSize = filesAll.size();while (true) {if (filesAll.size() == l) {break;}Thread.sleep(1000);//之前分片数量和现在分片数据之差,如果大于1那么就在上传,那么继续filesAll = FileUtil.listFileNames(absolutePath);if (filesAll.size() - beforeSize >= 1) {beforeSize = filesAll.size();//继续检测continue;}//如果是之前分片和现在的分片相等的,那么在阻塞2秒后检测是否发生变化,如果还没变化那么上传全部完成,可以进行合并了//当然这不是绝对的,只能解决网络短暂的波动,因为有可能发生断网很长时间,网络恢复后文件恢复上传, 这个问题是避免不了的,所以我们在下面的代码进行数量的效验// 因为我们不可能一直等着他网好,所以如果1~3秒内没有上传新的内容,那么我们默认判定上传完毕if (beforeSize == filesAll.size()) {Thread.sleep(2000);filesAll = FileUtil.listFileNames(absolutePath);if (beforeSize == filesAll.size()) {break;}}}//分片数量效验if (filesAll.size() != l) {//分片缺少 ,删除全部分片文件,从新上传FileUtil.clean(absolutePath);return AjaxResult.error();}//获取实际的文件名称,组装路径realFileName = realFileName(fileSliceName);realFileNamePath = physicalPath + uploaddir + realFileName;//从小到大文件进行按照序号排序 ,和检查分片文件是否有问题List<String> collect = fileSliceIsbadAndSort(file, fileSliceSize);int fileSliceCount = collect.size();FileUtil.touch(realFileNamePath);List<Future<?>> futures = new ArrayList<>();for (int i = 0; i < fileSliceCount; i++) {int finalI = i;Future<?> future = ExecutorUtils.createFuture(() -> {String fileNameTemp = collect.get(finalI);long fileTempSize = new File(absolutePath + File.separator+fileNameTemp).length();byte[] bytes = new byte[(int) fileTempSize];try (RandomAccessFile r = new RandomAccessFile(absolutePath + File.separator +fileNameTemp, "r")) {r.read(bytes, 0, (int) fileTempSize);} catch (IOException e) {e.printStackTrace();}try (RandomAccessFile w = new RandomAccessFile(realFileNamePath, "rw")) {//当前文件写入的位置w.seek(finalI * fileSliceSize);w.write(bytes);} catch (IOException e) {e.printStackTrace();}return 1;});futures.add(future);}//阻塞到全部线程执行完毕后ExecutorUtils.waitComplete(futures);//删除全部分片文件FileUtil.clean(absolutePath);} else {//没有这个分片相关的的目录return AjaxResult.error();}return AjaxResult.success(virtualPath + realFileName);}//获取分片文件的目录private String fileSliceMd5Dir(String fileSliceName, long fileSize) {int i = fileSliceName.indexOf(identification);String substring = fileSliceName.substring(0, i);String dir = DigestUtil.md5Hex(substring + fileSize);return dir;}//通过文件名称获取文件目录private String fileNameMd5Dir(String fileName, long fileSize) {return DigestUtil.md5Hex(fileName + fileSize);}//获取分片的实际文件名private String realFileName(String fileSliceName) {int i = fileSliceName.indexOf(identification);String substring = fileSliceName.substring(0, i);return substring;}//获取文件序号private int fileId(String fileSliceName) {int i = fileSliceName.indexOf(identification) + identification.length();String fileId = fileSliceName.substring(i);return Integer.parseInt(fileId);}//判断是否损坏private List<String> fileSliceIsbadAndSort(File file, long fileSliceSize) throws Exception {String absolutePath = file.getAbsolutePath();List<String> filesAll = FileUtil.listFileNames(absolutePath);if (filesAll.size() < 1) {//分片缺少,删除全部分片文件 ,从新上传FileUtil.clean(absolutePath);throw new SliceBadException();}//从小到大文件进行按照序号排序List<String> collect = filesAll.stream().sorted((a, b) -> fileId(a) - fileId(b)).collect(Collectors.toList());//判断文件是否损坏,将文件排序后,进行前后序号相差大于1那么就代表少分片了for (int i = 0; i < collect.size() - 1; i++) {//检测分片的连续度if (fileId(collect.get(i)) - fileId(collect.get(i + 1)) != -1) {//分片损坏 删除全部分片文件 ,从新上传FileUtil.clean(absolutePath);throw new SliceBadException();}//检测分片的完整度if (new File(absolutePath + File.separator + collect.get(i)).length() != fileSliceSize) {//分片损坏 删除全部分片文件 ,从新上传FileUtil.clean(absolutePath);throw new SliceBadException();}}return collect;}}
//大文件分片上传,比如10G的压缩包,或者视频等,这些文件太大了  (需要后端配合进行)
class FileSliceUpload {constructor(testingUrl, uploadUrl, margeUrl, fileSelect) {this.testingUrl = testingUrl; // 检测文件上传的urlthis.uploadUrl = uploadUrl;//文件上传接口this.margeUrl = margeUrl; // 合并文件接口this.fileSelect = fileSelect;this.fileObj = null;this.totalsize = null;this.blockSize = 1024 * 1024; //每次上传多少字节1mb(最佳)this.sta = 0; //起始位置this.end = this.sta + this.blockSize; //结束位置this.count = 0; //分片个数this.barId = "bar"; //进度条idthis.progressId = "progress";//进度数值IDthis.progressNow = 0;this.progressTotal = 0;this.fileSliceName = ""; //分片文件名称this.fileName = "";this.uploadFileInterval = null;  //上传文件定时器}/***  样式可以进行修改* @param {*} progressId   需要将进度条添加到那个元素下面*/addProgress(progressSelect) {let bar = document.createElement("div")bar.setAttribute("id", this.barId);let num = document.createElement("div")num.setAttribute("id", this.progressId);num.innerText = "0%"bar.appendChild(num);document.querySelector(progressSelect).appendChild(bar)}//续传  在上传前先去服务器检测之前是否有上传过这个文件,如果还有返回上传的的分片,那么进行续传// 将当前服务器上传的最后一个分片会从新上传, 避免因为网络的原因导致分片损坏sequelFile() {if (this.fileName) {var xhr = new XMLHttpRequest();//同步var url = this.testingUrl + "/" + this.fileName + "/" + this.blockSize + "/" + this.totalsize;xhr.open('GET', encodeURI(url), false);xhr.send();if (xhr.readyState === 4 && xhr.status === 200) {let ret = JSON.parse(xhr.response)if (ret.code == 0) {let data = ret.datathis.count = data.code;this.fileSliceName = data.fileSliceName//计算起始位置和结束位置this.sta = this.blockSize * this.count//计算结束位置this.end = this.sta + this.blockSizethis.progressNow = data.progressNow} else {this.sta = 0; //从头开始this.end = this.sta + this.blockSize;this.count = 0; //分片个数this.progressNow = 0;}}}}stopUploadFile() {console.log("stop!!!")clearInterval(this.uploadFileInterval)}// 文件上传(单文件)startUploadFile() {this.resetProgress();this.fileObj = document.querySelector(this.fileSelect).files[0];this.totalsize = this.fileObj.size;this.fileName = this.fileObj.name;this.progressTotal = Math.ceil(this.totalsize / this.blockSize)console.log("文件总大小:" + this.totalsize + ", 需要分片个数: " + this.progressTotal )//查询是否存在之前上传过此文件,然后继续this.sequelFile()let ref = this; //拿到当前对象的引用,因为是在异步中使用this就是他本身而不是classthis.uploadFileInterval = setInterval(function () {if (ref.sta > ref.totalsize) {//上传完毕后结束定时器clearInterval(ref.uploadFileInterval)//发送合并请求ref.margeUploadFile()console.log("stop" + ref.sta);return;};//分片名称ref.fileSliceName = ref.fileName + "-slice-" + ref.count++//分割文件 ,var blob1 = ref.fileObj.slice(ref.sta, ref.end);console.log("start: "+ ref.sta + ", end : "+ ref.end)var fd = new FormData();fd.append('part', blob1);fd.append('fileSliceName', ref.fileSliceName);fd.append('fileSize', ref.totalsize);var xhr = new XMLHttpRequest();xhr.open('POST', ref.uploadUrl, true);xhr.send(fd); //异步发送文件,不管是否成功, 会定期检测xhr.onreadystatechange = function () {if (xhr.readyState === 4 && xhr.status === 200) {let ret = JSON.parse(xhr.response)if (ret.code == 0) {console.log(ret.data);ref.progressNow++;ref.calculateProgress();}}}//起始位置等于上次上传的结束位置ref.sta = ref.end;//结束位置等于上次上传的结束位置+每次上传的字节ref.end = ref.sta + ref.blockSize;}, 100)}margeUploadFile() {console.log("检测上传的文件完整性..........");var xhr = new XMLHttpRequest();//文件分片的名称/分片大小/总大小var url = this.margeUrl + "/" + this.fileSliceName + "/" + this.blockSize + "/" + this.totalsize;xhr.open('GET', encodeURI(url), true);xhr.send(); //发送请求xhr.onreadystatechange = function () {if (xhr.readyState === 4 && xhr.status === 200) {let ret = JSON.parse(xhr.response)if (ret.code == 0) {console.log("文件上传完毕");let address = document.getElementById("address");address.value = ret.msg;address.style.display = "block"} else {console.log("上传完毕但是文件上传过程中出现了异常", ret);}}}}calculateProgress() {let bar = document.getElementById(this.barId)let progressEl = document.getElementById(this.progressId)let percent = Math.ceil((this.progressNow / this.progressTotal) * 100)if (percent > 100) {percent = 100}bar.style.width = percent + '%';bar.style.backgroundColor = 'green';progressEl.innerHTML = percent + '%'}resetProgress(){let bar = document.getElementById(this.barId)let progressEl = document.getElementById(this.progressId)bar.style.width = 0 + '%';bar.style.backgroundColor = 'green';progressEl.innerHTML = 0 + '%'}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/21868.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

为何使用代理池:

匿名性&#xff1a; 代理池允许爬虫在请求目标网站时使用不同的IP地址&#xff0c;从而保护真实身份。 防封锁&#xff1a; 通过动态切换IP&#xff0c;可以规避网站对特定IP的封锁&#xff0c;提高爬虫的稳定性。 分布式请求&#xff1a; 代理池使爬虫能够通过多个IP地址发起…

go语言接口之接口类型

接口类型具体描述了一系列方法的集合&#xff0c;一个实现了这些方法的具体类型是这个接口类型的 实例。 io.Writer类型是用的最广泛的接口之一&#xff0c;因为它提供了所有的类型写入bytes的抽象&#xff0c;包括文 件类型&#xff0c;内存缓冲区&#xff0c;网络链接&#x…

Science Robotics 可实现中心聚焦与多光谱成像的鸟类视觉启发钙钛矿人工视觉系统

一、前沿速览 来自韩国基础科学研究所&#xff08;IBS&#xff09;纳米粒子研究中心的研究人员及其合作者提出了一个利用鸟类视觉注视点和多光谱成像的人工视觉系统。近日在Science Robotics 上发表的文章引入了人工中央凹和垂直堆叠的钙钛矿光电探测器阵列&#xff0c;其设计…

webserver timer

定时器用来处理非活动链接。 webserver项目中&#xff0c;通过信号函数来实现定时。 调用alarm()系统调用&#xff0c;设置好时间&#xff0c;这段时间结束后&#xff0c;alarm会发出sig_alarm信号。而信号处理函数做的事情仅仅只是将代表该信号的值写入管道(pipefd)。 在event…

NLP基础——序列模型(动手学深度学习)

序列模型 定义 序列模型是自然语言处理&#xff08;NLP&#xff09;和机器学习领域中一类重要的模型&#xff0c;它们特别适合处理具有时间顺序或序列结构的数据&#xff0c;例如文本、语音信号或时间序列数据。 举个例子&#xff1a;一部电影的评分在不同时间段的评分可能是…

#!/usr/bin/env bash

#!/usr/bin/env bash 是一个在 Unix 和 Unix-like 系统&#xff08;如 Linux 和 macOS&#xff09;中常见的 shebang&#xff08;或称为 shebang 行、hashbang、pound bang 或 hash-bang&#xff09;指令。 这个指令有以下几个部分&#xff1a; #!&#xff1a;这是一个特殊的…

智慧校园的发展趋势

在21世纪的数字化浪潮中&#xff0c;教育领域正经历着前所未有的变革。智慧校园&#xff0c;作为这场变革的前沿阵地&#xff0c;其发展趋势正引领着未来教育的新模式。我们将探讨智慧校园在融合技术、全场景应用、生态建设、数据安全以及可持续发展等方面的崭新动向&#xff0…

In eMule and the Kad network, “distance“ meaning

In eMule and the Kad network, “distance” is not defined as the literal number of hops or jumps from one node to another. Instead, it is a mathematical metric used to measure how “close” two identifiers are to each other in the key space. This distance …

特征交叉系列:FFM场感知因子分解机原理与实践

从FM到FFM知识准备 在上一节中[特征交叉系列&#xff1a;完全理解FM因子分解机原理和代码实战]介绍了FM算法&#xff0c;FM因子分解机通过在逻辑回归基础上增加所有特征的二阶交互项实现特征的交叉&#xff0c;但是随着特征数的增多二阶交互的数量呈平方级别增长&#xff0c;F…

ArcGIS模型构建器实例:一键拓扑(附模型下载)

ArcGIS模型构建器特别适用于流程固定的工作流。 要素的拓扑处理就非常符合这一特点&#xff0c;一个要素的拓扑过程基本固定&#xff0c;但是每次拓扑都要来一轮操作就很烦&#xff0c;这正是模型构建器的用武之地。 下面以ArcGIS Pro为例介绍在模型构建器中的整个拓扑流程&a…

CPU 使用率过高问题排查

文章目录 CPU 使用率过高问题排查1. CPU使用率过高常见问题2. 压力测试2.1 stress安装参数说明测试示例 2.2 stress-ng安装参数说明测试示例 3. 问题排查3.1 使用 top 命令3.2 使用 ps 命令3.3 使用 perf top3.4 vmstat 命令常用信息内存信息磁盘信息 CPU 使用率过高问题排查 …

第一篇 逻辑门(与门、或门、非门、异或门)

一、实验目的 了解DE1-SOC开发板一些外设。 掌握常用组合逻辑门电路的基本原理。 学习Verilog HDL的基本语法。 学习使用ModelSim工具对设计的电路进行仿真&#xff0c;包括编写Testbench仿真代码&#xff0c;以及ModelSim工具的使用。 熟悉使用Quartus软件从创建Quartus工…

算法金 | Python 中有没有所谓的 main 函数?为什么?

​大侠幸会&#xff0c;在下全网同名[算法金] 0 基础转 AI 上岸&#xff0c;多个算法赛 Top [日更万日&#xff0c;让更多人享受智能乐趣] 定义和背景 在讨论Python为何没有像C或Java那样的明确的main函数之前&#xff0c;让我们先理解一下什么是main函数以及它在其他编程语言…

javaweb——js

JavaScript是一种网页脚本语言。JavaScript代码可以很容易的嵌入到HTML页面中。 js引入 JavaScript嵌入到HTML页面中 <body><script>alert("Hello JS")</script> </body>再HTML页面中插入外部脚本JavaScript <body><script src&…

GIS数据快捷共享发布工具使用时注意事项

我们所有工具软件下载解压后&#xff0c;不要放在C盘或桌面&#xff0c;这样会产生权限冲突问题问题&#xff0c;这是WINDOWS的安全保护&#xff0c;大家要注意&#xff01;也不要让解压目录嵌套太深&#xff0c;Windows目录长度识别是有一定限制的!如果可以&#xff0c;最好是…

微收付系统让客户有钱花,让商家有钱赚!

微收付系统让客户有钱花&#xff0c;让商家有钱赚&#xff01; 作者按&#xff1a;随着那场呼啸全球的疫情&#xff0c;谜一样的消失&#xff01;给全球经济带来了沉重的打击&#xff0c;经济不振和战争笼罩着世界每一个角落&#xff0c;实体店面临着收款难&#xff0c;有钱人花…

DALL-E 2之学习心得

一、简介 DALL-E 2 是 OpenAI 开发的一款人工智能图像生成器&#xff0c;它可以根据自然语言的文本描述创建图像和艺术形式。这是一个根据文本生成图像的人工智能系统&#xff0c;是 DALL-E 模型的升级版。 DALL-E 2 的特点包括&#xff1a; 图像生成&#xff1a;能够从文本描述…

用旧安卓手机当 linux 开发机

1. 下载 Termux (快速链接&#xff0c;如果失效或者要下载最新版请去github release 下载 ) 注意手机硬件&#xff0c;我这个是 64 的所以下 64 的 https://github.com/termux/termux-app/releases/download/v0.118.0/termux-app_v0.118.0github-debug_arm64-v8a.apk 2. 弄到…

C语言 数组——数组的其他应用之筛法求素数

目录 数组的其他应用 求100以内的所有素数 筛法求100以内的所有素数 自顶向下、逐步求精设计算法 数组的其他应用 求100以内的所有素数 筛法求100以内的所有素数 自顶向下、逐步求精设计算法 step 1&#xff1a;设计总体算法  初始化数组a&#xff0c;使a[2]2, a[3]3,..…

【Git】如何因格式问题需要重新修改再提交代码时的操作技巧

若是出现已经在本地分支提交了一笔代码&#xff0c;该笔提交由于格式问题需要重新修改再提交时&#xff0c;且由于问题需要将本地分支删除时&#xff0c;此时需先新建一个新的分支&#xff0c;新的分支建好后&#xff0c;执行下面的操作&#xff1a; a.在目录下执行创建新的分…