前言
前面的章節,關於分佈式緩存技術,我們分析了《分佈式緩存技術之Redis的使用以及原理》、這一節,繼續來說說MongoDB。
關於MongoDB,一共五小節內容,分別是:
- MongoDB的應用場景及實現原理
- MongoDB的常用命令及配置
- 手寫基於MongoDB的ORM框架
- 基於MongoDB實現網絡雲盤實戰
- MongoDB 4.0新特性
基本實現思路介紹
常見網盤有:百度網盤、騰訊微盤、360網盤、阿里OSS、七牛雲 …
本節內容就是通過MongoDB實現某盤的文件上傳下載的基本功能~
拋磚引玉
-
爲何要使用MongoDB來實現雲盤?
GridFS在Java中應用,作爲緩存中間件,是一種結構化的非關係型數據庫,適合雲盤場景 -
雲盤因何而存在?
來自我本人的一個項目,經過了改造,完全去Servlet化的框架(SpringBoot)
前端使用原生的Bootstrap+layui實現,後端以SpringBoot實現,數據庫使用MongoDB實現。 -
個人經驗分享
分享自己的經驗,相當於全公司所有項目中用到文件存儲依賴
技術準備:SpringBoot 2.0 + MongoDB 4.0 + Bootstrap3.0
設計思路
文件分類
公共資源public:/ 任何人都共享
回收站recycle:/ 根據權限,每個人都有一個回收站
我的文件my:/ 每個用戶一個虛擬雲盤
最終實現效果圖:
數據隔離
文件夾說明
- 我的文件: 每個人對應一個文件夾,僅此而已(它只不過一串有規律字符串)
- 匿名用戶 anonymous:本節代碼通過匿名登錄
- 註冊用戶:通常以單點登錄實現,暫時未開發
規則說明
規定:根目錄path /
子目錄 /XX/XXx/AA/BB/EE
子目錄 /XX/XXx/AA/DD/CC
子目錄 /XX/XXx/CC/DD
path 包含 /XX/XXx/AA/
在創建文件夾時,同級目錄下不允許重名
MongoDB存文件風險大?
用戶的註冊量達到了,因爲磁盤上目錄裏面的文件數量是有上限的!
高效存儲解決方案
- 磁盤存儲,MongoDB存儲
- 虛擬空間管理
- 多數據源操作
主要功能
- 登錄註銷
- 上傳
- 下載(FlashReader,swf,Word、PDF、Excel、MP4、MP3、PPT)
- 預覽
- 查詢
- 無限級目錄
- FFMPEG /多媒體的互轉 Word 轉成 Swf、 MP4/WVM… /MP4.H264
核心代碼演示
數據庫設計
手寫核心業務代碼
.json ajax交互的格式
.file 針對文件本身的操作,預覽,下載
如果做動靜分離之後,我會用Nginx來承擔一些功能
通常,文件的存儲有3種
- 直接寫磁盤,通過nginx來訪問。 顯然,性能下降
- 分佈式文件系統,比如FastDFS 推薦方案,性能是最高的,易擴容,擴容比較難定
- MongoDB,你把它當做緩存,方便 不推薦(只是因爲恰好我自己做過)
登錄/註銷
登錄:
@RequestMapping("/login.json")
public ResponseEntity login(@RequestParam("loginName") String loginName,
@RequestParam("loginPass") String loginPass,
@RequestParam("iframe") String iframe,
@RequestParam("callback") String callback,
@RequestParam("jumpto") String jumpto){
ResultMsg<?> data = memberService.login(loginName,loginPass);
HttpHeaders headers = new HttpHeaders();
headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
headers.add("Pragma", "no-cache");
headers.add("Expires", "0");
String json = JSON.toJSONString(data);
if(!(null == jumpto || "".equals(jumpto))) {
JSONObject obj = JSON.parseObject(json);
obj.getJSONObject("data").put("jumpto", jumpto);
json = obj.toString();
}
if("1".equals(iframe)) {
StringBuffer returnStr = new StringBuffer();
returnStr.append("window.parent." + ((callback == null) ? "callback" : callback) + "(" + json + ");");
returnStr.insert(0, "<script type=\"text/javascript\">").append("</script>");
return ResponseEntity
.ok()
.headers(headers)
.contentType(MediaType.parseMediaType("text/html"))
.body(returnStr.toString());
}else{
return ResponseEntity
.ok()
.headers(headers)
.contentType(MediaType.parseMediaType("application/json"))
.body(((callback == null) ? json : (callback +"(" + json + ")")));
}
}
註銷:
@GetMapping("/logout.json")
public Mono<Object> logout(){
ResultMsg<?> result = memberService.logout(null);
return Mono.just(result);
}
效果演示:
上傳文件
@RequestMapping("/upload/progress.json")
public ResponseEntity progress(@RequestParam("X-Progress-ID") String progressId,
@RequestParam("callback") String callback){
Progress progress = new Progress();
progress.setFinish(1);
HttpHeaders headers = new HttpHeaders();
headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
headers.add("Pragma", "no-cache");
headers.add("Expires", "0");
String json = JSON.toJSONString(progress);
return ResponseEntity
.ok()
.headers(headers)
.contentType(MediaType.parseMediaType("application/json"))
.body(((callback == null) ? json : (callback +"(" + json + ")")));
}
上傳成功後:
此時我們創建多層級文件夾,查看效果:
這裏就說明了,創建的文件夾實際上就是一串以唯一Id爲前綴的字符串而已,前綴採用MD5加密
並且在上傳的過程中,我會持久化文件到本地磁盤:
然後打開本地磁盤目錄:
d41d8cd98f00b204e9800998ecf8427e它就是文件的ID
在本地磁盤也是找到了對應的以三個字符分隔而成的目錄,實際上傳的文件也已經持久化到本地目錄
磁盤的讀寫性能是沒有內存的讀寫性能高的
下載
@RequestMapping("/download/{id:\\w+}.file")
public ResponseEntity download(@PathVariable(name="id") String id){
ResultMsg<?> resultMsg = uFileService.download(ExplorerConstants.ANONYMOUS,id);
File file = (File) resultMsg.getData();
MimetypesFileTypeMap mimeTypeMap = new MimetypesFileTypeMap();
HttpHeaders headers = new HttpHeaders();
try {
headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
headers.add("Content-Disposition", "attachment; filename=" + new String(file.getName().getBytes("UTF-8"), "iso8859-1"));
headers.add("Pragma", "no-cache");
headers.add("Expires", "0");
headers.add("Last-Modified", new Date().toString());
headers.add("ETag", String.valueOf(System.currentTimeMillis()));
headers.add("Content-Type",mimeTypeMap.getContentType(file));
}catch (Exception e){
e.printStackTrace();
}
return ResponseEntity
.ok()
.headers(headers)
.contentLength(file.length())
.contentType(MediaType.parseMediaType("application/x-msdownload"))
.body(new FileSystemResource(file));
}
瀏覽
@GetMapping(value="/preview/{id:\\w+}.file")
public ResponseEntity preview(@PathVariable(name="id") String id){
ResultMsg<?> resultMsg = uFileService.download(ExplorerConstants.ANONYMOUS,id);
File file = (File) resultMsg.getData();
MimetypesFileTypeMap mimeTypeMap = new MimetypesFileTypeMap();
HttpHeaders headers = new HttpHeaders();
try {
//預覽一定要設置Content-Type,否則打不開
headers.add("Content-Type",mimeTypeMap.getContentType(file));
}catch (Exception e){
e.printStackTrace();
}
return ResponseEntity
.ok()
.headers(headers)
.body(new FileSystemResource(file));
}
後記
本節網盤代碼完整版下載地址:
https://github.com/harrypottry/test-mongo-explorer
當然,此代碼只是一個簡單的DEMO,很多功能未完善,後續有機會再維護~
更多架構知識,歡迎關注本套Java系列文章,地址導航:Java架構師成長之路