# Caller Header 接收改造实施计划

## 概述

根据《后端Caller字段接收清单.md》的要求，需要在 11 个 API 接口中从 HTTP Header 读取 `Caller` 字段（请求人姓名）。

**核心业务含义**：`Caller` 记录了该次请求是由谁发起的，对于创建类接口需要**写入系统的创建人字段**，对于所有接口都需要**通过 Serilog 记录审计日志**。

**扩展性设计**：采用 Middleware 统一提取 + RequestTrackingContext 建模的方案。后续新增 `Device-Id`、`Request-Id` 等 Header 时，只需：

1. 在 `RequestTrackingContext` 类中加一个属性
2. 在 Middleware 中加一行读取代码

无需修改任何 Controller。

***

## 架构设计

```
HTTP Request (Header: Caller, Device-Id, Request-Id, ...)
       │
       ▼
┌─────────────────────────┐
│  RequestTrackingMiddleware │  ← 统一提取所有追踪 Header
│  存入 HttpContext.Items     │    写入 RequestTrackingContext
└───────────┬─────────────┘
            │
            ▼
┌─────────────────────────┐
│  Controller Action       │  ← HttpContext.GetRequestTrackingContext().Caller
│  - A 类：读取 + 记录日志   │     _logger.LogInformation(...)
│  - B 类：读取 + 写创建人   │     entity.Creator = ...GetCaller();
└─────────────────────────┘
```

***

## 涉及的文件

| # | 文件                                                                   | 操作       | 说明               |
| - | -------------------------------------------------------------------- | -------- | ---------------- |
| 1 | `src/CONTROLLER/Models/RequestTrackingContext.cs`                    | **新建**   | 追踪上下文模型          |
| 2 | `src/CONTROLLER/Middleware/RequestTrackingMiddleware.cs`             | **新建**   | 统一提取 Header      |
| 3 | `src/CONTROLLER/Extensions/HttpContextExtensions.cs`                 | **新建**   | HttpContext 扩展方法 |
| 4 | `src/CONTROLLER/Program.cs`                                          | **修改**   | 注册中间件            |
| 5 | `src/CONTROLLER/Controllers/LabelController.cs`                      | 修改 2 个方法 | 已有 Logger        |
| 6 | `src/CONTROLLER/Controllers/BagTagController.cs`                     | 修改 4 个方法 | 需注入 Logger       |
| 7 | `src/CONTROLLER/Controllers/ShippingHandoverFormController.cs`       | 修改 3 个方法 | 需注入 Logger       |
| 8 | `src/CONTROLLER/Controllers/ShippingHandoverFormBagTagController.cs` | 修改 1 个方法 | 需注入 Logger       |

***

## 接口改造分类

### A 类：仅记录 Caller 日志（7 个读操作接口）

| #  | 方法  | 端点                                                       |
| -- | --- | -------------------------------------------------------- |
| 1  | GET | `api/label/label-replace/waybill/{number}/download`      |
| 2  | GET | `api/label/label-replace/waybill/{number}/downloadNoTri` |
| 3  | GET | `api/bagtag/{tagNumber}/print`                           |
| 4  | GET | `api/shipping-handover/{bolNumber}/print`                |
| 7  | GET | `api/shipping-handover/generate-number`                  |
| 9  | GET | `api/bagtag/available`                                   |
| 10 | GET | `api/shipping-handover/bag-tag/associate-by-number/{id}` |

### B 类：记录日志 + 写入系统创建人字段（3 个创建/操作接口）

| # | 方法   | 端点                             | 当前代码                     | 改造内容                   |
| - | ---- | ------------------------------ | ------------------------ | ---------------------- |
| 5 | POST | `api/bagtag/generate`          | `creator` 硬编码 `"system"` | → 从 `Caller` Header 读取 |
| 6 | POST | `api/bagtag/auto-pack/start`   | `request.Creator` 从请求体取  | → 用 `Caller` Header 覆盖 |
| 8 | GET  | `api/shipping-handover/create` | `Creator` 从 URL 参数取      | → 用 `Caller` Header 覆盖 |

***

## 实施步骤

### 步骤 1：创建 `RequestTrackingContext` 模型

`src/CONTROLLER/Models/RequestTrackingContext.cs`

```csharp
namespace CONTROLLER.Models
{
    public class RequestTrackingContext
    {
        public string Caller { get; set; } = "system";
        // 后续扩展预留：
        // public string DeviceId { get; set; }
        // public string RequestId { get; set; }
    }
}
```

* 所有追踪 Header 的值集中在一个模型中

* 默认值 `"system"` 作为回退

* 后续新增 Header：添加属性 → Middleware 中加一行读取 → 完成

***

### 步骤 2：创建 `RequestTrackingMiddleware` 中间件

`src/CONTROLLER/Middleware/RequestTrackingMiddleware.cs`

