手把手編寫檔案伺服器

大南好南發表於2022-03-14

一、專案需求
最近做了人力資源管理相關的專案,涉及到檔案的上傳、下載和預覽等功能,由於條件有限,只能自己手寫檔案伺服器。

 二、思路
由於用的是SpringBoot版本,這邊進行相應的檔案引數配置以及專案部署的伺服器劃分出相應靜態檔案區域,進行流上傳,路徑儲存,下載和預覽通過轉換成Base64給前端來實現,下面就來看下程式碼的實現過程。

 三、實現過程
 1.在application.yml檔案進行配置宣告

#檔案上傳
upload:
path:
#臨時上傳目錄(本地測試環境)
temporary: D:/desk/up/
#正式上傳目錄(正式環境)
formal: /home/formal/file/CV/
#正式上傳目錄(正式環境)
formalOffer: /home/formal/file/offer/
#轉化後圖片儲存路徑(本地測試環境)
imgTemp: D:/desk/up/img
#轉化後圖片儲存路徑(正式環境)
img: /home/formal/file/picture/CV/
#轉化後圖片儲存路徑(正式環境)
imgOffer: /home/formal/file/picture/offer/
multipart:
#單個檔案最大記憶體
maxFileSize: 5MB
#所有檔案最大記憶體
maxRequestSize: 10MB

2.編寫FileConfig配置類

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

/**
* 功能描述: 檔案上傳配置類
*
* @author WCJ
* @date 2022/3/1 9:05
*/
@Data
@Configuration
public class FilesConfig {
/**
* 單檔案上傳最大記憶體
*/
@Value("${upload.multipart.maxFileSize}")
private String maxFileSize;

/**
* 多檔案上傳最大記憶體
*/
@Value("${upload.multipart.maxRequestSize}")
private String maxRequestSize;

/**
* 檔案上傳臨時目錄
*/
@Value("${upload.path.temporary}")
private String temporaryPath;

/**
* 簡歷檔案上傳正式目錄
*/
@Value("${upload.path.formal}")
private String formalPath;

/**
* 簡歷圖片生成路徑
*/
@Value("${upload.path.img}")
private String imgUrl;

/**
* offer檔案上傳正式目錄
*/
@Value("${upload.path.formalOffer}")
private String offerFormalPath;

/**
* offer圖片生成路徑
*/
@Value("${upload.path.imgOffer}")
private String offerImgUrl;

/**
* 測試環境圖片生成路徑
*/
@Value("${upload.path.imgTemp}")
private String tempImgUrl;

}

Tip1--這邊需要注意的是,由於使用到@Value路徑注入,如果配置資訊是寫在 application-dev.yml 裡面的話,在啟動專案是會出現類比dev配置檔案先載入的問題,出現載入報空的問題,解決方法是把配置資訊放在總配置檔案 application.yml 第一時間載入出來。

3.編寫對應Controller--檔案上傳

 

/**
* 服務物件
*/
@Resource(name = "fileBPO")
private FileBPO fileBPO;

/**
* 檔案配置物件
*/
@Resource
private FilesConfig filesConfig;
/**
* 設定檔案上傳大小:在配置檔案中限定
* @return
*/
@Bean
public MultipartConfigElement multipartConfigElement() {
MultipartConfigFactory factory = new MultipartConfigFactory();
//單個檔案最大
factory.setMaxFileSize(DataSize.parse(filesConfig.getMaxFileSize()));
//設定總上傳資料總大小
factory.setMaxRequestSize(DataSize.parse(filesConfig.getMaxRequestSize()));
return factory.createMultipartConfig();
}

/**
* 僅上傳簡歷返回檔案id
* @param request 檔案
* @return 簡歷檔案id
* @throws IOException
*/
@OperLog
@PostMapping("/uploadCV")
@ApiOperation(value= "上傳簡歷", notes = "僅上傳簡歷並返回檔案id\n"+
"{HttpServletRequest 流形式上傳\n" +
"MultipartFile檔案,(僅支援 doc、png、pdf、jpeg、jpg),<=5M\n" +
"}")
public AjaxResponse uploadCV(HttpServletRequest request) throws IOException {
AjaxResponse ajaxResponse = new AjaxResponse();
try{
MultipartHttpServletRequest mureq = (MultipartHttpServletRequest) request;
Map<String, MultipartFile> files = mureq.getFileMap();
MultipartFile fileNew = files.get("file");
//檔案過濾
// 檔名
String fileName = fileNew.getOriginalFilename();
// 在file資料夾中建立名為fileName的檔案
assert fileName != null;
int split = fileName.lastIndexOf(".");
// 檔案字尾,用於判斷上傳的檔案是否是合法的
String suffix = fileName.substring(split+1,fileName.length());
if("jpg".equals(suffix) || "jpeg".equals(suffix) || "png".equals(suffix)|| "pdf".equals(suffix) || "doc".equals(suffix)) {
// 正確的型別,儲存檔案
String fileId = fileBPO.uploadCV(fileNew, filesConfig.getFormalPath(),filesConfig.getImgUrl());
if(!StringUtils.isBlank(fileId)){
ajaxResponse.setData("data",fileId);
ajaxResponse.setMsg("上傳成功");
}else {
throw new BusinessException("上傳失敗:資料庫表同步失敗");
}
}else{
throw new BusinessException("上傳失敗:上傳簡歷(僅支援 doc、png、pdf、jpeg、jpg格式)");
}
}catch (Exception ex){
throw new BusinessException("上傳失敗:檔案接收處理異常"+ex.getMessage());
}
return ajaxResponse;

}

