1.項目背景
項目后端框架是springboot,后端與后端之間需要進行文件傳輸旁振,這個文件大小從幾兆到10G不等杂伟,當文件太大時祭芦,傳輸可能存在失敗超時等各種問題筷笨。所以涉及這種大文件傳輸時,直接傳輸是不可行的,需要有其他方式進行傳輸胃夏,傳輸?shù)姆绞街饕袃煞N:
1.http協(xié)議
傳輸?shù)奈募笮∮邢拗浦峄颍斘募酱髸r,傳輸較慢构订,而且會占用應(yīng)用需要的內(nèi)存侮叮,所以這種方式傳輸需要對文件進行拆分,將大文件拆分成小文件后再按小文件傳輸悼瘾。
2.ftp/minio等三方傳輸
在接收端搭建ftp/minio等三方的文件存儲服務(wù)器囊榜,然后通過接口向服務(wù)器傳輸數(shù)據(jù),這種傳輸方式對文件大小限制較小亥宿,不需要拆分卸勺,而且文件越大,與http協(xié)議相比傳輸?shù)男适窃娇斓摹?/p>
2.解決方案
1.文件拆分傳輸
文件拆分代碼:
public static void splitFile(String filePath, String outputPath){
File file=new File(filePath);
RandomAccessFile in=null;
RandomAccessFile out =null;
long length=file.length();//文件大小
log.info("需要上傳的zip文件大小為:{}kb",length/1024);
long splitSize=50*1024*1024;//單片文件大小,50M
long count=length%splitSize==0?(length/splitSize):(length/splitSize+1);//文件分片數(shù)
byte[] bt=new byte[1024];
try {
in=new RandomAccessFile(file, "r");
for (int i = 1; i <= count; i++) {
out = new RandomAccessFile(new File(outputPath+"/"+file.getName()+"."+i+".part"), "rw");//定義一個可讀可寫且后綴名為.part的二進制分片文件
long begin = (i-1)*splitSize;
long end = i* splitSize;
int len=0;
in.seek(begin);
while (in.getFilePointer()<end&&-1!=(len=in.read(bt))) {
out.write(bt, 0, len);
}
out.close();
}
log.info("文件分片成功烫扼,filePath={}",filePath);
} catch (Exception e) {
log.error("文件分片失敗,error:", e);
}finally {
try {
if(out!=null){
out.close();
}
if(in!=null){
in.close();
}
} catch (IOException e) {
}
}
}
文件合并代碼:
public static void mergeFile(String splitDir,String newFilePath){
File dir=new File(splitDir);//目錄對象
File[] fileArr=dir.listFiles(new FilenameFilter() {//分片文件
@Override
public boolean accept(File dir, String name) {
return name.endsWith(".part");
}
});
List<File> fileList = Arrays.asList(fileArr);
Collections.sort(fileList, new Comparator<File>() {//根據(jù)文件名稱對fileList順序排序
@Override
public int compare(File o1, File o2) {
int lastIndex11=o1.getName().lastIndexOf(".");
int lastIndex12=o1.getName().substring(0,lastIndex11).lastIndexOf(".")+1;
int lastIndex21=o2.getName().lastIndexOf(".");
int lastIndex22=o2.getName().substring(0,lastIndex21).lastIndexOf(".")+1;
int num1=Integer.parseInt(o1.getName().substring(lastIndex12,lastIndex11));
int num2=Integer.parseInt(o2.getName().substring(lastIndex22,lastIndex21));
return num1-num2;
}
});
RandomAccessFile in=null;
RandomAccessFile out =null;
try {
out=new RandomAccessFile(newFilePath, "rw");
for(File file:fileList){//按順序合成文件
in=new RandomAccessFile(file, "r");
int len=0;
byte[] bt=new byte[1024];
while (-1!=(len=in.read(bt))) {
out.write(bt, 0, len);
}
in.close();
}
log.info("文件合成成功,splitDir={},newFilePath={}", splitDir, newFilePath);
} catch (Exception e) {
log.error("文件合成失敗,splitDir={},newFilePath={}", splitDir, newFilePath);
}finally {
try {
if(in!=null){
in.close();
}
if(out!=null){
out.close();
}
} catch (IOException e) {
}
}
}
2.minio/http傳輸
參考minio/ftp部署文檔先搭建minio或者ftp服務(wù)器曙求,使用它們提供的api即可。
3.并發(fā)問題
上面提高的文件拆分傳輸方案是串行傳輸映企,當傳輸?shù)阶詈笠粋€文件時悟狱,需要有個字段標識是最后一個,然后這次接受完數(shù)據(jù)就可以將分片的文件進行合并堰氓。但是挤渐,當傳輸不是串行而是并行時,文件接受的順序不一定是發(fā)送文件的先后順序双絮,所以沒辦法根據(jù)發(fā)送順序來決定是否合并浴麻,可以使用java的CountDownLatch來輔助,需要接受的文件個數(shù)可以通過發(fā)送端傳送過來囤攀,當傳送一個后CountDownLatch計數(shù)建1软免,當為0時執(zhí)行合并。