Files
junhong_cmp_fiber/scripts/benchmark/generate_cards.go
huang 18daeae65a
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m17s
feat: 钱包系统分离 - 代理钱包与卡钱包完全隔离
## 变更概述
将统一钱包系统拆分为代理钱包和卡钱包两个独立系统,实现数据表和代码层面的完全隔离。

## 数据库变更
- 新增 6 张表:tb_agent_wallet、tb_agent_wallet_transaction、tb_agent_recharge_record、tb_card_wallet、tb_card_wallet_transaction、tb_card_recharge_record
- 删除 3 张旧表:tb_wallet、tb_wallet_transaction、tb_recharge_record
- 代理钱包:按 (shop_id, wallet_type) 唯一标识,支持主钱包和分佣钱包
- 卡钱包:按 (resource_type, resource_id) 唯一标识,支持物联网卡和设备

## 代码变更
- Model 层:新增 AgentWallet、AgentWalletTransaction、AgentRechargeRecord、CardWallet、CardWalletTransaction、CardRechargeRecord 模型
- Store 层:新增 6 个独立 Store,支持事务、乐观锁、Redis 缓存
- Service 层:重构 commission_calculation、commission_withdrawal、order、recharge 等 8 个服务
- Bootstrap 层:更新 Store 和 Service 依赖注入
- 常量层:按钱包类型重新组织常量和 Redis Key 生成函数

## 技术特性
- 乐观锁:使用 version 字段防止并发冲突
- 多租户:支持 shop_id_tag 和 enterprise_id_tag 过滤
- 事务管理:所有余额变动使用事务保证 ACID
- 缓存策略:Cache-Aside 模式,余额变动后删除缓存

## 业务影响
- 代理钱包和卡钱包业务完全隔离,互不影响
- 为独立监控、优化、扩展打下基础
- 提升代理钱包的稳定性和独立性

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-25 09:51:00 +08:00

