Files
junhong_cmp_fiber/internal/store/postgres/package_store.go
huang 931e140e8e
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m35s
feat: 实现 IoT 卡轮询系统(支持千万级卡规模)
实现功能:
- 实名状态检查轮询(可配置间隔)
- 卡流量检查轮询(支持跨月流量追踪)
- 套餐检查与超额自动停机
- 分布式并发控制(Redis 信号量)
- 手动触发轮询(单卡/批量/条件筛选)
- 数据清理配置与执行
- 告警规则与历史记录
- 实时监控统计(队列/性能/并发)

性能优化:
- Redis 缓存卡信息,减少 DB 查询
- Pipeline 批量写入 Redis
- 异步流量记录写入
- 渐进式初始化(10万卡/批)

压测工具(scripts/benchmark/):
- Mock Gateway 模拟上游服务
- 测试卡生成器
- 配置初始化脚本
- 实时监控脚本

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 17:32:44 +08:00

131 lines
4.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package postgres
import (
"context"
"gorm.io/gorm"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/store"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/middleware"
)
type PackageStore struct {
db *gorm.DB
}
func NewPackageStore(db *gorm.DB) *PackageStore {
return &PackageStore{db: db}
}
func (s *PackageStore) Create(ctx context.Context, pkg *model.Package) error {
return s.db.WithContext(ctx).Create(pkg).Error
}
func (s *PackageStore) GetByID(ctx context.Context, id uint) (*model.Package, error) {
var pkg model.Package
if err := s.db.WithContext(ctx).First(&pkg, id).Error; err != nil {
return nil, err
}
return &pkg, nil
}
func (s *PackageStore) GetByCode(ctx context.Context, code string) (*model.Package, error) {
var pkg model.Package
if err := s.db.WithContext(ctx).Where("package_code = ?", code).First(&pkg).Error; err != nil {
return nil, err
}
return &pkg, nil
}
func (s *PackageStore) Update(ctx context.Context, pkg *model.Package) error {
return s.db.WithContext(ctx).Save(pkg).Error
}
func (s *PackageStore) Delete(ctx context.Context, id uint) error {
return s.db.WithContext(ctx).Delete(&model.Package{}, id).Error
}
func (s *PackageStore) List(ctx context.Context, opts *store.QueryOptions, filters map[string]interface{}) ([]*model.Package, int64, error) {
var packages []*model.Package
var total int64
query := s.db.WithContext(ctx).Model(&model.Package{})
// 代理用户额外过滤:只能看到已分配的套餐
userType := middleware.GetUserTypeFromContext(ctx)
shopID := middleware.GetShopIDFromContext(ctx)
if userType == constants.UserTypeAgent && shopID > 0 {
query = query.Joins("INNER JOIN tb_shop_package_allocation ON tb_shop_package_allocation.package_id = tb_package.id").
Where("tb_shop_package_allocation.shop_id = ? AND tb_shop_package_allocation.status = ?",
shopID, constants.StatusEnabled)
}
if packageName, ok := filters["package_name"].(string); ok && packageName != "" {
query = query.Where("tb_package.package_name LIKE ?", "%"+packageName+"%")
}
if seriesID, ok := filters["series_id"].(uint); ok && seriesID > 0 {
query = query.Where("tb_package.series_id = ?", seriesID)
}
if status, ok := filters["status"]; ok {
query = query.Where("tb_package.status = ?", status)
}
if shelfStatus, ok := filters["shelf_status"]; ok {
query = query.Where("tb_package.shelf_status = ?", shelfStatus)
}
if packageType, ok := filters["package_type"].(string); ok && packageType != "" {
query = query.Where("tb_package.package_type = ?", packageType)
}
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)
}
if err := query.Find(&packages).Error; err != nil {
return nil, 0, err
}
return packages, total, nil
}
func (s *PackageStore) UpdateStatus(ctx context.Context, id uint, status int) error {
return s.db.WithContext(ctx).Model(&model.Package{}).Where("id = ?", id).Update("status", status).Error
}
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
}