Files
junhong_cmp_fiber/docs/gateway-client-usage.md
2026-01-30 17:05:44 +08:00

13 KiB
Raw Permalink Blame History

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):

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):

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 阶段自动初始化,通过依赖注入获取:

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,
    }
}

查询流量卡状态

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. 流量卡管理

查询流量使用情况

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
}

流量卡停机

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
}

流量卡复机

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
}

查询实名认证状态

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
}

获取实名认证链接

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. 设备管理

查询设备信息

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
}

查询设备卡槽信息

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
}

设置设备限速

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

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
}

设备切换卡

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
}

设备重启

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
}

设备恢复出厂设置

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 签名验证失败

错误处理最佳实践

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
}

错误分类处理

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))
    }
}

高级用法

自定义超时时间

client := gateway.NewClient(baseURL, appID, appSecret).
    WithTimeout(60 * time.Second)

resp, err := client.QueryCardStatus(ctx, &gateway.CardStatusReq{
    CardNo: iccid,
})

使用扩展字段(广电国网)

部分 API 支持 extend 扩展字段用于特殊参数:

err := s.gatewayClient.StopCard(ctx, &gateway.CardOperationReq{
    CardNo: iccid,
    Extend: "special_param=value",
})

Context 传递

所有 API 方法都支持 context.Context,可以传递超时、取消信号等:

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

resp, err := s.gatewayClient.QueryCardStatus(ctx, &gateway.CardStatusReq{
    CardNo: iccid,
})

最佳实践

1. 空值检查

func (s *Service) SafeCall(ctx context.Context) error {
    if s.gatewayClient == nil {
        return errors.New(errors.CodeGatewayError, "Gateway 客户端未配置")
    }

}

2. 日志记录

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. 重试机制

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. 批量处理

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%

go test -v ./internal/gateway
go test -cover ./internal/gateway

集成测试

使用 -short 标志跳过集成测试:

go test -v ./internal/gateway -short

运行集成测试(需要真实 Gateway 环境):

source .env.local && go test -v ./internal/gateway

故障排查

常见问题

1. Gateway 客户端未配置

错误: Gateway 客户端未配置

原因: 环境变量未设置或 Bootstrap 初始化失败

解决方案:

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 服务响应慢

解决方案:

client := client.WithTimeout(60 * time.Second)

3. 业务错误

错误: 业务错误: code=500, msg=xxx

原因: Gateway 服务端业务逻辑错误

解决方案: 检查请求参数是否正确,查看日志中的 TraceID 联系 Gateway 服务提供方

4. 签名验证失败

错误: 签名验证失败

原因: AppSecret 配置错误

解决方案: 检查 JUNHONG_GATEWAY_APP_SECRET 环境变量是否正确

相关文档