エフェクト画像
デモ体験アドレス:http://easymall.ysqorz.top/file/upload(長期的な有効性は保証されていません)
アイデアと解決策の議論
2回目のパス
ここでの「2回目のアップロード」とは、ユーザーがファイルのアップロードを選択すると、サーバーはファイルが以前にアップロードされたかどうかを検出し、サーバーがすでにファイルを保存している場合(まったく同じ)、すぐにフロントエンドに戻ります。 「ファイルは正常にアップロードされました」。フロントエンドはプログレスバーを100%に更新します。これにより、ユーザーは「セカンドパス」の感覚を得ることができます。
サーバーにアップロードされたリソースごとに、データベースのdb_fileテーブルにレコードを挿入する必要があります。各レコードには、ファイルのMD5値、アップロードされたバイト数など、およびもちろん他のファイル情報フィールドが含まれます。テーブルの構造については、後で詳しく説明します。
2番目の送信を実現するには、次の2つの問題を考慮する必要があります。
1.ファイルを一意に識別する方法は?
ファイル全体のMD5値を計算します。ここで、ファイルのMD5値を計算するためのオンラインツールを見つけました:http://www.metools.info/other/o21.html。
2.ファイル全体のMD5値を計算するにはどうすればよいですか?
(1)最初にファイル全体のMD5値を計算します。サーバーがファイルのMD5値を計算するには、ファイル全体を最初にサーバーにアップロードする必要があるため、この作業はフロントエンドで行う必要があります。これは「セカンドパス」の概念に違反していませんか?
(2)MD5値を計算するには、jsプラグインspark-md5.jsを使用する必要があります。Githubアドレス:https://github.com/satazor/js-spark-md5
上記のオンラインツールもjsプラグインを使用する必要があります。このプラグインの使い方は?デモはGithubのREADME.mdで提供されており、そのデモでは、大きなファイル(Gの数)ファイルのMD5値をブロックごとに計算できます。ブロックがない場合、大きなファイルのMD5値を計算する方法がないことを試しました。したがって、デモをコピーして変更するだけで済みます。
再開可能なアップロード(ブレークポイントアップロード)
転送を再開するとどのような影響がありますか?ユーザーが大きなファイルをアップロードしていて、途中で[キャンセル]をクリックしました。次回ファイルを再度アップロードするときは、最初からアップロードするのではなく、前回中断したところからアップロードを続けることができます。これは少し複雑です。実装ロジックにはフロントエンドとバックエンドが含まれます。
1.一般的なファイルアップロードの実装プロセス
一般的なファイルアップロードの場合、サーバーはMultipartFileを使用して受信し、フロントエンドはAjaxを使用してファイルを非同期にアップロードします。ファイルがGの数に達するなど、非常に大きい場合、サーバーは最初に最大アップロードサイズを設定する必要があります。ファイルのアップロードは間違いなく時間のかかる操作です。つまり、フロントエンドのファイル送信は時間のかかる操作であると同時に、サーバー側でのMultipartFileの受信も時間のかかる操作です。受信プロセス中に、サーバーは一時ファイルを生成します。デフォルトでは、Webアプリケーションサーバーの一時ディレクトリにあります。もちろん、指定することもできます。ファイルのアップロードが完了するか、受信プロセス中にエラーが発生すると、一時ファイルは自動的に削除されます。この現象を観察するために自分で確認することもできます。ここではこれ以上は説明しません。
2.ファイルのアップロードをキャンセルするにはどうすればよいですか?
Ajaxを使用してファイルを非同期にアップロードします。アップロードを終了する場合は、XMLHttpRequestのabort()メソッドのみを呼び出すことができます。このメソッドは、クライアントとサーバー間の接続を直接中断し、サーバーのストリーム読み取り例外を引き起こします(SpringMVCによってスローされます)。例外がスローされた後、コントローラーレイヤーと後続のロジックは実行されません。領収書の途中で生成された一時ファイルも自動的に削除されます。これは、アップロードの進行状況をデータベースに保存できないことも意味します。
3.ファイルをキャンセルした後、ファイルのアップロードの進行状況を保存するにはどうすればよいですか?
XMLHttpRequestのabort()メソッドによるものであれ、Webページが突然閉じられたり切断されたりするなど、ファイルのアップロードが終了すると、フロントエンドが一方的に切断され、サーバーが例外をスローして、一時ファイルが削除され、保存できませんアップロードの進行状況。この問題を解決するために、ブロック単位でアップロードするソリューションを使用できます。
フロントエンドでは、大きなファイル全体のアップロードされていない部分がjsによって同じサイズのn個のブロックに分割され、各ブロックのサイズはchunkSizeとして定義されます(例:2MB)。最後のブロックがchunkSizeより小さい場合、最後の2つのブロックが1つにマージされます。アップロードの各部分に対してajaxリクエストが開始されます。各部分が正常にアップロードされると、サーバーはNIOを介して独自に作成したファイルの最後にこの部分を追加し、同時にファイルの「アップロードされたバイト」を更新します。データベース内。
Ajaxリクエストが突然中断された場合、このセグメントのアップロードは失敗するだけで、以前に正常にアップロードされたセグメントには影響しません。その後、次回再度アップロードするときに、フロントエンドはサーバーから返されたファイル「アップロードされたバイト数」を受け取ります。次に、フロントエンドjsは、これに基づいてファイルのアップロードされていない部分を見つけ、アップロードされていない部分をブロックで再度アップロードできるようになります。
バックエンドインターフェイスの説明
データリターンフォーマットパッケージ
インターフェース紹介 | リクエスト方法 | リクエストパス | パラメータの説明をリクエストする | パラメータ備考をリクエストする | 成功時に返されるデータ |
サーバーにファイルリソースがあるかどうかを確認します | 役職 | / file / check | fileMd5:ファイル全体のMD5値 totalBytes:ファイル全体の合計バイト数 サフィックス:ファイルのサフィックス |
3つのリクエストパラメータすべてが必要です | FileCheckRspVo { uploadToken:このインターフェースによって発行されたトークン(Jwt)、アップロードに必要 uploadBytes:アップロードされたファイルのバイト数 } |
チャンクでアップロード | 役職 | / file / upload | ファイル:アップロードするセグメント uploadToken:アップロード時に携帯する必要のあるトークン |
両方のパラメーターが必要です | アップロードされたバイト数 |
/ file / checkインターフェイス(以下、チェックインターフェイスと呼びます)によって返されるデータにuploadTokenが含まれているのはなぜですか?リクエスト/ file / uploadインターフェース(以下、アップロードインターフェースと呼びます)が/ file / checkによって発行されたuploadTokenを運ぶのはなぜですか?
アップロードインターフェースはランダムにリクエストすることはできません。チェックインターフェースをリクエストした後でなければなりません。この順序を確実にするために、リクエストアップロードインターフェースはチェックインターフェースによって発行されたトークンを運ぶ必要があります(jwt、Baiduは自分で行うことができます)。このjwtトークンを偽造する方法はありません。間違ったトークンまたは期限切れのトークンを使用してアップロードインターフェースにアクセスすると、チェックアウトされます。!!
キーコード
キーコードとコメントは以下に掲載されていますが、実装の特定のロジックに注意してください。デモの完全なコードが必要な場合:xxx(後で追加してください)
フロントエンドコード
upload.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<!-- 不设置的话,手机端不会进行响应式布局 -->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>大文件断点续传</title>
<!-- 引入Bootstrap核心样式文件(必须) -->
<link rel="stylesheet" href="/lib/bootstrap/css/bootstrap.min.css">
<!-- 你自己的样式或其他文件 -->
<link rel="stylesheet" href="/css/upload.css">
<!--站点图标-->
<!-- ... -->
</head>
<body>
<div class="container">
<div class="progress progress-top">
<div id="progressbar" class="progress-bar progress-bar-success" role="progressbar"
aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="min-width: 2em;">
0%
</div>
</div>
<div class="form-group">
<div class="input-group">
<input id='location' class="form-control" onclick="$('#i-file').click();">
<div class="input-group-btn">
<input type="button" id="i-check" value="选择" class="btn btn-default" onclick="$('#i-file').click();">
<input type="button" value="上传" onClick="upload()" class="btn btn-default" >
<input type="button" value="取消" onClick="cancel()" class="btn btn-default" >
<input type="button" value="下载二维码" onClick="downloadQRCode()" class="btn btn-default" >
</div>
</div>
<input type="file" name="file" id='i-file' onchange="$('#location').val($('#i-file').val());" style="display: none">
<p class="help-block proccess-msg" id="proccess-msg"></p>
</div>
<img id="downloadQRcode" src="" />
</div>
<script src="/lib/jquery/jquery.min.js"></script>
<!-- 引入所有的Bootstrap的JS插件 -->
<script src="/lib/bootstrap/js/bootstrap.min.js"></script>
<script src="/lib/spark-md5.min.js"></script>
<script src="/js/upload.js"></script>
</body>
</html>
upload.js
// 真正上传文件的ajax请求
var uploadAjax = null;
var fileMd5; // 文件md5值
function downloadQRCode() {
if (fileMd5 != null) {
var url = '/file/qrcode/generate?fileMd5=' + fileMd5 + '&seconds=900';
$('#downloadQRcode').attr('src', url);
}
}
function upload() {
// 文件限制检查
var file = $('#i-file')[0].files[0];
if (file == null) {
return;
}
var suffix = file.name.substr(file.name.lastIndexOf('.'));
var type = file.type;
var totalBytes = file.size;
var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
console.log(suffix, type, totalBytes);
// 开始。通过回调函数,进行链式调用
calculteFileMd5();
// 计算文件的MD5值,分块计算,支持大文件
function calculteFileMd5() {
var chunkSize = 2097152, // Read in chunks of 2MB 。每一块的大小
chunks = Math.ceil(file.size / chunkSize), // 整个文件可分为多少块,向下取整
currentChunk = 0, // 当前加载的块。初始化为0
spark = new SparkMD5.ArrayBuffer(),
fileReader = new FileReader();
// fileReader加载文件数据到内存之后会执行此回调函数
fileReader.onload = function (e) {
refreshMsg('read chunk nr ' + (currentChunk + 1) + ' of ' + chunks);
spark.append(e.target.result); // Append array buffer
currentChunk++;
if (currentChunk < chunks) {
loadNext();
} else {
refreshMsg('finished loading');
// 计算出文件的md5值
fileMd5 = spark.end();
refreshMsg('computed hash: ' + fileMd5); // Compute hash
// 服务器检查文件是否存在
requestCheckFile();
}
};
// 开始计算
loadNext();
function loadNext() {
var start = currentChunk * chunkSize,
end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;
fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
}
}
// 请求服务器验证文件
function requestCheckFile() {
$.ajax({
url: '/file/check', // 提交到controller的url路径
type: "POST", // 提交方式
dataType: "json",
data: {
fileMd5: fileMd5,
totalBytes: totalBytes,
suffix: suffix
},
success: function (res) {
console.log(res);
if (res.code === 2000) {
var percentage = parseFloat(res.data.uploadedBytes) / totalBytes * 100;
refreshStatus(percentage);
if (res.data.uploadedBytes < totalBytes) {
requestRealUpload(res.data);
}
}
}
});
}
// 分块上传
function requestRealUpload(params) {
var chunkSize = 2097152; // 每一块的大小。2 M
//var chunks = Math.ceil((totalBytes - params.uploadedBytes) / chunkSize); // 尚未上传的部分可分为几块,取下整
//var currentChunk = 0; // 当前加载的块。初始化为0
uploadChunk(params.uploadedBytes);
// 请求服务端,上传一块
function uploadChunk(uploadedBytes) {
var formData = new FormData();
var start = uploadedBytes;
var end = Math.min(start + chunkSize, totalBytes);
console.log(start, end);
formData.append('file', blobSlice.call(file, start, end)); // [start, end)
formData.append('uploadToken', params.uploadToken); // 携带token
var preLoaded = 0; // 当前块的上一次加载的字节数,用于计算速度
var preTime = new Date().getTime(); // 上一次回调进度的时间
uploadAjax = $.ajax({
url: '/file/upload',
type: "POST",
data: formData,
cache: false,
contentType: false, // 必须 不设置内容类型
processData: false, // 必须 不处理数据
xhr: function() {
//获取原生的xhr对象
var xhr = $.ajaxSettings.xhr();
if (xhr.upload) {
//添加 progress 事件监听
//console.log(xhr.upload);
xhr.upload.onprogress = function(e) {
// e.loaded 应该是指当前块,已经加载到内存的字节数
// 这里的上传进度是整个文件的上传进度,并不是指当前这一块
var percentage = (start + e.loaded) / totalBytes * 100;
refreshStatus(percentage); // 更新百分比
// 计算速度
var now = new Date().getTime();
var duration = now - preTime; // 毫秒
var speed = ((e.loaded - preLoaded) / duration).toFixed(2); // KB/s
preLoaded = e.loaded;
preTime = now;
//if (duration > 1000) {
// 隔1秒才更新速度
refreshMsg('正在上传:' + speed + ' KB/s');
//}
};
xhr.upload.onabort = function() {
refreshMsg('已取消上传,服务端已保存上传完成的分块,下次重传可续传');
};
}
return xhr;
},
success: function(res) {
//成功回调
console.log(res);
if (res.code === 2000) {
if (res.data < totalBytes) {
uploadChunk(res.data); // 上传下一块
} else {
refreshMsg('上传完成!'); //所有块上传完成
}
} else {
refreshMsg(res.msg); // 当前块上传失败,提示错误,后续块停止上传
}
}
});
}
}
// 刷新进度条
function refreshStatus(percentage) {
var per = (percentage).toFixed(2);
console.log(per);
$('#progressbar').text(per + '%');
$('#progressbar').css({
width: per + '%'
});
}
// 更新提示信息
function refreshMsg(msg) {
$('#proccess-msg').text(msg);
}
}
// 直接终端上传的ajax请求,后端会抛出异常
function cancel() {
if (uploadAjax != null) {
console.log(uploadAjax);
uploadAjax.abort();
}
}
サーバーコード
FileController.java
package net.ysq.easymall.controller;
import java.io.IOException;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import com.auth0.jwt.interfaces.DecodedJWT;
import net.ysq.easymall.common.JwtUtils;
import net.ysq.easymall.common.ResultModel;
import net.ysq.easymall.common.StatusCode;
import net.ysq.easymall.po.DbFile;
import net.ysq.easymall.po.User;
import net.ysq.easymall.service.FileService;
import net.ysq.easymall.vo.FileCheckRspVo;
/**
* @author passerbyYSQ
* @date 2020-11-13 17:42:40
*/
@Controller
@RequestMapping("/file")
public class FileController {
@Autowired
private FileService fileService;
@GetMapping("/upload")
public String uploadPage() {
return "upload";
}
@PostMapping("/check")
@ResponseBody
public ResultModel<FileCheckRspVo> checkFileExist(String fileMd5, long totalBytes,
String suffix, HttpSession session) {
// 简单的参数检查,之后再全局处理优化
if (StringUtils.isEmpty(fileMd5) || totalBytes <= 0
|| StringUtils.isEmpty(suffix)) {
return ResultModel.error(StatusCode.PARAM_IS_INVALID);
}
/*
// 检查大小
DataSize size = DataSize.of(totalBytes, DataUnit.BYTES);
// 限制100 M
DataSize limit = DataSize.of(100, DataUnit.MEGABYTES);
if (size.compareTo(limit) > 0) {
String msg = String.format("当前文件大小为 %d MB,最大允许大小为 %d MB",
size.toMegabytes(), limit.toMegabytes());
return ResultModel.error(StatusCode.FILE_SIZE_EXCEEDED.getCode(), msg);
}
*/
User user = (User) session.getAttribute("user");
// 根据md5去数据库查询是否已存在文件
DbFile dbFile = fileService.checkFileExist(fileMd5);
// 如果不存在,则创建文件,并插入记录。如果已存在,返回结果
if (ObjectUtils.isEmpty(dbFile)) {
dbFile = fileService.createFile(fileMd5, totalBytes, suffix, user);
}
FileCheckRspVo fileCheckRspVo = new FileCheckRspVo();
fileCheckRspVo.setUploadedBytes(dbFile.getUploadedBytes());
if (dbFile.getUploadedBytes() < dbFile.getTotalBytes()) { // 未上传完,返回token
String uploadToken = fileService.generateUploadToken(user.getEmail(), dbFile);
fileCheckRspVo.setUploadToken(uploadToken);
}
return ResultModel.success(fileCheckRspVo);
}
@PostMapping("/upload")
@ResponseBody
public ResultModel<Long> uploadFile(MultipartFile file, String uploadToken) {
// 解析过程可能会抛出异常,全局进行捕获
DecodedJWT decodedJWT = JwtUtils.verifyJwt(uploadToken);
String fileMd5 = decodedJWT.getClaim("fileMd5").asString();
// 如果token验证通过(没有异常抛出),则肯定能找得到
DbFile dbFile = fileService.checkFileExist(fileMd5);
// 上传文件
long uploadedBytes = fileService.transfer(file, dbFile);
System.out.println("已上传:" + uploadedBytes);
//System.out.println("总大小:" + dbFile.getTotalBytes());
return ResultModel.success(uploadedBytes);
}
@GetMapping("/qrcode/generate")
public void downloadByQrcode(String fileMd5, long seconds,
HttpServletResponse response) throws IOException, Exception {
if (ObjectUtils.isEmpty(fileMd5)) {
throw new Exception("fileMd5为空");
}
if (ObjectUtils.isEmpty(seconds) || seconds <= 0) {
seconds = 60 * 15; // 15分钟
}
DbFile dbFile = fileService.checkFileExist(fileMd5);
if (ObjectUtils.isEmpty(dbFile)) {
throw new Exception("fileMd5错误");
}
fileService.generateDownloadQRCode(seconds, dbFile, response.getOutputStream());
}
@GetMapping("/qrcode/download")
public void downloadByQrcode(String downloadToken, HttpSession session,
HttpServletResponse response) {
System.out.println("download!!");
DecodedJWT decodedJWT = JwtUtils.verifyJwt(downloadToken);
String fileMd5 = decodedJWT.getClaim("fileMd5").asString();
DbFile dbFile = fileService.checkFileExist(fileMd5);
// 设置响应头
response.setHeader("Content-Type", "application/x-msdownload");
response.setHeader("Content-Disposition", "attachment; filename=" + dbFile.getRandName());
fileService.download(dbFile, response);
}
}
FileService.java
package net.ysq.easymall.service;
import java.io.OutputStream;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.multipart.MultipartFile;
import net.ysq.easymall.po.DbFile;
import net.ysq.easymall.po.User;
/**
* @author passerbyYSQ
* @date 2020-11-13 17:54:06
*/
public interface FileService {
// 下载
void download(DbFile dbFile, HttpServletResponse response);
// 生成下载的token
void generateDownloadQRCode(long seconds, DbFile dbFile, OutputStream outStream) throws Exception;
// 根据id查找
DbFile findById(Integer fileId);
// 根据fileMd5检查文件是否已存在
DbFile checkFileExist(String fileMd5);
// 在磁盘上创建文件,并将记录插入数据库
DbFile createFile(String fileMd5, long totalBytes, String suffix, User user);
// 生成上传文件的token
String generateUploadToken(String email, DbFile dbFile);
// 复制到目标目录
long transfer(MultipartFile file, DbFile dbFile);
}
FileServiceImpl.java
package net.ysq.easymall.service.impl;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.ResourceUtils;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import net.ysq.easymall.common.CloseUtils;
import net.ysq.easymall.common.JwtUtils;
import net.ysq.easymall.common.QRCodeUtils;
import net.ysq.easymall.dao.DbFileMapper;
import net.ysq.easymall.po.DbFile;
import net.ysq.easymall.po.User;
import net.ysq.easymall.service.FileService;
/**
* @author passerbyYSQ
* @date 2020-11-13 17:55:09
*/
@Service
public class FileServiceImpl implements FileService {
@Autowired
private DbFileMapper dbFileMapper;
@Override
public DbFile findById(Integer fileId) {
DbFile record = new DbFile();
record.setId(fileId);
return dbFileMapper.selectOne(record);
}
@Override
public DbFile checkFileExist(String fileMd5) {
DbFile record = new DbFile();
// 设置查询条件
record.setFileMd5(fileMd5);
// 找不到返回null
DbFile dbFile = dbFileMapper.selectOne(record);
//System.out.println(dbFile);
return dbFile;
}
@Override
public DbFile createFile(String fileMd5, long totalBytes, String suffix, User user) {
try {
// 创建目标目录
File classpath = ResourceUtils.getFile("classpath:");
File destDir = new File(classpath, "upload/" + user.getEmail());
if (!destDir.exists()) {
destDir.mkdirs(); // 递归创建创建多级
System.out.println("创建目录成功:" + destDir.getAbsolutePath());
}
// 利用UUID生成随机文件名
String randName = UUID.randomUUID().toString().replace("-", "") + suffix;
File destFile = new File(destDir, randName);
// 创建目标
destFile.createNewFile();
String path = user.getEmail() + "/" + randName;
DbFile dbFile = new DbFile();
dbFile.setFileMd5(fileMd5);
dbFile.setRandName(randName);
dbFile.setPath(path);
dbFile.setTotalBytes(totalBytes);
dbFile.setUploadedBytes(0L);
dbFile.setCreatorId(user.getId());
int count = dbFileMapper.insertSelective(dbFile);
return count == 1 ? dbFile : null;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@Override
public String generateUploadToken(String email, DbFile dbFile) {
Map<String, String> claims = new HashMap<>();
claims.put("fileMd5", dbFile.getFileMd5());
// 5分钟后过期
String jwt = JwtUtils.generateJwt(claims, 60 * 1000 * 5);
return jwt;
}
@Override
public void generateDownloadQRCode(long seconds, DbFile dbFile, OutputStream outStream) throws Exception {
Map<String, String> claims = new HashMap<>();
claims.put("fileMd5", dbFile.getFileMd5());
long millis = Duration.ofSeconds(seconds).toMillis();
String downloadToken = JwtUtils.generateJwt(claims, millis);
String downloadUrl = ServletUriComponentsBuilder
.fromCurrentContextPath()
.path("/file/qrcode/download")
.queryParam("downloadToken", downloadToken)
.toUriString();
QRCodeUtils.encode(downloadUrl, outStream);
//QRCodeUtil.generateWithStr(downloadUrl, outStream);
}
@Override
public long transfer(MultipartFile file, DbFile dbFile) {
InputStream inStream = null;
ReadableByteChannel inChannel = null;
FileOutputStream outStream = null;
FileChannel outChannel = null;
try {
inStream = file.getInputStream();
inChannel = Channels.newChannel(inStream);
File classpath = ResourceUtils.getFile("classpath:");
File destFile = new File(classpath, "upload/" + dbFile.getPath());
outStream = new FileOutputStream(destFile, true); // 注意,第二个参数为true,否则无法追加
outChannel = outStream.getChannel();
long count = outChannel.transferFrom(inChannel, outChannel.size(), file.getSize());
//long count = inChannel.transferTo(dbFile.getUploadedBytes(), inChannel.size(), outChannel);
DbFile record = new DbFile();
record.setId(dbFile.getId());
record.setUploadedBytes(dbFile.getUploadedBytes() + count);
// 更新已上传的字节数到数据库
dbFileMapper.updateByPrimaryKeySelective(record);
return record.getUploadedBytes();
} catch (IOException e) {
e.printStackTrace();
} finally {
CloseUtils.close(inChannel, inStream, outChannel, outStream);
}
return dbFile.getUploadedBytes();
}
@Override
public void download(DbFile dbFile, HttpServletResponse response) {
FileInputStream inStream = null;
FileChannel inChannel = null;
OutputStream outStream = null;
WritableByteChannel outChannel = null;
try {
File classpath = ResourceUtils.getFile("classpath:");
File destFile = new File(classpath, "upload/" + dbFile.getPath());
inStream = new FileInputStream(destFile);
inChannel = inStream.getChannel();
outStream = response.getOutputStream();
outChannel = Channels.newChannel(outStream);
inChannel.transferTo(0, inChannel.size(), outChannel);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
CloseUtils.close(outChannel, outStream, inChannel, inStream);
}
}
}