feat: 实现 IoT 卡轮询系统(支持千万级卡规模)
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m35s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m35s
实现功能: - 实名状态检查轮询(可配置间隔) - 卡流量检查轮询(支持跨月流量追踪) - 套餐检查与超额自动停机 - 分布式并发控制(Redis 信号量) - 手动触发轮询(单卡/批量/条件筛选) - 数据清理配置与执行 - 告警规则与历史记录 - 实时监控统计(队列/性能/并发) 性能优化: - Redis 缓存卡信息,减少 DB 查询 - Pipeline 批量写入 Redis - 异步流量记录写入 - 渐进式初始化(10万卡/批) 压测工具(scripts/benchmark/): - Mock Gateway 模拟上游服务 - 测试卡生成器 - 配置初始化脚本 - 实时监控脚本 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
91
internal/store/postgres/data_usage_record_store.go
Normal file
91
internal/store/postgres/data_usage_record_store.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
)
|
||||
|
||||
// DataUsageRecordStore 流量使用记录存储
|
||||
type DataUsageRecordStore struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewDataUsageRecordStore 创建流量使用记录存储实例
|
||||
func NewDataUsageRecordStore(db *gorm.DB) *DataUsageRecordStore {
|
||||
return &DataUsageRecordStore{db: db}
|
||||
}
|
||||
|
||||
// Create 创建流量使用记录
|
||||
func (s *DataUsageRecordStore) Create(ctx context.Context, record *model.DataUsageRecord) error {
|
||||
return s.db.WithContext(ctx).Create(record).Error
|
||||
}
|
||||
|
||||
// CreateBatch 批量创建流量使用记录
|
||||
func (s *DataUsageRecordStore) CreateBatch(ctx context.Context, records []*model.DataUsageRecord) error {
|
||||
if len(records) == 0 {
|
||||
return nil
|
||||
}
|
||||
return s.db.WithContext(ctx).CreateInBatches(records, 100).Error
|
||||
}
|
||||
|
||||
// GetLatestByCardID 获取卡的最新流量记录
|
||||
func (s *DataUsageRecordStore) GetLatestByCardID(ctx context.Context, cardID uint) (*model.DataUsageRecord, error) {
|
||||
var record model.DataUsageRecord
|
||||
if err := s.db.WithContext(ctx).
|
||||
Where("iot_card_id = ?", cardID).
|
||||
Order("check_time DESC").
|
||||
First(&record).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &record, nil
|
||||
}
|
||||
|
||||
// ListByCardID 获取卡的流量记录列表
|
||||
func (s *DataUsageRecordStore) ListByCardID(ctx context.Context, cardID uint, startTime, endTime *time.Time, page, pageSize int) ([]*model.DataUsageRecord, int64, error) {
|
||||
var records []*model.DataUsageRecord
|
||||
var total int64
|
||||
|
||||
query := s.db.WithContext(ctx).Model(&model.DataUsageRecord{}).Where("iot_card_id = ?", cardID)
|
||||
|
||||
if startTime != nil {
|
||||
query = query.Where("check_time >= ?", *startTime)
|
||||
}
|
||||
if endTime != nil {
|
||||
query = query.Where("check_time <= ?", *endTime)
|
||||
}
|
||||
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
offset := (page - 1) * pageSize
|
||||
if err := query.Order("check_time DESC").Offset(offset).Limit(pageSize).Find(&records).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return records, total, nil
|
||||
}
|
||||
|
||||
// DeleteOlderThan 删除指定时间之前的记录(用于数据清理)
|
||||
func (s *DataUsageRecordStore) DeleteOlderThan(ctx context.Context, before time.Time, batchSize int) (int64, error) {
|
||||
result := s.db.WithContext(ctx).
|
||||
Where("check_time < ?", before).
|
||||
Limit(batchSize).
|
||||
Delete(&model.DataUsageRecord{})
|
||||
return result.RowsAffected, result.Error
|
||||
}
|
||||
|
||||
// CountByCardID 统计卡的流量记录数量
|
||||
func (s *DataUsageRecordStore) CountByCardID(ctx context.Context, cardID uint) (int64, error) {
|
||||
var count int64
|
||||
if err := s.db.WithContext(ctx).Model(&model.DataUsageRecord{}).
|
||||
Where("iot_card_id = ?", cardID).
|
||||
Count(&count).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
@@ -410,3 +410,68 @@ func (s *IotCardStore) UpdateRechargeTrackingFields(ctx context.Context, cardID
|
||||
"first_recharge_triggered_by_series": triggeredJSON,
|
||||
}).Error
|
||||
}
|
||||
|
||||
// IotCardQueryFilter 卡查询过滤条件
|
||||
type IotCardQueryFilter struct {
|
||||
ShopID *uint // 店铺ID
|
||||
CardStatus *string // 卡状态
|
||||
CarrierCode *string // 运营商代码
|
||||
CardType *string // 卡类型
|
||||
EnablePolling *bool // 是否启用轮询
|
||||
Limit int // 限制数量
|
||||
}
|
||||
|
||||
// QueryIDsByFilter 根据过滤条件查询卡ID列表
|
||||
func (s *IotCardStore) QueryIDsByFilter(ctx context.Context, filter *IotCardQueryFilter) ([]uint, error) {
|
||||
query := s.db.WithContext(ctx).Model(&model.IotCard{}).Select("id")
|
||||
|
||||
// 应用过滤条件
|
||||
if filter.ShopID != nil {
|
||||
query = query.Where("shop_id = ?", *filter.ShopID)
|
||||
}
|
||||
if filter.CardStatus != nil && *filter.CardStatus != "" {
|
||||
query = query.Where("card_status = ?", *filter.CardStatus)
|
||||
}
|
||||
if filter.CarrierCode != nil && *filter.CarrierCode != "" {
|
||||
query = query.Where("carrier_code = ?", *filter.CarrierCode)
|
||||
}
|
||||
if filter.CardType != nil && *filter.CardType != "" {
|
||||
query = query.Where("card_type = ?", *filter.CardType)
|
||||
}
|
||||
if filter.EnablePolling != nil {
|
||||
query = query.Where("enable_polling = ?", *filter.EnablePolling)
|
||||
}
|
||||
|
||||
// 应用限制
|
||||
if filter.Limit > 0 {
|
||||
query = query.Limit(filter.Limit)
|
||||
}
|
||||
|
||||
var cardIDs []uint
|
||||
if err := query.Pluck("id", &cardIDs).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cardIDs, nil
|
||||
}
|
||||
|
||||
// BatchUpdatePollingStatus 批量更新卡的轮询状态
|
||||
func (s *IotCardStore) BatchUpdatePollingStatus(ctx context.Context, cardIDs []uint, enablePolling bool) error {
|
||||
if len(cardIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
return s.db.WithContext(ctx).
|
||||
Model(&model.IotCard{}).
|
||||
Where("id IN ?", cardIDs).
|
||||
Update("enable_polling", enablePolling).Error
|
||||
}
|
||||
|
||||
// BatchDelete 批量删除卡(软删除)
|
||||
func (s *IotCardStore) BatchDelete(ctx context.Context, cardIDs []uint) error {
|
||||
if len(cardIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
return s.db.WithContext(ctx).
|
||||
Where("id IN ?", cardIDs).
|
||||
Delete(&model.IotCard{}).Error
|
||||
}
|
||||
|
||||
@@ -97,3 +97,13 @@ func (s *PackageSeriesStore) List(ctx context.Context, opts *store.QueryOptions,
|
||||
func (s *PackageSeriesStore) UpdateStatus(ctx context.Context, id uint, status int) error {
|
||||
return s.db.WithContext(ctx).Model(&model.PackageSeries{}).Where("id = ?", id).Update("status", status).Error
|
||||
}
|
||||
|
||||
// GetByIDUnscoped 根据ID获取套餐系列(包括已删除的记录)
|
||||
// 用于关联查询场景,确保已删除的系列信息仍能被展示
|
||||
func (s *PackageSeriesStore) GetByIDUnscoped(ctx context.Context, id uint) (*model.PackageSeries, error) {
|
||||
var series model.PackageSeries
|
||||
if err := s.db.WithContext(ctx).Unscoped().First(&series, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &series, nil
|
||||
}
|
||||
|
||||
@@ -106,3 +106,25 @@ func (s *PackageStore) UpdateStatus(ctx context.Context, id uint, status int) er
|
||||
func (s *PackageStore) UpdateShelfStatus(ctx context.Context, id uint, shelfStatus int) error {
|
||||
return s.db.WithContext(ctx).Model(&model.Package{}).Where("id = ?", id).Update("shelf_status", shelfStatus).Error
|
||||
}
|
||||
|
||||
// GetByIDUnscoped 根据ID获取套餐(包括已删除的记录)
|
||||
// 用于关联查询场景,确保已删除的套餐信息仍能被展示
|
||||
func (s *PackageStore) GetByIDUnscoped(ctx context.Context, id uint) (*model.Package, error) {
|
||||
var pkg model.Package
|
||||
if err := s.db.WithContext(ctx).Unscoped().First(&pkg, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pkg, nil
|
||||
}
|
||||
|
||||
// GetByIDsUnscoped 批量获取套餐(包括已删除的记录)
|
||||
func (s *PackageStore) GetByIDsUnscoped(ctx context.Context, ids []uint) ([]*model.Package, error) {
|
||||
if len(ids) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
var packages []*model.Package
|
||||
if err := s.db.WithContext(ctx).Unscoped().Where("id IN ?", ids).Find(&packages).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return packages, nil
|
||||
}
|
||||
|
||||
114
internal/store/postgres/polling_alert_store.go
Normal file
114
internal/store/postgres/polling_alert_store.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
)
|
||||
|
||||
// PollingAlertRuleStore 告警规则存储
|
||||
type PollingAlertRuleStore struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewPollingAlertRuleStore 创建告警规则存储实例
|
||||
func NewPollingAlertRuleStore(db *gorm.DB) *PollingAlertRuleStore {
|
||||
return &PollingAlertRuleStore{db: db}
|
||||
}
|
||||
|
||||
// Create 创建告警规则
|
||||
func (s *PollingAlertRuleStore) Create(ctx context.Context, rule *model.PollingAlertRule) error {
|
||||
return s.db.WithContext(ctx).Create(rule).Error
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取告警规则
|
||||
func (s *PollingAlertRuleStore) GetByID(ctx context.Context, id uint) (*model.PollingAlertRule, error) {
|
||||
var rule model.PollingAlertRule
|
||||
if err := s.db.WithContext(ctx).First(&rule, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rule, nil
|
||||
}
|
||||
|
||||
// List 获取告警规则列表
|
||||
func (s *PollingAlertRuleStore) List(ctx context.Context) ([]*model.PollingAlertRule, error) {
|
||||
var rules []*model.PollingAlertRule
|
||||
if err := s.db.WithContext(ctx).Order("id ASC").Find(&rules).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rules, nil
|
||||
}
|
||||
|
||||
// ListEnabled 获取启用的告警规则
|
||||
func (s *PollingAlertRuleStore) ListEnabled(ctx context.Context) ([]*model.PollingAlertRule, error) {
|
||||
var rules []*model.PollingAlertRule
|
||||
if err := s.db.WithContext(ctx).Where("status = 1").Order("id ASC").Find(&rules).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rules, nil
|
||||
}
|
||||
|
||||
// Update 更新告警规则
|
||||
func (s *PollingAlertRuleStore) Update(ctx context.Context, rule *model.PollingAlertRule) error {
|
||||
return s.db.WithContext(ctx).Save(rule).Error
|
||||
}
|
||||
|
||||
// Delete 删除告警规则
|
||||
func (s *PollingAlertRuleStore) Delete(ctx context.Context, id uint) error {
|
||||
return s.db.WithContext(ctx).Delete(&model.PollingAlertRule{}, id).Error
|
||||
}
|
||||
|
||||
// PollingAlertHistoryStore 告警历史存储
|
||||
type PollingAlertHistoryStore struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewPollingAlertHistoryStore 创建告警历史存储实例
|
||||
func NewPollingAlertHistoryStore(db *gorm.DB) *PollingAlertHistoryStore {
|
||||
return &PollingAlertHistoryStore{db: db}
|
||||
}
|
||||
|
||||
// Create 创建告警历史
|
||||
func (s *PollingAlertHistoryStore) Create(ctx context.Context, history *model.PollingAlertHistory) error {
|
||||
return s.db.WithContext(ctx).Create(history).Error
|
||||
}
|
||||
|
||||
// List 获取告警历史列表
|
||||
func (s *PollingAlertHistoryStore) List(ctx context.Context, page, pageSize int, ruleID *uint) ([]*model.PollingAlertHistory, int64, error) {
|
||||
var histories []*model.PollingAlertHistory
|
||||
var total int64
|
||||
|
||||
query := s.db.WithContext(ctx).Model(&model.PollingAlertHistory{})
|
||||
if ruleID != nil {
|
||||
query = query.Where("rule_id = ?", *ruleID)
|
||||
}
|
||||
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
offset := (page - 1) * pageSize
|
||||
if err := query.Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&histories).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return histories, total, nil
|
||||
}
|
||||
|
||||
// GetLatestByRuleID 获取规则最近一条告警
|
||||
func (s *PollingAlertHistoryStore) GetLatestByRuleID(ctx context.Context, ruleID uint) (*model.PollingAlertHistory, error) {
|
||||
var history model.PollingAlertHistory
|
||||
if err := s.db.WithContext(ctx).Where("rule_id = ?", ruleID).Order("created_at DESC").First(&history).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &history, nil
|
||||
}
|
||||
|
||||
// UpdateNotificationStatus 更新通知发送状态
|
||||
func (s *PollingAlertHistoryStore) UpdateNotificationStatus(ctx context.Context, id uint, status string) error {
|
||||
return s.db.WithContext(ctx).Model(&model.PollingAlertHistory{}).
|
||||
Where("id = ?", id).
|
||||
Update("notification_status", status).Error
|
||||
}
|
||||
207
internal/store/postgres/polling_cleanup_store.go
Normal file
207
internal/store/postgres/polling_cleanup_store.go
Normal file
@@ -0,0 +1,207 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
)
|
||||
|
||||
// DataCleanupConfigStore 数据清理配置存储
|
||||
type DataCleanupConfigStore struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewDataCleanupConfigStore 创建数据清理配置存储实例
|
||||
func NewDataCleanupConfigStore(db *gorm.DB) *DataCleanupConfigStore {
|
||||
return &DataCleanupConfigStore{db: db}
|
||||
}
|
||||
|
||||
// Create 创建清理配置
|
||||
func (s *DataCleanupConfigStore) Create(ctx context.Context, config *model.DataCleanupConfig) error {
|
||||
return s.db.WithContext(ctx).Create(config).Error
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取清理配置
|
||||
func (s *DataCleanupConfigStore) GetByID(ctx context.Context, id uint) (*model.DataCleanupConfig, error) {
|
||||
var config model.DataCleanupConfig
|
||||
if err := s.db.WithContext(ctx).First(&config, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
// GetByTableName 根据表名获取清理配置
|
||||
func (s *DataCleanupConfigStore) GetByTableName(ctx context.Context, tableName string) (*model.DataCleanupConfig, error) {
|
||||
var config model.DataCleanupConfig
|
||||
if err := s.db.WithContext(ctx).Where("table_name = ?", tableName).First(&config).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
// List 获取所有清理配置
|
||||
func (s *DataCleanupConfigStore) List(ctx context.Context) ([]*model.DataCleanupConfig, error) {
|
||||
var configs []*model.DataCleanupConfig
|
||||
if err := s.db.WithContext(ctx).Order("id ASC").Find(&configs).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return configs, nil
|
||||
}
|
||||
|
||||
// ListEnabled 获取启用的清理配置
|
||||
func (s *DataCleanupConfigStore) ListEnabled(ctx context.Context) ([]*model.DataCleanupConfig, error) {
|
||||
var configs []*model.DataCleanupConfig
|
||||
if err := s.db.WithContext(ctx).Where("enabled = 1").Order("id ASC").Find(&configs).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return configs, nil
|
||||
}
|
||||
|
||||
// Update 更新清理配置
|
||||
func (s *DataCleanupConfigStore) Update(ctx context.Context, config *model.DataCleanupConfig) error {
|
||||
return s.db.WithContext(ctx).Save(config).Error
|
||||
}
|
||||
|
||||
// Delete 删除清理配置
|
||||
func (s *DataCleanupConfigStore) Delete(ctx context.Context, id uint) error {
|
||||
return s.db.WithContext(ctx).Delete(&model.DataCleanupConfig{}, id).Error
|
||||
}
|
||||
|
||||
// DataCleanupLogStore 数据清理日志存储
|
||||
type DataCleanupLogStore struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewDataCleanupLogStore 创建数据清理日志存储实例
|
||||
func NewDataCleanupLogStore(db *gorm.DB) *DataCleanupLogStore {
|
||||
return &DataCleanupLogStore{db: db}
|
||||
}
|
||||
|
||||
// Create 创建清理日志
|
||||
func (s *DataCleanupLogStore) Create(ctx context.Context, log *model.DataCleanupLog) error {
|
||||
return s.db.WithContext(ctx).Create(log).Error
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取清理日志
|
||||
func (s *DataCleanupLogStore) GetByID(ctx context.Context, id uint) (*model.DataCleanupLog, error) {
|
||||
var log model.DataCleanupLog
|
||||
if err := s.db.WithContext(ctx).First(&log, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &log, nil
|
||||
}
|
||||
|
||||
// Update 更新清理日志
|
||||
func (s *DataCleanupLogStore) Update(ctx context.Context, log *model.DataCleanupLog) error {
|
||||
return s.db.WithContext(ctx).Save(log).Error
|
||||
}
|
||||
|
||||
// List 分页获取清理日志
|
||||
func (s *DataCleanupLogStore) List(ctx context.Context, page, pageSize int, tableName string) ([]*model.DataCleanupLog, int64, error) {
|
||||
var logs []*model.DataCleanupLog
|
||||
var total int64
|
||||
|
||||
query := s.db.WithContext(ctx).Model(&model.DataCleanupLog{})
|
||||
if tableName != "" {
|
||||
query = query.Where("table_name = ?", tableName)
|
||||
}
|
||||
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
offset := (page - 1) * pageSize
|
||||
if err := query.Order("started_at DESC").Offset(offset).Limit(pageSize).Find(&logs).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return logs, total, nil
|
||||
}
|
||||
|
||||
// GetLatestRunning 获取正在运行的清理任务
|
||||
func (s *DataCleanupLogStore) GetLatestRunning(ctx context.Context, tableName string) (*model.DataCleanupLog, error) {
|
||||
var log model.DataCleanupLog
|
||||
if err := s.db.WithContext(ctx).Where("table_name = ? AND status = 'running'", tableName).First(&log).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &log, nil
|
||||
}
|
||||
|
||||
// DeleteOldRecords 删除指定表的过期记录
|
||||
// 返回删除的记录数
|
||||
func (s *DataCleanupLogStore) DeleteOldRecords(ctx context.Context, tableName string, retentionDays int, batchSize int) (int64, error) {
|
||||
cutoffTime := time.Now().AddDate(0, 0, -retentionDays)
|
||||
|
||||
// 根据表名执行不同的删除逻辑
|
||||
var result *gorm.DB
|
||||
switch tableName {
|
||||
case "tb_polling_alert_history":
|
||||
result = s.db.WithContext(ctx).
|
||||
Where("created_at < ?", cutoffTime).
|
||||
Limit(batchSize).
|
||||
Delete(&model.PollingAlertHistory{})
|
||||
case "tb_data_cleanup_log":
|
||||
result = s.db.WithContext(ctx).
|
||||
Where("started_at < ?", cutoffTime).
|
||||
Limit(batchSize).
|
||||
Delete(&model.DataCleanupLog{})
|
||||
case "tb_polling_manual_trigger_log":
|
||||
result = s.db.WithContext(ctx).
|
||||
Where("triggered_at < ?", cutoffTime).
|
||||
Limit(batchSize).
|
||||
Delete(&model.PollingManualTriggerLog{})
|
||||
default:
|
||||
// 对于其他表,使用原始 SQL
|
||||
result = s.db.WithContext(ctx).Exec(
|
||||
"DELETE FROM "+tableName+" WHERE created_at < ? LIMIT ?",
|
||||
cutoffTime, batchSize,
|
||||
)
|
||||
}
|
||||
|
||||
if result.Error != nil {
|
||||
return 0, result.Error
|
||||
}
|
||||
return result.RowsAffected, nil
|
||||
}
|
||||
|
||||
// CountOldRecords 统计指定表的过期记录数
|
||||
func (s *DataCleanupLogStore) CountOldRecords(ctx context.Context, tableName string, retentionDays int) (int64, error) {
|
||||
cutoffTime := time.Now().AddDate(0, 0, -retentionDays)
|
||||
|
||||
var count int64
|
||||
// 根据表名查询不同的时间字段
|
||||
switch tableName {
|
||||
case "tb_polling_alert_history":
|
||||
if err := s.db.WithContext(ctx).Model(&model.PollingAlertHistory{}).
|
||||
Where("created_at < ?", cutoffTime).
|
||||
Count(&count).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "tb_data_cleanup_log":
|
||||
if err := s.db.WithContext(ctx).Model(&model.DataCleanupLog{}).
|
||||
Where("started_at < ?", cutoffTime).
|
||||
Count(&count).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "tb_polling_manual_trigger_log":
|
||||
if err := s.db.WithContext(ctx).Model(&model.PollingManualTriggerLog{}).
|
||||
Where("triggered_at < ?", cutoffTime).
|
||||
Count(&count).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
default:
|
||||
// 对于其他表,使用原始 SQL
|
||||
row := s.db.WithContext(ctx).Raw(
|
||||
"SELECT COUNT(*) FROM "+tableName+" WHERE created_at < ?",
|
||||
cutoffTime,
|
||||
).Row()
|
||||
if err := row.Scan(&count); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
52
internal/store/postgres/polling_concurrency_config_store.go
Normal file
52
internal/store/postgres/polling_concurrency_config_store.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
)
|
||||
|
||||
// PollingConcurrencyConfigStore 并发控制配置存储
|
||||
type PollingConcurrencyConfigStore struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewPollingConcurrencyConfigStore 创建并发控制配置存储实例
|
||||
func NewPollingConcurrencyConfigStore(db *gorm.DB) *PollingConcurrencyConfigStore {
|
||||
return &PollingConcurrencyConfigStore{db: db}
|
||||
}
|
||||
|
||||
// List 获取所有并发控制配置
|
||||
func (s *PollingConcurrencyConfigStore) List(ctx context.Context) ([]*model.PollingConcurrencyConfig, error) {
|
||||
var configs []*model.PollingConcurrencyConfig
|
||||
if err := s.db.WithContext(ctx).Find(&configs).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return configs, nil
|
||||
}
|
||||
|
||||
// GetByTaskType 根据任务类型获取配置
|
||||
func (s *PollingConcurrencyConfigStore) GetByTaskType(ctx context.Context, taskType string) (*model.PollingConcurrencyConfig, error) {
|
||||
var config model.PollingConcurrencyConfig
|
||||
if err := s.db.WithContext(ctx).Where("task_type = ?", taskType).First(&config).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
// Update 更新并发控制配置
|
||||
func (s *PollingConcurrencyConfigStore) Update(ctx context.Context, config *model.PollingConcurrencyConfig) error {
|
||||
return s.db.WithContext(ctx).Save(config).Error
|
||||
}
|
||||
|
||||
// UpdateMaxConcurrency 更新最大并发数
|
||||
func (s *PollingConcurrencyConfigStore) UpdateMaxConcurrency(ctx context.Context, taskType string, maxConcurrency int, updatedBy uint) error {
|
||||
return s.db.WithContext(ctx).Model(&model.PollingConcurrencyConfig{}).
|
||||
Where("task_type = ?", taskType).
|
||||
Updates(map[string]interface{}{
|
||||
"max_concurrency": maxConcurrency,
|
||||
"updated_by": updatedBy,
|
||||
}).Error
|
||||
}
|
||||
125
internal/store/postgres/polling_config_store.go
Normal file
125
internal/store/postgres/polling_config_store.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store"
|
||||
)
|
||||
|
||||
// PollingConfigStore 轮询配置存储
|
||||
type PollingConfigStore struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewPollingConfigStore 创建轮询配置存储实例
|
||||
func NewPollingConfigStore(db *gorm.DB) *PollingConfigStore {
|
||||
return &PollingConfigStore{db: db}
|
||||
}
|
||||
|
||||
// Create 创建轮询配置
|
||||
func (s *PollingConfigStore) Create(ctx context.Context, config *model.PollingConfig) error {
|
||||
return s.db.WithContext(ctx).Create(config).Error
|
||||
}
|
||||
|
||||
// GetByID 根据 ID 获取轮询配置
|
||||
func (s *PollingConfigStore) GetByID(ctx context.Context, id uint) (*model.PollingConfig, error) {
|
||||
var config model.PollingConfig
|
||||
if err := s.db.WithContext(ctx).First(&config, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
// Update 更新轮询配置
|
||||
func (s *PollingConfigStore) Update(ctx context.Context, config *model.PollingConfig) error {
|
||||
return s.db.WithContext(ctx).Save(config).Error
|
||||
}
|
||||
|
||||
// Delete 删除轮询配置
|
||||
func (s *PollingConfigStore) Delete(ctx context.Context, id uint) error {
|
||||
return s.db.WithContext(ctx).Delete(&model.PollingConfig{}, id).Error
|
||||
}
|
||||
|
||||
// List 列表查询轮询配置
|
||||
func (s *PollingConfigStore) List(ctx context.Context, opts *store.QueryOptions, filters map[string]interface{}) ([]*model.PollingConfig, int64, error) {
|
||||
var configs []*model.PollingConfig
|
||||
var total int64
|
||||
|
||||
query := s.db.WithContext(ctx).Model(&model.PollingConfig{})
|
||||
|
||||
// 过滤条件
|
||||
if status, ok := filters["status"]; ok {
|
||||
query = query.Where("status = ?", status)
|
||||
}
|
||||
if cardCondition, ok := filters["card_condition"].(string); ok && cardCondition != "" {
|
||||
query = query.Where("card_condition = ?", cardCondition)
|
||||
}
|
||||
if cardCategory, ok := filters["card_category"].(string); ok && cardCategory != "" {
|
||||
query = query.Where("card_category = ?", cardCategory)
|
||||
}
|
||||
if carrierID, ok := filters["carrier_id"]; ok {
|
||||
query = query.Where("carrier_id = ?", carrierID)
|
||||
}
|
||||
if configName, ok := filters["config_name"].(string); ok && configName != "" {
|
||||
query = query.Where("config_name LIKE ?", "%"+configName+"%")
|
||||
}
|
||||
|
||||
// 统计总数
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 分页
|
||||
if opts == nil {
|
||||
opts = store.DefaultQueryOptions()
|
||||
}
|
||||
offset := (opts.Page - 1) * opts.PageSize
|
||||
query = query.Offset(offset).Limit(opts.PageSize)
|
||||
|
||||
// 排序
|
||||
if opts.OrderBy != "" {
|
||||
query = query.Order(opts.OrderBy)
|
||||
} else {
|
||||
query = query.Order("priority ASC, id DESC")
|
||||
}
|
||||
|
||||
if err := query.Find(&configs).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return configs, total, nil
|
||||
}
|
||||
|
||||
// ListEnabled 获取所有启用的轮询配置(按优先级排序)
|
||||
func (s *PollingConfigStore) ListEnabled(ctx context.Context) ([]*model.PollingConfig, error) {
|
||||
var configs []*model.PollingConfig
|
||||
if err := s.db.WithContext(ctx).
|
||||
Where("status = ?", 1).
|
||||
Order("priority ASC").
|
||||
Find(&configs).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return configs, nil
|
||||
}
|
||||
|
||||
// GetByName 根据名称获取轮询配置
|
||||
func (s *PollingConfigStore) GetByName(ctx context.Context, name string) (*model.PollingConfig, error) {
|
||||
var config model.PollingConfig
|
||||
if err := s.db.WithContext(ctx).Where("config_name = ?", name).First(&config).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
// UpdateStatus 更新配置状态
|
||||
func (s *PollingConfigStore) UpdateStatus(ctx context.Context, id uint, status int16, updatedBy uint) error {
|
||||
return s.db.WithContext(ctx).Model(&model.PollingConfig{}).
|
||||
Where("id = ?", id).
|
||||
Updates(map[string]interface{}{
|
||||
"status": status,
|
||||
"updated_by": updatedBy,
|
||||
}).Error
|
||||
}
|
||||
108
internal/store/postgres/polling_manual_trigger_store.go
Normal file
108
internal/store/postgres/polling_manual_trigger_store.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
)
|
||||
|
||||
// PollingManualTriggerLogStore 手动触发日志存储
|
||||
type PollingManualTriggerLogStore struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewPollingManualTriggerLogStore 创建手动触发日志存储实例
|
||||
func NewPollingManualTriggerLogStore(db *gorm.DB) *PollingManualTriggerLogStore {
|
||||
return &PollingManualTriggerLogStore{db: db}
|
||||
}
|
||||
|
||||
// Create 创建手动触发日志
|
||||
func (s *PollingManualTriggerLogStore) Create(ctx context.Context, log *model.PollingManualTriggerLog) error {
|
||||
return s.db.WithContext(ctx).Create(log).Error
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取手动触发日志
|
||||
func (s *PollingManualTriggerLogStore) GetByID(ctx context.Context, id uint) (*model.PollingManualTriggerLog, error) {
|
||||
var log model.PollingManualTriggerLog
|
||||
if err := s.db.WithContext(ctx).First(&log, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &log, nil
|
||||
}
|
||||
|
||||
// Update 更新手动触发日志
|
||||
func (s *PollingManualTriggerLogStore) Update(ctx context.Context, log *model.PollingManualTriggerLog) error {
|
||||
return s.db.WithContext(ctx).Save(log).Error
|
||||
}
|
||||
|
||||
// List 分页获取手动触发日志
|
||||
func (s *PollingManualTriggerLogStore) List(ctx context.Context, page, pageSize int, taskType string, triggeredBy *uint) ([]*model.PollingManualTriggerLog, int64, error) {
|
||||
var logs []*model.PollingManualTriggerLog
|
||||
var total int64
|
||||
|
||||
query := s.db.WithContext(ctx).Model(&model.PollingManualTriggerLog{})
|
||||
if taskType != "" {
|
||||
query = query.Where("task_type = ?", taskType)
|
||||
}
|
||||
if triggeredBy != nil {
|
||||
query = query.Where("triggered_by = ?", *triggeredBy)
|
||||
}
|
||||
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
offset := (page - 1) * pageSize
|
||||
if err := query.Order("triggered_at DESC").Offset(offset).Limit(pageSize).Find(&logs).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return logs, total, nil
|
||||
}
|
||||
|
||||
// GetRunning 获取正在运行的触发任务
|
||||
func (s *PollingManualTriggerLogStore) GetRunning(ctx context.Context, triggeredBy uint) ([]*model.PollingManualTriggerLog, error) {
|
||||
var logs []*model.PollingManualTriggerLog
|
||||
if err := s.db.WithContext(ctx).
|
||||
Where("triggered_by = ? AND status IN ('pending', 'processing')", triggeredBy).
|
||||
Order("triggered_at DESC").
|
||||
Find(&logs).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return logs, nil
|
||||
}
|
||||
|
||||
// UpdateProgress 更新触发进度
|
||||
func (s *PollingManualTriggerLogStore) UpdateProgress(ctx context.Context, id uint, processedCount, successCount, failedCount int) error {
|
||||
return s.db.WithContext(ctx).Model(&model.PollingManualTriggerLog{}).
|
||||
Where("id = ?", id).
|
||||
Updates(map[string]any{
|
||||
"processed_count": processedCount,
|
||||
"success_count": successCount,
|
||||
"failed_count": failedCount,
|
||||
}).Error
|
||||
}
|
||||
|
||||
// UpdateStatus 更新触发状态
|
||||
func (s *PollingManualTriggerLogStore) UpdateStatus(ctx context.Context, id uint, status string) error {
|
||||
updates := map[string]any{"status": status}
|
||||
if status == "completed" || status == "cancelled" {
|
||||
updates["completed_at"] = gorm.Expr("CURRENT_TIMESTAMP")
|
||||
}
|
||||
return s.db.WithContext(ctx).Model(&model.PollingManualTriggerLog{}).
|
||||
Where("id = ?", id).
|
||||
Updates(updates).Error
|
||||
}
|
||||
|
||||
// CountTodayTriggers 统计今日触发次数
|
||||
func (s *PollingManualTriggerLogStore) CountTodayTriggers(ctx context.Context, triggeredBy uint) (int64, error) {
|
||||
var count int64
|
||||
if err := s.db.WithContext(ctx).Model(&model.PollingManualTriggerLog{}).
|
||||
Where("triggered_by = ? AND DATE(triggered_at) = CURRENT_DATE", triggeredBy).
|
||||
Count(&count).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
Reference in New Issue
Block a user