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