feat(套餐真流量预警): AUG26-004 真流量预警规则、达量扫描通知与导出
新增 000228 迁移:规则表 tb_package_traffic_alert_rule(每套餐商品至多一条,无软删除,package_id 非部分唯一约束)、达量预警快照表 tb_package_traffic_alert(以主套餐使用记录 + 阈值快照为唯一键, 触发时冻结用量、额度、比例、阈值、到期时间、归属与资产快照),并为 tb_package_usage 新增扫描 范围部分索引 idx_package_usage_alert_scope;down 在预警表存在数据时阻断回滚。 新增规则维护接口 GET/POST/PUT /api/admin/package-traffic-alert-rules(仅超级管理员与平台账号): 创建校验套餐存在且真流量额度大于零,阈值为 1%~100% 的两位小数;修改只影响后续扫描,不回填也 不改写既有预警快照;全部写操作记录操作者、前后值与时间。 新增每日 06:00(Asia/Shanghai)扫描任务 package:traffic:alert:scan,与套餐临期扫描共用 data_cleanup 队列:按资产汇总当前有效套餐的真流量,分子取使用记录真已用量、分母取使用记录真总量快照,命中 主套餐规则阈值时在同一事务创建预警与可靠通知事件;重复执行以唯一冲突视为已处理,不重复投递, 不建停机锁、不调用运营商。 新增预警列表、详情与异步导出 GET /api/admin/package-traffic-alerts、GET /api/admin/package-traffic-alerts/:id、 POST /api/admin/package-traffic-alerts/export,列表与详情一律读冻结快照;新增通知类型 package.traffic.alert 与受控目标 package_traffic_alert_detail,目标解析仅对超级管理员与平台账号 返回可跳转,越权与不存在统一按资源不可见处理。 同步 OpenAPI(cmd/gendocs、cmd/api/docs.go、pkg/openapi/handlers.go)、审计动作与资源注册、上下文 健康检查证据;归档变更并同步 package-traffic-alert 主 Spec。
This commit is contained in:
81
internal/store/postgres/package_traffic_alert_store.go
Normal file
81
internal/store/postgres/package_traffic_alert_store.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
)
|
||||
|
||||
// PackageTrafficAlertStore 套餐真流量预警规则与预警事实的数据访问层。
|
||||
// 规则只提供创建、修改与查询(无删除入口,停用走 enabled);
|
||||
// 预警事实以「主套餐使用记录 + 阈值快照」唯一键幂等插入,冲突即视为已处理。
|
||||
type PackageTrafficAlertStore struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewPackageTrafficAlertStore 创建套餐真流量预警 Store。
|
||||
func NewPackageTrafficAlertStore(db *gorm.DB) *PackageTrafficAlertStore {
|
||||
return &PackageTrafficAlertStore{db: db}
|
||||
}
|
||||
|
||||
// DB 返回 Store 使用的数据库连接。
|
||||
func (s *PackageTrafficAlertStore) DB() *gorm.DB { return s.db }
|
||||
|
||||
// WithTx 返回绑定指定事务的 Store。
|
||||
func (s *PackageTrafficAlertStore) WithTx(tx *gorm.DB) *PackageTrafficAlertStore {
|
||||
return &PackageTrafficAlertStore{db: tx}
|
||||
}
|
||||
|
||||
// CreateRule 创建套餐真流量预警规则。
|
||||
func (s *PackageTrafficAlertStore) CreateRule(ctx context.Context, rule *model.PackageTrafficAlertRule) error {
|
||||
return s.db.WithContext(ctx).Create(rule).Error
|
||||
}
|
||||
|
||||
// ExistsRuleByPackageID 判断套餐商品是否已有当前规则。
|
||||
func (s *PackageTrafficAlertStore) ExistsRuleByPackageID(ctx context.Context, packageID uint) (bool, error) {
|
||||
var count int64
|
||||
if err := s.db.WithContext(ctx).Model(&model.PackageTrafficAlertRule{}).
|
||||
Where("package_id = ?", packageID).Count(&count).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
// LockRuleByID 在事务内按主键加行锁查询预警规则。
|
||||
func (s *PackageTrafficAlertStore) LockRuleByID(ctx context.Context, id uint) (*model.PackageTrafficAlertRule, error) {
|
||||
var rule model.PackageTrafficAlertRule
|
||||
if err := s.db.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).First(&rule, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rule, nil
|
||||
}
|
||||
|
||||
// UpdateRule 保存预警规则的阈值、启停与备注。
|
||||
func (s *PackageTrafficAlertStore) UpdateRule(ctx context.Context, rule *model.PackageTrafficAlertRule, operatorID uint) error {
|
||||
return s.db.WithContext(ctx).Model(&model.PackageTrafficAlertRule{}).Where("id = ?", rule.ID).Updates(map[string]any{
|
||||
"threshold_percent": rule.ThresholdPercent,
|
||||
"enabled": rule.Enabled,
|
||||
"remark": rule.Remark,
|
||||
"updater": operatorID,
|
||||
"updated_at": time.Now(),
|
||||
}).Error
|
||||
}
|
||||
|
||||
// CreateAlertIdempotent 幂等插入预警事实。
|
||||
// 返回 true 表示本次插入了新事实;返回 false 表示唯一键冲突,即该「使用记录 + 阈值快照」已被处理。
|
||||
// 调用方必须只在返回 true 时继续写通知事件与审计。
|
||||
func (s *PackageTrafficAlertStore) CreateAlertIdempotent(ctx context.Context, alert *model.PackageTrafficAlert) (bool, error) {
|
||||
result := s.db.WithContext(ctx).
|
||||
Clauses(clause.OnConflict{Columns: []clause.Column{
|
||||
{Name: "package_usage_id"}, {Name: "threshold_percent_snapshot"},
|
||||
}, DoNothing: true}).
|
||||
Create(alert)
|
||||
if result.Error != nil {
|
||||
return false, result.Error
|
||||
}
|
||||
return result.RowsAffected == 1, nil
|
||||
}
|
||||
Reference in New Issue
Block a user