2026-06-27 18:22:14 +08:00

108 lines
3.4 KiB
Go

package main
import (
"context"
"flag"
"fmt"
"log"
"os"
"os/signal"
"strings"
"syscall"
"time"
"hyapp/pkg/appcode"
"hyapp/services/wallet-service/internal/config"
walletservice "hyapp/services/wallet-service/internal/service/wallet"
mysqlstorage "hyapp/services/wallet-service/internal/storage/mysql"
)
// main 只做一次性礼物墙回填:复用 wallet-service 的投影状态机,不手写聚合 SQL。
func main() {
configPath := flag.String("config", "services/wallet-service/configs/config.yaml", "wallet-service config path")
appCode := flag.String("app-code", appcode.Default, "app code to backfill")
workerID := flag.String("worker-id", "gift-wall-backfill", "projection worker id")
batchSize := flag.Int("batch-size", 50, "events claimed per batch")
maxBatches := flag.Int("max-batches", 0, "maximum batches to process; 0 means until drained")
progressEvery := flag.Int("progress-every", 10, "print progress every N non-empty batches")
lockTTL := flag.Duration("lock-ttl", 30*time.Second, "projection lock ttl")
sleep := flag.Duration("sleep", 200*time.Millisecond, "pause between non-empty batches")
dryRun := flag.Bool("dry-run", false, "load config and ping mysql without processing")
flag.Parse()
normalizedAppCode := appcode.Normalize(strings.TrimSpace(*appCode))
if normalizedAppCode == "" {
log.Fatal("app-code is required")
}
normalizedWorkerID := strings.TrimSpace(*workerID)
if normalizedWorkerID == "" {
log.Fatal("worker-id is required")
}
cfg, err := config.Load(*configPath)
if err != nil {
log.Fatalf("load config: %v", err)
}
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
ctx = appcode.WithContext(ctx, normalizedAppCode)
repository, err := mysqlstorage.Open(ctx, cfg.MySQLDSN)
if err != nil {
log.Fatalf("open mysql: %v", err)
}
defer func() { _ = repository.Close() }()
if *dryRun {
if err := repository.Ping(ctx); err != nil {
log.Fatalf("ping mysql: %v", err)
}
fmt.Printf("dry_run_ok app_code=%s batch_size=%d worker_id=%s\n", normalizedAppCode, *batchSize, normalizedWorkerID)
return
}
if *progressEvery <= 0 {
*progressEvery = 1
}
svc := walletservice.New(repository)
total := 0
emptyBatches := 0
started := time.Now()
for batchNo := 1; ; batchNo++ {
if *maxBatches > 0 && batchNo > *maxBatches {
break
}
select {
case <-ctx.Done():
fmt.Fprintf(os.Stderr, "stopped app_code=%s total=%d batches=%d duration=%s\n", normalizedAppCode, total, batchNo-1, time.Since(started).Round(time.Millisecond))
return
default:
}
// ProjectPendingGiftWallEvents 会认领缺失或失败的事件,并在同一事务里写 user_gift_wall 与 done 标记。
processed, err := svc.ProjectPendingGiftWallEvents(ctx, normalizedWorkerID, *batchSize, *lockTTL)
if err != nil {
log.Fatalf("project batch %d: %v", batchNo, err)
}
total += processed
if processed == 0 || batchNo == 1 || batchNo%*progressEvery == 0 {
fmt.Printf("batch=%d processed=%d total=%d elapsed=%s\n", batchNo, processed, total, time.Since(started).Round(time.Millisecond))
}
if processed == 0 {
emptyBatches++
if emptyBatches >= 2 {
break
}
time.Sleep(*sleep)
continue
}
emptyBatches = 0
if *sleep > 0 {
time.Sleep(*sleep)
}
}
fmt.Printf("done app_code=%s total=%d duration=%s\n", normalizedAppCode, total, time.Since(started).Round(time.Millisecond))
}