SpringBoot系列(11):文件上傳實戰(zhàn)(提供包括NIO在內(nèi)的多種實現(xiàn)方式)
作者:
修羅debug
版權聲明:本文為博主原創(chuàng)文章,遵循 CC 4.0 by-sa 版權協(xié)議,轉載請附上原文出處鏈接和本聲明。
摘要: 在開發(fā)企業(yè)級應用項目業(yè)務模塊期間,“上傳文件/附件”的功能相信每個小伙伴都遇見過,甚至有的曾以代碼實戰(zhàn)過。本文Debug將帶領各位小伙伴重新回溫一下在Spring Web應用中如何實現(xiàn)文件的上傳,其中提供了包括Java NIO在內(nèi)的多種方式。
內(nèi)容:在企業(yè)級應用項目的開發(fā)過程中,“上傳文件/附件”這一功能相信很多小伙伴都實現(xiàn)過,當然啦,其實現(xiàn)方式也是迥異不同。接下來,Debug就給各位小伙伴展示一下在Java Web應用中如何實現(xiàn)文件的上傳。
在介紹實戰(zhàn)之前,我們先來創(chuàng)建一個數(shù)據(jù)庫表appendix,用于記錄存儲每個業(yè)務對象上傳上來的圖片的詳情,包括其所屬的業(yè)務對象的注解、所屬業(yè)務模塊、圖片名稱、大小、存儲的磁盤路徑等等,其DDL如下所示:
CREATE TABLE `appendix` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`module_id` int(11) DEFAULT NULL COMMENT '所屬模塊記錄主鍵id',
`module_code` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '所屬模塊編碼',
`module_name` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '所屬模塊名稱',
`name` varchar(100) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '文件名稱',
`size` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '文件大小',
`suffix` varchar(50) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '文件后綴名',
`file_url` varchar(500) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '文件訪問的磁盤目錄',
`is_active` tinyint(4) DEFAULT '1' COMMENT '是否有效(1=是;0=否)',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時間',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COMMENT='附件(文件)上傳記錄';
一、接下來,我們先來介紹第一種方式吧,基于Java NIO的方式實現(xiàn)文件上傳功能
1、首先,創(chuàng)建FileController,用于接收前端上傳過來的文件以及其他業(yè)務信息,其源代碼如下所示:
@RestController
@RequestMapping("file")
public class FileController extends AbstractController{
@Autowired
private IFileService fileService;
/**
* 為商品上傳圖片
* 上傳文件-方式1:MultipartHttpServletRequest 接收前端參數(shù)
* @return
*/
@RequestMapping(value = "upload/v1",method = RequestMethod.POST)
public BaseResponse uploadV1(MultipartHttpServletRequest request){
BaseResponse response=new BaseResponse(StatusCode.Success);
Map<String,Object> resMap= Maps.newHashMap();
try {
String url=fileService.uploadFileV1(request);
resMap.put("fileUrl",url);
}catch (Exception e){
e.printStackTrace();
response=new BaseResponse(StatusCode.Fail.getCode(),e.getMessage());
}
response.setData(resMap);
return response;
}
}
2、創(chuàng)建IFileService的實現(xiàn)類,用于處理實際的業(yè)務信息以及實現(xiàn)文件的上傳,其完整的源代碼如下所示:
/**
* 上傳文件
* @Author:debug (SteadyJack)
* @Date: 2019/10/27 11:08
**/
@Service
public class FileService implements IFileService{
private static final Logger log= LoggerFactory.getLogger(FileService.class);
@Autowired
private ItemMapper itemMapper;
@Autowired
private CommonFileService commonFileService;
//第一種方法
@Override
@Transactional(rollbackFor = Exception.class)
public String uploadFileV1(MultipartHttpServletRequest request) throws Exception {
MultipartFile multipartFile=request.getFile("appendix");
//實際的業(yè)務信息
String itemName=request.getParameter("itemName");
String itemCode=request.getParameter("itemCode");
String itemTotal=request.getParameter("itemTotal");
Item item=new Item(itemName,itemCode,Long.valueOf(itemTotal));
item.setPurchaseTime(DateTime.now().toDate());
itemMapper.insertSelective(item);
//實現(xiàn)文件的上傳
String url="";
if (item.getId()>0){
url=commonFileService.upload(multipartFile,item.getId(), Constant.SysModule.ModuleItem);
}
return url;
}
3、commonFileService.upload 方法即為真正的實現(xiàn)文件的上傳與數(shù)據(jù)庫記錄的存儲,其完整的源代碼如下所示:
/**
* @Author:debug (SteadyJack)
* @Date: 2019/10/27 11:36
**/
@Component
public class CommonFileService {
private static final SimpleDateFormat FORMAT=new SimpleDateFormat("yyyyMMdd");
@Autowired
private AppendixMapper appendixMapper;
/**
* 上傳文件 - nio的方式
* @param file
* @throws Exception
*/
public String upload(MultipartFile file, final Integer moduleId, final Constant.SysModule module) throws Exception{
String fileName=file.getOriginalFilename();
String suffix=StringUtils.substring(fileName,StringUtils.indexOf(fileName,"."));
Long size=file.getSize();
//附件輸入流
InputStream is=file.getInputStream();
//創(chuàng)建新文件存儲的磁盤目錄前綴、創(chuàng)建磁盤目錄
String filePathPrefix=FORMAT.format(DateTime.now().toDate())+File.separator+module.getCode()+moduleId;
String rootPath=Constant.FilePrefix+filePathPrefix;
Path path=Paths.get(rootPath);
if (!Files.exists(path)){
Files.createDirectories(path);
}
//創(chuàng)建新的文件
String newFileName=System.nanoTime()+suffix;
String newFile=rootPath+File.separator+newFileName;
path=Paths.get(newFile);
//方式一
//Files.copy(is,path, StandardCopyOption.REPLACE_EXISTING); //如果存在則覆蓋
//方式二
Files.write(path,file.getBytes());
Appendix entity=new Appendix(moduleId,module.getCode(),module.getName(),newFileName,size.toString(),suffix,newFile);
appendixMapper.insertSelective(entity);
return newFile;
}
}
下面進入測試環(huán)節(jié),三圖概括如下所示:
二、最后,我們再來介紹第二種方式吧,基于傳統(tǒng)File的方式實現(xiàn)文件上傳功能
1、同樣的道理,我們在FileController創(chuàng)建用于接收前端上傳文件請求的方法,其源代碼如下所示:
/**
* 上傳文件-方式2
* @return
*/
@RequestMapping(value = "upload/v2",method = RequestMethod.POST)
public BaseResponse uploadV2(@RequestParam("appendix") MultipartFile file, FileUploadRequest request){
BaseResponse response=new BaseResponse(StatusCode.Success);
Map<String,Object> resMap= Maps.newHashMap();
try {
String url=fileService.uploadFileV2(file,request);
resMap.put("fileUrl",url);
}catch (Exception e){
e.printStackTrace();
response=new BaseResponse(StatusCode.Fail.getCode(),e.getMessage());
}
response.setData(resMap);
return response;
}
//訪問圖片時:域名 + 圖片所在的磁盤目錄-即數(shù)據(jù)庫存儲的file_url
在這里,我們采用了不同于第一種“面向字段獲取”的方式,而是采用“面向?qū)ο蟆钡乃枷耄瑢⑺袠I(yè)務相關的信息封裝成實體對象FileUploadRequest進行接收,而文件/附件相關的文件數(shù)據(jù)流則采用MultipartFile對象進行接收,實體對象FileUploadRequest源代碼如下所示:
/**
* @Author:debug (SteadyJack)
* @Date: 2019/10/27 11:15
**/
@Data
public class FileUploadRequest implements Serializable{
private String itemName;
private String itemCode;
private Long itemTotal;
}
2、接下來是FileService處理業(yè)務信息以及文件上傳的邏輯,其完整的源代碼如下所示:
//第二種方法
@Override
@Transactional(rollbackFor = Exception.class)
public String uploadFileV2(MultipartFile file, FileUploadRequest request) throws Exception {
Item item=new Item(request.getItemName(),request.getItemCode(),request.getItemTotal());
item.setPurchaseTime(DateTime.now().toDate());
itemMapper.insertSelective(item);
String url="";
if (item.getId()>0){
url=commonFileService.upload2(file,item.getId(), Constant.SysModule.ModuleItem);
}
return url;
}
3、commonFileService.upload2()方法即為核心的用于處理文件上傳的邏輯方法,其完整的源代碼如下所示:
/**
* 上傳文件 - 傳統(tǒng)的方式
* @throws Exception
*/
public String upload2(MultipartFile multipartFile, final Integer moduleId, final Constant.SysModule module) throws Exception{
String fileName=multipartFile.getOriginalFilename();
String suffix=StringUtils.substring(fileName,StringUtils.indexOf(fileName,"."));
Long size=multipartFile.getSize();
//附件輸入流
InputStream is=multipartFile.getInputStream();
//創(chuàng)建新文件存儲的磁盤目錄前綴、創(chuàng)建磁盤目錄
String filePathPrefix=FORMAT.format(DateTime.now().toDate())+File.separator+module.getCode()+moduleId;
String rootPath=Constant.FilePrefix2+filePathPrefix;
//創(chuàng)建新的文件
String newFileName=System.nanoTime()+suffix;
String newFile=rootPath+File.separator+newFileName;
//創(chuàng)建目錄
File file=new File(newFile);
if (!file.getParentFile().exists()){
file.getParentFile().mkdirs();
}
//直接執(zhí)行數(shù)據(jù)流的轉換
multipartFile.transferTo(file);
Appendix entity=new Appendix(moduleId,module.getCode(),module.getName(),newFileName,size.toString(),suffix,newFile);
appendixMapper.insertSelective(entity);
return newFile;
}
4、從中,可以看出來其核心的處理方法為MultipartFile的transferTo方法實現(xiàn)文件的上傳存儲,即multipartFile.transferTo(file);接下來,進入測試環(huán)節(jié),三圖以概括吧:
至此,對于文件的上傳我們就介紹到這里了,對于文件的上傳實戰(zhàn),Debug建議可以綜合這兩種加以實現(xiàn),即“上傳文件的請求信息的接收采用第二種方式,對于文件的處理、存儲到具體的物理磁盤則采用第一種方式,即Java NIO的方式”。
好了,本篇文章我們就介紹到這里了,感興趣的小伙伴可以關注底部Debug的技術公眾號,或者加Debug的微信,拉你進“微信版”的真正技術交流群!一起學習、共同成長!
補充:
1、本文涉及到的相關的源代碼可以到此地址,check出來進行查看學習:
https://gitee.com/steadyjack/SpringBootTechnology
2、目前Debug已將本文所涉及的內(nèi)容整理錄制成視頻教程,感興趣的小伙伴可以前往觀看學習:
https://www.fightjava.com/web/index/course/detail/5
3、關注一下Debug的技術微信公眾號,最新的技術文章、技術課程以及技術專欄將會第一時間在公眾號發(fā)布哦!