fix: 修复轮询停机连坐、实名逆转防抖及carrier_stopped原因写入缺失三个逻辑缺陷
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m37s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m37s
This commit is contained in:
@@ -78,7 +78,7 @@ func (s *StopResumeService) EvaluateAndAct(ctx context.Context, card *model.IotC
|
||||
}
|
||||
// 取最高优先级原因(第一个)
|
||||
primaryReason := reasons[0]
|
||||
if !card.IsStandalone {
|
||||
if !card.IsStandalone && isDeviceScopeReason(primaryReason) {
|
||||
deviceID, bound, lookupErr := s.getCardDeviceID(ctx, card)
|
||||
if lookupErr != nil {
|
||||
s.logger.Error("查询设备绑定关系失败,降级为单卡停机",
|
||||
@@ -132,6 +132,13 @@ func isPollingStopReason(reason string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// isDeviceScopeReason 判断停机原因是否应扩散到设备维度
|
||||
// 套餐/流量是设备共享资源,需整体停机;实名是单卡属性,只停该卡
|
||||
func isDeviceScopeReason(reason string) bool {
|
||||
return reason == constants.StopReasonNoPackage ||
|
||||
reason == constants.StopReasonTrafficExhausted
|
||||
}
|
||||
|
||||
// getCardDeviceID 获取卡所属设备ID(非独立卡时有效)
|
||||
// 返回:deviceID, isDeviceBound, error
|
||||
func (s *StopResumeService) getCardDeviceID(ctx context.Context, card *model.IotCard) (uint, bool, error) {
|
||||
|
||||
@@ -97,6 +97,10 @@ func (h *PollingCardStatusHandler) Handle(ctx context.Context, t *asynq.Task) er
|
||||
fields := map[string]any{"last_card_status_check_at": now}
|
||||
if statusChanged {
|
||||
fields["network_status"] = newNetworkStatus
|
||||
// 网关侧停机事件:补写 stop_reason,便于状态追踪和区分手动停机
|
||||
if newNetworkStatus == constants.NetworkStatusOffline && card.StopReason == "" {
|
||||
fields["stop_reason"] = constants.StopReasonCarrierStopped
|
||||
}
|
||||
}
|
||||
if updateErr := h.iotCardStore.UpdateFields(ctx, cardID, fields); updateErr != nil {
|
||||
h.base.logger.Warn("更新卡状态信息失败", zap.Uint("card_id", cardID), zap.Error(updateErr))
|
||||
|
||||
@@ -139,6 +139,8 @@ func (h *PollingRealnameHandler) Handle(ctx context.Context, t *asynq.Task) erro
|
||||
zap.Int("new_status", newStatus))
|
||||
|
||||
if isFirstRealname {
|
||||
// 0→1 时清除逆转计数器
|
||||
h.base.redis.Del(ctx, constants.RedisPollingRealnameReversalCountKey(cardID))
|
||||
// 0→1:首次实名,触发套餐激活并立即复机评估(不等待下一个 carddata/package 周期)
|
||||
h.triggerFirstRealnameActivation(ctx, cardID)
|
||||
h.triggerDeviceRealnameActivation(ctx, cardID)
|
||||
@@ -152,15 +154,26 @@ func (h *PollingRealnameHandler) Handle(ctx context.Context, t *asynq.Task) erro
|
||||
}
|
||||
}
|
||||
} else if newStatus == constants.RealNameStatusNotVerified {
|
||||
// 1→0:实名逆转,立即触发停机评估(不等待下一个 carddata/package 周期,否则最长延迟 1 小时)
|
||||
h.base.logger.Warn("检测到实名逆转,立即触发停机评估",
|
||||
zap.Uint("card_id", cardID))
|
||||
if h.stopResumeSvc != nil {
|
||||
freshCard, loadErr := h.iotCardStore.GetByID(ctx, cardID)
|
||||
if loadErr == nil {
|
||||
if evalErr := h.stopResumeSvc.EvaluateAndAct(ctx, freshCard); evalErr != nil {
|
||||
h.base.logger.Warn("实名逆转后触发停机评估失败",
|
||||
zap.Uint("card_id", cardID), zap.Error(evalErr))
|
||||
reversalKey := constants.RedisPollingRealnameReversalCountKey(cardID)
|
||||
count, incrErr := h.base.redis.Incr(ctx, reversalKey).Result()
|
||||
if incrErr == nil {
|
||||
h.base.redis.Expire(ctx, reversalKey, 10*time.Minute)
|
||||
}
|
||||
h.base.logger.Warn("检测到实名逆转",
|
||||
zap.Uint("card_id", cardID),
|
||||
zap.Int64("reversal_count", count),
|
||||
zap.Int("threshold", constants.PollingRealnameReversalThreshold))
|
||||
if count >= int64(constants.PollingRealnameReversalThreshold) {
|
||||
h.base.redis.Del(ctx, reversalKey)
|
||||
h.base.logger.Warn("实名逆转达到阈值,触发停机评估",
|
||||
zap.Uint("card_id", cardID))
|
||||
if h.stopResumeSvc != nil {
|
||||
freshCard, loadErr := h.iotCardStore.GetByID(ctx, cardID)
|
||||
if loadErr == nil {
|
||||
if evalErr := h.stopResumeSvc.EvaluateAndAct(ctx, freshCard); evalErr != nil {
|
||||
h.base.logger.Warn("实名逆转后触发停机评估失败",
|
||||
zap.Uint("card_id", cardID), zap.Error(evalErr))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user