package main import ( "bufio" "context" "flag" "fmt" "os" "strconv" "strings" "bindbox-game/configs" "bindbox-game/internal/pkg/env" "bindbox-game/internal/pkg/logger" "bindbox-game/internal/repository/mysql" "bindbox-game/internal/repository/mysql/model" usersvc "bindbox-game/internal/service/user" welfaresvc "bindbox-game/internal/service/welfare_activity" "github.com/eiannone/keyboard" ) func main() { operator := flag.String("operator", "cli", "操作人标识") flag.Parse() env.Active() configs.Init() logg, err := logger.NewCustomLogger(logger.WithOutputInConsole(), logger.WithDebugLevel()) if err != nil { fmt.Printf("[ERR] 初始化日志失败: %v\n", err) os.Exit(1) } repo, err := mysql.New() if err != nil { fmt.Printf("[ERR] 初始化 MySQL 失败: %v\n", err) os.Exit(1) } defer repo.DbRClose() defer repo.DbWClose() userSvc := usersvc.New(logg, repo) welfareSvc := welfaresvc.New(logg, repo) ctx := context.Background() reader := bufio.NewReader(os.Stdin) fmt.Printf("[WARN] 当前环境: %s\n", env.Active().Value()) fmt.Println("[WARN] 该工具会写入真实已支付测试订单,仅用于 dev/fat/uat") userID, err := promptInt64(reader, "请输入用户ID") if err != nil { fmt.Printf("[ERR] 读取用户ID失败: %v\n", err) os.Exit(1) } user, err := userSvc.GetProfile(ctx, userID) if err != nil || user == nil { fmt.Printf("[ERR] 用户不存在: %v\n", err) os.Exit(1) } fmt.Println("[INFO] 用户信息") fmt.Printf("- 用户名: %s\n", fallback(user.Nickname, "-")) fmt.Printf("- 用户ID: %d\n", user.ID) fmt.Printf("- 手机号: %s\n", fallback(user.Mobile, "-")) packages, err := loadActivePackages(ctx, repo) if err != nil { fmt.Printf("[ERR] 加载活动次卡套餐失败: %v\n", err) os.Exit(1) } if len(packages) == 0 { fmt.Println("[WARN] 当前没有可用的活动次卡套餐") return } selectedPackage, err := selectPackage(packages) if err != nil { fmt.Printf("[ERR] 选择套餐失败: %v\n", err) os.Exit(1) } count, err := promptInt32(reader, "请输入购买次数") if err != nil { fmt.Printf("[ERR] 读取购买次数失败: %v\n", err) os.Exit(1) } purchase, err := userSvc.CreatePaidGamePassOrderForTest(ctx, usersvc.CreatePaidGamePassOrderForTestInput{ UserID: user.ID, PackageID: selectedPackage.ID, Count: count, Operator: *operator, }) if err != nil { fmt.Printf("[ERR] 模拟消费失败: %v\n", err) os.Exit(1) } fmt.Println("[OK] 模拟消费成功") fmt.Printf("- 订单号: %s\n", purchase.OrderNo) fmt.Printf("- 套餐: %s\n", purchase.PackageName) fmt.Printf("- 次数: %d\n", purchase.Count) fmt.Printf("- 支付金额: %s\n", formatAmount(purchase.TotalAmount)) fmt.Printf("- 支付时间: %s\n", purchase.PaidAt.Format("2006-01-02 15:04:05")) activities, err := welfareSvc.ListJoinableActivitiesForUser(ctx, user.ID) if err != nil { fmt.Printf("[ERR] 加载福利活动失败: %v\n", err) os.Exit(1) } if len(activities) == 0 { fmt.Println("[WARN] 当前没有可选的进行中福利活��") return } selected, err := selectActivity(activities) if err != nil { fmt.Printf("[ERR] 选择活动失败: %v\n", err) os.Exit(1) } fmt.Println("[INFO] 已选择福利活动") fmt.Printf("- 活动ID: %d\n", selected.ActivityID) fmt.Printf("- 活动名称: %s\n", selected.Title) fmt.Printf("- 当前消费/门槛: %s/%s\n", formatAmount(selected.CurrentPaid), formatAmount(selected.ThresholdAmount)) if err := welfareSvc.Join(ctx, selected.ActivityID, user.ID); err != nil { fmt.Printf("[ERR] 参与失败: %v\n", err) os.Exit(1) } fmt.Println("[OK] 参与成功") fmt.Printf("- 用户ID: %d\n", user.ID) fmt.Printf("- 活动ID: %d\n", selected.ActivityID) fmt.Printf("- 开奖时间: %s\n", selected.DrawTime.Format("2006-01-02 15:04:05")) fmt.Println("[INFO] 后续请等待系统自动开奖,再登录验证中奖、订单、背包和中奖名单") } func promptInt64(reader *bufio.Reader, label string) (int64, error) { fmt.Printf("%s: ", label) text, err := reader.ReadString('\n') if err != nil { return 0, err } return strconv.ParseInt(strings.TrimSpace(text), 10, 64) } func promptInt32(reader *bufio.Reader, label string) (int32, error) { value, err := promptInt64(reader, label) if err != nil { return 0, err } return int32(value), nil } func loadActivePackages(ctx context.Context, repo mysql.Repo) ([]*model.GamePassPackages, error) { var packages []*model.GamePassPackages err := repo.GetDbR().WithContext(ctx). Where("status = 1 AND deleted_at IS NULL"). Order("sort_order DESC, id ASC"). Find(&packages).Error return packages, err } func selectPackage(items []*model.GamePassPackages) (*model.GamePassPackages, error) { if err := keyboard.Open(); err != nil { return nil, err } defer keyboard.Close() index := 0 for { fmt.Println() fmt.Println("请选择活动次卡套餐(↑↓ 选择,Enter 确认,Esc 退出)") for i, item := range items { prefix := " " if i == index { prefix = "> " } activityScope := "全局" if item.ActivityID > 0 { activityScope = fmt.Sprintf("活动ID:%d", item.ActivityID) } fmt.Printf("%s%s | 套餐ID:%d | %s | %d次 | %s\n", prefix, item.Name, item.ID, activityScope, item.PassCount, formatAmount(item.Price)) } char, key, err := keyboard.GetKey() if err != nil { return nil, err } switch key { case keyboard.KeyArrowUp: if index > 0 { index-- } case keyboard.KeyArrowDown: if index < len(items)-1 { index++ } case keyboard.KeyEnter: return items[index], nil case keyboard.KeyEsc, keyboard.KeyCtrlC: return nil, fmt.Errorf("已取消选择") default: if char == 0 { continue } } } } func selectActivity(items []welfaresvc.JoinableActivityItem) (*welfaresvc.JoinableActivityItem, error) { if err := keyboard.Open(); err != nil { return nil, err } defer keyboard.Close() index := 0 for { fmt.Println() fmt.Println("请选择福利活动(↑↓ 选择,Enter 确认,Esc 退出)") for i, item := range items { prefix := " " if i == index { prefix = "> " } status := "未达标" if item.Joined { status = "已参与" } else if item.CanJoin { status = "可参与" } fmt.Printf("%s%s %s %s/%s %s\n", prefix, item.Title, typeLabel(item.Type), formatAmount(item.CurrentPaid), formatAmount(item.ThresholdAmount), status) } char, key, err := keyboard.GetKey() if err != nil { return nil, err } switch key { case keyboard.KeyArrowUp: if index > 0 { index-- } case keyboard.KeyArrowDown: if index < len(items)-1 { index++ } case keyboard.KeyEnter: return &items[index], nil case keyboard.KeyEsc, keyboard.KeyCtrlC: return nil, fmt.Errorf("已取消选择") default: if char == 0 { continue } } } } func formatAmount(cents int64) string { return fmt.Sprintf("%.2f", float64(cents)/100) } func typeLabel(t string) string { switch t { case welfaresvc.TypeWeekly: return "每周福利" case welfaresvc.TypeMonthly: return "每月福利" default: return "每日福利" } } func fallback(v, d string) string { if strings.TrimSpace(v) == "" { return d } return v }