```csharp
using CONTROLLER.Models;
using Microsoft.AspNetCore.Http;
using System.Linq;
using System.Threading.Tasks;

namespace CONTROLLER.Middleware
{
    public class RequestTrackingMiddleware
    {
        private readonly RequestDelegate _next;

        public RequestTrackingMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task InvokeAsync(HttpContext context)
        {
            var trackingContext = new RequestTrackingContext();

            var caller = context.Request.Headers["Caller"].FirstOrDefault();
            if (!string.IsNullOrWhiteSpace(caller))
            {
                trackingContext.Caller = caller;
            }
            // 后续扩展只需加一行：
            // var deviceId = context.Request.Headers["Device-Id"].FirstOrDefault();
            // if (!string.IsNullOrWhiteSpace(deviceId)) trackingContext.DeviceId = deviceId;

            context.Items["RequestTrackingContext"] = trackingContext;
            await _next(context);
        }
    }
}
```

* 在管道最前端统一提取所有追踪 Header

* 存入 `HttpContext.Items["RequestTrackingContext"]`

* 仅在 Header 有值时才覆盖默认值（保持回退逻辑）

***

### 步骤 3：创建 `HttpContextExtensions` 扩展方法

`src/CONTROLLER/Extensions/HttpContextExtensions.cs`

```csharp
using CONTROLLER.Models;
using Microsoft.AspNetCore.Http;

namespace CONTROLLER.Extensions
{
    public static class HttpContextExtensions
    {
        public static RequestTrackingContext GetRequestTrackingContext(this HttpContext context)
        {
            return context.Items["RequestTrackingContext"] as RequestTrackingContext
                   ?? new RequestTrackingContext();
        }

        public static string GetCaller(this HttpContext context)
        {
            return context.GetRequestTrackingContext().Caller;
        }
    }
}
```

* `GetRequestTrackingContext()` — 获取完整上下文（支持未来扩展）

* `GetCaller()` — 便捷方法，直接获取调用人

***

### 步骤 4：在 `Program.cs` 中注册中间件

在 `app.UseResponseCompression();` 之前（或其他合适位置）添加：

```csharp
app.UseMiddleware<RequestTrackingMiddleware>();
```

需要添加 using：

```csharp
using CONTROLLER.Middleware;
```

***

### 步骤 5：改造 `LabelController.cs`（A 类：2 个接口）

已有 `ILogger<LabelController> _logger`，添加 using + 在方法入口处记录 Caller 日志：

添加 using：

```csharp
using CONTROLLER.Extensions;
```

**接口 #1** — `DownloadLabelByWaybillNumber`：

```csharp
var caller = HttpContext.GetCaller();
_logger.LogInformation("[Caller: {Caller}] DownloadLabel, WaybillNumber: {WaybillNumber}", caller, waybillNumber);
```

**接口 #2** — `DownloadLabelByWaybillNumberNoTrigger`：

```csharp
var caller = HttpContext.GetCaller();
_logger.LogInformation("[Caller: {Caller}] DownloadLabelNoTrigger, WaybillNumber: {WaybillNumber}", caller, waybillNumber);
```

***

### 步骤 6：改造 `BagTagController.cs`（A 类 2 个 + B 类 2 个）

**前置改造**：注入 `ILogger<BagTagController>`：

* 添加 `using Microsoft.Extensions.Logging;` 和 `using CONTROLLER.Extensions;`

* 添加构造函数参数和私有字段 `_logger`

**A 类 — 接口 #3** `PrintBagTag`：

```csharp
var caller = HttpContext.GetCaller();
_logger.LogInformation("[Caller: {Caller}] PrintBagTag, TagNumber: {TagNumber}", caller, tagNumber);
```

**B 类 — 接口 #5** `GenerateBagTags`：

* **当前**：`var creator = /*...*/ "system";`（硬编码）

* **改为**：

```csharp
var caller = HttpContext.GetCaller();
_logger.LogInformation("[Caller: {Caller}] GenerateBagTags, Channel: {Channel}, Count: {Count}", caller, request.ChannelName, request.Count);
var generatedTags = await _bagTagService.GenerateBagTagsAsync(request.ChannelName, request.Count, caller);
```

**B 类 — 接口 #6** `StartAutoPack`：

```csharp
var caller = HttpContext.GetCaller();
_logger.LogInformation("[Caller: {Caller}] StartAutoPack, TagNumber: {TagNumber}", caller, request.TagNumber);
request.Creator = caller;
var result = await _bagTagService.StartAutoPackAsync(request.TagNumber, request.Creator);
```

**A 类 — 接口 #9** `GetAvailableBagTags`：

```csharp
var caller = HttpContext.GetCaller();
_logger.LogInformation("[Caller: {Caller}] GetAvailableBagTags, Channel: {Channel}", caller, channel);
```

***

### 步骤 7：改造 `ShippingHandoverFormController.cs`（A 类 2 个 + B 类 1 个）

**前置改造**：注入 `ILogger<ShippingHandoverFormController>`：

* 添加 `using Microsoft.Extensions.Logging;` 和 `using CONTROLLER.Extensions;`

