Spring Boot Controller 使用 @RequestBody + @ModelAttribute 接收请求,前后端应用示例:
前端 vue3:
发送:请求体和请求参数(对象)
TypeScript
// 下载
import { useTokenStore } from "@/stores/token";
import axios from "axios";
import { formatDateTimeToYYYYMMDDHHMMSS } from "@/utils/pubUtils";
function download() {
// 获取JWT令牌信息
const tokenStore = useTokenStore();
// 使用 axios 原始的功能,向后端发送请求,实现文件下载
axios({
// 请求方式
method: "post",
// 请求路径,因为夸域了(前端5173,后端8080),不能直接请求访问 http://localhost:8080/originalRecord/download
// url: "http://localhost:8080/originalRecord/download",
// 请求路径,因为有代理设置(详情查看vite.config.ts),最终是请求访问 http://localhost:8080/originalRecord/download
url: "/api/originalRecord/downloadFile",
// 请求标头
headers: {
// JWT令牌信息
Authorization: tokenStore.token
},
// 请求体,后端 spring boot 需要使用 @RequestBody 接收
data: props.detailDatas,
// 请求参数,请求参数是一个对象,后端 spring boot 可以使用 @ModelAttribute 接收,推荐使用。
// 也可以使用多个 @RequestParam 接收(如果对象属性有十几个,那么就需要十几个 @RequestParam 接收,这样应用很麻烦,不推荐)
params: resultTempletTableCurrentRow.value,
// 响应类型为 blob,用于接收二进制数据流
responseType: "blob"
})
.then((response) => {
// 获取响应标头的内容类型content-type属性
// @ts-ignore 忽略 null 报错
let contentType = response.headers.getContentType();
// 正常返回的是application/octet-stream,八位字节的二进制数据流
if (contentType === "text/html") {
ElMessage.error("请求无效,下载失败!");
return;
}
// 从响应标头中获取 content-disposition 属性的信息
const contentDisposition = response.headers["content-disposition"];
// 设置下载文件默认名称
let fileName = "原始记录" + formatDateTimeToYYYYMMDDHHMMSS() + ".xls";
// content-disposition 有内容
if (contentDisposition) {
// 通过正则表达式解析出文件名称,数据示例:['filename=Info.xlsx', 'Info.xlsx', '', index: 11, input: 'attachment;filename=Info.xlsx', groups: undefined]
const matchArray = contentDisposition.match(/filename=(.*?)(;|$)/);
if (matchArray && matchArray.length > 1) {
// 获取文件名称(索引为1的元素内容)
fileName = decodeURIComponent(matchArray[1]);
}
}
// 将接收到的响应消息体的内容(二进制数据流)result.data,创建为 Blob 对象,用于对文件的操作
const blob = new Blob([response.data], { type: response.headers["content-type"] });
// 将响应消息体的内容(二进制数据流)转换为url地址对象
const objectUrl = URL.createObjectURL(blob);
// 创建链接标签<a></a>
const a = document.createElement("a");
// 设置链接路径
a.href = objectUrl;
// 设置下载的文件名称
// a.setAttribute("download", fileName);
a.download = fileName;
// 增加链接标签
document.body.appendChild(a);
// 模拟点击链接标签,下载文件
a.click();
// 移除链接标签
document.body.removeChild(a);
// 移除url地址对象,释放资源
URL.revokeObjectURL(objectUrl);
})
.catch((error) => {
ElMessage.error("下载失败!" + error);
});
}
后端:
接收:@RequestBody接收请求体,@ModelAttribute接收请求参数(对象)
java
/**
* 下载文件(原始记录excel文件),实现从服务器文件系统(文件目录)中下载文件
* @RequestParam 只能用于绑定简单的数据类型(如 String、int、boolean 等),而不能直接绑定到一个对象。
* @ModelAttribute 可以将多个请求参数绑定到一个对象,会自动将请求参数映射到对象的字段上。
* @param sampleItemRecordList 样品项目记录列表 {@link List}<{@link SampleItemRecord}>
* @param recordTemplet 结果模板对象 {@link ResultTemplet}
* @return 文件数据流 {@link ResponseEntity}<{@link Resource}>
*/
@PostMapping("/downloadFile")
public ResponseEntity<Resource> downloadFile(@RequestBody List<SampleItemRecord> sampleItemRecordList,
@ModelAttribute ResultTemplet recordTemplet){
log.info("【原始记录打印】,下载,实现从服务器文件系统(文件目录)中下载文件,/originalRecord/downloadFile," +
"sampleItemRecordList = {},recordTemplet = {}", sampleItemRecordList, recordTemplet);
try {
// 生成原始记录文件,返回文件路径
String path = originalRecordService.createRecordFile(sampleItemRecordList, recordTemplet);
log.info("【原始记录打印】,生成文件路径:{}", path);
// 创建文件路径
Path filePath = Paths.get(path);
// 创建资源
Resource resource = new UrlResource(filePath.toUri());
// 资源存在 或者 可读
if (resource.exists() || resource.isReadable()) {
// 返回响应实体
return ResponseEntity
// 设置状态
.ok()
// 设置内容类型为 MediaType.APPLICATION_OCTET_STREAM,八位字节的二进制数据流
.contentType(MediaType.APPLICATION_OCTET_STREAM)
// 设置响应标头,添加属性 Content-Disposition,Content-Disposition就是当用户想把请求所得的内容存为一个文件的时候提供一个默认的文件名。
// 其属性值必须要加上attachment,如: attachment;filename="name.xlsx",就是文件名称的信息,并且文件名称需要用双引号包裹(不支持中文编码,需要编码转换)
// 设置内容处置为附件,并指定文件名,到时前端就可以解析这个响应头拿到这个文件名称进行下载
// .header("Content-Disposition", "attachment;filename=\"" + URLEncoder.encode(fileName, StandardCharsets.UTF_8) +"\"")
// 实际测试发现文件名称不用双引号包裹,也是可以达到需求目标,并且前端通过正则表达式解析出文件名称时还简单一些
.header("Content-Disposition", "attachment;filename=" +
URLEncoder.encode(PublicUtils.getFileName(path), StandardCharsets.UTF_8))
// 设置响应消息体为 resource
.body(resource);
} else {
// 文件没有找到
return ResponseEntity.notFound().build();
}
} catch (Exception e) {
// 文件下载失败
return ResponseEntity.internalServerError().build();
}
}
@ModelAttribute
是 Spring MVC 框架中的一个核心注解,主要用于数据绑定、模型管理和预处理逻辑。其作用可分为以下三类,结合具体场景和代码示例说明如下:
一、绑定请求参数到命令对象
-
• 作用:将 HTTP 请求参数(如表单字段、查询参数)自动绑定到方法参数的对象上,简化数据提取和封装流程。
-
• 示例:
java
下载
复制
运行
@PostMapping("/submit") public String submitForm(@ModelAttribute User user) { // 自动将请求参数绑定到User对象的属性(如user.name、user.age) return "result"; }
-
• Spring 会根据请求参数名与对象属性名匹配,自动填充数据
2
3
。
-
-
• 适用场景:表单提交、RESTful API 参数接收。
二、初始化模型数据(预处理)
-
• 作用:在控制器方法执行前,向模型(Model)中添加共享数据或初始化对象,供后续请求处理方法或视图使用。
-
• 方法级别:
java
下载
复制
运行
@ModelAttribute("allRoles") public List<String> populateRoles() { return Arrays.asList("Admin", "User"); // 数据自动添加到Model中 }
-
• 该方法会在同一控制器内所有
@RequestMapping
方法前执行1
5
。
-
-
• 类级别 :通过
@ControllerAdvice
实现全局模型属性共享4
。
-
-
• 适用场景:
-
• 预加载下拉框选项(如角色列表)
2
。 -
• 表单回显时初始化对象(如编辑页面加载数据库数据)
3
5
。
-
三、暴露返回值到模型
-
• 作用 :将方法返回值直接作为模型属性,无需显式调用
Model.addAttribute()
。 -
• 示例:
java
下载
复制
运行
@GetMapping("/edit/{id}") @ModelAttribute("user") // 返回值以"user"为属性名加入Model public User loadUser(@PathVariable Long id) { return userService.findById(id); }
-
• 视图层可直接通过
${user}
访问该对象3
5
。
-
四、高级特性与注意事项
-
- 命名规则:
-
• 默认以类名首字母小写作为属性名(如
User
→user
),可通过@ModelAttribute("customName")
自定义5
。
-
- 与
@SessionAttributes
结合:
-
• 实现跨请求的模型数据持久化(如多步骤表单)
3
。
- 与
-
- 性能优化:
-
• 避免在
@ModelAttribute
方法中执行耗时操作,因其会在每次请求前触发1
4
。
总结
@ModelAttribute
的核心价值在于:
-
- 简化数据绑定:自动封装请求参数到对象。
-
- 统一预处理:集中管理模型数据初始化。
-
- 增强可维护性:减少重复代码,提升架构清晰度
1
4
5
。
具体用法需根据业务场景选择,如简单表单绑定用参数注解,复杂预处理用方法级别注解。