SpringBoot结合antd组件Upload实现文件上传
项目实现的背景介绍,当后台采用Spring Security验证信息,用户登录token保存在header中
用户每次请求需要携带对应的header和用户角色的情况时,记录一下问题
前后端分离,后台使用SpringBoot 前端采用antd pro react
一、 后端编写业务代码过程中遇到的错误点
1. 配置SpringBoot文件上传
## ******************************* 配置文件上传 ***********************************
# 是否启用SpringMVC多分部上传功能
spring.servlet.multipart.enabled=true
# 将文件写入磁盘的阀值。值可以使用后缀“MB”或“KB”来表示兆字节或字节大小
spring.servlet.multipart.file-size-threshold=0
# 指定默认上传的文件夹
# spring.servlet.multipart.location=/upload
# 限制单个文件最大大小
spring.servlet.multipart.max-file-size=1MB
# 限制所有文件最大大小
spring.servlet.multipart.max-request-size=10MB
# 是否延迟多部件文件请求的参数和文件的解析
spring.servlet.multipart.resolve-lazily=false
## *********************** 配置静态资源文件 读取静态文件的依赖 ***************************************
spring.resources.static-locations=classpath:/static/
# 以jar包发布项目时,我们存储的路径是与jar包同级的static目录,因此我们需要在jar包目录的
# application.properties配置文件中设置静态资源路径,如下所示:
# 设置静态资源路径,多个以逗号分隔
# spring.resources.static-locations=classpath:static/,file:static/
spring.mvc.static-path-pattern=/static/**
- 上面简单的配置实现了springboot上传文件的配置,避免了SpringMVC复杂的xml文件的配置
- 以jar包发布项目时,我们存储的路径是与jar包同级的static目录,因此我们需要在jar包目录的application.properties配置文件中设置静态资源路径,如下所示:设置静态资源路径,多个以逗号分隔 spring.resources.static-locations=classpath:static/,file:static/
2. 后端采用SpringMVC的MultipartFile接受前端发过来的参数
@PostMapping("/modification-avatar")
public Map<String, Object> update(@RequestParam(value = "file",required = false) MultipartFile file, HttpServletRequest request) throws FileNotFoundException {
User user = getUserUtils.getUserByRequest(request);
Map<String, Object> map = personalService.modifyUserAvatar(file, request, user);
return map;
}
- 使用@RequestParam注解是为了进行参数绑定,这个位置只要保证前端传回的文件名为file时,该注解可以去掉;
- 添加required = false是标明该参数是非必要的条件,避免前端非法访问后台时报500 error ;
3. 编写service业务代码
/**
* @Param: file 前端上传过来的图片
* @Param: request 用户发送的请求,主要包含了用户的登录信息以及权限
* @Param: user 当前用户
* @description:TODO
* 修改用户头像
* @Return
*/
@Transactional(isolation = Isolation.REPEATABLE_READ,propagation = Propagation.REQUIRED,rollbackFor = {Exception.class,RuntimeException.class})
public Map<String, Object> modifyUserAvatar(MultipartFile file, HttpServletRequest request, User user) throws FileNotFoundException {
Map<String, Object> result = new HashMap<>(2);
if (file!=null && user != null){
// 上传到了当前服务器的相对路径下 但是当前服务器是内置tomcat 相对路径如下
// String path = request.getServletContext().getRealPath("static/user/picture/" + user.getUserName());
String path = ResourceUtils.getURL("classpath:").getPath()+"static/user/picture/" + user.getUserName();
// 上传文件
Map<String, Object> map = UploadPicUtils.uploadPicture(file, path);
// 根据工具类中标识的返回状态码,判断是否上传成功
if ((int)map.get("status")==0){
// 返回值为0代表上传成功
//修改数据库记录
User user1 = new User();
user1.setId(user.getId());
user1.setUserName(user.getUserName());
user1.setUserPic((String) map.get("url"));
User updateUser = userService.updateUser(user1);
if (updateUser!=null){
result.put("status","success");
//删除原来的头像
DeletePicUtils.delect(ResourceUtils.getURL("classpath:").getPath()+user.getUserPic());
}else {
result.put("status","error");
}
}else {
result.put("status","error");
}
}else {
result.put("status","error");
}
return result;
}
- 编写service业务代码中用到了自定义的图片上传工具,这里不是重点不重点介绍
在传统的单点架构中,即SSM项目中,获取当前项目的相对路径是通过以下方式获取
String path = request.getServletContext().getRealPath("static/user/picture/" + user.getUserName());
在SpringBoot的项目中通过以上方式是得到以下路径
- 分析,springboot是使用的是内置的tomcat,因此通过ServletContext获取的并不是当前项目所在的相对路径,而是tomcat当前执行的相对路径
- 解决: 采用 ResourceUtils.getURL(“classpath:“).getPath()这种方式即可获取当前项目的相对路径
二、 前端编写业务代码过程中遇到的错误点
1. 解决请求携带header
由于后端将用户的token 以及用户的权限角色保存在了header中,,因此在前端每一次请求时都需要加上相对应的header,根据这个需求编写每一次的相应和请求的前端拦截器
/**
* request 网络请求工具
* 更详细的 api 文档: https://github.com/umijs/umi-request
*/
import { extend } from 'umi-request';
import { notification } from 'antd';
const codeMessage = {
200: '服务器成功返回请求的数据。',
201: '新建或修改数据成功。',
202: '一个请求已经进入后台排队(异步任务)。',
204: '删除数据成功。',
400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
401: '用户没有权限(令牌、用户名、密码错误)。',
403: '用户访问成功,但是未授权,访问是被禁止的。',
404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
406: '请求的格式不可得。',
410: '请求的资源被永久删除,且不会再得到的。',
422: '当创建一个对象时,发生一个验证错误。',
500: '服务器发生错误,请检查服务器。',
502: '网关错误。',
503: '服务不可用,服务器暂时过载或维护。',
504: '网关超时。',
};
/**
* 异常处理程序
*/
const errorHandler = (error) => {
const { response } = error;
if (response && response.status) {
const errorText = codeMessage[response.status] || response.statusText;
const { status, url } = response;
notification.error({
message: `请求错误 ${status}: ${url}`,
description: errorText,
});
} else if (!response) {
notification.error({
description: '您的网络发生异常,无法连接服务器',
message: '网络异常',
});
}
return response;
};
/**
* 配置request请求时的默认参数
*/
const request = extend({
errorHandler,
// 默认错误处理
credentials: 'include', // 默认请求是否带上cookie
});
// request拦截器, 改变url 或 options.
request.interceptors.request.use(async (url, options) => {
// eslint-disable-next-line @typescript-eslint/naming-convention
const c_token = localStorage.getItem("Authorization");
const authority = localStorage.getItem("authority");
if (c_token) {
const headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': c_token,
'authority':authority
};
return (
{
url: url,
options: { ...options, headers: headers },
}
);
} else {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': c_token,
'authority':authority
};
return (
{
url: url,
options: { ...options },
}
);
}
})
// response拦截器, 处理response
request.interceptors.response.use((response, options) => {
const token = response.headers.get("Authorization");
const authority = response.headers.get("authority");
if (token) {
localStorage.setItem("Authorization", token);
localStorage.setItem("authority", authority);
}
return response;
});
export default request;
- 以上是request完整的封装代码,在前端每一次service中请求后端时必须使用上面封装的request进行请求
- 通过上面请求拦截器和相应拦截器将会将用户token和权限保存在header中,满足每次请求携带header的业务需求
2.实现图片上传
分析如下:
- 使用antd组件Upload可以方便实现文件的上传功能
- 上传头像时这里考虑两种情况: 一种采用Upload默认的表单提交,,但是表单提交时没有携带header,因此考虑解决携带header;另外一种是采用自定义提交规则,但是这个位置有两个考虑的点,1. 提交的Content-Type类型必须为 multipart/form-data 2. 携带header
另外还有一个主意点,采用自定义提交时不能使用antd封装的request进行请求,因为antd封装的request封装的contentType是application/json类型。
下面介绍采用默认表单提交的方式进行提交
const AvatarView = ({ avatar ,_this}) => ( <> <div className={styles.avatar_title}>头像↓</div> <div className={styles.avatar}> <img src={_this.state.imageUrl===undefined?avatar:_this.state.imageUrl} alt="头像" /> </div> <Upload name="file" showUploadList={false} // listType="picture-card" action="/api/user/modification-avatar" accept="image/png,image/gif,image/jpeg" beforeUpload={beforeUpload} onChange={_this.handleChange} headers={{ Authorization: localStorage.getItem("Authorization"), authority: localStorage.getItem("authority"), }} > <div className={styles.button_view}> <Button> <UploadOutlined /> 更换头像 </Button> </div> </Upload> </> );
在上面Upload组件中设置了对应的header相应头,解决了上面携带header的问题
下面介绍一下简单的上传前的验证
const getBase64 = (img, callback) => { const reader = new FileReader(); reader.addEventListener('load', () => callback(reader.result)); reader.readAsDataURL(img); } const beforeUpload = (file) => { const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png'|| file.type === 'image/gif'; if (!isJpgOrPng) { message.error('You can only upload JPG/PNG file!'); } const isLt2M = file.size / 1024 / 1024 < 2; if (!isLt2M) { message.error('Image must smaller than 2MB!'); } return isJpgOrPng && isLt2M; } handleChange = info => { if (info.file.status === 'uploading') { this.setState({ loading: true }); return; } if (info.file.status === 'done') { getBase64(info.file.originFileObj, imageUrl => this.setState({ imageUrl, loading: false, }), ); } };
3. 至此前后端分离项目实现简单头像修改的功能就实现了,做一个简单记录
- 上一篇: 求两个有序数组的中位数
- 下一篇: Mysql基础底层