chore: apply task changes

This commit is contained in:
2026-01-30 17:05:44 +08:00
parent 4856a88d41
commit 3f63fffbb1
22 changed files with 4696 additions and 8 deletions

View File

@@ -0,0 +1,749 @@
# Gateway API 参考文档
## 概述
本文档提供 Gateway 客户端所有 API 接口的完整参考,包括请求参数、响应格式和使用示例。
**API 分类**
- 流量卡管理7 个接口)
- 设备管理7 个接口)
**基础信息**
- 协议HTTPS
- 请求方法POST
- 内容类型application/json
- 编码方式UTF-8
- 加密方式AES-128-ECB + Base64
- 签名方式MD5
---
## 流量卡管理 API
### 1. 查询流量卡状态
查询流量卡的当前状态信息。
**方法**: `QueryCardStatus`
**请求参数**:
```go
type CardStatusReq struct {
CardNo string `json:"cardNo" validate:"required"`
}
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| CardNo | string | ✅ | 流量卡号ICCID |
**响应参数**:
```go
type CardStatusResp struct {
ICCID string `json:"iccid"`
CardStatus string `json:"cardStatus"`
Extend string `json:"extend,omitempty"`
}
```
| 参数 | 类型 | 说明 |
|------|------|------|
| ICCID | string | 流量卡 ICCID |
| CardStatus | string | 卡状态(准备、正常、停机) |
| Extend | string | 扩展字段(广电国网特殊参数) |
**使用示例**:
```go
resp, err := client.QueryCardStatus(ctx, &gateway.CardStatusReq{
CardNo: "898608070422D0010269",
})
if err != nil {
return err
}
fmt.Printf("卡状态: %s\n", resp.CardStatus)
```
---
### 2. 查询流量使用情况
查询流量卡的流量使用详情。
**方法**: `QueryFlow`
**请求参数**:
```go
type FlowQueryReq struct {
CardNo string `json:"cardNo" validate:"required"`
}
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| CardNo | string | ✅ | 流量卡号ICCID |
**响应参数**:
```go
type FlowUsageResp struct {
UsedFlow int64 `json:"usedFlow"`
Unit string `json:"unit"`
Extend string `json:"extend,omitempty"`
}
```
| 参数 | 类型 | 说明 |
|------|------|------|
| UsedFlow | int64 | 已用流量 |
| Unit | string | 流量单位MB |
| Extend | string | 扩展字段 |
**使用示例**:
```go
resp, err := client.QueryFlow(ctx, &gateway.FlowQueryReq{
CardNo: "898608070422D0010269",
})
if err != nil {
return err
}
fmt.Printf("已用流量: %d %s\n", resp.UsedFlow, resp.Unit)
```
---
### 3. 查询实名认证状态
查询流量卡的实名认证状态。
**方法**: `QueryRealnameStatus`
**请求参数**:
```go
type CardStatusReq struct {
CardNo string `json:"cardNo" validate:"required"`
}
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| CardNo | string | ✅ | 流量卡号ICCID |
**响应参数**:
```go
type RealnameStatusResp struct {
Status string `json:"status"`
Extend string `json:"extend,omitempty"`
}
```
| 参数 | 类型 | 说明 |
|------|------|------|
| Status | string | 实名认证状态 |
| Extend | string | 扩展字段 |
**使用示例**:
```go
resp, err := client.QueryRealnameStatus(ctx, &gateway.CardStatusReq{
CardNo: "898608070422D0010269",
})
if err != nil {
return err
}
fmt.Printf("实名状态: %s\n", resp.Status)
```
---
### 4. 流量卡停机
对流量卡执行停机操作。
**方法**: `StopCard`
**请求参数**:
```go
type CardOperationReq struct {
CardNo string `json:"cardNo" validate:"required"`
Extend string `json:"extend,omitempty"`
}
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| CardNo | string | ✅ | 流量卡号ICCID |
| Extend | string | ❌ | 扩展字段(广电国网特殊参数) |
**响应参数**: 无(成功返回 nil失败返回 error
**使用示例**:
```go
err := client.StopCard(ctx, &gateway.CardOperationReq{
CardNo: "898608070422D0010269",
})
if err != nil {
return err
}
fmt.Println("停机成功")
```
---
### 5. 流量卡复机
对流量卡执行复机操作。
**方法**: `StartCard`
**请求参数**:
```go
type CardOperationReq struct {
CardNo string `json:"cardNo" validate:"required"`
Extend string `json:"extend,omitempty"`
}
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| CardNo | string | ✅ | 流量卡号ICCID |
| Extend | string | ❌ | 扩展字段 |
**响应参数**: 无(成功返回 nil失败返回 error
**使用示例**:
```go
err := client.StartCard(ctx, &gateway.CardOperationReq{
CardNo: "898608070422D0010269",
})
if err != nil {
return err
}
fmt.Println("复机成功")
```
---
### 6. 获取实名认证跳转链接
获取流量卡实名认证的跳转链接。
**方法**: `GetRealnameLink`
**请求参数**:
```go
type CardStatusReq struct {
CardNo string `json:"cardNo" validate:"required"`
}
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| CardNo | string | ✅ | 流量卡号ICCID |
**响应参数**:
```go
type RealnameLinkResp struct {
Link string `json:"link"`
Extend string `json:"extend,omitempty"`
}
```
| 参数 | 类型 | 说明 |
|------|------|------|
| Link | string | 实名认证跳转链接HTTPS URL |
| Extend | string | 扩展字段 |
**使用示例**:
```go
resp, err := client.GetRealnameLink(ctx, &gateway.CardStatusReq{
CardNo: "898608070422D0010269",
})
if err != nil {
return err
}
fmt.Printf("实名链接: %s\n", resp.Link)
```
---
### 7. 批量查询(预留)
批量查询流量卡信息(暂未实现)。
**方法**: `BatchQuery`
**请求参数**:
```go
type BatchQueryReq struct {
CardNos []string `json:"cardNos" validate:"required,min=1,max=100"`
}
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| CardNos | []string | ✅ | 流量卡号列表最多100个 |
**响应参数**:
```go
type BatchQueryResp struct {
Results []CardStatusResp `json:"results"`
}
```
**状态**: ⚠️ 暂未实现,调用将返回错误
---
## 设备管理 API
### 1. 获取设备信息
通过卡号或设备 ID 查询设备的在线状态、信号强度、WiFi 信息等。
**方法**: `GetDeviceInfo`
**请求参数**:
```go
type DeviceInfoReq struct {
CardNo string `json:"cardNo,omitempty"`
DeviceID string `json:"deviceId,omitempty"`
}
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| CardNo | string | 二选一 | 流量卡号ICCID |
| DeviceID | string | 二选一 | 设备 ID/IMEI |
**响应参数**:
```go
type DeviceInfoResp struct {
IMEI string `json:"imei"`
OnlineStatus int `json:"onlineStatus"`
SignalLevel int `json:"signalLevel"`
WiFiSSID string `json:"wifiSsid,omitempty"`
WiFiEnabled int `json:"wifiEnabled"`
UploadSpeed int `json:"uploadSpeed"`
DownloadSpeed int `json:"downloadSpeed"`
Extend string `json:"extend,omitempty"`
}
```
| 参数 | 类型 | 说明 |
|------|------|------|
| IMEI | string | 设备 IMEI |
| OnlineStatus | int | 在线状态0:离线, 1:在线) |
| SignalLevel | int | 信号强度0-31 |
| WiFiSSID | string | WiFi 名称 |
| WiFiEnabled | int | WiFi 启用状态0:禁用, 1:启用) |
| UploadSpeed | int | 上行速率KB/s |
| DownloadSpeed | int | 下行速率KB/s |
| Extend | string | 扩展字段 |
**使用示例**:
```go
resp, err := client.GetDeviceInfo(ctx, &gateway.DeviceInfoReq{
DeviceID: "123456789012345",
})
if err != nil {
return err
}
fmt.Printf("设备状态: %s, 信号: %d\n",
map[int]string{0:"离线", 1:"在线"}[resp.OnlineStatus],
resp.SignalLevel,
)
```
---
### 2. 获取设备卡槽信息
查询设备的所有卡槽及其中的卡信息。
**方法**: `GetSlotInfo`
**请求参数**:
```go
type DeviceInfoReq struct {
CardNo string `json:"cardNo,omitempty"`
DeviceID string `json:"deviceId,omitempty"`
}
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| CardNo | string | 二选一 | 流量卡号ICCID |
| DeviceID | string | 二选一 | 设备 ID/IMEI |
**响应参数**:
```go
type SlotInfoResp struct {
IMEI string `json:"imei"`
Slots []SlotInfo `json:"slots"`
Extend string `json:"extend,omitempty"`
}
type SlotInfo struct {
SlotNo int `json:"slotNo"`
ICCID string `json:"iccid"`
CardStatus string `json:"cardStatus"`
IsActive int `json:"isActive"`
Extend string `json:"extend,omitempty"`
}
```
| 参数 | 类型 | 说明 |
|------|------|------|
| IMEI | string | 设备 IMEI |
| Slots | []SlotInfo | 卡槽信息列表 |
| SlotNo | int | 卡槽编号 |
| ICCID | string | 卡槽中的 ICCID |
| CardStatus | string | 卡状态(准备、正常、停机) |
| IsActive | int | 是否为当前使用的卡槽0:否, 1:是) |
**使用示例**:
```go
resp, err := client.GetSlotInfo(ctx, &gateway.DeviceInfoReq{
DeviceID: "123456789012345",
})
if err != nil {
return err
}
for _, slot := range resp.Slots {
fmt.Printf("卡槽%d: %s (%s)%s\n",
slot.SlotNo,
slot.ICCID,
slot.CardStatus,
map[int]string{0:"", 1:" [当前使用]"}[slot.IsActive],
)
}
```
---
### 3. 设置设备限速
设置设备的上行和下行速率限制。
**方法**: `SetSpeedLimit`
**请求参数**:
```go
type SpeedLimitReq struct {
DeviceID string `json:"deviceId" validate:"required"`
UploadSpeed int `json:"uploadSpeed" validate:"required,min=1"`
DownloadSpeed int `json:"downloadSpeed" validate:"required,min=1"`
Extend string `json:"extend,omitempty"`
}
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| DeviceID | string | ✅ | 设备 ID/IMEI |
| UploadSpeed | int | ✅ | 上行速率KB/s最小1 |
| DownloadSpeed | int | ✅ | 下行速率KB/s最小1 |
| Extend | string | ❌ | 扩展字段 |
**响应参数**: 无(成功返回 nil失败返回 error
**使用示例**:
```go
err := client.SetSpeedLimit(ctx, &gateway.SpeedLimitReq{
DeviceID: "123456789012345",
UploadSpeed: 100,
DownloadSpeed: 500,
})
if err != nil {
return err
}
fmt.Println("限速设置成功")
```
---
### 4. 设置设备 WiFi
设置设备的 WiFi 名称、密码和启用状态。
**方法**: `SetWiFi`
**请求参数**:
```go
type WiFiReq struct {
DeviceID string `json:"deviceId" validate:"required"`
SSID string `json:"ssid" validate:"required,min=1,max=32"`
Password string `json:"password" validate:"required,min=8,max=63"`
Enabled int `json:"enabled" validate:"required,oneof=0 1"`
Extend string `json:"extend,omitempty"`
}
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| DeviceID | string | ✅ | 设备 ID/IMEI |
| SSID | string | ✅ | WiFi 名称1-32字符 |
| Password | string | ✅ | WiFi 密码8-63字符 |
| Enabled | int | ✅ | 启用状态0:禁用, 1:启用) |
| Extend | string | ❌ | 扩展字段 |
**响应参数**: 无(成功返回 nil失败返回 error
**使用示例**:
```go
err := client.SetWiFi(ctx, &gateway.WiFiReq{
DeviceID: "123456789012345",
SSID: "MyWiFi",
Password: "password123",
Enabled: 1,
})
if err != nil {
return err
}
fmt.Println("WiFi设置成功")
```
---
### 5. 设备切换卡
切换设备当前使用的卡到指定的目标卡。
**方法**: `SwitchCard`
**请求参数**:
```go
type SwitchCardReq struct {
DeviceID string `json:"deviceId" validate:"required"`
TargetICCID string `json:"targetIccid" validate:"required"`
Extend string `json:"extend,omitempty"`
}
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| DeviceID | string | ✅ | 设备 ID/IMEI |
| TargetICCID | string | ✅ | 目标卡 ICCID |
| Extend | string | ❌ | 扩展字段 |
**响应参数**: 无(成功返回 nil失败返回 error
**使用示例**:
```go
err := client.SwitchCard(ctx, &gateway.SwitchCardReq{
DeviceID: "123456789012345",
TargetICCID: "898608070422D0010270",
})
if err != nil {
return err
}
fmt.Println("切换卡成功")
```
---
### 6. 设备恢复出厂设置
将设备恢复到出厂设置状态。
**方法**: `ResetDevice`
**请求参数**:
```go
type DeviceOperationReq struct {
DeviceID string `json:"deviceId" validate:"required"`
Extend string `json:"extend,omitempty"`
}
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| DeviceID | string | ✅ | 设备 ID/IMEI |
| Extend | string | ❌ | 扩展字段 |
**响应参数**: 无(成功返回 nil失败返回 error
**使用示例**:
```go
err := client.ResetDevice(ctx, &gateway.DeviceOperationReq{
DeviceID: "123456789012345",
})
if err != nil {
return err
}
fmt.Println("恢复出厂设置成功")
```
---
### 7. 设备重启
远程重启设备。
**方法**: `RebootDevice`
**请求参数**:
```go
type DeviceOperationReq struct {
DeviceID string `json:"deviceId" validate:"required"`
Extend string `json:"extend,omitempty"`
}
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| DeviceID | string | ✅ | 设备 ID/IMEI |
| Extend | string | ❌ | 扩展字段 |
**响应参数**: 无(成功返回 nil失败返回 error
**使用示例**:
```go
err := client.RebootDevice(ctx, &gateway.DeviceOperationReq{
DeviceID: "123456789012345",
})
if err != nil {
return err
}
fmt.Println("重启设备成功")
```
---
## 通用响应结构
所有 API 的底层响应都遵循统一的 Gateway 响应格式:
```go
type GatewayResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data json.RawMessage `json:"data"`
TraceID string `json:"trace_id"`
}
```
| 参数 | 类型 | 说明 |
|------|------|------|
| Code | int | 业务状态码200 = 成功) |
| Msg | string | 业务提示信息 |
| Data | json.RawMessage | 业务数据(原始 JSON |
| TraceID | string | 链路追踪 ID |
**成功响应示例**:
```json
{
"code": 200,
"msg": "成功",
"data": {
"iccid": "898608070422D0010269",
"cardStatus": "正常"
},
"trace_id": "abc123xyz"
}
```
**失败响应示例**:
```json
{
"code": 404,
"msg": "卡号不存在",
"data": null,
"trace_id": "abc123xyz"
}
```
---
## 错误码说明
### Gateway 业务错误码
| 错误码 | 说明 | 解决方案 |
|-------|------|---------|
| 200 | 成功 | - |
| 400 | 请求参数错误 | 检查请求参数格式和内容 |
| 401 | 认证失败 | 检查 AppID 和 AppSecret |
| 404 | 资源不存在 | 检查卡号或设备 ID 是否正确 |
| 500 | 服务器内部错误 | 联系 Gateway 服务提供方 |
### 客户端错误码
客户端封装的统一错误码(`pkg/errors/codes.go`
| 错误码 | 常量 | 说明 |
|-------|------|------|
| 1110 | CodeGatewayError | Gateway 连接失败 |
| 1111 | CodeGatewayTimeout | Gateway 请求超时 |
| 1112 | CodeGatewayBusinessError | Gateway 业务错误 |
| 1113 | CodeGatewayInvalidResp | Gateway 响应解析失败 |
| 1114 | CodeGatewaySignError | Gateway 签名验证失败 |
---
## 请求流程
### 完整请求流程
```
1. 构造业务请求参数
2. 序列化为 JSON
3. AES-128-ECB 加密Base64 编码)
4. 生成 MD5 签名(参数排序 + appSecret
5. 构造最终请求体
{
"appId": "xxx",
"data": "encrypted_base64_string",
"sign": "md5_signature",
"timestamp": 1706620800000
}
6. POST 请求到 Gateway
7. 解析响应 JSON
8. 检查业务状态码
9. 解密并解析业务数据
10. 返回结果或错误
```
### 签名算法
```
1. 将请求参数按 key 字母序排序
2. 拼接为 key1=value1&key2=value2 格式
3. 末尾追加 &appSecret=xxx
4. 计算 MD5 哈希
5. 转为大写字符串
```
**示例**:
```
参数: {cardNo: "123", appId: "abc"}
排序: appId=abc&cardNo=123
追加: appId=abc&cardNo=123&appSecret=secret
MD5: D41D8CD98F00B204E9800998ECF8427E
```
---
## 相关文档
- [Gateway 客户端使用指南](./gateway-client-usage.md) - 详细的使用指南和最佳实践
- [错误处理指南](./003-error-handling/使用指南.md) - 统一错误处理规范

View File

@@ -0,0 +1,551 @@
# Gateway 客户端使用指南
## 概述
Gateway 客户端是对第三方 Gateway API 的 Go 封装,提供流量卡和设备管理的统一接口。客户端内置了 AES-128-ECB 加密、MD5 签名验证、HTTP 连接池管理等功能。
**核心特性**
- ✅ 自动加密/签名处理
- ✅ 统一错误处理
- ✅ HTTP Keep-Alive 连接池
- ✅ 可配置超时时间
- ✅ 完整的测试覆盖88.8%
## 配置说明
### 环境变量配置
Gateway 客户端通过环境变量配置,支持以下参数:
| 环境变量 | 说明 | 默认值 | 必填 |
|---------|------|--------|------|
| `JUNHONG_GATEWAY_BASE_URL` | Gateway API 基础 URL | - | ✅ |
| `JUNHONG_GATEWAY_APP_ID` | 应用 ID | - | ✅ |
| `JUNHONG_GATEWAY_APP_SECRET` | 应用密钥 | - | ✅ |
| `JUNHONG_GATEWAY_TIMEOUT` | 请求超时时间(秒) | 30 | ❌ |
### 配置示例
**开发环境** (`.env.local`):
```bash
export JUNHONG_GATEWAY_BASE_URL=https://lplan.whjhft.com/openapi
export JUNHONG_GATEWAY_APP_ID=60bgt1X8i7AvXqkd
export JUNHONG_GATEWAY_APP_SECRET=BZeQttaZQt0i73moF
export JUNHONG_GATEWAY_TIMEOUT=30
```
**生产环境** (`docker-compose.yml`):
```yaml
services:
api:
environment:
- JUNHONG_GATEWAY_BASE_URL=https://gateway.prod.example.com
- JUNHONG_GATEWAY_APP_ID=${GATEWAY_APP_ID}
- JUNHONG_GATEWAY_APP_SECRET=${GATEWAY_APP_SECRET}
- JUNHONG_GATEWAY_TIMEOUT=60
```
## 使用示例
### 1. 基础用法
#### 获取客户端实例
Gateway 客户端在 Bootstrap 阶段自动初始化,通过依赖注入获取:
```go
package iot_card
import (
"context"
"github.com/break/junhong_cmp_fiber/internal/gateway"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"go.uber.org/zap"
)
type Service struct {
gatewayClient *gateway.Client
logger *zap.Logger
}
func New(gatewayClient *gateway.Client, logger *zap.Logger) *Service {
return &Service{
gatewayClient: gatewayClient,
logger: logger,
}
}
```
#### 查询流量卡状态
```go
func (s *Service) SyncCardStatus(ctx context.Context, iccid string) error {
if s.gatewayClient == nil {
return errors.New(errors.CodeGatewayError, "Gateway 客户端未配置")
}
resp, err := s.gatewayClient.QueryCardStatus(ctx, &gateway.CardStatusReq{
CardNo: iccid,
})
if err != nil {
s.logger.Error("查询卡状态失败", zap.String("iccid", iccid), zap.Error(err))
return errors.Wrap(errors.CodeGatewayError, err, "查询卡状态失败")
}
s.logger.Info("查询卡状态成功",
zap.String("iccid", resp.ICCID),
zap.String("status", resp.CardStatus),
)
return nil
}
```
### 2. 流量卡管理
#### 查询流量使用情况
```go
func (s *Service) GetFlowUsage(ctx context.Context, iccid string) (*gateway.FlowUsageResp, error) {
resp, err := s.gatewayClient.QueryFlow(ctx, &gateway.FlowQueryReq{
CardNo: iccid,
})
if err != nil {
return nil, errors.Wrap(errors.CodeGatewayError, err, "查询流量失败")
}
return resp, nil
}
```
#### 流量卡停机
```go
func (s *Service) StopCard(ctx context.Context, iccid string) error {
err := s.gatewayClient.StopCard(ctx, &gateway.CardOperationReq{
CardNo: iccid,
})
if err != nil {
return errors.Wrap(errors.CodeGatewayError, err, "停机失败")
}
s.logger.Info("停机成功", zap.String("iccid", iccid))
return nil
}
```
#### 流量卡复机
```go
func (s *Service) StartCard(ctx context.Context, iccid string) error {
err := s.gatewayClient.StartCard(ctx, &gateway.CardOperationReq{
CardNo: iccid,
})
if err != nil {
return errors.Wrap(errors.CodeGatewayError, err, "复机失败")
}
s.logger.Info("复机成功", zap.String("iccid", iccid))
return nil
}
```
#### 查询实名认证状态
```go
func (s *Service) CheckRealnameStatus(ctx context.Context, iccid string) (string, error) {
resp, err := s.gatewayClient.QueryRealnameStatus(ctx, &gateway.CardStatusReq{
CardNo: iccid,
})
if err != nil {
return "", errors.Wrap(errors.CodeGatewayError, err, "查询实名状态失败")
}
return resp.Status, nil
}
```
#### 获取实名认证链接
```go
func (s *Service) GetRealnameLink(ctx context.Context, iccid string) (string, error) {
resp, err := s.gatewayClient.GetRealnameLink(ctx, &gateway.CardStatusReq{
CardNo: iccid,
})
if err != nil {
return "", errors.Wrap(errors.CodeGatewayError, err, "获取实名链接失败")
}
return resp.Link, nil
}
```
### 3. 设备管理
#### 查询设备信息
```go
func (s *Service) GetDeviceInfo(ctx context.Context, imei string) (*gateway.DeviceInfoResp, error) {
resp, err := s.gatewayClient.GetDeviceInfo(ctx, &gateway.DeviceInfoReq{
DeviceID: imei,
})
if err != nil {
return nil, errors.Wrap(errors.CodeGatewayError, err, "查询设备信息失败")
}
return resp, nil
}
```
#### 查询设备卡槽信息
```go
func (s *Service) GetDeviceSlots(ctx context.Context, imei string) ([]gateway.SlotInfo, error) {
resp, err := s.gatewayClient.GetSlotInfo(ctx, &gateway.DeviceInfoReq{
DeviceID: imei,
})
if err != nil {
return nil, errors.Wrap(errors.CodeGatewayError, err, "查询卡槽信息失败")
}
return resp.Slots, nil
}
```
#### 设置设备限速
```go
func (s *Service) SetDeviceSpeed(ctx context.Context, imei string, uploadKBps, downloadKBps int) error {
err := s.gatewayClient.SetSpeedLimit(ctx, &gateway.SpeedLimitReq{
DeviceID: imei,
UploadSpeed: uploadKBps,
DownloadSpeed: downloadKBps,
})
if err != nil {
return errors.Wrap(errors.CodeGatewayError, err, "设置限速失败")
}
s.logger.Info("设置限速成功",
zap.String("imei", imei),
zap.Int("upload", uploadKBps),
zap.Int("download", downloadKBps),
)
return nil
}
```
#### 设置设备 WiFi
```go
func (s *Service) ConfigureWiFi(ctx context.Context, imei, ssid, password string, enabled bool) error {
enabledInt := 0
if enabled {
enabledInt = 1
}
err := s.gatewayClient.SetWiFi(ctx, &gateway.WiFiReq{
DeviceID: imei,
SSID: ssid,
Password: password,
Enabled: enabledInt,
})
if err != nil {
return errors.Wrap(errors.CodeGatewayError, err, "设置WiFi失败")
}
return nil
}
```
#### 设备切换卡
```go
func (s *Service) SwitchDeviceCard(ctx context.Context, imei, targetICCID string) error {
err := s.gatewayClient.SwitchCard(ctx, &gateway.SwitchCardReq{
DeviceID: imei,
TargetICCID: targetICCID,
})
if err != nil {
return errors.Wrap(errors.CodeGatewayError, err, "切换卡失败")
}
s.logger.Info("切换卡成功",
zap.String("imei", imei),
zap.String("targetICCID", targetICCID),
)
return nil
}
```
#### 设备重启
```go
func (s *Service) RebootDevice(ctx context.Context, imei string) error {
err := s.gatewayClient.RebootDevice(ctx, &gateway.DeviceOperationReq{
DeviceID: imei,
})
if err != nil {
return errors.Wrap(errors.CodeGatewayError, err, "重启设备失败")
}
s.logger.Info("重启设备成功", zap.String("imei", imei))
return nil
}
```
#### 设备恢复出厂设置
```go
func (s *Service) ResetDevice(ctx context.Context, imei string) error {
err := s.gatewayClient.ResetDevice(ctx, &gateway.DeviceOperationReq{
DeviceID: imei,
})
if err != nil {
return errors.Wrap(errors.CodeGatewayError, err, "恢复出厂设置失败")
}
s.logger.Info("恢复出厂设置成功", zap.String("imei", imei))
return nil
}
```
## 错误处理
### 统一错误码
Gateway 客户端使用统一的错误码系统(`pkg/errors/codes.go`
| 错误码 | 说明 |
|-------|------|
| `1110` | Gateway 连接失败 |
| `1111` | Gateway 请求超时 |
| `1112` | Gateway 业务错误 |
| `1113` | Gateway 响应解析失败 |
| `1114` | Gateway 签名验证失败 |
### 错误处理最佳实践
```go
func (s *Service) ProcessCard(ctx context.Context, iccid string) error {
resp, err := s.gatewayClient.QueryCardStatus(ctx, &gateway.CardStatusReq{
CardNo: iccid,
})
if err != nil {
s.logger.Error("查询卡状态失败",
zap.String("iccid", iccid),
zap.Error(err),
)
return errors.Wrap(errors.CodeGatewayError, err, "查询卡状态失败")
}
return nil
}
```
### 错误分类处理
```go
func (s *Service) HandleGatewayError(err error) {
switch {
case strings.Contains(err.Error(), "超时"):
s.logger.Warn("Gateway 请求超时,请稍后重试")
case strings.Contains(err.Error(), "业务错误"):
s.logger.Error("Gateway 业务处理失败", zap.Error(err))
case strings.Contains(err.Error(), "连接失败"):
s.logger.Error("Gateway 连接失败,请检查网络", zap.Error(err))
default:
s.logger.Error("Gateway 未知错误", zap.Error(err))
}
}
```
## 高级用法
### 自定义超时时间
```go
client := gateway.NewClient(baseURL, appID, appSecret).
WithTimeout(60 * time.Second)
resp, err := client.QueryCardStatus(ctx, &gateway.CardStatusReq{
CardNo: iccid,
})
```
### 使用扩展字段(广电国网)
部分 API 支持 `extend` 扩展字段用于特殊参数:
```go
err := s.gatewayClient.StopCard(ctx, &gateway.CardOperationReq{
CardNo: iccid,
Extend: "special_param=value",
})
```
### Context 传递
所有 API 方法都支持 `context.Context`,可以传递超时、取消信号等:
```go
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
resp, err := s.gatewayClient.QueryCardStatus(ctx, &gateway.CardStatusReq{
CardNo: iccid,
})
```
## 最佳实践
### 1. 空值检查
```go
func (s *Service) SafeCall(ctx context.Context) error {
if s.gatewayClient == nil {
return errors.New(errors.CodeGatewayError, "Gateway 客户端未配置")
}
}
```
### 2. 日志记录
```go
func (s *Service) CallWithLogging(ctx context.Context, iccid string) error {
s.logger.Info("开始查询卡状态", zap.String("iccid", iccid))
resp, err := s.gatewayClient.QueryCardStatus(ctx, &gateway.CardStatusReq{
CardNo: iccid,
})
if err != nil {
s.logger.Error("查询失败", zap.String("iccid", iccid), zap.Error(err))
return err
}
s.logger.Info("查询成功",
zap.String("iccid", resp.ICCID),
zap.String("status", resp.CardStatus),
)
return nil
}
```
### 3. 重试机制
```go
func (s *Service) QueryWithRetry(ctx context.Context, iccid string) (*gateway.CardStatusResp, error) {
maxRetries := 3
var lastErr error
for i := 0; i < maxRetries; i++ {
resp, err := s.gatewayClient.QueryCardStatus(ctx, &gateway.CardStatusReq{
CardNo: iccid,
})
if err == nil {
return resp, nil
}
lastErr = err
s.logger.Warn("重试查询",
zap.Int("attempt", i+1),
zap.String("iccid", iccid),
zap.Error(err),
)
time.Sleep(time.Second * time.Duration(i+1))
}
return nil, errors.Wrap(errors.CodeGatewayError, lastErr, "重试失败")
}
```
### 4. 批量处理
```go
func (s *Service) BatchSyncStatus(ctx context.Context, iccids []string) error {
for _, iccid := range iccids {
if err := s.SyncCardStatus(ctx, iccid); err != nil {
s.logger.Error("同步失败", zap.String("iccid", iccid), zap.Error(err))
continue
}
}
return nil
}
```
## 测试
### 单元测试
Gateway 客户端提供完整的单元测试覆盖88.8%
```bash
go test -v ./internal/gateway
go test -cover ./internal/gateway
```
### 集成测试
使用 `-short` 标志跳过集成测试:
```bash
go test -v ./internal/gateway -short
```
运行集成测试(需要真实 Gateway 环境):
```bash
source .env.local && go test -v ./internal/gateway
```
## 故障排查
### 常见问题
#### 1. Gateway 客户端未配置
**错误**: `Gateway 客户端未配置`
**原因**: 环境变量未设置或 Bootstrap 初始化失败
**解决方案**:
```bash
export JUNHONG_GATEWAY_BASE_URL=https://lplan.whjhft.com/openapi
export JUNHONG_GATEWAY_APP_ID=your_app_id
export JUNHONG_GATEWAY_APP_SECRET=your_app_secret
```
#### 2. 请求超时
**错误**: `Gateway 请求超时`
**原因**: 网络延迟或 Gateway 服务响应慢
**解决方案**:
```go
client := client.WithTimeout(60 * time.Second)
```
#### 3. 业务错误
**错误**: `业务错误: code=500, msg=xxx`
**原因**: Gateway 服务端业务逻辑错误
**解决方案**: 检查请求参数是否正确,查看日志中的 `TraceID` 联系 Gateway 服务提供方
#### 4. 签名验证失败
**错误**: `签名验证失败`
**原因**: `AppSecret` 配置错误
**解决方案**: 检查 `JUNHONG_GATEWAY_APP_SECRET` 环境变量是否正确
## 相关文档
- [Gateway API 参考](./gateway-api-reference.md) - 完整的 API 接口文档
- [错误处理指南](./003-error-handling/使用指南.md) - 统一错误处理规范
- [测试连接管理规范](./testing/test-connection-guide.md) - 测试规范和最佳实践