手把手編寫文件伺服器

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

 二、思路
由於用的是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);
}

 四、尾言

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