# 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) - 测试规范和最佳实践