Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Failing after 8m39s
- 迁移 000259~000265:套餐记录沉默期字段与扫描索引、套餐默认沉默期、延期政策/流水/订单/批次/明细 5 张表、默认政策与系统配置(总开关默认关闭)、企微审批白名单、卡与设备出库时间及出库记录 - 沉默期设置(订单发放、自动购包、钱包续费)、后台购买时调整(超默认值按政策免审或企微审批)、统一判定与激活闸门、排队与孤儿扫描跳过过期套餐、过期套餐禁止退款、每 10 分钟扫描 - 总开关专用接口与打开前 4 项检查;延期政策;延期资格与计费;H5 自助与后台二维码在线支付(商户池路由)、支付回调与查单恢复、线下登记、特殊/批量延期、异常重试与原路退款;沉默期审批场景与终态处理 - 换货沉默期流水与出库时间;卡/设备列表、详情、导出与出库记录接口;延期订单与即将过期套餐列表及导出;财务流水纳入延期支付 - 沉默期与出库时间回填工具 cmd/silence-backfill;前端接口变更说明 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
117 lines
4.2 KiB
Go
117 lines
4.2 KiB
Go
// Package packagesilence 实现实名即生效套餐沉默期的设置、判定、扫描、总开关与延期用例(add-package-silence-period)。
|
||
package packagesilence
|
||
|
||
import (
|
||
"context"
|
||
"strconv"
|
||
"sync"
|
||
"time"
|
||
|
||
"github.com/redis/go-redis/v9"
|
||
"go.uber.org/zap"
|
||
"gorm.io/gorm"
|
||
|
||
"github.com/break/junhong_cmp_fiber/internal/infrastructure/systemconfig"
|
||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||
)
|
||
|
||
// switchCacheTTL 是总开关在进程内的缓存时间;应急关闭最多延迟这么久在各进程生效。
|
||
const switchCacheTTL = 5 * time.Second
|
||
|
||
// RegisterConfigDefinitions 注册沉默期相关系统配置。
|
||
// 总开关注册为只读:通用系统配置接口不能修改,只能走带打开前检查的专用接口。
|
||
func RegisterConfigDefinitions(registry *systemconfig.Registry, logger *zap.Logger) {
|
||
if registry == nil {
|
||
return
|
||
}
|
||
minDays, maxDays := int64(constants.SilencePackageDaysMin), int64(constants.SilencePackageDaysMax)
|
||
minPrice, maxPrice := int64(0), int64(1000000)
|
||
definitions := []systemconfig.Definition{
|
||
{Key: constants.SystemConfigSilenceEnabled, Module: constants.SystemConfigModulePackage, ValueType: constants.SystemConfigTypeBool,
|
||
DefaultValue: "false", Readonly: true,
|
||
Description: "沉默期总开关:关闭时只记录沉默期,不拦截激活、不扫描、不展示、延期申请拒绝;只能通过沉默期总开关接口切换"},
|
||
{Key: constants.SystemConfigSilenceDefaultDays, Module: constants.SystemConfigModulePackage, ValueType: constants.SystemConfigTypeInt,
|
||
DefaultValue: strconv.Itoa(constants.SilenceDefaultPeriodDays), Description: "新建实名即生效套餐时预填的默认沉默期(天)",
|
||
Min: &minDays, Max: &maxDays},
|
||
{Key: constants.SystemConfigSilenceExtensionUnitPrice, Module: constants.SystemConfigModulePackage, ValueType: constants.SystemConfigTypeInt,
|
||
DefaultValue: strconv.Itoa(constants.SilenceDefaultUnitPrice), Description: "新建延期政策时预填的单价(分/月),实际计价以政策为准",
|
||
Min: &minPrice, Max: &maxPrice},
|
||
}
|
||
for _, definition := range definitions {
|
||
if _, exists := registry.Get(definition.Key); exists {
|
||
continue
|
||
}
|
||
if err := registry.Register(definition); err != nil && logger != nil {
|
||
logger.Error("注册沉默期系统配置失败,将使用代码默认值", zap.String("config_key", definition.Key), zap.Error(err))
|
||
}
|
||
}
|
||
}
|
||
|
||
// ConfigReader 读取系统配置的最小接缝。
|
||
type ConfigReader interface {
|
||
Get(ctx context.Context, key string) (string, error)
|
||
}
|
||
|
||
// Switch 读取沉默期总开关 package.silence.enabled。
|
||
// 读取失败时按关闭处理(行为与现状一致),不因配置故障误拦截激活或退款。
|
||
type Switch struct {
|
||
reader ConfigReader
|
||
logger *zap.Logger
|
||
|
||
mu sync.Mutex
|
||
cached bool
|
||
expiresAt time.Time
|
||
}
|
||
|
||
// NewSwitch 创建总开关读取器。
|
||
func NewSwitch(reader ConfigReader, logger *zap.Logger) *Switch {
|
||
if logger == nil {
|
||
logger = zap.NewNop()
|
||
}
|
||
return &Switch{reader: reader, logger: logger}
|
||
}
|
||
|
||
// NewDefaultSwitch 用独立注册表与读取器创建总开关(API 与 Worker 共用同一份数据库与缓存数据)。
|
||
func NewDefaultSwitch(db *gorm.DB, redisClient *redis.Client, logger *zap.Logger) *Switch {
|
||
registry := systemconfig.NewRegistry()
|
||
RegisterConfigDefinitions(registry, logger)
|
||
var cache systemconfig.Cache
|
||
if redisClient != nil {
|
||
cache = systemconfig.NewRedisCache(redisClient)
|
||
}
|
||
reader := systemconfig.NewReader(db, registry, cache, systemconfig.NewLogAlertSink(logger))
|
||
return NewSwitch(reader, logger)
|
||
}
|
||
|
||
// Enabled 返回总开关是否打开。
|
||
func (s *Switch) Enabled(ctx context.Context) bool {
|
||
if s == nil || s.reader == nil {
|
||
return false
|
||
}
|
||
s.mu.Lock()
|
||
defer s.mu.Unlock()
|
||
now := time.Now()
|
||
if now.Before(s.expiresAt) {
|
||
return s.cached
|
||
}
|
||
value, err := s.reader.Get(ctx, constants.SystemConfigSilenceEnabled)
|
||
if err != nil {
|
||
s.logger.Warn("读取沉默期总开关失败,按关闭处理", zap.Error(err))
|
||
s.cached = false
|
||
} else {
|
||
s.cached = value == "true"
|
||
}
|
||
s.expiresAt = now.Add(switchCacheTTL)
|
||
return s.cached
|
||
}
|
||
|
||
// Invalidate 清除进程内缓存(本进程切换开关后立即生效)。
|
||
func (s *Switch) Invalidate() {
|
||
if s == nil {
|
||
return
|
||
}
|
||
s.mu.Lock()
|
||
s.expiresAt = time.Time{}
|
||
s.mu.Unlock()
|
||
}
|