Files
junhong_cmp_fiber/internal/task/polling_realname_scope.go
邹进 f49baff7c2
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m35s
feat: 停复机新规则转正,删除总开关与旧口径
- 删除总开关 stop_resume.unified_rule_enabled(配置、加载、部署工作流与 docker-compose.prod.yml 环境变量)
- 复机不看停机原因:删除只对轮询停因复机的旧分支与 ListByDeviceIDAndPollingStopReasons;设备复机取全部停机卡
- 实名门槛只按卡所属通道实名管控判断,删除行业卡与卡/设备实名策略放行;暂停轮询的卡不自动停复机无条件生效
- 通道阈值新周期解锁后的复机条件固定为新规则复机条件
- 资产自动续费复机同步新规则:不看停因、暂停轮询不复机、设备取全部停机卡、复机成功清除停因
- 实名轮询不再按行业卡跳过;所属运营商账户未开启实名管控时跳过查询,但卡未实名且所在资产有等待实名激活的套餐时照常查询,避免囤货套餐无法激活
2026-10-25 18:13:51 +09:00

95 lines
3.5 KiB
Go

package task
import (
"context"
"sync"
"time"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
)
// realnameControlCacheTTL 是运营商账户实名管控在进程内的缓存时长;账户配置修改最迟在该时长后生效。
const realnameControlCacheTTL = time.Minute
type realnameControlEntry struct {
controlled bool
loadedAt time.Time
}
var realnameControlCache = struct {
sync.Mutex
values map[uint]realnameControlEntry
}{values: map[uint]realnameControlEntry{}}
// realnameQuerySkippable 判断本次实名轮询能否跳过查询:卡所属运营商账户未开启实名管控时实名结果不影响停复机,跳过;
// 但卡未实名且所在资产(绑定设备时按设备)有等待实名激活的套餐时照常查询,避免囤货套餐因发现不了实名而无法激活。
// 账户或套餐查询失败返回错误,调用方按可恢复失败处理。
func (b *PollingBase) realnameQuerySkippable(ctx context.Context, card *model.IotCard) (bool, error) {
controlled, err := b.carrierRealnameControlled(ctx, card.CarrierID)
if err != nil {
return false, err
}
if controlled {
return false, nil
}
if card.RealNameStatus == constants.RealNameStatusVerified {
return true, nil
}
pending, err := b.hasPendingRealnameActivation(ctx, card)
if err != nil {
return false, err
}
return !pending, nil
}
func (b *PollingBase) carrierRealnameControlled(ctx context.Context, carrierID uint) (bool, error) {
realnameControlCache.Lock()
entry, ok := realnameControlCache.values[carrierID]
realnameControlCache.Unlock()
if ok && time.Since(entry.loadedAt) < realnameControlCacheTTL {
return entry.controlled, nil
}
if b.db == nil {
return false, errors.New(errors.CodeInternalError, "实名轮询数据库未配置")
}
var values []int
if err := b.db.WithContext(ctx).Model(&model.Carrier{}).Where("id = ?", carrierID).Limit(1).
Pluck("realname_control_enabled", &values).Error; err != nil {
return false, errors.Wrap(errors.CodeDatabaseError, err, "查询运营商账户实名管控失败")
}
if len(values) == 0 {
return false, errors.New(errors.CodeNotFound, "卡所属运营商账户不存在,无法判断实名管控")
}
controlled := values[0] == 1
realnameControlCache.Lock()
realnameControlCache.values[carrierID] = realnameControlEntry{controlled: controlled, loadedAt: time.Now()}
realnameControlCache.Unlock()
return controlled, nil
}
// hasPendingRealnameActivation 判断卡所在资产是否有等待实名激活的待生效套餐。
func (b *PollingBase) hasPendingRealnameActivation(ctx context.Context, card *model.IotCard) (bool, error) {
query := b.db.WithContext(ctx).Model(&model.PackageUsage{}).
Where("pending_realname_activation = ? AND status = ?", true, constants.PackageUsageStatusPending)
var deviceIDs []uint
if !card.IsStandalone {
if err := b.db.WithContext(ctx).Model(&model.DeviceSimBinding{}).
Where("iot_card_id = ? AND bind_status = ?", card.ID, constants.BindStatusBound).
Limit(1).Pluck("device_id", &deviceIDs).Error; err != nil {
return false, errors.Wrap(errors.CodeDatabaseError, err, "查询卡绑定设备失败")
}
}
if len(deviceIDs) > 0 {
query = query.Where("device_id = ?", deviceIDs[0])
} else {
query = query.Where("iot_card_id = ?", card.ID)
}
var count int64
if err := query.Limit(1).Count(&count).Error; err != nil {
return false, errors.Wrap(errors.CodeDatabaseError, err, "查询待实名激活套餐失败")
}
return count > 0, nil
}