前言
前面的章节,关于分布式缓存技术,我们分析了《分布式缓存技术之Redis的使用以及原理》、这一节,继续来说说MongoDB。
关于MongoDB,一共五小节内容,分别是:
基本实现思路介绍
常见网盘有:百度网盘、腾讯微盘、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架构师成长之路