# 订单日志管理系统设计方案

## 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、操作类型和操作结果等条件查询，以及分页查询功能。

通过合理的索引设计和性能优化，系统可以高效地处理大量的日志数据，为业务运营提供有力的支持。