* 添加构造函数参数和私有字段 `_logger`

**B 类 — 接口 #8** `CreateShippingHandoverForm`：

```csharp
var caller = HttpContext.GetCaller();
_logger.LogInformation("[Caller: {Caller}] CreateShippingHandoverForm, HandoverNumber: {Number}, Channel: {Channel}", caller, HandoverNumber, Channel);
// 用 Caller 覆盖 Creator，Caller 为空时回退为 "system"
form.Creator = caller;
```

**A 类 — 接口 #7** `GenerateShippingHandoverNumber`：

```csharp
var caller = HttpContext.GetCaller();
_logger.LogInformation("[Caller: {Caller}] GenerateShippingHandoverNumber, Channel: {Channel}", caller, channel);
```

**A 类 — 接口 #4/#11** `PrintBillOfLading`：

```csharp
var caller = HttpContext.GetCaller();
_logger.LogInformation("[Caller: {Caller}] PrintBillOfLading, BolNumber: {BolNumber}", caller, bolNumber);
```

***

### 步骤 8：改造 `ShippingHandoverFormBagTagController.cs`（A 类：1 个接口）

**前置改造**：注入 `ILogger<ShippingHandoverFormBagTagController>`：

* 添加 `using Microsoft.Extensions.Logging;` 和 `using CONTROLLER.Extensions;`

* 添加构造函数参数和私有字段 `_logger`

**A 类 — 接口 #10** `AssociateBagTagsByNumber`：

```csharp
var caller = HttpContext.GetCaller();
_logger.LogInformation("[Caller: {Caller}] AssociateBagTagsByNumber, ShippingHandoverFormId: {Id}, TagNumbers: {Tags}", caller, shippingHandoverFormId, bagTagNumbers);
```

***

### 步骤 9：验证

```bash
dotnet build src/CONTROLLER/CONTROLLER.csproj
```

***

## 未来扩展示例

假设后续要加 `Device-Id` 和 `Request-Id` 两个 Header：

**只需修改 2 个文件：**

1. `RequestTrackingContext.cs` — 加 2 个属性：

```csharp
public string DeviceId { get; set; }
public string RequestId { get; set; }
```

1. `RequestTrackingMiddleware.cs` — 加 2 行：

```csharp
var deviceId = context.Request.Headers["Device-Id"].FirstOrDefault();
if (!string.IsNullOrWhiteSpace(deviceId)) trackingContext.DeviceId = deviceId;

var requestId = context.Request.Headers["Request-Id"].FirstOrDefault();
if (!string.IsNullOrWhiteSpace(requestId)) trackingContext.RequestId = requestId;
```

**Controller 中可直接使用**：

```csharp
var ctx = HttpContext.GetRequestTrackingContext();
_logger.LogInformation("Caller: {C}, Device: {D}, Request: {R}", ctx.Caller, ctx.DeviceId, ctx.RequestId);
```

**无需修改任何 Controller 的业务逻辑。**

***

## B 类接口改造对照表（写入创建人字段）

| # | 方法                           | 当前代码                      | 改造后代码                                              |
| - | ---------------------------- | ------------------------- | -------------------------------------------------- |
| 5 | `GenerateBagTags`            | `var creator = "system";` | `var caller = HttpContext.GetCaller();` 传入 service |
| 6 | `StartAutoPack`              | `request.Creator`         | `request.Creator = HttpContext.GetCaller();`       |
| 8 | `CreateShippingHandoverForm` | `form.Creator = Creator;` | `form.Creator = HttpContext.GetCaller();`          |

***

## A 类接口日志格式

```
[Caller: zhangsan] DownloadLabel, WaybillNumber: YW202605270001
[Caller: system] PrintBagTag, TagNumber: BT202605270001
[Caller: zhangsan] PrintBillOfLading, BolNumber: BOL202605270001
```

***

## 改造汇总

| 步骤     | 文件                                        | 操作                   | A 类   | B 类   |
| ------ | ----------------------------------------- | -------------------- | ----- | ----- |
| 1      | `Models/RequestTrackingContext.cs`        | 新建                   | —     | —     |
| 2      | `Middleware/RequestTrackingMiddleware.cs` | 新建                   | —     | —     |
| 3      | `Extensions/HttpContextExtensions.cs`     | 新建                   | —     | —     |
| 4      | `Program.cs`                              | 注册中间件                | —     | —     |
| 5      | `LabelController.cs`                      | 修改 2 个方法             | 2     | 0     |
| 6      | `BagTagController.cs`                     | 添加 Logger + 修改 4 个方法 | 2     | 2     |
| 7      | `ShippingHandoverFormController.cs`       | 添加 Logger + 修改 3 个方法 | 2     | 1     |
| 8      | `ShippingHandoverFormBagTagController.cs` | 添加 Logger + 修改 1 个方法 | 1     | 0     |
| 9      | 构建验证                                      | `dotnet build`       | —     | —     |
| **合计** | 8 个文件                                     | <br />               | **7** | **3** |

