Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Failing after 38s
- 字段 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>
116 lines
4.5 KiB
Go
116 lines
4.5 KiB
Go
// 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)
|
||
}
|