基於SpringMVC的檔案(增刪改查)上傳、下載、更新、刪除
一、專案背景
摘要:最近一直在忙著專案的事,3個專案過去了,發現有一個共同的業務,那就是附件的處理,附件包括各種文件,當然還有圖片等特殊檔案,由於時間的關係,每次都是匆匆忙忙的搞定上線,稱這專案的空檔,整理了一份附件上傳、下載、刪除的專案,主要就是附件的處理,情況包含以下幾種:
1. 表單個附件共存
2. 只有附件
3. 只有表單
其中,後兩種處理方式簡單,本文主要說明的是第一種的處理方案。
二、專案需求
整體來說,專案需求還是不復雜的,這裡單獨把附件和表單資料提交拿出來說,就是表單中的有附件的情況,表單中的附件隨時可以進行替換、刪除、新增等操作。折騰了很久,終於把附件上傳這檔子事理清楚了,這裡做個記錄,與各位大神共勉。
三、專案架構
專案架構採用的是比較常用的傳統的javaWeb專案開發框架,Spring4.3.4,hibernate5(ssh),MySQL 5.7,Tomcat7.0,關於該專案的如何整合,就不再多說了,網上都有,搭建一套框架,應該不是問題。該業務實現的思想就是:資料庫存放檔案路徑,這裡是物理路徑,注意物理路徑和虛擬路徑的區別,檔案存放在伺服器,需要的時候通過資料庫表中的物理路徑可以找到相應的檔案,增刪改查都是可以的。
四、技術實現
4.1 資料庫建立
開啟MySQL管理工具或者CMD dos介面進入MySQL建立資料庫,這裡,我使用管理工具建立的,首先是檔案表:
欄位可以根據業務的不同適當新增,我做個例子,有這幾個欄位就夠了,其中relationID是和我們的業務表管理的,主外來鍵關聯或者普通關聯。下面是業務表的建立:
資料庫表大概就是這樣,附件表和業務表關聯,當然關聯的方式有很多,我只選擇了最簡單的主外來鍵關聯。
4.2 後臺程式碼編寫
專案架構使用的hibernate,hibernate主要的有點就是基於物件,非常適合面向物件程式設計的本質,下面建立物件,採用Spring註解的方式:
package com.common.model; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id;View Codeimport javax.persistence.Table; @Entity @Table(name="tab_userinfo") public class TabUserinfo { @Id @GeneratedValue(strategy = GenerationType.AUTO) private int id; private String username; private String password; private String relationID; private String remark; public TabUserinfo() { super(); } public TabUserinfo(int id, String username, String password, String remark) { super(); this.id = id; this.username = username; this.password = password; this.remark = remark; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getRelationID() { return relationID; } public void setRelationID(String relationID) { this.relationID = relationID; } public String getRemark() { return remark; } public void setRemark(String remark) { this.remark = remark; } @Override public String toString() { return "TabUserinfo [id=" + id + ", username=" + username + ", password=" + password + ", relationID=" + relationID + ", remark=" + remark + "]"; } } package com.common.model; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table(name="tab_userinfo") public class TabUserinfo { @Id @GeneratedValue(strategy = GenerationType.AUTO) private int id; private String username; private String password; private String relationID; private String remark; public TabUserinfo() { super(); } public TabUserinfo(int id, String username, String password, String remark) { super(); this.id = id; this.username = username; this.password = password; this.remark = remark; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getRelationID() { return relationID; } public void setRelationID(String relationID) { this.relationID = relationID; } public String getRemark() { return remark; } public void setRemark(String remark) { this.remark = remark; } @Override public String toString() { return "TabUserinfo [id=" + id + ", username=" + username + ", password=" + password + ", relationID=" + relationID + ", remark=" + remark + "]"; } }
檔案上傳:
@CrossOrigin(origins = "*", maxAge = 3600) @Controller @RequestMapping("user") public class UserController { protected Logger log = Logger.getLogger(UserController.class); @Autowired ServiceI service; @RequestMapping(value="userAddFile",produces = {"application/json;charset=UTF-8"},method=RequestMethod.POST) @ResponseBody public String addFile(TabUserinfo userinfo,@RequestParam(value="file",required=true) MultipartFile [] uploadFile, HttpServletRequest request){ JSONObject jsonRusult=new JSONObject(); String UUIDString=UUID.randomUUID().toString(); String date=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()); boolean flag=false; List<Object> fileList=null; ReturnStatus rStatus=null; log.info("UUID:"+UUIDString); //儲存表單資料 try { if(null!=userinfo){ log.info("資料userinfo:"+userinfo.toString()); userinfo.setRelationID(UUIDString); service.saveOrUpdate(userinfo); flag=true; } if(flag){ //上傳附件 fileList=FileUtils.uploadFile(service, UUIDString, date, uploadFile, request); } if(null!=fileList && fileList.size()!=0){ rStatus=new ReturnStatus("0000", "檔案上傳成功!"); jsonRusult.put("status", rStatus); jsonRusult.put("fileInfo", fileList); }else{ rStatus=new ReturnStatus("0003", "檔案上傳失敗!"); jsonRusult.put("fileInfo", null); } } catch (IllegalStateException e) { log.error("檔案上傳失敗",e); service.delete(userinfo);//保證事務 } catch (IOException e) { log.error("檔案上傳失敗",e); service.delete(userinfo); } return JSON.toJSONString(jsonRusult,SerializerFeature.WriteMapNullValue); }View Code
檔案上傳工具類:
public static List<Object> uploadFile(ServiceI service,String relationID,String date, MultipartFile [] uploadFile,HttpServletRequest request) throws IllegalStateException, IOException{ ServletContext servletContext = request.getServletContext(); List<Object> fileList=new ArrayList<>(); UploadFile fileEntity=null; for(int i=0;i<uploadFile.length;i++){ String filePath= servletContext.getRealPath("/upload"); log.info("檔案存放磁碟路徑:"+filePath); String rePath=request.getScheme()+"://"+request.getServerName()+":"+ request.getServerPort()+request.getContextPath()+"/upload"; log.info("取檔案路徑:"+rePath); MultipartFile file=uploadFile[i]; String fileName=file.getOriginalFilename(); String fileNameS=""; String fileType=fileName.split("\\.")[fileName.split("\\.").length-1]; if(StringUtil.isNull(fileName,fileType)){ fileNameS=UUID.randomUUID()+"."+fileType; if(!file.isEmpty()){ if(fileType.contains("jpg") || fileType.contains("png") || fileType.contains("gif")){ filePath+="\\image\\"+fileNameS; rePath+="/image/"+fileNameS; }else{ filePath+="\\file\\"+fileNameS; rePath+="/file/"+fileNameS; } log.info("檔案路徑filePath:"+filePath); log.info("檔案路徑rePath:"+rePath); File fileS=new File(filePath); if(!fileS.getParentFile().exists()){ fileS.getParentFile().mkdirs(); } file.transferTo(fileS); fileEntity=saveFileInfo(service,fileName,filePath,relationID,date); if(null!=fileEntity){ fileEntity.setFilePath(rePath); fileList.add(fileEntity); } }else{ fileList.add("檔案不存在");; } } } return fileList; } /** * 儲存檔案資訊 * @param fileName * @param filePath */ private static UploadFile saveFileInfo(ServiceI service,String fileName,String filePath,String relationID, String date) { UploadFile fileEntity=new UploadFile(); fileEntity.setFileName(fileName); fileEntity.setFilePath(filePath); fileEntity.setRelationID(relationID); fileEntity.setUploadTime(date); service.saveOrUpdate(fileEntity); return fileEntity; }View Code
這裡需要注意的是:在多檔案上上傳的時候一定要注意,檔案路徑的獲取,一定是每個檔案獲取一次,如下圖:
如果一次性獲取,會發生意外:如圖
檔案路徑找不到
這樣只能上傳第一個檔案,而且業務表中沒有成功插入資料。
4.3 Postman進行測試:
下面我們再來看資料庫中是否有資料
證明我們的介面是好用的,這裡解釋下為什麼要返回檔案的相關資訊,因為對於圖片來說,我們會上傳完成顯示預覽圖,對於檔案來說返回連結,可以下載檢視等,因此這麼返回的路徑。在前段中配置SRC就可以進行下載操作
如下圖:
我們把連結複製進瀏覽器首先看圖的:
再來看檔案的:
這樣可以方便我們對檔案進行後續的操作。
下面來說正事,我在這個專案上面才過的坑,希望大家引以為戒,不要掉進去。
1. 檔案上傳路徑
因為我們的專案是在Eclipse上進行開發測試的,因此上傳的檔案會存在Eclipse工作空間中去,存到工作空間之後,在E:\workspace\eclipse_workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\webapps下,是沒有許可權訪問的,因此讀取檔案的時候會報錯,所以最好的辦法就是制定一個本地伺服器的物理路徑,然後通過Tomcat中的server.xml 進行配置對映,存到服務其中的物理路徑是:E:\service\webapps\common\upload\file\fd6e9dfb-7d1c-4fc6-8954-851258f6acc4.doc,那麼只要我們開啟的Tomcat服務,就可以通過:IP/common/upload/file/fd6e9dfb-7d1c-4fc6-8954-851258f6acc4.doc訪問到我們上傳的檔案。其中common是專案名稱,upload是指定上傳的資料夾。
另一種情況,我們可以直接把專案打成war包部署在Tomcat服務上,上傳的檔案就可以在我們伺服器部署的位置找到,我採取的就是這種方式:
當然了,我們也可以通過介面的方式對檔案進項下載,思想就是:通過相關條件找到資料庫中存放的檔案路徑,拿到檔案路徑生成檔案以二進位制的方式返回給瀏覽器。下面是採用SpringMVC開發的下載檔案介面:
/** * 返回下載流的二進位制 * @param path * @return * @throws UnsupportedEncodingException */ public static ResponseEntity<byte[]> getStreamByPath(String filePath,HttpServletRequest request, HttpServletResponse response,String fileName) throws UnsupportedEncodingException { response.setCharacterEncoding("utf-8"); response.setContentType( "application/x-msdownload"); response.addHeader("Content-Disposition","attachment;fileName=" + URLEncoder.encode(fileName, "UTF-8"));// 設定檔名 System.out.println("fdsfdsfsdf:"+filePath); URL url=null; ResponseEntity<byte[]> entity=null; InputStream is =null; try { // url=new URL(filePath); // System.out.println(url.toString()); // File file=new File(url.getPath()); File file=new File(filePath); if(file.exists()){ String mimeType = URLConnection.guessContentTypeFromName(fileName); if(mimeType==null){ mimeType = "application/octet-stream"; } response.setContentType(mimeType); byte[] body = null; is = new FileInputStream(file); body = new byte[is.available()]; is.read(body); HttpHeaders headers = new HttpHeaders(); headers.add("Content-Disposition", "attchement;filename="+ URLEncoder.encode(fileName, "UTF-8")); HttpStatus statusCode = HttpStatus.OK; headers.setContentDispositionFormData("attachment", file.getName()); headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); entity = new ResponseEntity<byte[]>(body, headers, statusCode); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally{ if(is!=null){ try { is.close(); } catch (IOException e) { e.printStackTrace(); } } } return entity; }View Code
下面是檔案刪除,檔案刪除其實很簡單了,我們按照相關條件找到檔案,然後刪除即可。
/** * 檔案刪除 * @param path * @return * @throws UnsupportedEncodingException */ public static boolean deleteFile(String filePath,HttpServletRequest request, HttpServletResponse response,String fileName) throws UnsupportedEncodingException { boolean flag=false; if(StringUtil.isNull(fileName,filePath)){ File file=new File(filePath); if(file.exists()){ flag=file.delete(); } } return flag; }View Code
切記:刪除檔案和資料庫中的記錄一定是一個事務,刪除記錄的同時刪除資料庫中的記錄,否則會出現資料不一致的情況。
最後附上相關配置檔案:
1. SpringMVC.xml
2. web.xml
五、總結
在做這些專案的時候,遇到的檔案上傳的坑大概就這麼多,目前想到的附件上傳只有這一種方式,大家如果有什麼好的方法,歡迎評論區討論!