# 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 文档注释