Files
junhong_cmp_fiber/cmd/silence-backfill/main.go
邹进 62ec94adec
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Failing after 38s
refactor: 出库时间更名为首次购买套餐时间,换货新资产不再按换货完成时间记录
- 字段 outbound_at → first_purchase_at,记录表 tb_asset_outbound_record → tb_asset_first_purchase_record,接口 /outbound-records → /first-purchase-records,筛选参数与导出列同步更名
- 换货后的新资产不因换货写入,在当前世代真正支付套餐订单时才记录;记录来源只保留 order / backfill,去掉换货单字段
- 迁移 000266 重命名列、表、索引与约束;同步 Spec、设计、任务与前端接口变更说明

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 16:47:02 +09:00

116 lines
4.5 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.
// silence-backfill 是沉默期与首次购买套餐时间历史回填工具(add-package-silence-period,技术方案 §6、§17.4)。
//
// 用法(数据库连接只读取显式传入的 DB_* 环境变量):
//
// DB_HOST=... DB_PORT=... DB_USER=... DB_PASSWORD=... DB_NAME=... DB_SSLMODE=disable \
// go run ./cmd/silence-backfill -target silence # 只输出影响清单(默认 dry-run)
// go run ./cmd/silence-backfill -target silence -execute # 业务确认清单后执行
// go run ./cmd/silence-backfill -target first_purchase # 首次购买套餐时间:只输出可回填数与无订单数
// go run ./cmd/silence-backfill -target first_purchase -execute
//
// 两种回填都只处理为空的记录、分批执行、可重复执行;可在沉默期总开关关闭时运行。
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"os"
"strings"
"time"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/logger"
packagesilenceapp "github.com/break/junhong_cmp_fiber/internal/application/packagesilence"
auditInfra "github.com/break/junhong_cmp_fiber/internal/infrastructure/audit"
"github.com/break/junhong_cmp_fiber/pkg/auditcontext"
"github.com/break/junhong_cmp_fiber/pkg/constants"
)
func main() {
target := flag.String("target", "", "回填对象:silence(套餐沉默期)/ first_purchase(资产首次购买套餐时间)")
execute := flag.Bool("execute", false, "执行写入;不传时只输出影响清单")
flag.Parse()
if *target != "silence" && *target != "first_purchase" {
fail("必须指定 -target silence 或 -target first_purchase")
}
db, err := openDB()
if err != nil {
fail(err.Error())
}
ctx := auditcontext.With(context.Background(), auditcontext.Context{
ActorKind: constants.AuditActorSystemTask, ActorID: "silence-backfill", ActorName: "沉默期历史回填工具",
Source: constants.AuditSourceWorker,
})
backfill := packagesilenceapp.NewBackfill(db, auditInfra.NewWriter(auditInfra.NewRegistry(), nil))
started := time.Now()
mode := "dry-run(只出清单)"
if *execute {
mode = "execute(执行写入)"
}
fmt.Printf("目标库:%s,对象:%s,模式:%s\n", os.Getenv("DB_NAME"), *target, mode)
var report any
switch *target {
case "silence":
silence, err := backfill.SilenceBackfill(ctx, *execute)
if err != nil {
fail(err.Error())
}
printSilence(silence)
report = silence
case "first_purchase":
firstPurchase, err := backfill.FirstPurchaseBackfill(ctx, *execute)
if err != nil {
fail(err.Error())
}
report = firstPurchase
}
encoded, _ := json.MarshalIndent(report, "", " ")
fmt.Println(string(encoded))
fmt.Printf("耗时:%s\n", time.Since(started).Round(time.Millisecond))
}
func printSilence(report *packagesilenceapp.SilenceBackfillReport) {
fmt.Printf("待回填记录 %d,可回填 %d,未配置默认沉默期 %d(不回填)\n", report.Candidates, report.Backfillable, report.UnconfiguredCount)
fmt.Printf("期内已实名(记为结束)%d,其中实名时间为空 %d;仍在期内 %d;打开总开关后将过期 %d,其中无法付费延期 %d\n",
report.EndedInPeriod, report.RealnamedWithoutTime, report.Waiting, report.WillExpire, report.WillExpireNoPaidExtend)
fmt.Println("按代理分布:")
for _, item := range packagesilenceapp.SortedCounts(report.ByShop) {
fmt.Printf(" %s:%d\n", item.Name, item.Count)
}
fmt.Println("按套餐分布:")
for _, item := range packagesilenceapp.SortedCounts(report.ByPackage) {
fmt.Printf(" %s:%d\n", item.Name, item.Count)
}
if len(report.UnconfiguredPackages) > 0 {
fmt.Println("未配置默认沉默期的套餐:")
for _, item := range packagesilenceapp.SortedCounts(report.UnconfiguredPackages) {
fmt.Printf(" %s:%d\n", item.Name, item.Count)
}
}
}
func openDB() (*gorm.DB, error) {
required := []string{"DB_HOST", "DB_PORT", "DB_USER", "DB_PASSWORD", "DB_NAME"}
for _, key := range required {
if strings.TrimSpace(os.Getenv(key)) == "" {
return nil, fmt.Errorf("缺少环境变量 %s(只使用显式传入的数据库连接)", key)
}
}
sslMode := os.Getenv("DB_SSLMODE")
if sslMode == "" {
sslMode = "disable"
}
dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s TimeZone=Asia/Shanghai",
os.Getenv("DB_HOST"), os.Getenv("DB_PORT"), os.Getenv("DB_USER"), os.Getenv("DB_PASSWORD"), os.Getenv("DB_NAME"), sslMode)
return gorm.Open(postgres.Open(dsn), &gorm.Config{Logger: logger.Default.LogMode(logger.Warn)})
}
func fail(message string) {
fmt.Fprintln(os.Stderr, "回填失败:"+message)
os.Exit(1)
}