refactor: 流量系统重构 — 增量累加算法 + 日粒度缓冲 + 旧详单清理
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m16s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m16s
核心改造: - 增量算法:流量计算从覆盖式改为增量累加(gateway - lastReading),支持上游运营商重置检测 - 日流量缓冲:insertDataUsageRecord 改为 Redis INCRBYFLOAT,每日凌晨落盘到 tb_card_daily_usage - 运营商:新增 data_reset_day 字段(联通=27,其余=1) - IoT卡:新增 last_gateway_reading_mb 字段存储上次网关读数 - 查询层:新建 TrafficQueryService 合并 Redis(今日)+ DB(历史)数据源 - 清理:删除 DataUsageRecord model/store,移除 polling_handler 旧引用 迁移:000094-000097(carrier字段、iot_card字段、数据初始化、日流量表)
This commit is contained in:
57
internal/store/postgres/card_daily_usage_store.go
Normal file
57
internal/store/postgres/card_daily_usage_store.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
)
|
||||
|
||||
// CardDailyUsageStore 卡日流量记录存储
|
||||
// 同时供落盘任务和查询层使用
|
||||
type CardDailyUsageStore struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewCardDailyUsageStore(db *gorm.DB) *CardDailyUsageStore {
|
||||
return &CardDailyUsageStore{db: db}
|
||||
}
|
||||
|
||||
// Upsert 覆盖写入日流量记录(幂等安全)
|
||||
// ON CONFLICT (iot_card_id, date) DO UPDATE SET usage_mb = EXCLUDED.usage_mb
|
||||
func (s *CardDailyUsageStore) Upsert(ctx context.Context, record *model.CardDailyUsage) error {
|
||||
return s.db.WithContext(ctx).
|
||||
Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "iot_card_id"}, {Name: "date"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"usage_mb", "updated_at"}),
|
||||
}).
|
||||
Create(record).Error
|
||||
}
|
||||
|
||||
// UpsertBatch 批量覆盖写入日流量记录
|
||||
func (s *CardDailyUsageStore) UpsertBatch(ctx context.Context, records []*model.CardDailyUsage) error {
|
||||
if len(records) == 0 {
|
||||
return nil
|
||||
}
|
||||
return s.db.WithContext(ctx).
|
||||
Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "iot_card_id"}, {Name: "date"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"usage_mb", "updated_at"}),
|
||||
}).
|
||||
CreateInBatches(records, 200).Error
|
||||
}
|
||||
|
||||
// ListByCardIDAndDateRange 查询卡在日期范围内的日流量记录
|
||||
func (s *CardDailyUsageStore) ListByCardIDAndDateRange(ctx context.Context, cardID uint, startDate, endDate time.Time) ([]*model.CardDailyUsage, error) {
|
||||
var records []*model.CardDailyUsage
|
||||
if err := s.db.WithContext(ctx).
|
||||
Where("iot_card_id = ? AND date >= ? AND date <= ?", cardID, startDate, endDate).
|
||||
Order("date ASC").
|
||||
Find(&records).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user