fix: 卡或绑定设备关闭轮询时不再自动停复机
关闭轮询的卡/设备已导入其他卡管,EvaluateAndAct 入口直接返回,不停机也不复机。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
38
internal/service/iot_card/stop_resume_polling_guard.go
Normal file
38
internal/service/iot_card/stop_resume_polling_guard.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package iot_card
|
||||
|
||||
import (
|
||||
"context"
|
||||
stderrors "errors"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
)
|
||||
|
||||
// pollingDisabled 判断卡是否退出本系统自动停复机:卡级关闭轮询,或卡绑定的设备关闭轮询。
|
||||
// 关闭轮询的卡/设备已交由其他卡管管理,本系统任何入口都不得再对其自动停机或复机。
|
||||
// 查询失败时返回错误,由调用方放弃本次停复机,避免在状态不明时误操作。
|
||||
func (s *StopResumeService) pollingDisabled(ctx context.Context, card *model.IotCard) (bool, error) {
|
||||
if !card.EnablePolling {
|
||||
return true, nil
|
||||
}
|
||||
if s.db == nil {
|
||||
return false, nil
|
||||
}
|
||||
deviceID, bound, err := s.getCardDeviceID(ctx, card)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(errors.CodeDatabaseError, err, "查询卡绑定设备失败")
|
||||
}
|
||||
if !bound {
|
||||
return false, nil
|
||||
}
|
||||
var device model.Device
|
||||
if err := s.db.WithContext(ctx).Select("id", "enable_polling").First(&device, deviceID).Error; err != nil {
|
||||
if stderrors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return false, nil
|
||||
}
|
||||
return false, errors.Wrap(errors.CodeDatabaseError, err, "查询设备轮询状态失败")
|
||||
}
|
||||
return !device.EnablePolling, nil
|
||||
}
|
||||
45
internal/service/iot_card/stop_resume_polling_guard_test.go
Normal file
45
internal/service/iot_card/stop_resume_polling_guard_test.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package iot_card
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
)
|
||||
|
||||
// 关闭轮询的卡已交由其他卡管:无论在线还是停机,EvaluateAndAct 都必须直接返回,
|
||||
// 不得进入停机原因判断或复机流程(服务未注入任何 store/网关,进入后续流程会直接 panic)。
|
||||
func TestEvaluateAndAct_CardPollingDisabledSkipsStopResume(t *testing.T) {
|
||||
svc := &StopResumeService{logger: zap.NewNop()}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
card *model.IotCard
|
||||
}{
|
||||
{
|
||||
name: "在线卡无套餐不停机",
|
||||
card: &model.IotCard{NetworkStatus: constants.NetworkStatusOnline, EnablePolling: false, IsStandalone: false},
|
||||
},
|
||||
{
|
||||
name: "轮询原因停机的卡不复机",
|
||||
card: &model.IotCard{NetworkStatus: constants.NetworkStatusOffline, StopReason: constants.StopReasonNoPackage, EnablePolling: false},
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if err := svc.EvaluateAndAct(context.Background(), tc.card); err != nil {
|
||||
t.Fatalf("期望直接返回 nil,实际 %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluateAndAct_NilCard(t *testing.T) {
|
||||
svc := &StopResumeService{logger: zap.NewNop()}
|
||||
if err := svc.EvaluateAndAct(context.Background(), nil); err != nil {
|
||||
t.Fatalf("期望 nil,实际 %v", err)
|
||||
}
|
||||
}
|
||||
@@ -108,7 +108,19 @@ func (s *StopResumeService) SetPollingCallback(callback PollingCallback) {
|
||||
// EvaluateAndAct 停复机统一入口
|
||||
// 根据卡的当前网络状态,自动判断并执行停机或复机操作
|
||||
// 设备/单卡维度通过 card.IsStandalone 推导(false 则为设备维度,停/复机覆盖设备下所有卡)
|
||||
// 卡或其绑定设备关闭轮询时直接返回,不停机也不复机。
|
||||
func (s *StopResumeService) EvaluateAndAct(ctx context.Context, card *model.IotCard) error {
|
||||
if card == nil {
|
||||
return nil
|
||||
}
|
||||
disabled, err := s.pollingDisabled(ctx, card)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if disabled {
|
||||
s.logger.Debug("卡或绑定设备已关闭轮询,跳过自动停复机", zap.Uint("card_id", card.ID))
|
||||
return nil
|
||||
}
|
||||
switch card.NetworkStatus {
|
||||
case constants.NetworkStatusOnline:
|
||||
// 卡在线,检查是否需要停机
|
||||
|
||||
Reference in New Issue
Block a user