478 lines
12 KiB
Go
478 lines
12 KiB
Go
// 任务中心配置组合测试工具
|
|
// 功能:
|
|
// 1. 生成所有有效的任务配置组合到 MySQL 数据库
|
|
// 2. 模拟用户任务进度
|
|
// 3. 验证任务功能是否正常
|
|
|
|
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"time"
|
|
|
|
"bindbox-game/configs"
|
|
"bindbox-game/internal/repository/mysql"
|
|
tcmodel "bindbox-game/internal/repository/mysql/task_center"
|
|
|
|
"gorm.io/datatypes"
|
|
)
|
|
|
|
// ================================
|
|
// 常量定义
|
|
// ================================
|
|
|
|
const (
|
|
// 任务指标
|
|
MetricFirstOrder = "first_order"
|
|
MetricOrderCount = "order_count"
|
|
MetricOrderAmount = "order_amount"
|
|
MetricInviteCount = "invite_count"
|
|
|
|
// 操作符
|
|
OperatorGTE = ">="
|
|
OperatorEQ = "="
|
|
|
|
// 时间窗口
|
|
WindowDaily = "daily"
|
|
WindowWeekly = "weekly"
|
|
WindowMonthly = "monthly"
|
|
WindowLifetime = "lifetime"
|
|
|
|
// 奖励类型
|
|
RewardTypePoints = "points"
|
|
RewardTypeCoupon = "coupon"
|
|
RewardTypeItemCard = "item_card"
|
|
RewardTypeTitle = "title"
|
|
RewardTypeGameTicket = "game_ticket"
|
|
)
|
|
|
|
// TaskCombination 表示一种任务配置组合
|
|
type TaskCombination struct {
|
|
Name string
|
|
Metric string
|
|
Operator string
|
|
Threshold int64
|
|
Window string
|
|
RewardType string
|
|
}
|
|
|
|
// TestResult 测试结果
|
|
type TestResult struct {
|
|
Name string
|
|
Passed bool
|
|
Message string
|
|
}
|
|
|
|
// ================================
|
|
// 配置组合生成器
|
|
// ================================
|
|
|
|
// GenerateAllCombinations 生成所有有效的任务配置组合
|
|
func GenerateAllCombinations() []TaskCombination {
|
|
metrics := []struct {
|
|
name string
|
|
operators []string
|
|
threshold int64
|
|
}{
|
|
{MetricFirstOrder, []string{OperatorEQ}, 1},
|
|
{MetricOrderCount, []string{OperatorGTE, OperatorEQ}, 3},
|
|
{MetricOrderAmount, []string{OperatorGTE, OperatorEQ}, 10000},
|
|
{MetricInviteCount, []string{OperatorGTE, OperatorEQ}, 2},
|
|
}
|
|
windows := []string{WindowDaily, WindowWeekly, WindowMonthly, WindowLifetime}
|
|
rewards := []string{RewardTypePoints, RewardTypeCoupon, RewardTypeItemCard, RewardTypeTitle, RewardTypeGameTicket}
|
|
|
|
var combinations []TaskCombination
|
|
idx := 0
|
|
for _, m := range metrics {
|
|
for _, op := range m.operators {
|
|
for _, w := range windows {
|
|
for _, r := range rewards {
|
|
idx++
|
|
combinations = append(combinations, TaskCombination{
|
|
Name: fmt.Sprintf("测试任务%03d_%s_%s_%s", idx, m.name, w, r),
|
|
Metric: m.name,
|
|
Operator: op,
|
|
Threshold: m.threshold,
|
|
Window: w,
|
|
RewardType: r,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return combinations
|
|
}
|
|
|
|
// generateRewardPayload 根据奖励类型生成对应的 JSON payload
|
|
func generateRewardPayload(rewardType string) string {
|
|
switch rewardType {
|
|
case RewardTypePoints:
|
|
return `{"points": 100}`
|
|
case RewardTypeCoupon:
|
|
return `{"coupon_id": 1, "quantity": 1}`
|
|
case RewardTypeItemCard:
|
|
return `{"card_id": 1, "quantity": 1}`
|
|
case RewardTypeTitle:
|
|
return `{"title_id": 1}`
|
|
case RewardTypeGameTicket:
|
|
return `{"game_code": "minesweeper", "amount": 5}`
|
|
default:
|
|
return `{}`
|
|
}
|
|
}
|
|
|
|
// ================================
|
|
// 数据库操作
|
|
// ================================
|
|
|
|
// SeedAllCombinations 将所有配置组合写入数据库
|
|
func SeedAllCombinations(repo mysql.Repo, dryRun bool) error {
|
|
db := repo.GetDbW()
|
|
combos := GenerateAllCombinations()
|
|
|
|
fmt.Printf("准备生成 %d 个任务配置组合\n", len(combos))
|
|
if dryRun {
|
|
fmt.Println("【试运行模式】不会实际写入数据库")
|
|
for i, c := range combos {
|
|
fmt.Printf(" %3d. %s (指标=%s, 操作符=%s, 窗口=%s, 奖励=%s)\n",
|
|
i+1, c.Name, c.Metric, c.Operator, c.Window, c.RewardType)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// 开始事务
|
|
tx := db.Begin()
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
tx.Rollback()
|
|
}
|
|
}()
|
|
|
|
// 清理旧的测试数据
|
|
if err := tx.Where("name LIKE ?", "测试任务%").Delete(&tcmodel.Task{}).Error; err != nil {
|
|
tx.Rollback()
|
|
return fmt.Errorf("清理旧任务失败: %v", err)
|
|
}
|
|
fmt.Println("已清理旧的测试任务数据")
|
|
|
|
created := 0
|
|
for _, combo := range combos {
|
|
// 检查是否已存在
|
|
var exists tcmodel.Task
|
|
if err := tx.Where("name = ?", combo.Name).First(&exists).Error; err == nil {
|
|
fmt.Printf(" 跳过: %s (已存在)\n", combo.Name)
|
|
continue
|
|
}
|
|
|
|
// 插入任务
|
|
task := &tcmodel.Task{
|
|
Name: combo.Name,
|
|
Description: fmt.Sprintf("测试 %s + %s + %s + %s", combo.Metric, combo.Operator, combo.Window, combo.RewardType),
|
|
Status: 1,
|
|
Visibility: 1,
|
|
}
|
|
if err := tx.Create(task).Error; err != nil {
|
|
tx.Rollback()
|
|
return fmt.Errorf("插入任务失败: %v", err)
|
|
}
|
|
|
|
// 插入档位
|
|
tier := &tcmodel.TaskTier{
|
|
TaskID: task.ID,
|
|
Metric: combo.Metric,
|
|
Operator: combo.Operator,
|
|
Threshold: combo.Threshold,
|
|
Window: combo.Window,
|
|
Priority: 0,
|
|
}
|
|
if err := tx.Create(tier).Error; err != nil {
|
|
tx.Rollback()
|
|
return fmt.Errorf("插入档位失败: %v", err)
|
|
}
|
|
|
|
// 插入奖励
|
|
payload := generateRewardPayload(combo.RewardType)
|
|
reward := &tcmodel.TaskReward{
|
|
TaskID: task.ID,
|
|
TierID: tier.ID,
|
|
RewardType: combo.RewardType,
|
|
RewardPayload: datatypes.JSON(payload),
|
|
Quantity: 10,
|
|
}
|
|
if err := tx.Create(reward).Error; err != nil {
|
|
tx.Rollback()
|
|
return fmt.Errorf("插入奖励失败: %v", err)
|
|
}
|
|
|
|
created++
|
|
if created%10 == 0 {
|
|
fmt.Printf(" 已创建 %d 个任务...\n", created)
|
|
}
|
|
}
|
|
|
|
if err := tx.Commit().Error; err != nil {
|
|
return fmt.Errorf("提交事务失败: %v", err)
|
|
}
|
|
|
|
fmt.Printf("✅ 成功创建 %d 个任务配置组合\n", created)
|
|
return nil
|
|
}
|
|
|
|
// ================================
|
|
// 模拟用户任务
|
|
// ================================
|
|
|
|
// SimulateUserTask 模拟用户完成任务
|
|
func SimulateUserTask(repo mysql.Repo, userID int64, taskID int64) error {
|
|
db := repo.GetDbW()
|
|
|
|
// 查询任务和档位
|
|
var task tcmodel.Task
|
|
if err := db.Where("id = ?", taskID).First(&task).Error; err != nil {
|
|
return fmt.Errorf("任务不存在: %v", err)
|
|
}
|
|
|
|
var tier tcmodel.TaskTier
|
|
if err := db.Where("task_id = ?", taskID).First(&tier).Error; err != nil {
|
|
return fmt.Errorf("档位不存在: %v", err)
|
|
}
|
|
|
|
fmt.Printf("模拟任务: %s (指标=%s, 阈值=%d)\n", task.Name, tier.Metric, tier.Threshold)
|
|
|
|
// 创建或更新用户进度
|
|
progress := &tcmodel.UserTaskProgress{
|
|
UserID: userID,
|
|
TaskID: taskID,
|
|
ClaimedTiers: datatypes.JSON("[]"),
|
|
}
|
|
|
|
// 根据指标类型设置进度
|
|
switch tier.Metric {
|
|
case MetricFirstOrder:
|
|
progress.FirstOrder = 1
|
|
progress.OrderCount = 1
|
|
progress.OrderAmount = 10000
|
|
case MetricOrderCount:
|
|
progress.OrderCount = tier.Threshold
|
|
case MetricOrderAmount:
|
|
progress.OrderAmount = tier.Threshold
|
|
progress.OrderCount = 1
|
|
case MetricInviteCount:
|
|
progress.InviteCount = tier.Threshold
|
|
}
|
|
|
|
// Upsert
|
|
if err := db.Where("user_id = ? AND task_id = ?", userID, taskID).
|
|
Assign(progress).
|
|
FirstOrCreate(progress).Error; err != nil {
|
|
return fmt.Errorf("创建进度失败: %v", err)
|
|
}
|
|
|
|
fmt.Printf("✅ 用户 %d 的任务进度已更新: order_count=%d, order_amount=%d, invite_count=%d, first_order=%d\n",
|
|
userID, progress.OrderCount, progress.OrderAmount, progress.InviteCount, progress.FirstOrder)
|
|
|
|
return nil
|
|
}
|
|
|
|
// ================================
|
|
// 验证功能
|
|
// ================================
|
|
|
|
// VerifyAllConfigs 验证所有配置是否正确
|
|
func VerifyAllConfigs(repo mysql.Repo) []TestResult {
|
|
db := repo.GetDbR()
|
|
var results []TestResult
|
|
|
|
// 1. 检查任务数量
|
|
var taskCount int64
|
|
var sampleTasks []tcmodel.Task
|
|
db.Model(&tcmodel.Task{}).Where("name LIKE ?", "测试任务%").Count(&taskCount)
|
|
db.Model(&tcmodel.Task{}).Where("name LIKE ?", "测试任务%").Limit(5).Find(&sampleTasks)
|
|
|
|
var sampleMsg string
|
|
for _, t := range sampleTasks {
|
|
sampleMsg += fmt.Sprintf("[%d:%s] ", t.ID, t.Name)
|
|
}
|
|
|
|
results = append(results, TestResult{
|
|
Name: "任务数量检查",
|
|
Passed: taskCount > 0,
|
|
Message: fmt.Sprintf("找到 %d 个测试任务. 样本: %s", taskCount, sampleMsg),
|
|
})
|
|
|
|
// 2. 检查每种指标的覆盖
|
|
metrics := []string{MetricFirstOrder, MetricOrderCount, MetricOrderAmount, MetricInviteCount}
|
|
for _, m := range metrics {
|
|
var count int64
|
|
db.Model(&tcmodel.TaskTier{}).Where("metric = ?", m).Count(&count)
|
|
results = append(results, TestResult{
|
|
Name: fmt.Sprintf("指标覆盖: %s", m),
|
|
Passed: count > 0,
|
|
Message: fmt.Sprintf("找到 %d 个档位使用此指标", count),
|
|
})
|
|
}
|
|
|
|
// 3. 检查每种时间窗口的覆盖
|
|
windows := []string{WindowDaily, WindowWeekly, WindowMonthly, WindowLifetime}
|
|
for _, w := range windows {
|
|
var count int64
|
|
db.Model(&tcmodel.TaskTier{}).Where("window = ?", w).Count(&count)
|
|
results = append(results, TestResult{
|
|
Name: fmt.Sprintf("时间窗口覆盖: %s", w),
|
|
Passed: count > 0,
|
|
Message: fmt.Sprintf("找到 %d 个档位使用此时间窗口", count),
|
|
})
|
|
}
|
|
|
|
// 4. 检查每种奖励类型的覆盖
|
|
rewards := []string{RewardTypePoints, RewardTypeCoupon, RewardTypeItemCard, RewardTypeTitle, RewardTypeGameTicket}
|
|
for _, r := range rewards {
|
|
var count int64
|
|
db.Model(&tcmodel.TaskReward{}).Where("reward_type = ?", r).Count(&count)
|
|
results = append(results, TestResult{
|
|
Name: fmt.Sprintf("奖励类型覆盖: %s", r),
|
|
Passed: count > 0,
|
|
Message: fmt.Sprintf("找到 %d 个奖励使用此类型", count),
|
|
})
|
|
}
|
|
|
|
// 5. 检查奖励 payload 格式
|
|
var rewardList []tcmodel.TaskReward
|
|
db.Limit(20).Find(&rewardList)
|
|
for _, r := range rewardList {
|
|
var data map[string]interface{}
|
|
err := json.Unmarshal([]byte(r.RewardPayload), &data)
|
|
passed := err == nil
|
|
msg := "JSON 格式正确"
|
|
if err != nil {
|
|
msg = fmt.Sprintf("JSON 解析失败: %v", err)
|
|
}
|
|
results = append(results, TestResult{
|
|
Name: fmt.Sprintf("奖励Payload格式: ID=%d, Type=%s", r.ID, r.RewardType),
|
|
Passed: passed,
|
|
Message: msg,
|
|
})
|
|
}
|
|
|
|
return results
|
|
}
|
|
|
|
// PrintResults 打印测试结果
|
|
func PrintResults(results []TestResult) {
|
|
passed := 0
|
|
failed := 0
|
|
|
|
fmt.Println("\n========== 测试结果 ==========")
|
|
for _, r := range results {
|
|
status := "✅ PASS"
|
|
if !r.Passed {
|
|
status = "❌ FAIL"
|
|
failed++
|
|
} else {
|
|
passed++
|
|
}
|
|
fmt.Printf("%s | %s | %s\n", status, r.Name, r.Message)
|
|
}
|
|
fmt.Println("==============================")
|
|
fmt.Printf("总计: %d 通过, %d 失败\n", passed, failed)
|
|
}
|
|
|
|
// ================================
|
|
// 主程序
|
|
// ================================
|
|
|
|
func main() {
|
|
// 命令行参数
|
|
action := flag.String("action", "help", "操作类型: seed/simulate/verify/integration/invite-test/help")
|
|
dryRun := flag.Bool("dry-run", false, "试运行模式,不实际写入数据库")
|
|
userID := flag.Int64("user", 8888, "用户ID (用于 simulate 或 integration)")
|
|
taskID := flag.Int64("task", 0, "任务ID")
|
|
flag.Parse()
|
|
|
|
// 显示帮助
|
|
if *action == "help" {
|
|
fmt.Println(`
|
|
任务中心配置组合测试工具
|
|
|
|
用法:
|
|
go run main.go -action=<操作>
|
|
|
|
操作类型:
|
|
seed - 生成所有配置组合到数据库
|
|
simulate - 简单模拟用户进度 (仅修改进度表)
|
|
integration - 真实集成测试 (触发 OnOrderPaid, 验证全流程)
|
|
invite-test - 邀请全链路测试 (模拟邀请、下单、双端奖励发放)
|
|
verify - 验证配置是否正确
|
|
|
|
参数:
|
|
-dry-run - 试运行模式,不实际写入数据库
|
|
-user - 用户ID (默认: 8888)
|
|
-task - 任务ID
|
|
|
|
示例:
|
|
# 邀请全链路测试
|
|
go run main.go -action=invite-test
|
|
`)
|
|
return
|
|
}
|
|
|
|
// 初始化数据库连接
|
|
repo, err := mysql.New()
|
|
if err != nil {
|
|
log.Fatalf("连接数据库失败: %v", err)
|
|
}
|
|
|
|
cfg := configs.Get()
|
|
fmt.Printf("已连接到数据库: %s\n", cfg.MySQL.Write.Name)
|
|
fmt.Printf("时间: %s\n", time.Now().Format("2006-01-02 15:04:05"))
|
|
|
|
// 执行操作
|
|
switch *action {
|
|
case "seed":
|
|
if err := SeedAllCombinations(repo, *dryRun); err != nil {
|
|
log.Printf("生成配置失败: %v", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
case "simulate":
|
|
if *taskID == 0 {
|
|
fmt.Println("请指定任务ID: -task=<ID>")
|
|
os.Exit(1)
|
|
}
|
|
if err := SimulateUserTask(repo, *userID, *taskID); err != nil {
|
|
log.Printf("模拟失败: %v", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
case "integration":
|
|
// 确保用户存在
|
|
if err := ensureUserExists(repo, *userID, "测试用户"); err != nil {
|
|
log.Printf("预检用户失败: %v", err)
|
|
os.Exit(1)
|
|
}
|
|
if err := IntegrationTest(repo); err != nil {
|
|
log.Printf("集成测试失败: %v", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
case "invite-test":
|
|
if err := InviteAndTaskIntegrationTest(repo); err != nil {
|
|
log.Printf("邀请测试失败: %v", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
case "verify":
|
|
results := VerifyAllConfigs(repo)
|
|
PrintResults(results)
|
|
|
|
default:
|
|
fmt.Printf("未知操作: %s\n", *action)
|
|
os.Exit(1)
|
|
}
|
|
}
|