Skip to content

Latest commit

 

History

History
293 lines (223 loc) · 6.5 KB

File metadata and controls

293 lines (223 loc) · 6.5 KB

参数验证问题修复说明 🔧

问题现象

使用组合操作接口时,即使传了 fileName 参数,仍然报错:

{
  "success": false,
  "message": "参数校验失败: 文件名不能为空; 文件名不能为空",
  "data": null
}

请求示例(应该正常工作但报错):

{
  "fileName": "公式.xlsx",
  "writeRequest": {
    "sheetName": "伤寒法要",
    "cells": [...]
  },
  "readRequest": {
    "sheetName": "伤寒法要",
    "cells": [...]
  }
}

问题根因

时序问题

  1. Spring 参数验证: 在方法执行进行(@Valid 注解)
  2. 自动传递逻辑: 在方法内部执行
@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种方案:

  1. 自定义验证器 - 复杂,过度设计
  2. 修改验证注解位置 - 无法解决时序问题
  3. 移除内部验证 + 手动验证 - 简单有效

实施修复

1. 移除内部字段的验证注解

ExcelWriteRequest.java:

// 修改前
@NotBlank(message = "文件名不能为空")
private String fileName;

// 修改后
/**
 * Excel文件名(不含路径)
 * 在组合操作(operation)中,此字段会自动从外层传递,无需手动指定
 */
private String fileName;

ExcelReadRequest.java: 同样的修改

2. 在独立接口中添加手动验证

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 接口: 同样的手动验证

3. /operation 接口保持不变

@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": [...]
}

响应: ❌ "文件名不能为空"

✅ 独立读取接口

同样的验证逻辑

验证方法

测试1: 组合操作(之前失败,现在应该成功)

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"}]
    }
  }'

预期: ✅ 返回成功响应

测试2: 独立写入(应该要求 fileName)

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"}]
  }'

预期: ❌ 返回 "文件名不能为空"

测试3: Web测试工具

  1. 访问 http://localhost:8080/admin/test
  2. 选择 "写入并读取(组合操作)"
  3. 点击 "📝 加载示例"
  4. 点击 "🚀 发送请求"

预期: ✅ 成功返回结果

影响范围

✅ 不影响现有功能

  • /write/read 接口的行为完全一致
  • 手动验证的错误信息与之前相同
  • 只是验证的位置从注解改为代码

✅ 修复了新功能

  • /operation 接口现在可以正常工作
  • 只需在外层指定一次 fileName
  • 简化了 API 使用

✅ 向后兼容

  • 如果你在 writeRequestreadRequest 中明确指定了 fileName,仍然会被尊重
  • 优先级:内部指定 > 外层默认

技术总结

学到的经验

  1. Spring 验证顺序: @Valid 在方法调用前执行,无法访问方法内的逻辑
  2. 验证策略: 对于复合请求,外层验证 + 内部自动传递是更好的设计
  3. 手动验证: 在某些场景下,手动验证比注解验证更灵活

设计模式

这是一个典型的 数据传递模式(Data Propagation Pattern):

外层请求
  ├─ 验证外层字段
  ├─ 自动传递到内部请求
  └─ 调用服务层

相关文档

更新日期

v1.2.2 - 2024-10-24


问题已修复,现在可以正常使用组合操作接口!