# 运单导入校验功能 - 最终实现总结 ## 完成时间 2026-09-08 ## 实现方式 **两步校验机制**: 1. **选择文件后** → 立即调用校验接口(`/import-batch/validate`),只校验不入库 2. **点击确认导入** → 调用确认接口(`/import-batch/confirm`),再次校验并入库 --- ## 一、用户使用流程 ### 1. 上传文件阶段 1. 用户点击"添加附件"按钮 2. 选择 Excel 文件 3. **前端自动调用校验接口** `POST /blade-transport/waybill-manage/import-batch/validate` 4. 后端返回结果: - **校验通过**:返回 JSON,前端提示"数据校验通过" - **校验失败**:返回 Excel 文件流,浏览器自动下载错误明细表,前端提示"数据校验失败,已自动下载错误明细表,请修正后重新上传" ### 2. 确认导入阶段 1. 用户查看预览数据,确认无误 2. 点击"确认导入"按钮 3. **前端调用确认接口** `POST /blade-transport/waybill-manage/import-batch/confirm` 4. 后端再次校验并入库: - **校验通过**:入库成功,返回 JSON,前端提示"导入成功" - **校验失败**:返回 Excel 文件流,浏览器自动下载错误明细表(防止数据在上传和确认之间被修改) --- ## 二、后端实现 ### 1. 新增接口 #### 1.1 校验接口 **路径**: `POST /blade-transport/waybill-manage/import-batch/validate` **功能**: 只校验数据,不入库 **返回**: - 校验通过:JSON 成功响应 - 校验失败:Excel 文件流(包含错误信息) **实现位置**: - 控制器:`WaybillController.validateImportBatch()` - 服务接口:`IWaybillImportBatchService.validate()` - 服务实现:`WaybillImportBatchServiceImpl.validate()` #### 1.2 确认导入接口(修改) **路径**: `POST /blade-transport/waybill-manage/import-batch/confirm` **功能**: 再次校验并入库 **返回**: - 校验通过:入库成功,JSON 成功响应 - 校验失败:Excel 文件流(包含错误信息) **实现位置**: - 控制器:`WaybillController.confirmImportBatch()` - 服务接口:`IWaybillImportBatchService.confirm()` - 服务实现:`WaybillImportBatchServiceImpl.confirm()` ### 2. 修改的文件 #### 2.1 控制器 **文件**: `/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/WaybillController.java` ```java @PostMapping("/import-batch/validate") @ApiOperationSupport(order = 6) @Operation(summary = "校验运单批量导入数据") public void validateImportBatch(@RequestBody WaybillImportBatchRequest request, HttpServletResponse response) { waybillImportBatchService.validate(request, response); } @PostMapping("/import-batch/confirm") @ApiOperationSupport(order = 7) @Operation(summary = "确认运单批量导入") public void confirmImportBatch(@RequestBody WaybillImportBatchRequest request, HttpServletResponse response) { waybillImportBatchService.confirm(request, response); } ``` #### 2.2 服务接口 **文件**: `/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IWaybillImportBatchService.java` ```java public interface IWaybillImportBatchService extends BaseService { WaybillImportBatch saveDraft(WaybillImportBatchRequest request); void validate(WaybillImportBatchRequest request, HttpServletResponse response); void confirm(WaybillImportBatchRequest request, HttpServletResponse response); IPage page(IPage page, WaybillImportBatchRequest request); BusinessRemoveResultVO removeBatches(String ids); } ``` #### 2.3 服务实现 **文件**: `/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillImportBatchServiceImpl.java` **新增方法**: - `validate()` - 校验数据,不入库 - `mapToExcel()` - 将 Map 转换为 Excel 对象 **修改方法**: - `confirm()` - 校验并入库 - `persist()` - 移除内部校验逻辑 #### 2.4 Excel 实体 **文件**: `/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/WaybillImportBatchExcel.java` **新增字段**: ```java /** 导入失败原因(不导出到模板,仅用于失败明细) */ private String errorMessage; ``` --- ## 三、前端实现 ### 1. 修改的文件 #### 1.1 API 文件 **文件**: `src/api/business/waybill-manage.js` **新增接口**: ```javascript export const validateImport = data => request({ url: `${baseUrl}/import-batch/validate`, method: 'post', data, responseType: 'blob' }); ``` #### 1.2 组件文件 **文件**: `src/views/business/components/waybill-import-dialog.vue` **新增方法**: ```javascript const performValidation = async () => { if (!rows.value.length) return; try { const response = await api.validateImport(buildImportPayload()); if (response instanceof Blob) { // 校验失败,下载错误明细 const url = window.URL.createObjectURL(response); const link = document.createElement('a'); link.href = url; link.download = `运单导入失败明细_${dayjs().format('YYYYMMDDHHmmss')}.xlsx`; link.click(); window.URL.revokeObjectURL(url); ElMessage.warning('数据校验失败,已自动下载错误明细表,请修正后重新上传'); } else { // 校验通过 ElMessage.success('数据校验通过'); } } catch (error) { console.error('校验失败:', error); ElMessage.error('校验接口调用失败'); } }; ``` **修改方法**: - `fileChange()` - 文件上传后调用 `performValidation()` --- ## 四、校验规则 ### 已实现的校验规则 #### 4.1 格式校验 - ✅ 车牌号格式(公路运输:首位汉字 + 次位大写字母 + 7-8 位长度) - ✅ 手机号格式(11 位数字) - ✅ 运输方式枚举值 - ✅ 计量单位枚举值 - ✅ 正数校验(数量、里程) - ✅ 日期时间格式(YYYY-MM-DD 或 YYYY-MM-DD HH:mm:ss) #### 4.2 逻辑校验 - ✅ 运费合计 = 运费 + 其他费用合计 - ✅ 日期关系(发货时间不能晚于完成时间) - ✅ 配载标识号一致性(同一配载标识号的车牌号必须一致) - ✅ 同一运单标识号一致性(同一运单标识号的车牌号必须一致) #### 4.3 必填项校验 - ✅ 车牌号/航班号/船号/班列号 - ✅ 运输方式 - ✅ 发货地址 - ✅ 到货地址 - ✅ 货物名称 - ✅ 货物类型 - ✅ 数量 - ✅ 数量单位 - ✅ 实际发货时间(status=completed 时) - ✅ 实际完成时间(status=completed 时) ### 错误明细 Excel 格式 - ✅ 在原始 Excel 最后一列追加"错误信息"列 - ✅ 错误信息以红色字体显示 - ✅ 校验通过的行显示空字符串 - ✅ 多个错误用 "; " 分隔 - ✅ 列宽自动调整 --- ## 五、技术要点 ### 5.1 两次校验的原因 1. **第一次校验(上传后)**: - 及早发现问题,用户可以立即修正 - 避免用户填写其他表单项后才发现数据有问题 2. **第二次校验(确认导入时)**: - 防止数据在上传和确认之间被修改 - 确保入库数据的准确性 ### 5.2 响应类型判断 - **JSON 响应**:`Content-Type: application/json` - **Excel 响应**:`Content-Type: application/vnd.ms-excel` 前端通过 `responseType: 'blob'` 接收响应,根据响应类型判断: - `Blob` 类型 → 校验失败,下载文件 - 其他类型 → 校验通过,显示提示 ### 5.3 事务处理 - `validate()` 方法**不开启事务**,只读操作 - `confirm()` 方法**开启事务**,校验失败时不入库,校验通过后才入库 ### 5.4 性能优化 - 校验逻辑复用(`validateImportRows()` 方法) - 使用 `TreeMap` 确保错误信息按行号排序 - 使用 `Map` 数据结构优化跨行校验性能 --- ## 六、测试验证 ### 6.1 编译验证 ✅ **后端编译成功** - `mvn clean compile -DskipTests` ✅ **前端构建成功** - `pnpm run build` ### 6.2 待测试项 #### 功能测试 - [ ] 上传完全正确的数据 - 第一次校验提示"数据校验通过" - 点击确认导入,提示"导入成功" - [ ] 上传错误数据(如车牌号格式错误) - 第一次校验自动下载错误明细 - 修正后重新上传,校验通过 - 点击确认导入,提示"导入成功" - [ ] 上传混合数据(部分正确部分错误) - 下载的错误明细最后一列显示错误信息(红色) - 正确的行显示空字符串 #### 边界测试 - [ ] 上传空文件 - [ ] 上传非 Excel 文件 - [ ] 上传超大文件(>5000 行) - [ ] 同一配载标识号车牌不一致 - [ ] 运费合计不匹配 #### 性能测试 - [ ] 上传 1000 行数据,校验响应时间 - [ ] 上传 5000 行数据,校验响应时间 - [ ] 并发上传测试 --- ## 七、对比改造前后 ### 改造前 - ❌ 前端校验,规则分散 - ❌ 前端生成错误 Excel - ❌ 前后端规则不一致 - ❌ 只在确认导入时校验 ### 改造后 - ✅ 后端校验,规则集中 - ✅ 后端生成错误 Excel - ✅ 统一的校验规则 - ✅ 上传后立即校验 + 确认导入时再次校验 - ✅ 使用成熟的工具类(`ImportFailureExcelUtil`) --- ## 八、后续工作 ### 短期 1. **联调测试** - 启动前后端服务,完整流程测试 2. **Bug 修复** - 根据测试结果修复问题 3. **运输计划导入** - 实现类似的两步校验机制 ### 长期 1. **校验规则配置化** - 将规则抽取为配置,便于维护 2. **地址库集成** - 实现真实的地址匹配校验 3. **性能优化** - 针对大文件导入进行优化 4. **国际化** - 支持多语言错误信息 --- ## 九、相关文档 ### 已创建的文档 1. **导入校验规则-后端实现文档.md**(前端项目) 2. **导入校验改造总结.md**(前端项目) 3. **运单导入校验-前后端对接完成总结.md**(后端项目) 4. **本文档**(最终实现总结) ### 参考实现 - **港口码头导入**: `/blade-system/port-terminal/import-port-terminal` - **导入失败工具类**: `org.springblade.common.excel.ImportFailureExcelUtil` --- ## 十、API 接口文档 ### 10.1 校验接口 #### 请求 ``` POST /blade-transport/waybill-manage/import-batch/validate Content-Type: application/json { "projectId": 123, "contractId": 456, "status": "completed", "rows": [...] } ``` #### 响应 **成功(校验通过)**: ``` HTTP/1.1 200 OK Content-Type: application/json { "code": 200, "success": true, "msg": "校验通过" } ``` **失败(校验不通过)**: ``` HTTP/1.1 200 OK Content-Type: application/vnd.ms-excel Content-Disposition: attachment; filename=运单导入失败明细20260908190730.xlsx [Excel Binary Data] ``` ### 10.2 确认导入接口 #### 请求 ``` POST /blade-transport/waybill-manage/import-batch/confirm Content-Type: application/json { "projectId": 123, "contractId": 456, "status": "completed", "rows": [...] } ``` #### 响应 **成功(导入成功)**: ``` HTTP/1.1 200 OK Content-Type: application/json { "code": 200, "success": true, "msg": "操作成功" } ``` **失败(校验不通过)**: ``` HTTP/1.1 200 OK Content-Type: application/vnd.ms-excel Content-Disposition: attachment; filename=运单导入失败明细20260908190730.xlsx [Excel Binary Data] ``` --- **完成状态**: ✅ 前后端代码已完成,编译通过,等待联调测试 **实现方式**: 两步校验机制(上传后校验 + 确认导入时再次校验) **编写人**: Claude Code **版本**: 2.0 **最后更新**: 2026-09-08 19:10