SpringBoot文件上传
SpringBoot文件上传
文件上传的本质
文件上传的流程,相当于是复制,是读取了要上传文件的二进制流,并把这个二进制流的信息复制给服务器指定路径的另一个文件
文件上传的细节点
-
在服务器中需要有专门的目录来存放上传路径 ,在新的上传请求传来时,必应判断该上传路径是否存在,若不存在 ,则需要先创建该上传目录。
-
在服务器中的文件,为了防止上传文件的名称重复(防止重复提交同一文件),从而产生冲突等问题,应用 UUID 来生成随机 ID 为新文件命名
-
文件上传应当将上传至服务器的url地址保存在数据库中
获取原文件名称
在前端向后端发起上传请求时,传参中必然带有文件信息,此时在 Controller 层对应的接口中会接收到 MultipartFile 类型的 file
通过以下代码可获取原文件的信息:
//原文件名称
String OriginFileName = file.getOriginalFilename();
//原文件文件名最大长度
int fileNamelength = file.getOriginalFilename().length()
获取新文件路径(名称)
/**
* 编码文件名
*/
public static final String extractFilename(MultipartFile file)
{
String fileName = file.getOriginalFilename();
//获得文件名后缀
String extension = getExtension(file);
//拼接上时间
fileName = DateUtils.datePath() + "/" + IdUtils.fastUUID() + "." + extension;
return fileName;
}
为什么要拼接上时间,是因为window类似mysql也有自己的索引规则,通过时间来查找,能减轻搜索压力
将远程文件存入本地
假设,存放新文件的变量名称为 newFile ,远程文件变量名称为 oldFile
则可通过以下代码将远程文件保存至本地文件中:
oldFile.transferTo(newFile);
修改上传文件类型
upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
其中DEFAULT_ALLOWED_EXTENSION是上传时候默认携带的一个参数,存储了一个集合(文件类型)
可以发现这里面没有视频的类型,要想该上传可以通过视频类型的文件,可以在这个集合上加上视频对应的类型
案例
前端采取ElementUI上传的组件
这个项目上传是需要验证权限的,所以请求时候在请求头携带了一个认证的参数
<template>
<!-- 普通单个文件上传-->
<div class="component-upload-image">
<el-upload v-if="chooseFlag ==0"
drag
:action="uploadImgUrl"
:on-success="handleUploadSuccess"
:before-upload="handleBeforeUpload"
:on-error="handleUploadError"
name="file"
:show-file-list="false"
:headers="headers"
>
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
</el-upload>
</template>
<script>
import { getToken } from '@/utils/auth';
export default {
name:"Upload",
components: {},
data() {
return {
headers: {
Authorization: "Bearer " + getToken(),
},
uploadUrlPath: "没有文件上传",
};
},
props: {
value: {
type: String,
default: "",
},
chooseFlag: {
value: Number,
default: 0,
},
uploadImgUrl: {
type: String,
default: "/v1/admin/common/upload", // 上传的图片服务器地址
},
},
methods: {
handleUploadSuccess(res) {
this.uploadUrlPath = JSON.stringify(res);
this.loading.close();
//把uploadUrlPath封装在upload-event,别的页面可以通过组件upload-event来调用这个属性
this.$emit("upload-event",this.uploadUrlPath);
},
handleBeforeUpload() {
this.loading = this.$loading({
lock: true,
text: "上传中",
background: "rgba(0, 0, 0, 0.7)",
});
},
handleUploadError() {
this.$message({
type: "error",
message: "上传失败",
});
this.loading.close();
},
},
watch: {},
};
</script>
<style scoped lang="scss">
.avatar {
width: 100%;
height: 100%;
}
</style>
后端
CommonController
这里处理上传后的文件名称和请求路径进行一个拼接
public class CommonController {
@Autowired
private ServerConfig serverConfig;
/**
* 通用上传请求
*/
@PostMapping("/upload")
public ResultJson uploadFile(MultipartFile file) throws Exception {
try {
// 上传文件路径(配置文件中的)
String filePath = NiuaConfig.getUploadPath();
// 上传并返回新文件名称
String fileName = FileUploadUtils.upload(filePath, file);
//获取请求路径
String url = serverConfig.getUrl() + fileName;//http://127.0.0.1:9527
Map<String, String> prams = new HashMap<String, String>();
prams.put("fileName", fileName);
//profile/upload/2022/07/28/94ac7ece-3371-40fb-80d0-8f42231df2f7.jpg
prams.put("url", url);
//http://127.0.0.1:9527/profile/upload/2022/07/28/94ac7ece-3371-40fb-80d0-8f42231df2f7.jpg
return ResultJson.ok(prams);
} catch (Exception e) {
return ResultJson.failure(ResultCode.BAD_REQUEST, e.getMessage());
}
}
}
获取新文件名称
String fileName = FileUploadUtils.upload(filePath, file);
根据文件路径上传,这里携带了DEFAULT_ALLOWED_EXTENSION参数,表示默认文件上传的类型
/**
* 根据文件路径上传
*
* @param baseDir 相对应用的基目录
* @param file 上传的文件
* @return 文件名称
* @throws IOException
*/
public static final String upload(String baseDir, MultipartFile file) throws IOException
{
try
{
return upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
}
catch (Exception e)
{
throw new IOException(e.getMessage(), e);
}
}
文件上传
/**
* 文件上传
*
* @param baseDir 相对应用的基目录
* @param file 上传的文件
* @param allowedExtension 上传文件类型
* @return 返回上传成功的文件名
* @throws FileSizeLimitExceededException 如果超出最大大小
* @throws FileNameLengthLimitExceededException 文件名太长
* @throws IOException 比如读写文件出错时
* @throws InvalidExtensionException 文件校验异常
*/
public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension)
throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException,
InvalidExtensionException
{
//文件大小检验
int fileNamelength = file.getOriginalFilename().length();
if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH)
{
throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH);
}
//文件类型检验
assertAllowed(file, allowedExtension);
//获取拼接说时间的文件名称(编码新文件名)
String fileName = extractFilename(file);
//返回一个文件的绝对路径
File desc = getAbsoluteFile(baseDir, fileName);
//把远程文件存入本地
file.transferTo(desc);
String pathFileName = getPathFileName(baseDir, fileName);
return pathFileName;//profile/upload/2022/07/28/94ac7ece-3371-40fb-80d0-8f42231df2f7.jpg
}
返回的数据格式ResultJson.java
package tech.niua.common.model;
import lombok.Data;
import java.io.Serializable;
/**
* @author Wangzhen
* RESTful API 返回类型
* createAt: 2020/5/29
*/
@Data
public class ResultJson<T> implements Serializable{
private static final long serialVersionUID = 783015033603078674L;
private int code;
private String msg;
private T data;
public static ResultJson ok() {
return ok("");
}
public static ResultJson ok(Object o) {
return new ResultJson(ResultCode.SUCCESS, o);
}
public static ResultJson failure(ResultCode code) {
return failure(code, "");
}
public static ResultJson failure(ResultCode code, Object o) {
return new ResultJson(code, o);
}
public ResultJson(ResultCode resultCode) {
setResultCode(resultCode);
}
public ResultJson(ResultCode resultCode, T data) {
setResultCode(resultCode);
this.data = data;
}
public void setResultCode(ResultCode resultCode) {
this.code = resultCode.getCode();
this.msg = resultCode.getMsg();
}
@Override
public String toString() {
return "{" +
"\"code\":" + code +
", \"msg\":\"" + msg + '\"' +
", \"data\":\"" + data + '\"'+
'}';
}
}
返回的数据格式ResultJson.java
package tech.niua.common.model;
import lombok.Data;
import java.io.Serializable;
/**
* @author Wangzhen
* RESTful API 返回类型
* createAt: 2020/5/29
*/
@Data
public class ResultJson<T> implements Serializable{
private static final long serialVersionUID = 783015033603078674L;
private int code;
private String msg;
private T data;
public static ResultJson ok() {
return ok("");
}
public static ResultJson ok(Object o) {
return new ResultJson(ResultCode.SUCCESS, o);
}
public static ResultJson failure(ResultCode code) {
return failure(code, "");
}
public static ResultJson failure(ResultCode code, Object o) {
return new ResultJson(code, o);
}
public ResultJson(ResultCode resultCode) {
setResultCode(resultCode);
}
public ResultJson(ResultCode resultCode, T data) {
setResultCode(resultCode);
this.data = data;
}
public void setResultCode(ResultCode resultCode) {
this.code = resultCode.getCode();
this.msg = resultCode.getMsg();
}
@Override
public String toString() {
return "{" +
"\"code\":" + code +
", \"msg\":\"" + msg + '\"' +
", \"data\":\"" + data + '\"'+
'}';
}
}
返回的数据格式ResultJson.java
package tech.niua.common.model;
import lombok.Data;
import java.io.Serializable;
/**
* @author Wangzhen
* RESTful API 返回类型
* createAt: 2020/5/29
*/
@Data
public class ResultJson<T> implements Serializable{
private static final long serialVersionUID = 783015033603078674L;
private int code;
private String msg;
private T data;
public static ResultJson ok() {
return ok("");
}
public static ResultJson ok(Object o) {
return new ResultJson(ResultCode.SUCCESS, o);
}
public static ResultJson failure(ResultCode code) {
return failure(code, "");
}
public static ResultJson failure(ResultCode code, Object o) {
return new ResultJson(code, o);
}
public ResultJson(ResultCode resultCode) {
setResultCode(resultCode);
}
public ResultJson(ResultCode resultCode, T data) {
setResultCode(resultCode);
this.data = data;
}
public void setResultCode(ResultCode resultCode) {
this.code = resultCode.getCode();
this.msg = resultCode.getMsg();
}
@Override
public String toString() {
return "{" +
"\"code\":" + code +
", \"msg\":\"" + msg + '\"' +
", \"data\":\"" + data + '\"'+
'}';
}
}
编码新文件名
通过UUID(避免重复)给文件编码新的文件名,并且按照指定的样式返回
/**
* 编码文件名
*/
public static final String extractFilename(MultipartFile file)
{
String fileName = file.getOriginalFilename();
//获得文件名后缀
String extension = getExtension(file);
//拼接上时间
fileName = DateUtils.datePath() + "/" + IdUtils.fastUUID() + "." + extension;
return fileName;
}
上传成功效果图