225 lines
6.1 KiB
Go
Raw Permalink 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.
//go:build ignore
// +build ignore
package main
import (
"context"
"flag"
"fmt"
"log"
"math/rand"
"os"
"sync"
"sync/atomic"
"time"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
// IotCard 简化的卡模型
type IotCard struct {
ID uint `gorm:"primaryKey"`
ICCID string `gorm:"column:iccid;uniqueIndex:idx_iot_card_iccid,where:deleted_at IS NULL"`
CardCategory string `gorm:"column:card_category;default:normal"`
CarrierID uint `gorm:"column:carrier_id"`
Status int `gorm:"column:status;default:1"`
ActivationStatus int `gorm:"column:activation_status;default:0"`
RealNameStatus int `gorm:"column:real_name_status;default:0"`
NetworkStatus int `gorm:"column:network_status;default:0"`
EnablePolling bool `gorm:"column:enable_polling;default:true"`
Creator uint `gorm:"column:creator"`
Updater uint `gorm:"column:updater"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time `gorm:"index"`
}
func (IotCard) TableName() string {
return "tb_iot_card"
}
var (
totalCards = flag.Int("total", 10000000, "要生成的卡数量")
batchSize = flag.Int("batch", 10000, "每批插入数量")
workers = flag.Int("workers", 10, "并行 worker 数量")
startICCID = flag.String("start", "898600000", "起始 ICCID 前缀9位总长度不超过20位")
clearOld = flag.Bool("clear", false, "是否清空现有测试卡")
insertedCount int64
startTime time.Time
)
func main() {
flag.Parse()
fmt.Println("=== 生成测试卡数据 ===")
fmt.Printf("目标数量: %d 张\n", *totalCards)
fmt.Printf("批次大小: %d\n", *batchSize)
fmt.Printf("并行数: %d\n", *workers)
fmt.Println("")
// 连接数据库
dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
os.Getenv("JUNHONG_DATABASE_HOST"),
os.Getenv("JUNHONG_DATABASE_PORT"),
os.Getenv("JUNHONG_DATABASE_USER"),
os.Getenv("JUNHONG_DATABASE_PASSWORD"),
os.Getenv("JUNHONG_DATABASE_DBNAME"),
)
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
})
if err != nil {
log.Fatalf("连接数据库失败: %v", err)
}
// 配置连接池
sqlDB, _ := db.DB()
sqlDB.SetMaxOpenConns(50)
sqlDB.SetMaxIdleConns(25)
fmt.Println("✓ 数据库连接成功")
// 检查现有卡数量
var existingCount int64
db.Model(&IotCard{}).Count(&existingCount)
fmt.Printf("现有卡数量: %d\n", existingCount)
if *clearOld {
fmt.Println("清空现有测试卡...")
// 只删除 ICCID 以 898600000 开头的测试卡
db.Exec("DELETE FROM tb_iot_card WHERE iccid LIKE '898600000%'")
fmt.Println("✓ 清空完成")
}
// 开始生成
startTime = time.Now()
ctx := context.Background()
// 创建任务通道
taskCh := make(chan int, *workers*2)
var wg sync.WaitGroup
// 启动 worker
for i := 0; i < *workers; i++ {
wg.Add(1)
go func(workerID int) {
defer wg.Done()
worker(ctx, db, workerID, taskCh)
}(i)
}
// 分发任务
batches := *totalCards / *batchSize
for i := 0; i < batches; i++ {
taskCh <- i
}
close(taskCh)
// 等待完成
wg.Wait()
elapsed := time.Since(startTime)
fmt.Println("")
fmt.Println("=== 生成完成 ===")
fmt.Printf("总插入: %d 张\n", atomic.LoadInt64(&insertedCount))
fmt.Printf("耗时: %v\n", elapsed)
fmt.Printf("速度: %.0f 张/秒\n", float64(atomic.LoadInt64(&insertedCount))/elapsed.Seconds())
// 验证
var finalCount int64
db.Model(&IotCard{}).Count(&finalCount)
fmt.Printf("数据库总卡数: %d\n", finalCount)
}
func worker(ctx context.Context, db *gorm.DB, workerID int, taskCh <-chan int) {
rng := rand.New(rand.NewSource(time.Now().UnixNano() + int64(workerID)))
for batchIndex := range taskCh {
cards := generateBatch(rng, *startICCID, batchIndex, *batchSize)
// 批量插入
err := db.WithContext(ctx).CreateInBatches(cards, 1000).Error
if err != nil {
log.Printf("Worker %d 插入失败: %v", workerID, err)
continue
}
count := atomic.AddInt64(&insertedCount, int64(len(cards)))
// 进度报告
if count%100000 == 0 {
elapsed := time.Since(startTime).Seconds()
speed := float64(count) / elapsed
eta := float64(*totalCards-int(count)) / speed
fmt.Printf("进度: %d/%d (%.1f%%) | 速度: %.0f/秒 | ETA: %.0f秒\n",
count, *totalCards, float64(count)*100/float64(*totalCards), speed, eta)
}
}
}
func generateBatch(rng *rand.Rand, iccidPrefix string, batchIndex int, size int) []IotCard {
cards := make([]IotCard, size)
now := time.Now()
for i := 0; i < size; i++ {
// 使用前缀 + 序号生成 ICCID总长度 20 位)
// 例如: 898600000 (9位) + 00000000001 (11位) = 20 位
cardIndex := batchIndex*size + i
iccid := fmt.Sprintf("%s%011d", iccidPrefix, cardIndex)
// 随机分配状态(匹配轮询配置条件)
// 实名状态: 0=未实名, 1=实名中, 2=已实名
// 网络状态: 0=停机, 1=正常
// 配置匹配逻辑:
// - not_real_name: RealNameStatus == 0 或 1
// - real_name: RealNameStatus == 2 && NetworkStatus != 1
// - activated: RealNameStatus == 2 && NetworkStatus == 1
r := rng.Float64()
var realNameStatus, activationStatus, networkStatus int
if r < 0.10 {
// 10% 未实名 -> 匹配 not_real_name 配置
realNameStatus = 0
activationStatus = 0
networkStatus = 0
} else if r < 0.30 {
// 20% 已实名未激活 -> 匹配 real_name 配置
realNameStatus = 2
activationStatus = 0
networkStatus = 0
} else {
// 70% 已激活 -> 匹配 activated 配置(流量+套餐检查)
realNameStatus = 2
activationStatus = 1
networkStatus = 1
}
// 随机卡类型
cardCategory := "normal"
if rng.Float64() < 0.05 {
cardCategory = "industry"
}
cards[i] = IotCard{
ICCID: iccid,
CardCategory: cardCategory,
CarrierID: uint(rng.Intn(3) + 1), // 1-3 运营商
Status: 1,
ActivationStatus: activationStatus,
RealNameStatus: realNameStatus,
NetworkStatus: networkStatus,
EnablePolling: true,
Creator: 1,
Updater: 1,
CreatedAt: now,
UpdatedAt: now,
}
}
return cards
}