Files
LabelChange-server/OrderLogManagementDesign.md
2026-06-01 16:30:29 +08:00

653 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 订单日志管理系统设计方案
## 1. 需求分析
### 1.1 业务背景
label_replace_requests 是一个换单请求的订单表,用于存储标签替换请求的相关信息。为了更好地追踪和管理订单的操作历史,需要建立一个订单日志管理表,用于记录订单所有的操作和操作结果。
### 1.2 功能需求
- 记录订单的所有操作,包括换单、集包、数据更新、下单、取消订单等
- 记录操作结果,如成功、失败
- 记录详细的操作说明,如每次集包的关联袋牌信息、数据更新的具体内容等
- 支持按订单号、尾程单号、操作类型、操作结果等条件查询日志
- 支持日志的分页查询和管理
## 2. 技术方案
### 2.1 数据库设计
#### 2.1.1 表结构设计
**表名order_logs**
| 字段名 | 数据类型 | 约束 | 描述 |
|--------|----------|------|------|
| Id | BIGINT UNSIGNED | PRIMARY KEY, AUTO_INCREMENT | 主键ID自增 |
| NeutralWaybillNumber | VARCHAR(100) | NOT NULL | 中性面单单号(必填) |
| FinalMileTrackingNumber | VARCHAR(100) | NULL | 尾程跟踪单号 |
| OperationType | VARCHAR(50) | NOT NULL | 操作类型(换单、集包、数据更新、下单、取消订单等) |
| OperationResult | VARCHAR(20) | NOT NULL | 操作结果(成功、失败) |
| OperationDescription | TEXT | NOT NULL | 操作说明(详细描述操作内容) |
| Operator | VARCHAR(100) | NULL | 操作人 |
| CreatedAt | DATETIME | NOT NULL | 操作时间 |
#### 2.1.2 索引设计
- `idx_neutral_waybill_number`:中性面单单号索引,用于快速查询特定订单的所有操作日志
- `idx_final_mile_tracking_number`:尾程跟踪单号索引,用于通过尾程单号查询日志
- `idx_operation_type`:操作类型索引,用于按操作类型查询日志
- `idx_operation_result`:操作结果索引,用于按操作结果查询日志
- `idx_created_at`:操作时间索引,用于按时间范围查询日志和排序
#### 2.1.3 关联关系
- 移除了与 `label_replace_requests` 表的外键关联
- 通过 `NeutralWaybillNumber``FinalMileTrackingNumber` 与订单表建立逻辑关联
### 2.2 代码实现
#### 2.2.1 枚举定义
```csharp
// MDL/Enums/OrderLogEnums.cs
using System;
namespace MDL.Enums
{
/// <summary>
/// 订单日志操作类型枚举
/// </summary>
public static class OrderLogOperationType
{
/// <summary>
/// 换单
/// </summary>
public const string REPLACE = "换单";
/// <summary>
/// 集包
/// </summary>
public const string PACK = "集包";
/// <summary>
/// 数据更新
/// </summary>
public const string UPDATE = "数据更新";
/// <summary>
/// 下单
/// </summary>
public const string CREATE_ORDER = "下单";
/// <summary>
/// 取消订单
/// </summary>
public const string CANCEL_ORDER = "取消订单";
}
/// <summary>
/// 订单日志操作结果枚举
/// </summary>
public static class OrderLogOperationResult
{
/// <summary>
/// 成功
/// </summary>
public const string SUCCESS = "成功";
/// <summary>
/// 失败
/// </summary>
public const string FAILED = "失败";
}
}
```
#### 2.2.2 实体类
```csharp
// MDL/Models/OrderLogEntity.cs
using System;
namespace MDL.Models
{
/// <summary>
/// 订单日志实体类
/// </summary>
public class OrderLogEntity
{
/// <summary>
/// 主键ID自增
/// </summary>
public long Id { get; set; }
/// <summary>
/// 中性面单单号
/// </summary>
public string NeutralWaybillNumber { get; set; }
/// <summary>
/// 尾程跟踪单号
/// </summary>
public string FinalMileTrackingNumber { get; set; }
/// <summary>
/// 操作类型(换单、集包、数据更新、下单、取消订单等)
/// </summary>
public string OperationType { get; set; }
/// <summary>
/// 操作结果(成功、失败)
/// </summary>
public string OperationResult { get; set; }
/// <summary>
/// 操作说明(详细描述操作内容)
/// </summary>
public string OperationDescription { get; set; }
/// <summary>
/// 操作人
/// </summary>
public string Operator { get; set; }
/// <summary>
/// 操作时间
/// </summary>
public DateTime CreatedAt { get; set; }
}
}
```
#### 2.2.3 仓储接口
```csharp
// DAL/Interfaces/IOrderLogRepository.cs
using System.Collections.Generic;
using System.Threading.Tasks;
using MDL.Models;
namespace DAL.Interfaces
{
/// <summary>
/// 订单日志仓储接口
/// </summary>
public interface IOrderLogRepository
{
/// <summary>
/// 插入订单日志
/// </summary>
/// <param name="log">订单日志实体</param>
/// <returns>插入是否成功</returns>
Task<bool> InsertOrderLogAsync(OrderLogEntity log);
/// <summary>
/// 根据中性面单单号获取订单日志列表
/// </summary>
/// <param name="neutralWaybillNumber">中性面单单号</param>
/// <returns>订单日志实体列表</returns>
Task<List<OrderLogEntity>> GetOrderLogsByWaybillNumberAsync(string neutralWaybillNumber);
/// <summary>
/// 根据尾程跟踪单号获取订单日志列表
/// </summary>
/// <param name="finalMileTrackingNumber">尾程跟踪单号</param>
/// <returns>订单日志实体列表</returns>
Task<List<OrderLogEntity>> GetOrderLogsByTrackingNumberAsync(string finalMileTrackingNumber);
/// <summary>
/// 获取所有订单日志列表(分页)
/// </summary>
/// <param name="pageIndex">页码</param>
/// <param name="pageSize">每页大小</param>
/// <returns>订单日志实体列表</returns>
Task<List<OrderLogEntity>> GetAllOrderLogsAsync(int pageIndex, int pageSize);
/// <summary>
/// 根据操作类型和操作结果获取订单日志列表
/// </summary>
/// <param name="operationType">操作类型</param>
/// <param name="operationResult">操作结果</param>
/// <param name="pageIndex">页码</param>
/// <param name="pageSize">每页大小</param>
/// <returns>订单日志实体列表</returns>
Task<List<OrderLogEntity>> GetOrderLogsByOperationAsync(string operationType, string operationResult, int pageIndex, int pageSize);
}
}
```
#### 2.2.4 仓储实现
```csharp
// DAL/Repositories/OrderLogRepository.cs
using System.Collections.Generic;
using System.Threading.Tasks;
using MDL.Models;
using DAL.Interfaces;
using SqlSugar;
namespace DAL.Repositories
{
/// <summary>
/// 订单日志仓储实现类
/// </summary>
public class OrderLogRepository : IOrderLogRepository
{
private readonly ISqlSugarClient _db;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="db">SqlSugar客户端</param>
public OrderLogRepository(ISqlSugarClient db)
{
_db = db;
}
/// <summary>
/// 插入订单日志
/// </summary>
/// <param name="log">订单日志实体</param>
/// <returns>插入是否成功</returns>
public async Task<bool> InsertOrderLogAsync(OrderLogEntity log)
{
var result = await _db.Insertable(log).ExecuteCommandAsync();
return result > 0;
}
/// <summary>
/// 根据中性面单单号获取订单日志列表
/// </summary>
/// <param name="neutralWaybillNumber">中性面单单号</param>
/// <returns>订单日志实体列表</returns>
public async Task<List<OrderLogEntity>> GetOrderLogsByWaybillNumberAsync(string neutralWaybillNumber)
{
return await _db.Queryable<OrderLogEntity>()
.Where(x => x.NeutralWaybillNumber == neutralWaybillNumber)
.OrderBy(x => x.CreatedAt)
.ToListAsync();
}
/// <summary>
/// 根据尾程跟踪单号获取订单日志列表
/// </summary>
/// <param name="finalMileTrackingNumber">尾程跟踪单号</param>
/// <returns>订单日志实体列表</returns>
public async Task<List<OrderLogEntity>> GetOrderLogsByTrackingNumberAsync(string finalMileTrackingNumber)
{
return await _db.Queryable<OrderLogEntity>()
.Where(x => x.FinalMileTrackingNumber == finalMileTrackingNumber)
.OrderBy(x => x.CreatedAt)
.ToListAsync();
}
/// <summary>
/// 获取所有订单日志列表(分页)
/// </summary>
/// <param name="pageIndex">页码</param>
/// <param name="pageSize">每页大小</param>
/// <returns>订单日志实体列表</returns>
public async Task<List<OrderLogEntity>> GetAllOrderLogsAsync(int pageIndex, int pageSize)
{
return await _db.Queryable<OrderLogEntity>()
.OrderBy(x => x.CreatedAt, OrderByType.Desc)
.ToPageListAsync(pageIndex, pageSize);
}
/// <summary>
/// 根据操作类型和操作结果获取订单日志列表
/// </summary>
/// <param name="operationType">操作类型</param>
/// <param name="operationResult">操作结果</param>
/// <param name="pageIndex">页码</param>
/// <param name="pageSize">每页大小</param>
/// <returns>订单日志实体列表</returns>
public async Task<List<OrderLogEntity>> GetOrderLogsByOperationAsync(string operationType, string operationResult, int pageIndex, int pageSize)
{
var query = _db.Queryable<OrderLogEntity>();
if (!string.IsNullOrEmpty(operationType))
{
query = query.Where(x => x.OperationType == operationType);
}
if (!string.IsNullOrEmpty(operationResult))
{
query = query.Where(x => x.OperationResult == operationResult);
}
return await query
.OrderBy(x => x.CreatedAt, OrderByType.Desc)
.ToPageListAsync(pageIndex, pageSize);
}
}
}
```
#### 2.2.5 服务接口
```csharp
// BLL/Interfaces/IOrderLogService.cs
using System.Collections.Generic;
using System.Threading.Tasks;
using MDL.Models;
namespace BLL.Interfaces
{
/// <summary>
/// 订单日志服务接口
/// </summary>
public interface IOrderLogService
{
/// <summary>
/// 记录订单操作日志
/// </summary>
/// <param name="neutralWaybillNumber">中性面单单号</param>
/// <param name="finalMileTrackingNumber">尾程跟踪单号</param>
/// <param name="operationType">操作类型</param>
/// <param name="operationResult">操作结果</param>
/// <param name="operationDescription">操作说明</param>
/// <param name="operator">操作人</param>
/// <returns>记录是否成功</returns>
Task<bool> RecordOrderLogAsync(string neutralWaybillNumber, string finalMileTrackingNumber, string operationType, string operationResult, string operationDescription, string @operator = null);
/// <summary>
/// 根据中性面单单号获取订单日志列表
/// </summary>
/// <param name="neutralWaybillNumber">中性面单单号</param>
/// <returns>订单日志实体列表</returns>
Task<List<OrderLogEntity>> GetOrderLogsByWaybillNumberAsync(string neutralWaybillNumber);
/// <summary>
/// 根据尾程跟踪单号获取订单日志列表
/// </summary>
/// <param name="finalMileTrackingNumber">尾程跟踪单号</param>
/// <returns>订单日志实体列表</returns>
Task<List<OrderLogEntity>> GetOrderLogsByTrackingNumberAsync(string finalMileTrackingNumber);
/// <summary>
/// 获取所有订单日志列表(分页)
/// </summary>
/// <param name="pageIndex">页码</param>
/// <param name="pageSize">每页大小</param>
/// <returns>订单日志实体列表</returns>
Task<List<OrderLogEntity>> GetAllOrderLogsAsync(int pageIndex, int pageSize);
/// <summary>
/// 根据操作类型和操作结果获取订单日志列表
/// </summary>
/// <param name="operationType">操作类型</param>
/// <param name="operationResult">操作结果</param>
/// <param name="pageIndex">页码</param>
/// <param name="pageSize">每页大小</param>
/// <returns>订单日志实体列表</returns>
Task<List<OrderLogEntity>> GetOrderLogsByOperationAsync(string operationType, string operationResult, int pageIndex, int pageSize);
}
}
```
#### 2.2.6 服务实现
```csharp
// BLL/Services/OrderLogService.cs
using System.Collections.Generic;
using System.Threading.Tasks;
using MDL.Models;
using BLL.Interfaces;
using DAL.Interfaces;
namespace BLL.Services
{
/// <summary>
/// 订单日志服务实现类
/// </summary>
public class OrderLogService : IOrderLogService
{
private readonly IOrderLogRepository _orderLogRepository;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="orderLogRepository">订单日志仓储</param>
public OrderLogService(IOrderLogRepository orderLogRepository)
{
_orderLogRepository = orderLogRepository;
}
/// <summary>
/// 记录订单操作日志
/// </summary>
/// <param name="neutralWaybillNumber">中性面单单号</param>
/// <param name="finalMileTrackingNumber">尾程跟踪单号</param>
/// <param name="operationType">操作类型</param>
/// <param name="operationResult">操作结果</param>
/// <param name="operationDescription">操作说明</param>
/// <param name="operator">操作人</param>
/// <returns>记录是否成功</returns>
public async Task<bool> RecordOrderLogAsync(string neutralWaybillNumber, string finalMileTrackingNumber, string operationType, string operationResult, string operationDescription, string @operator = null)
{
var log = new OrderLogEntity
{
NeutralWaybillNumber = neutralWaybillNumber,
FinalMileTrackingNumber = finalMileTrackingNumber,
OperationType = operationType,
OperationResult = operationResult,
OperationDescription = operationDescription,
Operator = @operator,
CreatedAt = System.DateTime.Now
};
return await _orderLogRepository.InsertOrderLogAsync(log);
}
/// <summary>
/// 根据中性面单单号获取订单日志列表
/// </summary>
/// <param name="neutralWaybillNumber">中性面单单号</param>
/// <returns>订单日志实体列表</returns>
public async Task<List<OrderLogEntity>> GetOrderLogsByWaybillNumberAsync(string neutralWaybillNumber)
{
return await _orderLogRepository.GetOrderLogsByWaybillNumberAsync(neutralWaybillNumber);
}
/// <summary>
/// 根据尾程跟踪单号获取订单日志列表
/// </summary>
/// <param name="finalMileTrackingNumber">尾程跟踪单号</param>
/// <returns>订单日志实体列表</returns>
public async Task<List<OrderLogEntity>> GetOrderLogsByTrackingNumberAsync(string finalMileTrackingNumber)
{
return await _orderLogRepository.GetOrderLogsByTrackingNumberAsync(finalMileTrackingNumber);
}
/// <summary>
/// 获取所有订单日志列表(分页)
/// </summary>
/// <param name="pageIndex">页码</param>
/// <param name="pageSize">每页大小</param>
/// <returns>订单日志实体列表</returns>
public async Task<List<OrderLogEntity>> GetAllOrderLogsAsync(int pageIndex, int pageSize)
{
return await _orderLogRepository.GetAllOrderLogsAsync(pageIndex, pageSize);
}
/// <summary>
/// 根据操作类型和操作结果获取订单日志列表
/// </summary>
/// <param name="operationType">操作类型</param>
/// <param name="operationResult">操作结果</param>
/// <param name="pageIndex">页码</param>
/// <param name="pageSize">每页大小</param>
/// <returns>订单日志实体列表</returns>
public async Task<List<OrderLogEntity>> GetOrderLogsByOperationAsync(string operationType, string operationResult, int pageIndex, int pageSize)
{
return await _orderLogRepository.GetOrderLogsByOperationAsync(operationType, operationResult, pageIndex, pageSize);
}
}
}
```
### 2.3 依赖注入配置
需要在依赖注入容器中注册订单日志相关的服务和仓储:
```csharp
// 注册订单日志仓储
services.AddScoped<IOrderLogRepository, OrderLogRepository>();
// 注册订单日志服务
services.AddScoped<IOrderLogService, OrderLogService>();
```
## 3. 使用示例
### 3.1 记录下单操作日志
```csharp
// 注入订单日志服务
private readonly IOrderLogService _orderLogService;
public async Task CreateOrderAsync(string neutralWaybillNumber, string finalMileTrackingNumber)
{
// 处理下单逻辑
bool success = await ProcessCreateOrder(neutralWaybillNumber, finalMileTrackingNumber);
// 记录下单操作日志
await _orderLogService.RecordOrderLogAsync(
neutralWaybillNumber: neutralWaybillNumber,
finalMileTrackingNumber: finalMileTrackingNumber,
operationType: OrderLogOperationType.CREATE_ORDER,
operationResult: success ? OrderLogOperationResult.SUCCESS : OrderLogOperationResult.FAILED,
operationDescription: success ? $"客户在 {DateTime.Now} 下单成功" : $"客户在 {DateTime.Now} 下单失败",
@operator: "Customer"
);
}
```
### 3.2 记录取消订单操作日志
```csharp
public async Task CancelOrderAsync(string neutralWaybillNumber, string finalMileTrackingNumber)
{
// 处理取消订单逻辑
bool success = await ProcessCancelOrder(neutralWaybillNumber, finalMileTrackingNumber);
// 记录取消订单操作日志
await _orderLogService.RecordOrderLogAsync(
neutralWaybillNumber: neutralWaybillNumber,
finalMileTrackingNumber: finalMileTrackingNumber,
operationType: OrderLogOperationType.CANCEL_ORDER,
operationResult: success ? OrderLogOperationResult.SUCCESS : OrderLogOperationResult.FAILED,
operationDescription: success ? $"客户在 {DateTime.Now} 取消订单成功" : $"客户在 {DateTime.Now} 取消订单失败",
@operator: "Customer"
);
}
```
### 3.3 记录换单操作日志
```csharp
public async Task ProcessLabelReplaceAsync(string neutralWaybillNumber, string finalMileTrackingNumber)
{
// 处理换单逻辑
bool success = await ProcessReplace(neutralWaybillNumber, finalMileTrackingNumber);
// 记录换单操作日志
await _orderLogService.RecordOrderLogAsync(
neutralWaybillNumber: neutralWaybillNumber,
finalMileTrackingNumber: finalMileTrackingNumber,
operationType: OrderLogOperationType.REPLACE,
operationResult: success ? OrderLogOperationResult.SUCCESS : OrderLogOperationResult.FAILED,
operationDescription: success ? "换单成功" : "换单失败",
@operator: "System"
);
}
```
### 3.4 记录集包操作日志
```csharp
public async Task ProcessPackAsync(string neutralWaybillNumber, string bagTag)
{
// 处理集包逻辑
bool success = await ProcessPacking(neutralWaybillNumber, bagTag);
// 记录集包操作日志
await _orderLogService.RecordOrderLogAsync(
neutralWaybillNumber: neutralWaybillNumber,
finalMileTrackingNumber: null, // 可能没有尾程单号
operationType: OrderLogOperationType.PACK,
operationResult: success ? OrderLogOperationResult.SUCCESS : OrderLogOperationResult.FAILED,
operationDescription: success ? $"关联袋牌 {bagTag} 成功" : $"关联袋牌 {bagTag} 失败",
@operator: "System"
);
}
```
### 3.5 记录数据更新操作日志
```csharp
public async Task UpdateBillOfLadingAsync(string neutralWaybillNumber, string billOfLadingNumber)
{
// 处理数据更新逻辑
bool success = await UpdateBillOfLading(neutralWaybillNumber, billOfLadingNumber);
// 记录数据更新操作日志
await _orderLogService.RecordOrderLogAsync(
neutralWaybillNumber: neutralWaybillNumber,
finalMileTrackingNumber: null, // 可能没有尾程单号
operationType: OrderLogOperationType.UPDATE,
operationResult: success ? OrderLogOperationResult.SUCCESS : OrderLogOperationResult.FAILED,
operationDescription: success ? "更新提单号成功" : "更新提单号失败",
@operator: "System"
);
}
```
### 3.6 查询订单日志
```csharp
// 根据中性面单单号查询订单日志
var logs = await _orderLogService.GetOrderLogsByWaybillNumberAsync("NW1234567890");
// 根据尾程跟踪单号查询订单日志
var logs = await _orderLogService.GetOrderLogsByTrackingNumberAsync("FM1234567890");
// 分页查询所有订单日志
var logs = await _orderLogService.GetAllOrderLogsAsync(pageIndex: 1, pageSize: 20);
// 根据操作类型和操作结果查询订单日志
var logs = await _orderLogService.GetOrderLogsByOperationAsync(
operationType: OrderLogOperationType.CREATE_ORDER,
operationResult: OrderLogOperationResult.SUCCESS,
pageIndex: 1,
pageSize: 20
);
```
## 4. 性能优化
### 4.1 索引优化
- 已在表结构中定义了必要的索引包括关联ID、中性面单单号、操作类型、操作结果和操作时间的索引
- 这些索引将提高查询性能,特别是在按中性面单单号、操作类型和操作结果查询时
### 4.2 分页查询
- 实现了分页查询功能,避免一次性加载大量日志数据
- 建议在前端展示时使用分页,提高用户体验
### 4.3 日志清理策略
- 考虑到日志数据会随着时间增长而变得庞大,建议制定日志清理策略
- 可以定期清理 older 日志数据,或者将 older 日志数据归档到历史表中
## 5. 总结
本设计方案实现了订单日志管理系统,用于记录订单的所有操作和操作结果。通过建立独立的订单日志表,并提供完整的日志记录和查询功能,可以更好地追踪和管理订单的操作历史,提高系统的可追溯性和可维护性。
系统支持记录换单、集包、数据更新等操作类型以及成功、失败等操作结果并提供详细的操作说明。同时系统提供了多种查询方式包括按中性面单单号、标签替换请求ID、操作类型和操作结果等条件查询以及分页查询功能。
通过合理的索引设计和性能优化,系统可以高效地处理大量的日志数据,为业务运营提供有力的支持。