All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 12m59s
108 lines
4.7 KiB
Go
108 lines
4.7 KiB
Go
// Package carrierthreshold 编排运营商通道流量阈值的达量判定与周期锁事实。
|
||
//
|
||
// 本包拥有 tb_carrier_traffic_threshold_lock 的全部读写:达量判定在流量观测事务内写入周期锁
|
||
// 与可靠停机事件,消费者以提交认领字段取得至多一次的外部调用权,周期处理与恢复扫描只查询锁行。
|
||
// 本包不调用任何运营商接口,也不在数据库事务内发起外部 I/O:停复机执行通过 CardCommander
|
||
// 端口复用既有停复机单一事实源。
|
||
package carrierthreshold
|
||
|
||
import (
|
||
"context"
|
||
"time"
|
||
|
||
"go.uber.org/zap"
|
||
"gorm.io/gorm"
|
||
|
||
domain "github.com/break/junhong_cmp_fiber/internal/domain/carrierthreshold"
|
||
"github.com/break/junhong_cmp_fiber/internal/infrastructure/messaging/outbox"
|
||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||
)
|
||
|
||
// CardCommander 是通道阈值停复机对卡与运营商接口的能力边界。
|
||
//
|
||
// 实现必须复用既有停复机单一事实源(重试、Integration Log、统一审计、卡状态观测序列)与
|
||
// 既有套餐/流量/实名/风险判定;本包绝不复制这些规则,也绝不直接调用运营商接口。
|
||
type CardCommander interface {
|
||
// StopCardForThreshold 执行通道阈值达量停机,成功时写回卡停机状态与停因。
|
||
StopCardForThreshold(ctx context.Context, cardID uint) (domain.CommandOutcome, error)
|
||
// ResumeCardForThreshold 执行新周期复机,成功时写回卡在线状态(只清除通道阈值停因)。
|
||
ResumeCardForThreshold(ctx context.Context, cardID uint) (domain.CommandOutcome, error)
|
||
// ResumeReady 判断解锁后的卡是否满足自动复机条件;不满足时返回可安全记录的原因。
|
||
ResumeReady(ctx context.Context, cardID uint) (ready bool, reason string, err error)
|
||
// CardNetworkStatus 只查询运营商卡状态并映射为本地网络状态;known 为 false 表示状态不可判定。
|
||
CardNetworkStatus(ctx context.Context, cardID uint) (status int, known bool, integrationID string, err error)
|
||
// ConfirmCardState 按已确认的运营商结果补写卡状态,覆盖 Gateway 成功但 DB 更新失败的场景。
|
||
ConfirmCardState(ctx context.Context, cardID uint, offline bool) error
|
||
}
|
||
|
||
// Service 执行通道阈值达量判定并维护周期锁事实。
|
||
type Service struct {
|
||
db *gorm.DB
|
||
// repository 是公共 Outbox 仓储:达量停机事件与周期复机事件必须与锁事实在同一事务写入(ENG-OUTBOX-001)。
|
||
repository *outbox.Repository
|
||
logger *zap.Logger
|
||
// commander 是停复机执行端口;未注入时消费者与 cron 拒绝执行外部调用。
|
||
commander CardCommander
|
||
now func() time.Time
|
||
}
|
||
|
||
// NewService 创建通道阈值用例;repository 决定达量判定能否写出可靠停复机事件。
|
||
func NewService(db *gorm.DB, repository *outbox.Repository) *Service {
|
||
return &Service{db: db, repository: repository, logger: zap.NewNop(), now: time.Now}
|
||
}
|
||
|
||
// SetLogger 注入通道阈值运行日志。
|
||
func (s *Service) SetLogger(logger *zap.Logger) *Service {
|
||
if s == nil {
|
||
return s
|
||
}
|
||
if logger == nil {
|
||
s.logger = zap.NewNop()
|
||
return s
|
||
}
|
||
s.logger = logger
|
||
return s
|
||
}
|
||
|
||
// SetCommander 注入停复机执行端口(消费者与周期处理必需)。
|
||
func (s *Service) SetCommander(commander CardCommander) *Service {
|
||
if s == nil {
|
||
return s
|
||
}
|
||
s.commander = commander
|
||
return s
|
||
}
|
||
|
||
// ChannelThresholdLocked 判断该卡当前计费周期是否持有通道阈值停机锁。
|
||
//
|
||
// 供复机入口前置拒绝复用:持锁即拒绝,判定口径与锁生效口径完全一致(按锁行自身运营商的
|
||
// data_reset_day 判断周期归属),不另立一套判定。
|
||
func (s *Service) ChannelThresholdLocked(ctx context.Context, cardID uint) (bool, error) {
|
||
lock, err := s.ActiveLock(ctx, cardID, s.now())
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
return lock != nil, nil
|
||
}
|
||
|
||
// carrierResetDays 按运营商 ID 集合显式查询上游流量重置日(ENG-MODEL-001:不使用关联标签)。
|
||
// 返回结果只包含仍然存在的运营商,缺失 ID 由调用方按各自语义处理。
|
||
func (s *Service) carrierResetDays(ctx context.Context, carrierIDs []uint) (map[uint]int, error) {
|
||
resetDays := make(map[uint]int, len(carrierIDs))
|
||
if len(carrierIDs) == 0 {
|
||
return resetDays, nil
|
||
}
|
||
var carriers []model.Carrier
|
||
if err := s.db.WithContext(ctx).
|
||
Select("id", "data_reset_day").
|
||
Where("id IN ?", carrierIDs).
|
||
Find(&carriers).Error; err != nil {
|
||
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询运营商上游流量重置日失败")
|
||
}
|
||
for index := range carriers {
|
||
resetDays[carriers[index].ID] = carriers[index].DataResetDay
|
||
}
|
||
return resetDays, nil
|
||
}
|