feat: 补全网关停复机调用的关键日志(调用前/成功后均输出 INFO)
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled

This commit is contained in:
2026-03-31 19:15:45 +08:00
parent 9d50424edc
commit 69ee754c19
4 changed files with 189 additions and 24 deletions

View File

@@ -835,6 +835,9 @@ func (s *Service) StopDevice(ctx context.Context, deviceID uint) (*dto.DeviceSus
}
if s.gatewayClient != nil {
log.Info("调用网关停机(设备)",
zap.Uint("device_id", deviceID),
zap.String("iccid", card.ICCID))
if gwErr := s.gatewayClient.StopCard(ctx, &gateway.CardOperationReq{CardNo: card.ICCID}); gwErr != nil {
log.Error("设备停机-调网关停机失败",
zap.Uint("device_id", deviceID),
@@ -846,6 +849,9 @@ func (s *Service) StopDevice(ctx context.Context, deviceID uint) (*dto.DeviceSus
})
continue
}
log.Info("网关停机成功(设备)",
zap.Uint("device_id", deviceID),
zap.String("iccid", card.ICCID))
}
now := time.Now()
@@ -936,6 +942,9 @@ func (s *Service) StartDevice(ctx context.Context, deviceID uint) error {
}
if s.gatewayClient != nil {
log.Info("调用网关复机(设备)",
zap.Uint("device_id", deviceID),
zap.String("iccid", card.ICCID))
if gwErr := s.gatewayClient.StartCard(ctx, &gateway.CardOperationReq{CardNo: card.ICCID}); gwErr != nil {
log.Error("设备复机-调网关复机失败",
zap.Uint("device_id", deviceID),
@@ -944,6 +953,9 @@ func (s *Service) StartDevice(ctx context.Context, deviceID uint) error {
lastErr = gwErr
continue
}
log.Info("网关复机成功(设备)",
zap.Uint("device_id", deviceID),
zap.String("iccid", card.ICCID))
}
now := time.Now()

View File

@@ -5,6 +5,7 @@ import (
"github.com/break/junhong_cmp_fiber/internal/gateway"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"go.uber.org/zap"
)
// validateCardAccess 通过 ICCID 验证卡存在且当前用户有权限访问
@@ -62,9 +63,15 @@ func (s *Service) GatewayStopCard(ctx context.Context, iccid string) error {
if err := s.validateCardAccess(ctx, iccid); err != nil {
return err
}
return s.gatewayClient.StopCard(ctx, &gateway.CardOperationReq{
s.logger.Info("调用网关停机(手动)", zap.String("iccid", iccid))
if err := s.gatewayClient.StopCard(ctx, &gateway.CardOperationReq{
CardNo: iccid,
})
}); err != nil {
s.logger.Error("网关停机失败(手动)", zap.String("iccid", iccid), zap.Error(err))
return err
}
s.logger.Info("网关停机成功(手动)", zap.String("iccid", iccid))
return nil
}
// GatewayStartCard 恢复卡服务(通过 Gateway API
@@ -72,7 +79,13 @@ func (s *Service) GatewayStartCard(ctx context.Context, iccid string) error {
if err := s.validateCardAccess(ctx, iccid); err != nil {
return err
}
return s.gatewayClient.StartCard(ctx, &gateway.CardOperationReq{
s.logger.Info("调用网关复机(手动)", zap.String("iccid", iccid))
if err := s.gatewayClient.StartCard(ctx, &gateway.CardOperationReq{
CardNo: iccid,
})
}); err != nil {
s.logger.Error("网关复机失败(手动)", zap.String("iccid", iccid), zap.Error(err))
return err
}
s.logger.Info("网关复机成功(手动)", zap.String("iccid", iccid))
return nil
}

View File

@@ -237,6 +237,10 @@ func (s *StopResumeService) stopCardWithRetry(ctx context.Context, card *model.I
return errors.New(errors.CodeInternalError, "Gateway 未配置,停复机操作不可用")
}
s.logger.Info("调用网关停机",
zap.Uint("card_id", card.ID),
zap.String("iccid", card.ICCID))
var lastErr error
for i := 0; i < s.maxRetries; i++ {
if i > 0 {
@@ -250,12 +254,16 @@ func (s *StopResumeService) stopCardWithRetry(ctx context.Context, card *model.I
CardNo: card.ICCID,
})
if err == nil {
s.logger.Info("网关停机成功",
zap.Uint("card_id", card.ID),
zap.String("iccid", card.ICCID))
return nil
}
lastErr = err
s.logger.Warn("调用停机接口失败,准备重试",
zap.Int("attempt", i+1),
zap.String("iccid", card.ICCID),
zap.Error(err))
}
@@ -268,6 +276,10 @@ func (s *StopResumeService) resumeCardWithRetry(ctx context.Context, card *model
return errors.New(errors.CodeInternalError, "Gateway 未配置,停复机操作不可用")
}
s.logger.Info("调用网关复机",
zap.Uint("card_id", card.ID),
zap.String("iccid", card.ICCID))
var lastErr error
for i := 0; i < s.maxRetries; i++ {
if i > 0 {
@@ -281,12 +293,16 @@ func (s *StopResumeService) resumeCardWithRetry(ctx context.Context, card *model
CardNo: card.ICCID,
})
if err == nil {
s.logger.Info("网关复机成功",
zap.Uint("card_id", card.ID),
zap.String("iccid", card.ICCID))
return nil
}
lastErr = err
s.logger.Warn("调用复机接口失败,准备重试",
zap.Int("attempt", i+1),
zap.String("iccid", card.ICCID),
zap.Error(err))
}

View File

@@ -2,6 +2,7 @@ package task
import (
"context"
"sort"
"strconv"
"strings"
"time"
@@ -34,6 +35,7 @@ type PollingHandler struct {
gatewayClient *gateway.Client
iotCardStore *postgres.IotCardStore
concurrencyStore *postgres.PollingConcurrencyConfigStore
pollingConfigStore *postgres.PollingConfigStore // 用于 requeueCard 读取配置间隔
deviceSimBindingStore *postgres.DeviceSimBindingStore
packageUsageStore *postgres.PackageUsageStore
usageService *packagepkg.UsageService
@@ -56,6 +58,7 @@ func NewPollingHandler(
gatewayClient: gatewayClient,
iotCardStore: postgres.NewIotCardStore(db, redis),
concurrencyStore: postgres.NewPollingConcurrencyConfigStore(db),
pollingConfigStore: postgres.NewPollingConfigStore(db),
deviceSimBindingStore: postgres.NewDeviceSimBindingStore(db, redis),
packageUsageStore: postgres.NewPackageUsageStore(db, redis),
usageService: usageService,
@@ -350,6 +353,9 @@ func (h *PollingHandler) stopCardByUsageExhausted(ctx context.Context, card *mod
// 调用 Gateway 停机
if h.gatewayClient != nil {
h.logger.Info("调用网关停机",
zap.Uint("card_id", card.ID),
zap.String("iccid", card.ICCID))
if err := h.gatewayClient.StopCard(ctx, &gateway.CardOperationReq{
CardNo: card.ICCID,
}); err != nil {
@@ -359,12 +365,15 @@ func (h *PollingHandler) stopCardByUsageExhausted(ctx context.Context, card *mod
zap.Error(err))
return
}
h.logger.Info("网关停机成功",
zap.Uint("card_id", card.ID),
zap.String("iccid", card.ICCID))
}
// 更新数据库:卡的网络状态
now := time.Now()
updates := map[string]any{
"network_status": 0, // 停机
"network_status": 0,
"stopped_at": now,
"stop_reason": constants.StopReasonTrafficExhausted,
"updated_at": now,
@@ -670,6 +679,9 @@ func (h *PollingHandler) stopCards(ctx context.Context, cards []*model.IotCard,
continue
}
h.logger.Info("调用网关停机",
zap.Uint("card_id", card.ID),
zap.String("iccid", card.ICCID))
err := h.gatewayClient.StopCard(ctx, &gateway.CardOperationReq{
CardNo: card.ICCID,
})
@@ -678,7 +690,6 @@ func (h *PollingHandler) stopCards(ctx context.Context, cards []*model.IotCard,
zap.Uint("card_id", card.ID),
zap.String("iccid", card.ICCID),
zap.Error(err))
// 继续处理其他卡,不中断
continue
}
@@ -764,27 +775,24 @@ func (h *PollingHandler) releaseConcurrency(ctx context.Context, taskType string
}
// requeueCard 重新将卡加入队列
// 从 PollingConfig 读取实际配置间隔;无匹配配置或间隔为 NULL 时不入队(停止轮询该卡)
func (h *PollingHandler) requeueCard(ctx context.Context, cardID uint, taskType string) error {
// 获取配置中的检查间隔
var intervalSeconds int
switch taskType {
case constants.TaskTypePollingRealname:
intervalSeconds = 300 // 默认 5 分钟
case constants.TaskTypePollingCarddata:
intervalSeconds = 600 // 默认 10 分钟
case constants.TaskTypePollingPackage:
intervalSeconds = 600 // 默认 10 分钟
case constants.TaskTypePollingProtect:
intervalSeconds = 300 // 默认 5 分钟
default:
intervalSeconds, err := h.getMatchedPollingInterval(ctx, cardID, taskType)
if err != nil {
h.logger.Warn("获取轮询配置间隔失败,跳过入队",
zap.Uint("card_id", cardID),
zap.String("task_type", taskType),
zap.Error(err))
return nil
}
if intervalSeconds <= 0 {
// 无匹配配置或该任务类型间隔为 NULL不再轮询该卡
h.logger.Debug("无匹配轮询配置,跳过入队",
zap.Uint("card_id", cardID),
zap.String("task_type", taskType))
return nil
}
// 计算下次检查时间
nextCheck := time.Now().Add(time.Duration(intervalSeconds) * time.Second)
// 确定队列 key
var queueKey string
switch taskType {
case constants.TaskTypePollingRealname:
@@ -795,15 +803,117 @@ func (h *PollingHandler) requeueCard(ctx context.Context, cardID uint, taskType
queueKey = constants.RedisPollingQueuePackageKey()
case constants.TaskTypePollingProtect:
queueKey = constants.RedisPollingQueueProtectKey()
default:
return nil
}
// 添加到队列
nextCheck := time.Now().Add(time.Duration(intervalSeconds) * time.Second)
return h.redis.ZAdd(ctx, queueKey, redis.Z{
Score: float64(nextCheck.Unix()),
Member: strconv.FormatUint(uint64(cardID), 10),
}).Err()
}
// getMatchedPollingInterval 读取该卡匹配的 PollingConfig返回指定任务类型的间隔秒数
// 优先读取 Redis 缓存miss 时回退到 DB无匹配或间隔为 NULL 返回 0
func (h *PollingHandler) getMatchedPollingInterval(ctx context.Context, cardID uint, taskType string) (int, error) {
card, err := h.getCardWithCache(ctx, cardID)
if err != nil {
return 0, err
}
configs, err := h.getPollingConfigsFromCache(ctx)
if err != nil || len(configs) == 0 {
// Redis 缓存未命中,回退查 DB
configs, err = h.pollingConfigStore.ListEnabled(ctx)
if err != nil {
return 0, err
}
}
for _, cfg := range configs {
if !h.matchPollingConfig(cfg, card) {
continue
}
switch taskType {
case constants.TaskTypePollingRealname:
if cfg.RealnameCheckInterval != nil && *cfg.RealnameCheckInterval > 0 {
return *cfg.RealnameCheckInterval, nil
}
case constants.TaskTypePollingCarddata:
if cfg.CarddataCheckInterval != nil && *cfg.CarddataCheckInterval > 0 {
return *cfg.CarddataCheckInterval, nil
}
case constants.TaskTypePollingPackage:
if cfg.PackageCheckInterval != nil && *cfg.PackageCheckInterval > 0 {
return *cfg.PackageCheckInterval, nil
}
case constants.TaskTypePollingProtect:
// protect 任务暂无专用间隔字段,使用 realname 间隔兜底
if cfg.RealnameCheckInterval != nil && *cfg.RealnameCheckInterval > 0 {
return *cfg.RealnameCheckInterval, nil
}
}
// 匹配到配置但该任务类型间隔为 NULL说明此卡不需要此类轮询
return 0, nil
}
return 0, nil
}
// getPollingConfigsFromCache 从 Redis HASH 缓存读取已启用的轮询配置列表,按 priority 升序排列
// 与 Scheduler.syncConfigsToRedis 写入格式对应
func (h *PollingHandler) getPollingConfigsFromCache(ctx context.Context) ([]*model.PollingConfig, error) {
key := constants.RedisPollingConfigsCacheKey()
result, err := h.redis.HGetAll(ctx, key).Result()
if err != nil || len(result) == 0 {
return nil, err
}
configs := make([]*model.PollingConfig, 0, len(result))
for _, jsonStr := range result {
var cfg model.PollingConfig
if err := sonic.UnmarshalString(jsonStr, &cfg); err != nil {
h.logger.Warn("解析轮询配置缓存失败", zap.Error(err))
continue
}
configs = append(configs, &cfg)
}
sort.Slice(configs, func(i, j int) bool {
return configs[i].Priority < configs[j].Priority
})
return configs, nil
}
// matchPollingConfig 判断卡是否满足配置的匹配条件
// 逻辑与 polling.Scheduler.matchConfigConditions 保持一致
func (h *PollingHandler) matchPollingConfig(cfg *model.PollingConfig, card *model.IotCard) bool {
if cfg.CardCondition != "" && cfg.CardCondition != h.cardCondition(card) {
return false
}
if cfg.CardCategory != "" && cfg.CardCategory != card.CardCategory {
return false
}
if cfg.CarrierID != nil && *cfg.CarrierID != card.CarrierID {
return false
}
return true
}
// cardCondition 根据卡的实名和网络状态返回条件字符串
// 逻辑与 polling.Scheduler.getCardCondition 保持一致
func (h *PollingHandler) cardCondition(card *model.IotCard) string {
if card.RealNameStatus != constants.RealNameStatusVerified {
return "not_real_name"
}
if card.NetworkStatus == constants.NetworkStatusOnline {
return "activated"
}
return "real_name"
}
// triggerPackageCheck 触发套餐检查
func (h *PollingHandler) triggerPackageCheck(ctx context.Context, cardID uint) {
key := constants.RedisPollingManualQueueKey(constants.TaskTypePollingPackage)
@@ -1009,12 +1119,19 @@ func (h *PollingHandler) HandleProtectConsistencyCheck(ctx context.Context, t *a
zap.Uint("device_id", deviceID))
if h.gatewayClient != nil {
h.logger.Info("调用网关停机(保护期)",
zap.Uint("card_id", card.ID),
zap.String("iccid", card.ICCID))
if err := h.gatewayClient.StopCard(ctx, &gateway.CardOperationReq{CardNo: card.ICCID}); err != nil {
h.logger.Error("保护期一致性停机失败",
zap.Uint("card_id", card.ID),
zap.String("iccid", card.ICCID),
zap.Error(err))
return nil
}
h.logger.Info("网关停机成功(保护期)",
zap.Uint("card_id", card.ID),
zap.String("iccid", card.ICCID))
}
h.db.Model(&model.IotCard{}).Where("id = ?", card.ID).Updates(map[string]any{
@@ -1035,12 +1152,19 @@ func (h *PollingHandler) HandleProtectConsistencyCheck(ctx context.Context, t *a
zap.Uint("device_id", deviceID))
if h.gatewayClient != nil {
h.logger.Info("调用网关复机(保护期)",
zap.Uint("card_id", card.ID),
zap.String("iccid", card.ICCID))
if err := h.gatewayClient.StartCard(ctx, &gateway.CardOperationReq{CardNo: card.ICCID}); err != nil {
h.logger.Error("保护期一致性复机失败",
zap.Uint("card_id", card.ID),
zap.String("iccid", card.ICCID),
zap.Error(err))
return nil
}
h.logger.Info("网关复机成功(保护期)",
zap.Uint("card_id", card.ID),
zap.String("iccid", card.ICCID))
}
h.db.Model(&model.IotCard{}).Where("id = ?", card.ID).Updates(map[string]any{