【文件系统】Vue+SpringBoot模拟文件系统
0. 相关知识预告
- Vue Cli2
- ElementUI
- SpringBoot
- MultipartFile 【form-data】
1. 展示文件列表模拟
1.1 前端
使用Vue Cli2 + ElementUI
1.1.1 展示样式介绍
- 使用el-table展示列表,关键属性是 :data 绑定里面所有行数据【tableData是自定义的一个变量】
- el-table只需要我们设计表头,数据会根据我们定义的一个数组变量内容自动加载,但只有加载el-table有的字段
- 使用<template>相关标签是为了自定义表头,相关样式可以去elementUI官网学习。
效果如下:
代码如下:【值得注意的就是min-width=100%,很好用】
<el-table v-loading="loading" :data="tableData" style="width: 100%" :default-sort="{prop: 'date', order: 'descending'}" max-height="1080" element-loading-text="拼命加载中" element-loading-spinner="el-icon-loading" v-contextmenu:contextmenu>
<!-- min-width100%能实现header拉长,name独大 -->
<el-table-column prop="name" label="文件名" min-width="100%">
<template slot="header">
<i class="el-icon-document hidden-xs-only"></i>
<span>文件名</span>
<el-upload class="upload" action="/file/uploadFile" :data="data" multiple :show-file-list="false" :before-upload="beforeUpload">
<el-button size="small" type="primary">点击上传</el-button>
</el-upload>
</template>
</el-table-column>
<el-table-column prop="lastModifyTime" label="修改时间" sortable width="180">
<template slot="header">
<span>修改时间</span>
</template>
</el-table-column>
<el-table-column prop="size" label="大小" sortable width="180">
<template slot="header">
<span>大小</span>
</template>
</el-table-column>
<el-table-column prop="operation" label="操作" width="180">
<template slot-scope="scope">
<el-button type="text" icon="el-icon-download"></el-button>
<el-button type="text" icon="el-icon-close" @click="confirm"></el-button>
</template>
</el-table-column>
</el-table>
1.1.2 异步访问接口
- 参数方面直接传入,这样是属于params式传入。参数以json串形式传过去则为query请求体式传输,一定要注意这里。
- 因为post还可以通过params式传入,和get一样拼接在地址后面。
- 后端对于两种传参的处理是不一样的。axios默认post为query传输,get为params传输
this.$http.post("/file/showAllFiles", {
curUrl: "\\d\\c",
uid: 9
}).then((res) => {
this.tableData = res.data;
});
1.2 后端SpringBoot
1.2.1 Controller
- post映射指定
- @ResponseBody表示该控制器自动将结果转为json串异步返回
- 参数!!!重中之重。通过map并指定body来接收前端的json串,要么你就弄一个实体对象接收比如Student对象。千万不能用【@RequestParam(“curUrl”) String curUrl】这样接收,这样只允许post请求中的params式参数【就是get类型】,所以如果用了RequestParam,即使前端请求成功,后端也拿不到任何数据。因为这样spring就不认json串。我们需要RequestBody接受query参数
- 注意,需要前端传参uid,访问用户自身专属的文件夹
- 提前把前端传的curUrl相对路径转换为本地绝对路径
@PostMapping("/showAllFiles")
@ResponseBody
/**
* @param uid 表示用户id
* @param curUrl 表示当前所在的文件夹位置,返回该文件夹下的所有文件
* 注意该接口只对文件夹有效
*/
public List showAllFiles(@RequestBody Map<String, String> map) {
String localUrl = store + map.get("uid") + map.get("curUrl");
System.out.println(localUrl);
List<FileInfo> res = fileService.showAllFiles(localUrl);
return res;
}
1.2.2 Service
- 大体通过java导入的File包进行处理
- File包能够直接访问本地文件夹,直接获取文件列表
- 我还需要文件的大小,文件的最后修改时间,这些都可以直接获取
- 注意 file.length() 就是文件的大小了
/**
* @param curUrl 传入的是本地绝对路径
*
* @return 返回当前目录下的所有FileInfo(不包括子代)
* info内容包括文件/目录名,后缀,文件大小,文件最后修改时间(已经格式化),是否是目录
*/
@Override
public List<FileInfo> showAllFiles(String curUrl) {
File dir = new File(curUrl);
//先判断文件夹是否存在
if(!dir.exists()) {
System.out.println("目录不存在");
return null;
}
//获取文件列表
File[] fileList = dir.listFiles();
//FileInfo是我自定义的一个实体类,把文件中我需要的信息注入作为一个单位返回
List<FileInfo> res = new ArrayList<>();
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
for(File file : fileList) {
//如果是目录,不加入大小,默认“-”
if(file.isDirectory()) {
res.add(new FileInfo(file.getName()," ", "-", df.format(new Date(file.lastModified())), 1));
}else {
String name = file.getName();
//得到的结果是B,字节
double len = Double.valueOf(file.length());
String size = "";
if(len < 1024) {
size = "" + String.format("%.2f", len) + "b";
}else if(len >= 1024 && len < 1048576) {
size = "" + String.format("%.2f", len/1024) + "kb";
}else if(len >= 1048576 && len < 1073741824) {
size = "" + String.format("%.2f", len/1048576) + "mb";
}
res.add(new FileInfo(name, name.substring(name.lastIndexOf(".")+1), size, df.format(new Date(file.lastModified())), 0));
}
}
}
return res;
}
1.3 效果展示
-
访问用户9的\d\c\a位置
-
刷新页面,渲染vue组件时自动请求showAllFiles接口
-
成功访问
2. 新建文件夹模拟
2.1 前端
前端直接提供一个按钮去访问对应接口就行,这里不演示。
2.2 后端
2.2.1 Controller
- 同理接收body参数,所以用map和RequestBody
- 需要知道是哪位用户(uid)在哪里新建(curUrl),找到对应的本地路径
@PostMapping("/newFolder")
@ResponseBody
/**
* @param curUrl 表示当前创建的文件夹所在的位置
*/
public Integer newFloder(@RequestBody Map<String, String> map) {
String localUrl = store + map.get("uid") + map.get("curUrl");
int res = fileService.newFolder(localUrl);
return res;
}
2.2.2 Service
- 调用Files的静态方法创建文件夹
- 成功返回1,失败返回0
@Override
public Integer newFolder(String localUrl) {
//创建本地文件夹,curUrl应该包含name
try{
Files.createDirectories(Paths.get(localUrl));
return 1;
}catch (IOException e){
e.printStackTrace();
return 0;
}
}
值得注意的是:我们需要在注册的时候,先为当前注册成功的用户创建一个专属文件夹。
2.3 效果展示
-
模拟接口
-
查看结果【创建成功】
3. 上传文件模拟
这里要提醒,需要学习form-data和multipart
3.0 form-data和multipart
enctype:一个html表单的post请求过程中enctype【中文是编码方式】有三种类型
Content-Type:用于定义用户的浏览器或相关设备如何显示将要加载的数据,或者如何处理将要加载的数据
-
application/x-www-urlencoded
- 编码方式是ASCII
- 是最普通的post编码,数据会被编码到body中传输
- 表单默认是这个
-
multipart/form-data【其实了解他是个传文件的就差不多了】
-
是基于post方法来传递二进制数据的,一般能用来传递文件
-
如图,解释一下,每一行的一个------WebKitFormBoundarynRpsHcUvTeBqpdsH是一个boundary,是可以自定义的,是一个分隔符,你就当他用来分隔各个参数的就行了,有头有尾。
-
参数名与参数值之间会有两行换行,这是官方规定的格式,照着了解就行。
-
整体格式总结
--分隔符(boundary)[换行] Content-Disposition: form-data; name="参数名"[换行] [换行] 参数值[换行] --分隔符(boundary)[换行] Content-Disposition: form-data; name="图片名"; filename="图片文件名"[换行] Content-Type: 类型[换行] [换行] 图片文件的二进制内容[换行] --分隔符(boundary)--
-
- text-plain
- 传递的数据是文本数据,既然是文本,浏览器不会做过多处理。
3.1 前端
使用Vue Cli2 + ElementUI
- 使用el-upload进行用户本地文件选取以及自动上传
- 注意使用multiple,不然一个用户只能传一个文件
- 注意也不要添加limit参数
- 默认就是自动上传
- 这里比较关键是data是能传入额外参数,而我们后端需要知道uid和当前路径
- 注意这里使用的是el组件的action,data是属于query参数,直接拼在url后的,后端能直接用@RequestParam
<el-upload class="upload"
action="/file/uploadFile"
data="{curUrl:'\\d\\c\\a', uid:9}"
multiple
:show-file-list="false">
<el-button size="small" type="primary">点击上传</el-button>
</el-upload>
- 效果如下:
- 选择文件上传即可。
3.2 后端
3.2.1 Controller
- 不多说了
- 前端data是属于query参数,直接拼在url后的,后端能直接用@RequestParam
@PostMapping("/uploadFile")
@ResponseBody
public Integer uploadFile(@RequestParam("curUrl") String curUrl, @RequestParam("uid") Integer uid,@RequestParam("file") MultipartFile file) {
String localUrl = store + uid + curUrl;
fileService.uploadFile(file, localUrl);
return 1;
}
3.2.2 Service
- 这里我封装了一个工具类,待会介绍
@Override
public Integer uploadFile(MultipartFile file, String curUrl) {
MultipartFileUtil.addFile(file, curUrl);
return 1;
}
3.2.3 springboot的yaml配置
- 对spirngboot的application.yaml或者其他配置文件都好,配置multipart相关参数
- 不设置大小,默认1m作为单个文件上传上限
spring:
# web处理multipart请求懒加载
servlet:
multipart:
resolve-lazily: true
#设置单个文件大小,单位MB和KB都可以
max-file-size: 1000MB
#设置总上传的数据大小,单位MB和KB都可以
max-request-size: 10000MB
tomcat:
max-swallow-size: 100MB
3.2.4 重点!MultipartFileUtil工具类
-
比较重要的就是获取multipartFile的文件名file.getOriginalFilename();
-
剩下考察你对文件IO流熟不熟悉,这里采用一个手动缓存读写。
-
毕竟都是二进制而已,直接读写即可。
-
源码,可以直接复制使用
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
public class MultipartFileUtil {
public static void addFile(MultipartFile file,String uri){
OutputStream outputStream = null;
InputStream inputStream = null;
String fileName = null;
try {
inputStream = file.getInputStream();
fileName = file.getOriginalFilename();
//log.info("fileName="+fileName);
} catch (IOException e) {
e.printStackTrace();
}
try {
// 2、保存到临时文件
// 1K的数据缓冲
byte[] bs = new byte[1024];
// 读取到的数据长度
int len;
// 输出的文件流保存到本地文件
File tempFile = new File(uri);
if (!tempFile.exists()) {
tempFile.mkdirs();
}
//跨平台写法,windows和linux都适用
outputStream = new FileOutputStream(tempFile.getPath() + File.separator + file.getOriginalFilename());
// 开始读取和写入os
while ((len = inputStream.read(bs)) != -1) {
outputStream.write(bs, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 完毕,关闭所有链接
try {
outputStream.close();
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
3.3 效果展示
-
前端选择文件,确定后自动上传
-
本地查看文件是否下载成功【成功下载到本地】
-
刷新页面
4. 问题解决总结
-
el-table的一个min-width=100%使用解决表头问题
-
@RequestParam 或者 直接参数对应需要前端发送的请求参数是query参数【1对1】
-
如果前端请求参数是body参数【json串】,后端要使用@RequestBody,配合map或者实体类进行接收
-
file.length是文件大小
-
前后端对接的时候上传文件使用multipart/form-data,就是通过二进制流传递文件,el-upload能通过:data去传递额外参数
-
多使用工具类封装
-
通过配置文件处理multipart文件上传大小限制
-
搞文件系统当然要想到用File包