## MODIFIED Requirements

### Requirement: 统一请求方法

系统 SHALL 提供 `doRequest` 方法，统一处理加密、签名、HTTP 请求和响应解析。请求参数 SHALL 直接接收结构体，内部自动序列化并包装为 `{"params": <JSON>}` 格式。

#### Scenario: 请求参数自动序列化

- **WHEN** 调用 `doRequest(ctx, "/device/speed-limit", &SpeedLimitReq{CardNo: "xxx", SpeedLimit: 1024})`
- **THEN** 请求结构体自动通过 `sonic.Marshal` 序列化
- **AND** 序列化结果嵌入 `{"params": <序列化JSON>}` 中进行加密和签名

#### Scenario: 成功的 API 调用

- **WHEN** 调用 `doRequest(ctx, "/flow-card/status", req)`
- **THEN** 业务数据使用 AES-128-ECB 加密
- **AND** 请求使用 MD5 签名
- **AND** HTTP POST 发送到 `{baseURL}/flow-card/status`
- **AND** 响应中的 `data` 字段返回为 `json.RawMessage`

#### Scenario: 网络错误

- **WHEN** HTTP 请求失败（网络中断、DNS 解析失败）
- **THEN** 返回 `CodeGatewayError` 错误
- **AND** 错误信息包含原始网络错误

#### Scenario: 请求超时

- **WHEN** HTTP 请求超过配置的超时时间
- **THEN** 返回 `CodeGatewayTimeout` 错误
- **AND** Context 超时错误被正确识别

#### Scenario: 响应格式错误

- **WHEN** Gateway 响应无法解析为 JSON
- **THEN** 返回 `CodeGatewayInvalidResp` 错误

#### Scenario: Gateway 业务错误

- **WHEN** Gateway 响应中 `code != 200`
- **THEN** 返回 `CodeGatewayError` 错误
- **AND** 错误信息包含 Gateway 的 code 和 msg

### Requirement: 泛型响应解析方法

系统 SHALL 提供 `doRequestWithResponse[T any]` 泛型方法，自动完成请求发送和响应反序列化。

#### Scenario: 自动反序列化响应

- **WHEN** 调用 `doRequestWithResponse[CardStatusResp](ctx, "/flow-card/status", req)`
- **THEN** 返回 `*CardStatusResp` 类型的结构体
- **AND** 内部调用 `doRequest` 获取 `json.RawMessage` 后自动 unmarshal

#### Scenario: 反序列化失败

- **WHEN** Gateway 返回的 JSON 无法匹配目标结构体
- **THEN** 返回 `CodeGatewayInvalidResp` 错误
- **AND** 错误信息为 "解析 Gateway 响应失败"

### Requirement: 请求结构体直接序列化

系统 SHALL 消除手动 `map[string]interface{}` 构建，所有业务方法直接将请求结构体传递给 `doRequest` 或 `doRequestWithResponse`。

#### Scenario: 设备限速请求

- **WHEN** 调用 `SetSpeedLimit(ctx, &SpeedLimitReq{CardNo: "xxx", SpeedLimit: 1024})`
- **THEN** `SpeedLimitReq` 结构体直接序列化为 JSON
- **AND** 不再手动构建 `map[string]interface{}`

#### Scenario: 流量卡停机请求

- **WHEN** 调用 `StopCard(ctx, &CardOperationReq{CardNo: "xxx", Extend: "ext"})`
- **THEN** `CardOperationReq` 结构体直接序列化
- **AND** `Extend` 字段通过 `json:"extend,omitempty"` 标签在为空时自动省略

### Requirement: Gateway 客户端结构

系统 SHALL 提供 `gateway.Client` 结构体，封装所有 Gateway API 调用。

客户端字段：
- `baseURL string` - Gateway API 基础 URL
- `appID string` - 应用 ID
- `appSecret string` - 应用密钥
- `httpClient *http.Client` - HTTP 客户端（支持连接复用）
- `timeout time.Duration` - 请求超时时间
- `logger *zap.Logger` - 日志记录器
- `maxRetries int` - 最大重试次数

#### Scenario: 创建 Gateway 客户端

- **WHEN** 调用 `gateway.NewClient(baseURL, appID, appSecret, logger)`
- **THEN** 返回已初始化的 `Client` 实例
- **AND** HTTP 客户端配置正确（支持 Keep-Alive）
- **AND** 默认最大重试次数为 2

#### Scenario: 配置超时时间

- **WHEN** 调用 `client.WithTimeout(30 * time.Second)`
- **THEN** 客户端的 `timeout` 字段更新为 30 秒
- **AND** 返回客户端自身（支持链式调用）
