使用组合操作接口时,即使传了 fileName 参数,仍然报错:
{
"success": false,
"message": "参数校验失败: 文件名不能为空; 文件名不能为空",
"data": null
}请求示例(应该正常工作但报错):
{
"fileName": "公式.xlsx",
"writeRequest": {
"sheetName": "伤寒法要",
"cells": [...]
},
"readRequest": {
"sheetName": "伤寒法要",
"cells": [...]
}
}- Spring 参数验证: 在方法执行前进行(
@Valid注解) - 自动传递逻辑: 在方法内部执行
@PostMapping("/operation")
public ResponseEntity<ExcelResponse> performOperation(
@Valid @RequestBody ExcelOperationRequest request) { // ← 这里先验证
// ← 到这里才传递 fileName,但已经晚了
if (request.getWriteRequest() != null &&
request.getWriteRequest().getFileName() == null) {
request.getWriteRequest().setFileName(request.getFileName());
}
...
}请求到达
↓
@Valid 验证 → 检查 writeRequest.fileName (null) → ❌ 验证失败!
↓ 检查 readRequest.fileName (null) → ❌ 验证失败!
返回错误
↓
(方法内的自动传递逻辑根本没有机会执行)
考虑了3种方案:
- ❌ 自定义验证器 - 复杂,过度设计
- ❌ 修改验证注解位置 - 无法解决时序问题
- ✅ 移除内部验证 + 手动验证 - 简单有效
ExcelWriteRequest.java:
// 修改前
@NotBlank(message = "文件名不能为空")
private String fileName;
// 修改后
/**
* Excel文件名(不含路径)
* 在组合操作(operation)中,此字段会自动从外层传递,无需手动指定
*/
private String fileName;ExcelReadRequest.java: 同样的修改
ExcelController.java /write 接口:
@PostMapping("/write")
public ResponseEntity<ExcelResponse> writeExcel(
@Valid @RequestBody ExcelWriteRequest request) {
// 手动验证 fileName
if (request.getFileName() == null || request.getFileName().trim().isEmpty()) {
return ResponseEntity.badRequest().body(ExcelResponse.builder()
.success(false)
.message("文件名不能为空")
.build());
}
excelService.writeExcel(request);
...
}ExcelController.java /read 接口: 同样的手动验证
@PostMapping("/operation")
public ResponseEntity<ExcelResponse> performOperation(
@Valid @RequestBody ExcelOperationRequest request) {
// 外层 fileName 有验证(@NotBlank)
// 自动传递给内部
if (request.getWriteRequest() != null &&
request.getWriteRequest().getFileName() == null) {
request.getWriteRequest().setFileName(request.getFileName());
}
if (request.getReadRequest() != null &&
request.getReadRequest().getFileName() == null) {
request.getReadRequest().setFileName(request.getFileName());
}
excelService.writeAndRead(...);
...
}请求(只需外层 fileName):
{
"fileName": "公式.xlsx",
"writeRequest": {
"sheetName": "伤寒法要",
"cells": [
{"cellAddress": "D8", "value": "女", "valueType": "STRING"}
]
},
"readRequest": {
"sheetName": "伤寒法要",
"cells": [
{"cellAddress": "I8"}
]
}
}响应: ✅ 成功
请求(必须有 fileName):
{
"fileName": "test.xlsx",
"sheetName": "Sheet1",
"cells": [
{"cellAddress": "A1", "value": "test", "valueType": "STRING"}
]
}响应: ✅ 成功
如果缺少 fileName:
{
"sheetName": "Sheet1",
"cells": [...]
}响应: ❌ "文件名不能为空"
同样的验证逻辑
curl -X POST "http://localhost:8080/api/excel/operation" \
-H "X-API-Token: your_token" \
-H "Content-Type: application/json" \
-d '{
"fileName": "公式.xlsx",
"writeRequest": {
"sheetName": "伤寒法要",
"cells": [
{"cellAddress": "D8", "value": "女", "valueType": "STRING"}
]
},
"readRequest": {
"sheetName": "伤寒法要",
"cells": [{"cellAddress": "I8"}]
}
}'预期: ✅ 返回成功响应
curl -X POST "http://localhost:8080/api/excel/write" \
-H "X-API-Token: your_token" \
-H "Content-Type: application/json" \
-d '{
"sheetName": "Sheet1",
"cells": [{"cellAddress": "A1", "value": "test"}]
}'预期: ❌ 返回 "文件名不能为空"
- 访问
http://localhost:8080/admin/test - 选择 "写入并读取(组合操作)"
- 点击 "📝 加载示例"
- 点击 "🚀 发送请求"
预期: ✅ 成功返回结果
/write和/read接口的行为完全一致- 手动验证的错误信息与之前相同
- 只是验证的位置从注解改为代码
/operation接口现在可以正常工作- 只需在外层指定一次
fileName - 简化了 API 使用
- 如果你在
writeRequest或readRequest中明确指定了fileName,仍然会被尊重 - 优先级:内部指定 > 外层默认
- Spring 验证顺序:
@Valid在方法调用前执行,无法访问方法内的逻辑 - 验证策略: 对于复合请求,外层验证 + 内部自动传递是更好的设计
- 手动验证: 在某些场景下,手动验证比注解验证更灵活
这是一个典型的 数据传递模式(Data Propagation Pattern):
外层请求
├─ 验证外层字段
├─ 自动传递到内部请求
└─ 调用服务层
- 组合操作API优化说明
- 多Sheet快速上手
- API测试指南
- 更新日志 - v1.2.2
v1.2.2 - 2024-10-24
问题已修复,现在可以正常使用组合操作接口! ✅