二十三 、 文件上传
实现效果:用户点击上传按钮、选择上传的头像,确定自动上传,将上传的文件保存到指定的目录中,并重新命名,生成访问链接,返回给前端进行回显。
1、前端实现
vue3+AntDesignVue实现
html
<template>
<!--图片回显-->
<a-avatar :size="100">
<template #icon>
<img :src="userStore.userInfo.avatar+'?' + new Date().getTime()" alt="">
</template>
</a-avatar>
<!--图片上传按钮 -->
<!--showUploadList=fales 不显示上传列表;:multiple="false" 只上传1个;accept=".png,.jpg" 限制文件格式-->
<a-upload
name="file"
:file-list="fileList"
:showUploadList="false"
:beforeUpload="beforeUpload"
:onChange="handleChange"
:multiple="false"
accept=".png,.jpg"
>
<a-button class="mt-4">
上传头像
</a-button>
</a-upload>
</template>
<script setup lang="ts">
import type { UnwrapRef } from 'vue';
import { updateUserAvatarApi } from '~/api/file/file.ts';
const { t } = useI18n()
//文件列表
const fileList = ref([]);
//
async function beforeUpload(file){
//文件类型,限制,可以不用写,因为,在accept=".png,.jpg" 已经限制了
var fileName = file.name.substring(file.name.lastIndexOf('.') + 1)
if (fileName!='png' && fileName!='jpg') {
message.error('文件类型必须是png或jpg!')
return false
}
//文件大小(3M=3*1024KB*1024字节)
var fileSize=file.size;
if(fileSize > 3*1024*1024){
message.error("图片大小不能大于3M");
return false
}
try {
const formData = new FormData();
formData.append('file', file); // 将文件添加到 FormData 对象中
// 假设updateUserAvatarApi返回的是一个Promise,且解析为包含fileUrl的对象
const response = await updateUserAvatarApi(formData);
if(response.data.code==1){
fileList.value=[]; //清空文件列表(没用因为在beforeUpload中,我直接showUploadList="false"不显示列表)
userStore.userInfo.avatar=response.data.data;
message.success('头像上传成功');
}else{
message.error('头像上传失败,请重试');
}
} catch (error) {
// 处理上传失败的情况
message.error(error);
}
// 返回false以阻止<a-upload>的默认上传行为
return false;
}
// 处理文件上传或移除后的逻辑
function handleChange(info) {
// info.fileList 是更新后的文件列表
// 但由于限制了multiple为false,所以这里fileList应该始终只有一个文件或为空
fileList.value = info.fileList.slice(-1);
}
</script>
请求函数
ts
// 后面的方法是用户自己头像
export function updateUserAvatarApi(param: any) {
return usePost<FileUrl>('/upload/uploadUserAvater', param, {
// 设置为false的时候不会携带token
token: true,
// 开发模式下使用自定义的接口
customDev: true,
// 是否开启全局请求loading
loading: false,
// 设置请求头
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
export interface FileUrl {
data: string
}
2、后端实现
配置拦截器的静态资源映射,方便对上传后的文件进行访问
java
/**
* 配置拦截器的静态资源映射
*/
@Slf4j
@Configuration
public class ResourConfigure implements WebMvcConfigurer {
// 静态资源映射
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
/**
* 资源映射路径
* addResourceHandler:访问映射路径
* addResourceLocations:资源绝对路径
*/
String osName=System.getProperty("os.name");
String fileUploadResources="/static/**";
String win="win";
if(osName.toLowerCase().startsWith(win)){
ApplicationHome applicationHome=new ApplicationHome(this.getClass());
String pre="file:"+applicationHome.getDir().getParentFile().getParentFile()+ "\\src\\main\\resources\\static\\";
registry.addResourceHandler(fileUploadResources)
.addResourceLocations(pre);
}else {
ApplicationHome applicationHome=new ApplicationHome(this.getClass());
String pre="file:"+applicationHome.getDir().getParentFile().getParentFile()+ "/src/main/resources/static/";
registry.addResourceHandler(fileUploadResources)
.addResourceLocations(pre);
}
}
}
写相关配置,在application.properties中,方便生成访问链接
properties
#根据自己的需求进行修改即可
#端口号
server.port=8080
#服务地址
server.address=localhost
#访问路径
server.servlet.context-path=/
#限制单个文件的上传大小
spring.servlet.multipart.max-file-size=5MB
#限制整个请求的最大大小
spring.servlet.multipart.max-request-size=5MB
创建文件上传工具类,方便对文件上传进行操作。
在此之前,确保这个目录的存在:(\src\main\resources\static\)可根据自己需求进行修改,在工具类中
java
/**
* 文件上传工具类
*/
@Component
public class FileUpload {
@Value("${server.servlet.context-path}")
private String contextPath;
@Value("${server.port}")
private String serverPort;
@Value("${server.address}")
private String serverAddress;
@Autowired
private ServletContext servletContext;
public String uploadFile(MultipartFile file, String folder) {
// 获取图片的原始名字
String originalFilename = file.getOriginalFilename();
if (originalFilename == null || originalFilename.isEmpty()) {
throw new IllegalArgumentException("文件名不能为空");
}
// 获取文件的后缀和新文件名
String ext = "." + originalFilename.substring(originalFilename.lastIndexOf('.') + 1);
String uuid = UUID.randomUUID().toString().replace("-", "");
String fileName = uuid + ext;
// 构建目标文件路径
String pre = getResourcePath(folder);
String filePath = pre + fileName;
// 上传图片
try {
file.transferTo(new File(filePath));
// 返回访问链接
return getAccessPath(folder, fileName);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public String uploadFile(MultipartFile file, String folder, String fileName) {
// 获取图片的原始名字
String originalFilename = file.getOriginalFilename();
if (originalFilename == null || originalFilename.isEmpty()) {
throw new IllegalArgumentException("文件名不能为空");
}
// 获取文件的后缀和新文件名
String ext = "." + originalFilename.substring(originalFilename.lastIndexOf('.') + 1);
fileName = fileName + ext;
// 构建目标文件路径
String pre = getResourcePath(folder);
String filePath = pre + fileName;
// 上传图片
try {
file.transferTo(new File(filePath));
// 返回访问链接
return getAccessPath(folder, fileName);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
//获取上传路径
private String getResourcePath(String folder) {
// 获取操作系统的名称
String osName = System.getProperty("os.name");
String win = "win";
// 获取项目的根目录
String projectRoot = System.getProperty("user.dir");
// 根据操作系统生成正确的路径
String staticPath;
if (osName.toLowerCase().startsWith(win)) {
//windos系统
staticPath = projectRoot + "\\src\\main\\resources\\static\\" + folder + "\\";
} else {
//Linux系统
staticPath = projectRoot + "/src/main/resources/static/" + folder + "/";
}
// 如果目录不存在,就创建目录
File dir = new File(staticPath);
if (!dir.exists()) {
dir.mkdirs();
}
return staticPath;
}
//构建访问路径
private String getAccessPath(String folder, String fileName) {
// 构建访问路径
return "http://" + serverAddress + ":" + serverPort + contextPath + "static/" + folder + "/" + fileName;
}
}
可以设置一个常量类,来指定上传到的目录
java
public class FileUploadFolder {
public static final String USER_AVATER_FOLDER = "user_avatar";
public static final String ACTIVITY_FOLDER = "activity";
}
后端接口编写:Controller层,根据自己的项目来即可
java
@Tag(name="文件上传接口")
@RestController
@RequestMapping("/upload")
@Slf4j
public class UploadController {
@Autowired
private UserService userService;
/**
* 修改用户头像
* @param file
* @return
*/
@Operation(summary = "修改用户头像")
@PostMapping("/uploadUserAvater")
public ResponseResult uploadUserAvater(@RequestParam("file") MultipartFile file){
if(file.isEmpty()){
return ResponseResult.error("头像为空,请重新选择");
}
String imgUrl = userService.uploadUserAvater(file);
if(imgUrl==null){
return ResponseResult.error("头像上传失败");
}
return ResponseResult.success(imgUrl);
}
}
Service层:根据自己的项目来即可
java
@Slf4j
@Service
@Transactional
public class UserServiceImpl implements UserService {
//根据自己的项目来,注入对应的Bean
@Autowired
private final UserMapper userMapper;
//注入写好的工具类
@Autowired
private FileUpload fileUpload;
/**
* 修改用户头像
* @param file
* @return
*/
@Override
public String uploadUserAvater(MultipartFile file) {
// 获取用户获取用户信息(根据自己的项目来,获取用户ID,来查询用户修改头像链接)
Long uid = Long.parseLong(StpUtil.getLoginId().toString());
UserInfo userInfo = userMapper.getUserById(uid);
// 调用文件上传工具类,传入:文件,保存到的文件夹,文件名
//设置文件名是为了替换旧的头像文件
String imgUrl= fileUpload.uploadFile(file, FileUploadFolder.USER_AVATER_FOLDER,userInfo.getUsername());
if(imgUrl!=null) {
userInfo.setAvatar(imgUrl);
userMapper.updateById(userInfo);
log.info("头像上传成功:" + imgUrl);
//返回生成的链接
return imgUrl;
}
return null;
}
}
3、测试与效果
上传后的图片
生成的访问链接,存入到数据库的样子