Tip2--這個地方一開始傳參用的是 @RequestParam("file")MultipartFile file 但是postman測試始終拿不到檔案資訊,出現下圖報錯,後面想了下:一般在前端上傳檔案的話,他會有專門的一個id ,然後後端根據id來確認是不是這個檔案,後面我們postman是拿到這個檔案了,可是沒有對應的識別id,然後就是一直為空,後面我換了一個檔案上傳的寫法:用HttpServletRequest的方式,終於拿到的流資訊。

4.編寫對應BPO方法及其實現類

/**
* 上傳檔案返回檔案id
* @param file 檔案
* @param path 原檔案路徑
* @param imgPath 圖片路徑
* @return 檔案id
*/
String uploadCV(MultipartFile file, String path,String imgPath) throws IOException;
@Override
public String uploadCV(MultipartFile file, String path,String imgPath) throws IOException {
//獲取當前登入人
HashMap<String,String> map = UserUtil.getAbb001AndUserId();
String userid= map.get("userid");
//時間資料夾
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
String dateStr ="";
try {
//1、日期轉字串
Calendar calendar = Calendar.getInstance();
Date date = calendar.getTime();
dateStr = sdf.format(date);

} catch (Exception e) {
throw new BusinessException(e.getMessage());
}

//新的檔案存放路徑加上新的檔名
String fileName = file.getOriginalFilename();
int split = fileName.lastIndexOf(".");

// 檔名、檔案字尾
String name = fileName.substring(0,split);
String suffix = fileName.substring(split+1,fileName.length());
String uuId = UUID.randomUUID().toString().replace("-", "");
String newName = name+ "." + suffix;
String newPath = path + userid+"/"+dateStr +"/"+uuId+"/"+newName;
File file1 = new File(newPath);
//判斷檔案目錄是否存在
this.checkFile(newPath);
//儲存檔案
file.transferTo(file1);
//新建檔案上傳物件
AccessoryInfo accessoryInfo = new AccessoryInfo();
//TODO 將對應的檔案路徑、大小、格式等基本資訊儲存到資料庫
//檔案轉化
~~String png = imgPath + userid+"/"+dateStr +"/"+ uuId+"/"+name + ".png";~~
~~String pdf = imgPath + userid+"/"+dateStr +"/"+uuId+"/"+name+ ".pdf";~~
~~String imgSource = this.toPng(suffix,newPath,pdf,png);~~
accessoryInfo.setImgSource(imgSource);

//同步新增到檔案上傳表
int save = 0;
try{
save= accessoryInfoBPO.saveAccessoryInfo(accessoryInfo);
//返回檔案id
return accessoryInfo.getId();
}catch (Exception e){
throw new BusinessException("AccessoryInfo表新增失敗"+e.getMessage());
}

}
/**
* 判斷檔案目錄是否存在
* @param path 檔案路徑
* @return
*/
public void checkFile(String path) {
File file1 = new File(path);
//判斷檔案目錄是否存在
if (!file1.getParentFile().exists()) {
//建立檔案存放目錄
//無論是幾個/,都是建立一個資料夾
//mkdirs(): 建立多層目錄,如:E:/upload/2019
//mkdir(): 只建立一層目錄,如:E:upload
//如果不加這一行不會建立新的資料夾,會報系統找不到路徑
file1.getParentFile().mkdirs();
}
}

5.編寫對應Controller--檔案下載

/**
* 簡歷下載
* @param ccb212 簡歷檔案id
* @return 簡歷下載情況
*/
@OperLog
@GetMapping("/downFile")
@ApiOperation(value= "簡歷下載", notes = "根據檔案id返回base64\n"+
"{\"ccb212\": \"簡歷檔案id\",\n" +
"}")
public AjaxResponse downFile(@RequestParam("ccb212") String ccb212) {
AjaxResponse ajaxResponse = new AjaxResponse();
//根據檔案id查詢相應的檔案儲存路徑
String base64 = cb02BPO.getUrlById(ccb212);
if (!StringUtils.isBlank(base64)){
//轉base64
File file = new File(base64);
ToPngUtil toPngUtil = new ToPngUtil();
String toBase64 = toPngUtil.toBase64(file);
//
ajaxResponse.setData("data",toBase64);
ajaxResponse.setMsg("下載成功");
}
return ajaxResponse;
}
/**
* 將檔案轉化為Base64字串
* @param file
* @return Base64字串
*/
public String toBase64(File file){

InputStream in = null;
byte[] data = null;
try{
in = new FileInputStream(file);
data = new byte[in.available()];
in.read(data);
}catch (IOException e){
throw new BusinessException("檔案轉化為Base64字串格式失敗:"+e.getMessage());
}finally{
if (in != null) {
try {
in.close();
} catch (IOException e) {
throw new BusinessException("檔案轉化為Base64字串格式失敗:"+e.getMessage());
}
}
}
//Base64編碼
BASE64Encoder encoder = new BASE64Encoder();
return "data:image/png;base64," + encoder.encode(data);
}

 四、尾言

看到這兒,基本的檔案上傳伺服器功能就已經實現了,可以根據業務的需求對方法進行自適應操作,後續筆者也會持續更新相關的工具類解析,看到這裡的小夥伴們,你們的點贊和評論是對筆者最大的肯定和動力~

相關文章