feat: 实现 IoT 卡轮询系统(支持千万级卡规模)
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:
2026-02-05 17:32:44 +08:00
parent b11edde720
commit 931e140e8e
104 changed files with 16883 additions and 87 deletions

View 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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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
}