# 订单日志管理系统设计方案 ## 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 { /// /// 订单日志操作类型枚举 /// public static class OrderLogOperationType { /// /// 换单 /// public const string REPLACE = "换单"; /// /// 集包 /// public const string PACK = "集包"; /// /// 数据更新 /// public const string UPDATE = "数据更新"; /// /// 下单 /// public const string CREATE_ORDER = "下单"; /// /// 取消订单 /// public const string CANCEL_ORDER = "取消订单"; } /// /// 订单日志操作结果枚举 /// public static class OrderLogOperationResult { /// /// 成功 /// public const string SUCCESS = "成功"; /// /// 失败 /// public const string FAILED = "失败"; } } ``` #### 2.2.2 实体类 ```csharp // MDL/Models/OrderLogEntity.cs using System; namespace MDL.Models { /// /// 订单日志实体类 /// public class OrderLogEntity { /// /// 主键ID,自增 /// public long Id { get; set; } /// /// 中性面单单号 /// public string NeutralWaybillNumber { get; set; } /// /// 尾程跟踪单号 /// public string FinalMileTrackingNumber { get; set; } /// /// 操作类型(换单、集包、数据更新、下单、取消订单等) /// public string OperationType { get; set; } /// /// 操作结果(成功、失败) /// public string OperationResult { get; set; } /// /// 操作说明(详细描述操作内容) /// public string OperationDescription { get; set; } /// /// 操作人 /// public string Operator { get; set; } /// /// 操作时间 /// 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 { /// /// 订单日志仓储接口 /// public interface IOrderLogRepository { /// /// 插入订单日志 /// /// 订单日志实体 /// 插入是否成功 Task InsertOrderLogAsync(OrderLogEntity log); /// /// 根据中性面单单号获取订单日志列表 /// /// 中性面单单号 /// 订单日志实体列表 Task> GetOrderLogsByWaybillNumberAsync(string neutralWaybillNumber); /// /// 根据尾程跟踪单号获取订单日志列表 /// /// 尾程跟踪单号 /// 订单日志实体列表 Task> GetOrderLogsByTrackingNumberAsync(string finalMileTrackingNumber); /// /// 获取所有订单日志列表(分页) /// /// 页码 /// 每页大小 /// 订单日志实体列表 Task> GetAllOrderLogsAsync(int pageIndex, int pageSize); /// /// 根据操作类型和操作结果获取订单日志列表 /// /// 操作类型 /// 操作结果 /// 页码 /// 每页大小 /// 订单日志实体列表 Task> 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 { /// /// 订单日志仓储实现类 /// public class OrderLogRepository : IOrderLogRepository { private readonly ISqlSugarClient _db; /// /// 构造函数 /// /// SqlSugar客户端 public OrderLogRepository(ISqlSugarClient db) { _db = db; } /// /// 插入订单日志 /// /// 订单日志实体 /// 插入是否成功 public async Task InsertOrderLogAsync(OrderLogEntity log) { var result = await _db.Insertable(log).ExecuteCommandAsync(); return result > 0; } /// /// 根据中性面单单号获取订单日志列表 /// /// 中性面单单号 /// 订单日志实体列表 public async Task> GetOrderLogsByWaybillNumberAsync(string neutralWaybillNumber) { return await _db.Queryable() .Where(x => x.NeutralWaybillNumber == neutralWaybillNumber) .OrderBy(x => x.CreatedAt) .ToListAsync(); } /// /// 根据尾程跟踪单号获取订单日志列表 /// /// 尾程跟踪单号 /// 订单日志实体列表 public async Task> GetOrderLogsByTrackingNumberAsync(string finalMileTrackingNumber) { return await _db.Queryable() .Where(x => x.FinalMileTrackingNumber == finalMileTrackingNumber) .OrderBy(x => x.CreatedAt) .ToListAsync(); } /// /// 获取所有订单日志列表(分页) /// /// 页码 /// 每页大小 /// 订单日志实体列表 public async Task> GetAllOrderLogsAsync(int pageIndex, int pageSize) { return await _db.Queryable() .OrderBy(x => x.CreatedAt, OrderByType.Desc) .ToPageListAsync(pageIndex, pageSize); } /// /// 根据操作类型和操作结果获取订单日志列表 /// /// 操作类型 /// 操作结果 /// 页码 /// 每页大小 /// 订单日志实体列表 public async Task> GetOrderLogsByOperationAsync(string operationType, string operationResult, int pageIndex, int pageSize) { var query = _db.Queryable(); 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 { /// /// 订单日志服务接口 /// public interface IOrderLogService { /// /// 记录订单操作日志 /// /// 中性面单单号 /// 尾程跟踪单号 /// 操作类型 /// 操作结果 /// 操作说明 /// 操作人 /// 记录是否成功 Task RecordOrderLogAsync(string neutralWaybillNumber, string finalMileTrackingNumber, string operationType, string operationResult, string operationDescription, string @operator = null); /// /// 根据中性面单单号获取订单日志列表 /// /// 中性面单单号 /// 订单日志实体列表 Task> GetOrderLogsByWaybillNumberAsync(string neutralWaybillNumber); /// /// 根据尾程跟踪单号获取订单日志列表 /// /// 尾程跟踪单号 /// 订单日志实体列表 Task> GetOrderLogsByTrackingNumberAsync(string finalMileTrackingNumber); /// /// 获取所有订单日志列表(分页) /// /// 页码 /// 每页大小 /// 订单日志实体列表 Task> GetAllOrderLogsAsync(int pageIndex, int pageSize); /// /// 根据操作类型和操作结果获取订单日志列表 /// /// 操作类型 /// 操作结果 /// 页码 /// 每页大小 /// 订单日志实体列表 Task> 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 { /// /// 订单日志服务实现类 /// public class OrderLogService : IOrderLogService { private readonly IOrderLogRepository _orderLogRepository; /// /// 构造函数 /// /// 订单日志仓储 public OrderLogService(IOrderLogRepository orderLogRepository) { _orderLogRepository = orderLogRepository; } /// /// 记录订单操作日志 /// /// 中性面单单号 /// 尾程跟踪单号 /// 操作类型 /// 操作结果 /// 操作说明 /// 操作人 /// 记录是否成功 public async Task 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); } /// /// 根据中性面单单号获取订单日志列表 /// /// 中性面单单号 /// 订单日志实体列表 public async Task> GetOrderLogsByWaybillNumberAsync(string neutralWaybillNumber) { return await _orderLogRepository.GetOrderLogsByWaybillNumberAsync(neutralWaybillNumber); } /// /// 根据尾程跟踪单号获取订单日志列表 /// /// 尾程跟踪单号 /// 订单日志实体列表 public async Task> GetOrderLogsByTrackingNumberAsync(string finalMileTrackingNumber) { return await _orderLogRepository.GetOrderLogsByTrackingNumberAsync(finalMileTrackingNumber); } /// /// 获取所有订单日志列表(分页) /// /// 页码 /// 每页大小 /// 订单日志实体列表 public async Task> GetAllOrderLogsAsync(int pageIndex, int pageSize) { return await _orderLogRepository.GetAllOrderLogsAsync(pageIndex, pageSize); } /// /// 根据操作类型和操作结果获取订单日志列表 /// /// 操作类型 /// 操作结果 /// 页码 /// 每页大小 /// 订单日志实体列表 public async Task> GetOrderLogsByOperationAsync(string operationType, string operationResult, int pageIndex, int pageSize) { return await _orderLogRepository.GetOrderLogsByOperationAsync(operationType, operationResult, pageIndex, pageSize); } } } ``` ### 2.3 依赖注入配置 需要在依赖注入容器中注册订单日志相关的服务和仓储: ```csharp // 注册订单日志仓储 services.AddScoped(); // 注册订单日志服务 services.AddScoped(); ``` ## 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、操作类型和操作结果等条件查询,以及分页查询功能。 通过合理的索引设计和性能优化,系统可以高效地处理大量的日志数据,为业务运营提供有力的支持。