前言:
在傳統的管理系統項目一般都遇到有附件的上傳與下載業務,我最近參與的項目中用到了附件的上傳與下載功能,今天正好有空整理一下。
業務邏輯:
附件先上傳到臨時目錄,業務頁面點擊保存后臨時目錄附件移動到正式目錄下,並將業務數據和附件相關信息保存入庫。
廢話不多說,直接上代碼!!!
- 批量上傳附件
前端 vue 代碼
前端 使用 Element-UI 的上傳控件
<el-card>
<h1>1.上傳附件</h1>
<el-upload
class="upload-demo"
drag
:headers="headers"
name="files"
:on-remove="remove"
:on-success="success"
:auto-upload="true"
action="api/basic/file/uploadFile"
multiple>
<i class="el-icon-upload"></i>
<div class="el-upload__text">將文件拖到此處,或<em>點擊上傳</em></div>
<div class="el-upload__tip" slot="tip">只能上傳doc docx文件,且不超10MB</div>
</el-upload>
</el-card>
上圖中 :on-success="success" 代表附件上傳成功后的鈎子
接收后端返回的臨時目錄
//文件上傳成功時的鈎子
success(res, file, fileList) {
this.fileList.length = 0
for (let i = 0; i < fileList.length; i++) {
if(fileList[i].response)
this.fileList.push(fileList[i].response.list[0])
}
console.log(`已上傳文件列表`, this.fileList)
},
后端 接口
配置文件的上傳路徑(前綴) 在application-dev.yml 中 ,項目打包發布到對應的服務,附件上傳后的位置就是配置的路徑
獲取application-dev.yml配置路徑
/**
*@Description 文件上傳處理方法 臨時目錄
*@Param MultipartFile file
*@Author
*@Date 2020/4/22 18:24
*@return R
*/
@PostMapping("/uploadFile")
public R uploadFile(@RequestParam(required = false, value = "files") MultipartFile[] multipartFiles) throws IOException {
List<AccessoryQueryExt> list = new ArrayList<AccessoryQueryExt>();
if(multipartFiles.length > 0){
for (MultipartFile file : multipartFiles){
R r = fileService.uploadFile(file,filePath);
String path = (String) r.get("path");
String fileName = (String) r.get("fileName");
AccessoryQueryExt accessoryQueryExt = new AccessoryQueryExt();
accessoryQueryExt.setName(fileName);
accessoryQueryExt.setTemporaryPath(path);
list.add(accessoryQueryExt);
}
return R.success().put("list",list);
}
return R.fail();
}
上傳的實現類
@Transactional(rollbackFor = Exception.class)
@Override
public R uploadFile(MultipartFile file,String filePath) throws IOException{
// 判斷文件是否為空
if(file.isEmpty()){
return R.fail().setMessage("file is empty");
}
String fileName = file.getOriginalFilename();
// 獲取文件名后綴
String suffixName = fileName.substring(fileName.lastIndexOf("."));
// 附件重命名 = 生成32位隨機碼 + 源文件后綴
String uuid = UUID.randomUUID().toString().trim().replaceAll("-", "");
String newFileName = uuid.concat(suffixName);
// 設置文件臨時存儲路徑
String path = "Temporary".concat("/").concat(newFileName);
// 全路徑
String fullPath = filePath.concat(path);
File dest = new File(fullPath);
// 判斷目錄是否存在
if(!dest.getParentFile().exists()){
dest.getParentFile().mkdirs();
}
// 寫入文件
file.transferTo(dest);
// 將臨時目錄和原附件名稱返回
return R.success().put("path",path).put("fileName",fileName);
}
2. 移動附件至正式目錄
前端 vue
<el-card>
<h1>2.保存附件</h1>
<el-button @click="save()">保存表單</el-button>
</el-card>
save(){
console.log(this.fileList)
for (let i = 0; i < this.fileList.length; i++){
this.fileList[i].isDelete = false // 此處為業務邏輯參數
this.fileList[i].relaId = "1" // 此處為業務邏輯參數
}
http
.post('basic/file/saveFile', this.fileList)
.then(function (res) {
console.log(res)
})
.catch(function (err) {
console.log(err);
});
}
后端接口及實現
/**
*@Description 保存附件至正式目錄
*@Param accessoryQueryExt 附件接收參數實體
*@Author
*@Date 2020/5/25 11:50
*@return R
*/
@PostMapping("/saveFile")
public R saveFile(@RequestBody List<AccessoryQueryExt> accessoryQueryExts){
for (AccessoryQueryExt accessoryQueryExt : accessoryQueryExts){
fileService.saveFile(accessoryQueryExt, filePath);
}
return R.success();
}
@Override
public R saveFile(AccessoryQueryExt accessoryQueryExt, String filePath) {
if(accessoryQueryExt.getIsDelete()){
// 刪除附件表中對應的數據並刪除附件
QueryWrapper<Accessory> wrapper = new QueryWrapper<Accessory>();
wrapper.eq("RELA_ID", accessoryQueryExt.getRelaId());
List<Accessory> accessories = this.baseMapper.selectList(wrapper);
if(accessories != null && accessories.size() > 0){
for (Accessory accessory : accessories){
// 刪除附件
new File(filePath.concat(accessory.getPath())).delete();
this.baseMapper.deleteById(accessory.getAccessoryId());
}
}
}
// 獲取附件臨時目錄
String temporaryPath = accessoryQueryExt.getTemporaryPath();
if(StringUtils.isEmpty(temporaryPath)){
throw new RuntimeException("附件臨時目錄為空");
}
String formatDate = new SimpleDateFormat("yyyyMM").format(new Date());
// 附件正式目錄 /data/uploadFile/業務類型碼/yyyymm 業務類型碼 從字典表中獲取
String businessTypeName = "common";
if(StringUtils.isNotBlank(accessoryQueryExt.getBusinessTypeName())){
businessTypeName = accessoryQueryExt.getBusinessTypeName();
}
String savePath = businessTypeName.concat("/").concat(formatDate).concat("/").concat(temporaryPath.substring(10));
String formalPath = filePath.concat(savePath);
File dest = new File(formalPath);
// 判斷目錄是否存在
if(!dest.getParentFile().exists()){
dest.getParentFile().mkdirs();
}
File fileOld = new File(filePath.concat(temporaryPath));
String sizeUnit = getPrintSize(fileOld.length());
String[] split = sizeUnit.split(":");
if(fileOld.renameTo(dest)){
// 將文件上傳信息寫入數據庫
Accessory accessory = new Accessory();
// 關聯ID
accessory.setRelaId(new BigDecimal(accessoryQueryExt.getRelaId()));
String perfix = accessoryQueryExt.getName().substring(accessoryQueryExt.getName().lastIndexOf(".") + 1);
if(FileTypeUtil.fileType(perfix)){
// 文件類型 01 文檔 02 圖片 03 視頻
accessory.setFileType("01");
} else if(FileTypeUtil.imageType(perfix)){
accessory.setFileType("02");
} else if(FileTypeUtil.videoType(perfix)){
accessory.setFileType("03");
}
if(filePath.indexOf(":") != -1){
//文件存儲類型 01 本地 02 遠程
accessory.setFileStoreType("01");
} else {
accessory.setFileStoreType("02");
}
//文件名
accessory.setName(accessoryQueryExt.getName());
accessory.setPath(savePath);
// 附件大小
accessory.setSize(new BigDecimal(split[0]));
// 附件單位
accessory.setUnit(split[1]);
this.baseMapper.insert(accessory);
return R.success();
}
return R.fail().setMessage("移動附件失敗");
}
/**
* 根據文件大小轉換為B、KB、MB、GB單位字符串顯示
* @param fileSize 文件的大小(long型)
* @return 返回 轉換后帶有單位的字符串
*/
public static String getPrintSize(long fileSize){
String strFileSize = null;
if(fileSize < 1024){
strFileSize = fileSize + ":" +"B";
return strFileSize;
}
DecimalFormat df = new DecimalFormat("######0.00");
if ((fileSize >= 1024) && (fileSize < 1024*1024)){ //KB
strFileSize = df.format(((double)fileSize)/1024) + ":" +"KB";
}else if((fileSize >= 1024*1024)&&(fileSize < 1024*1024*1024)){ //MB
strFileSize = df.format(((double)fileSize)/(1024*1024)) + ":" +"MB";
}else{ //GB
strFileSize = df.format(((double)fileSize)/(1024*1024*1024)) + ":" +"GB";
}
return strFileSize;
}
- 批量下載並壓縮
前端 vue
<el-card>
<h1>3.下載附件</h1>
<el-button @click="download()">下載附件</el-button>
</el-card>
download:function(){
http
({
method: 'post',
url: 'basic/file/downloadFile?relaId=1',
data: {}, //可選參數,后台用 @RequestBody接收
responseType: 'blob'
})
.then(function(res)
{
let data= res.data
var blob = new Blob([data])
// 創建下載的鏈接
var a = document.createElement('a');
a.style.display = 'none';
var href = window.URL.createObjectURL(blob);
a.href = href;
//顯示下載文件名 獲取后端返回的文件名
let filename= res.headers['content-disposition'].split('=')[1]
// 文件名中有中文 則對文件名進行轉碼 decodeURI
a.download= decodeURI(filename)
// 利用a標簽做下載
document.body.appendChild(a);
// 點擊下載
a.click();
// 下載后移除元素
document.body.removeChild(a);
// 釋放掉blob對象
window.URL.revokeObjectURL(href);
})
.catch(function (error)
{
console.info('response.error'+error.message)
});
}
后端 接口
/**
*@Description 文件下載處理方法
*@Param relaId 關聯ID
*@Author
*@Date 2020/4/23 13:47
*@return R
*/
@GetMapping("/downloadFile")
public void downloadFile(HttpServletResponse response,@RequestParam("relaId") String relaId){
fileService.downloadFile(relaId,response,filePath);
}
@Override
public void downloadFile(String relaId, HttpServletResponse response, String filePath)
{
// 從數據庫獲取文件信息
QueryWrapper<Accessory> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("RELA_ID",relaId);
List<Accessory> accessoryList = this.baseMapper.selectList(queryWrapper);
if(accessoryList != null && accessoryList.size() > 1){
// 壓縮包名稱
String fileName = String.valueOf(System.currentTimeMillis());
batchDownload(accessoryList,fileName, response, filePath);
}
// 若只有一個文件,則不用壓縮
if(accessoryList != null && accessoryList.size() == 1){
Accessory accessory = accessoryList.get(0);
download(accessory,filePath,response);
}
}
/**
* 批量下載並壓縮
*/
public void batchDownload(List<Accessory> accessoryList,String filename, HttpServletResponse response, String filePath) {
//創建zip文件並返回zip文件路徑
String zipPath = createZipAndReturnPath(accessoryList, filename, filePath);
try {
response.reset();
response.setCharacterEncoding("UTF-8");
response.setContentType("application/zip;charset=utf-8");
response.setHeader("content-disposition", "attachment;filename="+filename+".zip");
// 返回文件名,需要設置下面代碼
response.setHeader("Access-Control-Expose-Headers", "content-disposition,content-length");
//開始下載
BufferedInputStream is = new BufferedInputStream(new FileInputStream(new File(zipPath)));
BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
byte[] buff = new byte[1024];
int len = 0;
while ((len = is.read(buff, 0, buff.length)) != -1) {
out.write(buff, 0, len);
}
out.close();
out.flush();
is.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 批量打包
* @param filePath 根目錄
* @param filename 壓縮包名稱
* @return zip文件保存絕對路徑
*/
public String createZipAndReturnPath(List<Accessory> accessoryList,String filename, String filePath) {
try {
//生成打包下載后的zip文件:文件名.zip
String papersZipName = filename+".zip";
//zip文件保存路徑
String zipPath = filePath + papersZipName;
ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipPath));
for (Accessory accessory : accessoryList){
//獲得文件相對路徑
String relativePath = accessory.getPath();
//獲得文件名
String fileName = accessory.getName();
//獲得下載文件完整路徑
String downloadPath = filePath + relativePath;
FileInputStream fis = new FileInputStream(downloadPath);
out.putNextEntry(new ZipEntry(fileName));
//寫入壓縮包
int len;
byte[] buffer = new byte[1024];
while ((len = fis.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
out.closeEntry();
fis.close();
}
out.close();
out.flush();
return zipPath;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 下載
*/
public void download(Accessory accessory, String filePath, HttpServletResponse response){
String fileName = accessory.getName();
String path = accessory.getPath();
File file = new File(filePath.concat(path));
FileInputStream fis = null;
BufferedInputStream bis = null;
try {
String extension = ext(path);
//設置response內容的類型
response = setContextType(extension, response);
// 設置文件名
response.addHeader("content-disposition", "attachment;fileName=" + URLEncoder.encode(fileName,"UTF-8" ));
response.setHeader("content-length", Long.toString(file.length()));
// 返回文件名,需要設置下面代碼
response.setHeader("Access-Control-Expose-Headers", "content-disposition,content-length");
byte[] buffer = new byte[1024];
fis = new FileInputStream(file);
bis = new BufferedInputStream(fis);
OutputStream os = response.getOutputStream();
int i = bis.read(buffer);
while (i != -1) {
os.write(buffer, 0, i);
i = bis.read(buffer);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (bis != null) {
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 設置response內容的類型
*/
public static HttpServletResponse setContextType(String extension, HttpServletResponse response){
if("JPG".equalsIgnoreCase(extension)){
response.setContentType("application/x-jpg");
} else if("GIF".equalsIgnoreCase(extension)){
response.setContentType("image/gif");
} else if("mp4".equalsIgnoreCase(extension)){
response.setContentType("video/mpeg4");
} else if("avi".equalsIgnoreCase(extension)){
response.setContentType("video/avi");
} else if("WMV".equalsIgnoreCase(extension)){
response.setContentType("video/x-ms-wmv");
} else if("txt".equalsIgnoreCase(extension)){
response.setContentType("text/plain");
} else if("doc".equalsIgnoreCase(extension)){
response.setContentType("application/msword");
} else if("pdf".equalsIgnoreCase(extension)){
response.setContentType("application/pdf");
} else if("xls".equalsIgnoreCase(extension)){
response.setContentType("application/vnd.ms-excel");
}
return response;
}