在Spring 6中,使用基于Servlet标准的 MultipartResolver确保了更好的兼容性和性能,同时减少了对外部库的依赖。
MultipartResolver是一个接口,定义了解析multipart请求(包括文件上传)的策略。Spring提供了多种实现,包括基于Servlet容器的实现和过去常用的基于Apache Commons FileUpload的实现。
主要实现
StandardServletMultipartResolver
CommonsMultipartResolver (spring6中不再使用)
具体代码实现如下:
import jakarta.servlet.Filter;
import jakarta.servlet.MultipartConfigElement;
import jakarta.servlet.ServletRegistration;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMVCConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
// 配置过滤器
protected Filter[] getServletFilters() {
// 配置字符编码过滤器
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
characterEncodingFilter.setForceResponseEncoding(true);
characterEncodingFilter.setForceRequestEncoding(true);
// 配置HiddenHttpMethodFilter
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
return new Filter[]{characterEncodingFilter, hiddenHttpMethodFilter};
}
@Override
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
//设置允许上传的单个文件大小为
long maxFileSize = 500*1024*1024;
//设置允许上传的总文件大小2000M
long maxRequestSize = 2000*1024*1024;
//设置文件上传阀值
int fileSizeThreshold = 0;
registration.setMultipartConfig(new MultipartConfigElement(null,maxFileSize,maxRequestSize,fileSizeThreshold));
}
}
SpringMVC配置类:
@Configuration
@ComponentScan("com.ssm.controller")
@EnableWebMvc
public class SpringMVCConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("/static/");
registry.addResourceHandler("/upload/**").addResourceLocations("/upload/");
}
//文件上传的时候要添加这个类的实例处理表单
@Bean
public MultipartResolver multipartResolver() {
//创建一个
StandardServletMultipartResolver multipartResolver =
new StandardServletMultipartResolver();
//返回
return multipartResolver;
}}
@Controller
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/users")
@ResponseBody
public Result findAll(){
List<User> all = userService.findAll();
return new Result(200,all);
}
@RequestMapping(value = "/update",method = RequestMethod.POST)
@ResponseBody
public Result updateUser(HttpServletRequest request,
User user,
@RequestParam("file") MultipartFile multipartFile){
//处理用户数据
/* User user = new User();
user.setId(id);
user.setUserName(userName);
user.setRealName(realName);
user.setTel(tel);
user.setGender(gender);
user.setRegisterTime(registerTime);
user.setLastLoginTime(lastLoginTime);
System.out.println(user);
*/
System.out.println("user:"+user);
System.out.println(multipartFile.getOriginalFilename());
// 获取请求参数的名字
String name = multipartFile.getName();
System.out.println(name); //fileName
// 获取的是文件真实的名字
String originalFilename = multipartFile.getOriginalFilename();
System.out.println(originalFilename); //touxiang.jpeg
InputStream in = null; // 输入流,负责读客户端的文件
File destFile=null;
try {
in = multipartFile.getInputStream();
BufferedInputStream bis = new BufferedInputStream(in); // 封装成带有缓冲区的输入流
// 输出流
ServletContext application = request.getServletContext();
String realPath = application.getRealPath("/upload");
File file = new File(realPath);
if(!file.exists()){
file.mkdirs();
}
destFile = new File(file.getAbsolutePath() ,"tx"+ originalFilename.substring(originalFilename.lastIndexOf(".")));
multipartFile.transferTo(destFile);
} catch (IOException e) {
e.printStackTrace();
}
//保存到数据库
user.setImgUrl("/upload/"+destFile.getName());
userService.update(user);
return new Result(200,"/upload/"+destFile.getName(),"上传成功");
}
@RequestMapping("/users/{id}")
@ResponseBody
public Result findById(@PathVariable("id") Integer id){
User user = userService.findById(id);
return new Result(200,user);
}
}
前端页面:
<template>
<div>
<div class="container" style="overflow: scroll;">
<div class="handle-box">
<el-row class="top-btn">
<el-col :span="12">
<el-button type="success" @click="isEdit != isEdit">点击修改</el-button>
<el-button @click="rollback" style="color:#1C84C6">返回</el-button>
</el-col>
<!-- <el-col :span="12">
<el-input class="handle-input" placeholder="请输入要查询的日志关键字">
<template #append>
<el-button :icon="Search" />
</template>
</el-input>
</el-col> -->
</el-row>
</div>
<el-divider></el-divider>
<el-form :model="userForm" label-width="auto" style="max-width: 600px;">
<el-form-item label="点击修改头像">
<el-upload ref="chargeFormRef"
class="avatar-uploader"
:show-file-list="false"
:auto-upload="false"
:on-change="handleChange"
>
<!-- <img v-if="chargeForm.imageUrl" :src="chargeForm.imageUrl" class="avatar" /> -->
<el-avatar v-if="chargeForm.imageUrl" :src="chargeForm.imageUrl" class="avatar" />
<el-icon v-else class="avatar-uploader-icon">
<Plus />
</el-icon>
</el-upload>
</el-form-item>
<el-form-item label="用户名">
<el-input v-model="userForm.userName" />
</el-form-item>
<el-form-item label="真实姓名">
<el-input v-model="userForm.realName" />
</el-form-item>
<el-form-item label="手机号">
<el-input v-model="userForm.tel" />
</el-form-item>
<el-form-item label="手机号">
<el-radio-group v-model="userForm.gender">
<el-radio value="男" label="男">男</el-radio>
<el-radio value="男" label="女">女</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="注册时间">
<el-input v-model="userForm.registerTime" disabled />
</el-form-item>
<el-form-item label="上次登录时间" disabled>
<el-input v-model="userForm.lastLoginTime" disabled/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="save">保存</el-button>
<el-button>取消</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script setup>
import { ref, reactive, toRefs,onMounted } from 'vue'
import { Search,Plus } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import { reqUpload,saveUser } from '../api/upload'
/*******返回****** */
/**上传图片组件的显示 */
const props = defineProps({
flag: String
})
const { flag } = toRefs(props)
const emit = defineEmits(['update:flag'])
const rollback = () => {
emit('update:flag', '1')
}
const isEdit = ref(false)
//信息的收集
let chargeForm = reactive({
imageUrl: '/api/upload/tx.jpg',
})
const chargeFormRef = ref()
const userForm = reactive({
id:2,
userName:'超级管理员',
realName:'张老师',
tel:'136123456789',
gender:'男',
registerTime:'2024-12-11',
lastLoginTime:'2024-12-01',
file:null
})
const save = ()=>{
//调用后端接口保存用修改之后的信息
saveUser(userForm).then(res=>{
chargeForm.imageUrl ='/api/'+ res.data.data
})
}
const handleChange= (uploadFile)=>{
//选中图片的回显
chargeForm.imageUrl= URL.createObjectURL(uploadFile.raw)
userForm.file=uploadFile.raw
}
onMounted(()=>{
// 页面挂在完成 根据id 查询用户信息 当前小案例中没有做
})
</script >
<style lang="less" scoped>
.avatar-uploader .avatar {
width: 178px;
height: 178px;
display: block;
}
.handle-box {
margin-bottom: 20px;
}
.handle-input {
float: right;
width: 240px;
}
.table {
width: 100%;
font-size: 14px;
}
.red {
color: #F56C6C;
}
.blue {
color: #1296e2;
font-weight: bold;
}
.mr10 {
margin-right: 10px;
}
.table-td-thumb {
display: block;
margin: auto;
width: 40px;
height: 40px;
}
.cell {
display: inline-block;
.el-button {
margin-left: 0px;
}
}
.right-btn {
justify-content: center;
.el-button {
margin-right: 6px;
padding: 1px 5px;
}
}
.form-demo {
.el-input {
width: 60%;
}
.el-input-number {
width: 60%;
}
}
.handle-input .el-input-group__append button.el-button,
.el-input-group__append button.el-button:hover {
background-color: #1C84C6;
color: #fff;
font-weight: 700;
}
</style>
<style>
.avatar-uploader .el-upload {
border: 1px dashed var(--el-border-color);
/* border-radius: 6px; */
border-radius: 150px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
}
.avatar-uploader .el-upload:hover {
border-color: var(--el-color-primary);
}
.el-icon.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
text-align: center;
}
</style>
axios请求:
export const saveUser = (data) => {
// 创建了一个新的 FormData 对象,用于构建表单数据,并将file添加到FormData对象中
const formData = new FormData();
for (const key in data) {
if (data.hasOwnProperty(key)) {
const value = data[key];
// 注意:文本、数字可以直接添加,文件则需要用append添加
formData.append(key, value);
}
}
console.log(formData)
return request({
url:`/update`,
method:'post',
data:formData,
})
}