# MES 上传状态跟踪与异步上传设计 > **Date:** 2026-04-20 > **Author:** Claude ## 1. 目标 在库存表添加 MES 上传状态字段,通过异步方式上传 MES 数据,不干扰主业务逻辑,所有 MES 调用均记录详细日志。 ## 2. 数据库变更 ### 2.1 Dt_StockInfo 表新增字段 ```sql ALTER TABLE [dbo].[Dt_StockInfo] ADD [MesUploadStatus] TINYINT NOT NULL DEFAULT 0; GO ``` ### 2.2 枚举定义 | 值 | 含义 | |----|------| | 0 | 未上传(从未调用过MES) | | 1 | 组盘上传成功 | | 2 | 组盘上传失败 | | 3 | 拆盘上传成功 | | 4 | 拆盘上传失败 | | 5 | 进站上传成功 | | 6 | 进站上传失败 | | 7 | 出站上传成功 | | 8 | 出站上传失败 | | 9 | NG上报成功 | | 10 | NG上报失败 | 在 `WIDESEA_Common` 项目新增枚举类 `MesUploadStatusEnum`。 ## 3. 后端设计 ### 3.1 核心异步方法 在 `WIDESEA_TaskInfoService/TaskService.cs` 中新增 `MesUploadAsync` 私有异步方法: ```csharp /// /// 异步执行MES上传 - 不阻塞主业务逻辑 /// /// 托盘号 /// MES操作类型枚举 /// 具体的MES调用函数 private async Task MesUploadAsync(string palletCode, MesUploadStatusEnum mesType, Func>> uploadFunc) { var stopwatch = Stopwatch.StartNew(); string requestJson = ""; string responseJson = ""; bool isSuccess = false; string errorMessage = ""; try { // 记录请求日志 var mesLog = new MesApiLogDto { PalletCode = palletCode, ApiType = mesType.ToString(), RequestTime = DateTime.Now }; // 调用MES var result = await uploadFunc(); stopwatch.Stop(); isSuccess = result?.Data?.IsSuccess ?? false; errorMessage = result?.Data?.Msg ?? result?.ErrorMessage ?? "未知错误"; responseJson = JsonConvert.SerializeObject(result); // 更新库存表状态 var uploadStatus = isSuccess ? (int)mesType : (int)mesType + 1; // 奇数=成功,偶数=失败 await _stockInfoService.UpdateMesUploadStatusAsync(palletCode, uploadStatus); // 记录日志 mesLog.ResponseTime = DateTime.Now; mesLog.Duration = stopwatch.ElapsedMilliseconds; mesLog.RequestParams = requestJson; mesLog.ResponseResult = responseJson; mesLog.Status = isSuccess ? "成功" : "失败"; mesLog.ErrorMessage = errorMessage; mesLog.CreateTime = DateTime.Now; _ = _mesLogService.LogAsync(mesLog); // 不等待 } catch (Exception ex) { stopwatch.Stop(); errorMessage = ex.Message; // 更新状态为失败 var uploadStatus = (int)mesType + 1; _stockInfoService.UpdateMesUploadStatusAsync(palletCode, uploadStatus).ConfigureAwait(false); // 记录异常日志 var mesLog = new MesApiLogDto { PalletCode = palletCode, ApiType = mesType.ToString(), RequestTime = DateTime.Now, ResponseTime = DateTime.Now, Duration = stopwatch.ElapsedMilliseconds, RequestParams = requestJson, ResponseResult = responseJson, Status = "失败", ErrorMessage = errorMessage, CreateTime = DateTime.Now }; _ = _mesLogService.LogAsync(mesLog); // 不等待 } } ``` ### 3.2 调用方式 在各个 MES 操作处统一使用 `Task.Run` + `MesUploadAsync`: ```csharp // 组盘确认 public async Task GroupPalletConfirmAsync(string palletCode, string deviceName) { // ... 业务逻辑 ... // 异步MES上传,不阻塞主逻辑 _ = Task.Run(() => MesUploadAsync(palletCode, MesUploadStatusEnum.GroupPalletSuccess, async () => { return await _mesService.BindContainerAsync(bindRequest, token); })); return content.OK("组盘成功"); } // 拆盘确认 public async Task SplitPalletConfirmAsync(string palletCode, string deviceName) { // ... 业务逻辑 ... _ = Task.Run(() => MesUploadAsync(palletCode, MesUploadStatusEnum.SplitPalletSuccess, async () => { return await _mesService.UnBindContainerAsync(unbindRequest, token); })); return content.OK("拆盘成功"); } ``` ### 3.3 IStockInfoService 新增方法 在 `IStockInfoService` 接口新增: ```csharp /// /// 更新MES上传状态 /// /// 托盘号 /// 状态值 Task UpdateMesUploadStatusAsync(string palletCode, int status); ``` 在 `StockInfoService` 中实现该方法。 ### 3.4 日志记录规范 所有 MES 调用统一记录以下字段: | 字段 | 说明 | |------|------| | PalletCode | 托盘号 | | ApiType | 接口类型(组盘/拆盘/进站/出站/NG上报) | | RequestTime | 请求时间 | | ResponseTime | 响应时间 | | Duration | 耗时(ms) | | RequestParams | 请求JSON | | ResponseResult | 响应JSON | | Status | 成功/失败 | | ErrorMessage | 失败原因 | ## 4. 前端变更 ### 4.1 库存页面操作列新增按钮 在 `WMS/WIDESEA_WMSClient/src/extension/stock/stock.jsx` 中扩展操作列: ```jsx let extension = { components: { gridHeader: "", gridBody: "", gridFooter: "", modelHeader: "", modelBody: "", modelFooter: "", }, tableAction: "stock", buttons: { view: ["Export"], box: [] }, methods: { onInit() { return true; }, onInited() { // 注入组盘/拆盘按钮 this.editTableButtons = [ { name: "组盘", onClick: this.onGroupPallet }, { name: "拆盘", onClick: this.onSplitPallet } ]; return true; }, async onGroupPallet({ row }) { // 调用组盘接口 let result = await this.$api.post("/Stock/GroupPalletConfirm", { palletCode: row.palletCode }); if (result.status) { this.$Message.success("组盘成功"); this.$refs.grid.search(); } else { this.$Message.error(result.message || "组盘失败"); } }, async onSplitPallet({ row }) { // 调用拆盘接口 let result = await this.$api.post("/Stock/SplitPalletConfirm", { palletCode: row.palletCode }); if (result.status) { this.$Message.success("拆盘成功"); this.$refs.grid.search(); } else { this.$Message.error(result.message || "拆盘失败"); } } }, }; export default extension; ``` ### 4.2 库存列表新增状态列 在 `WMS/WIDESEA_WMSClient/src/views/stock/stock.vue` 的 `columns` 中新增: ```vue { field: "mesUploadStatus", title: "MES状态", type: "int", width: 120, bind: { key: "mesUploadStatusEnum", data: [] } } ``` ### 4.3 字典配置 在 `mesUploadStatusEnum` 字典中添加: | value | label | |-------|-------| | 0 | 未上传 | | 1 | 组盘成功 | | 2 | 组盘失败 | | 3 | 拆盘成功 | | 4 | 拆盘失败 | | 5 | 进站成功 | | 6 | 进站失败 | | 7 | 出站成功 | | 8 | 出站失败 | | 9 | NG上报成功 | | 10 | NG上报失败 | ## 5. 文件变更清单 | 操作 | 文件 | |------|------| | 新增 | `WMS/WIDESEA_WMSServer/WIDESEA_Common/StockEnum/MesUploadStatusEnum.cs` | | 修改 | `WMS/WIDESEA_WMSServer/WIDESEA_Model/Models/Stock/Dt_StockInfo.cs` - 新增 MesUploadStatus 字段 | | 修改 | `WMS/WIDESEA_WMSServer/WIDESEA_IStockService/IStockInfoService.cs` - 新增 UpdateMesUploadStatusAsync | | 修改 | `WMS/WIDESEA_WMSServer/WIDESEA_StockService/StockInfoService.cs` - 实现 UpdateMesUploadStatusAsync | | 修改 | `WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService.cs` - 新增 MesUploadAsync + 各处调用 | | 修改 | `WMS/WIDESEA_WMSClient/src/extension/stock/stock.jsx` - 新增组盘/拆盘按钮 | | 修改 | `WMS/WIDESEA_WMSClient/src/views/stock/stock.vue` - 新增MES状态列 | | 新增 | 数据库变更脚本 | ## 6. 自检清单 - [ ] `MesUploadStatusEnum` 枚举值奇数为成功,偶数为失败,+1运算正确 - [ ] `MesUploadAsync` 方法内所有 MES 调用记录详细日志(请求JSON、响应JSON、耗时、错误原因) - [ ] 组盘/拆盘/进站/出站/NG上报均通过 `Task.Run` 异步执行,不阻塞主逻辑 - [ ] 前端组盘/拆盘按钮调用 `GroupPalletConfirmAsync` / `SplitPalletConfirmAsync` - [ ] 库存列表正确显示 `mesUploadStatus` 字段 - [ ] 所有 public 方法均有 XML 文档注释