Executable
+160
@@ -0,0 +1,160 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
THEATER_TAG_TYPE = 5
|
||||
THEATER_KIND = 8
|
||||
THEATER_DIMENSION = "剧场"
|
||||
|
||||
|
||||
def find_video_mongodb_uri(config):
|
||||
def walk(value, path=""):
|
||||
if isinstance(value, dict):
|
||||
for key, child in value.items():
|
||||
child_path = f"{path}.{key}" if path else key
|
||||
yield from walk(child, child_path)
|
||||
elif isinstance(value, list):
|
||||
for index, child in enumerate(value):
|
||||
yield from walk(child, f"{path}[{index}]")
|
||||
else:
|
||||
yield path, value
|
||||
|
||||
for path, value in walk(config):
|
||||
if isinstance(value, str) and value.startswith("mongodb://") and "video" in path.lower():
|
||||
return value
|
||||
raise RuntimeError("video MongoDB URI was not found in the supplied config")
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description="Repair one test short drama that references animation tags")
|
||||
parser.add_argument("--config", required=True, help="test app.json path")
|
||||
parser.add_argument("--media-id", required=True, help="target short-drama ObjectID")
|
||||
parser.add_argument("--backup", required=True, help="non-existing rollback evidence path")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
from bson import ObjectId, json_util
|
||||
from pymongo import MongoClient
|
||||
|
||||
media_id = ObjectId(args.media_id)
|
||||
backup_path = Path(args.backup)
|
||||
if backup_path.exists():
|
||||
raise RuntimeError("backup path already exists")
|
||||
|
||||
with open(args.config, "r", encoding="utf-8") as config_file:
|
||||
uri = find_video_mongodb_uri(json.load(config_file))
|
||||
|
||||
client = MongoClient(uri, serverSelectionTimeoutMS=5000, connectTimeoutMS=5000)
|
||||
database = client.get_default_database()
|
||||
media = database.media.find_one({"_id": media_id}, max_time_ms=5000)
|
||||
if media is None:
|
||||
raise RuntimeError("target media does not exist")
|
||||
if media.get("mediaType") != "drama" or media.get("updatedAct") != "codex-test-seed":
|
||||
raise RuntimeError("target is not the expected seeded test short drama")
|
||||
|
||||
old_tag_ids = list(media.get("tags") or [])
|
||||
if len(old_tag_ids) > 20:
|
||||
raise RuntimeError("target has too many tags")
|
||||
old_tags = list(database.media_tag.find({"_id": {"$in": old_tag_ids}}, max_time_ms=5000))
|
||||
old_tags_by_id = {tag["_id"]: tag for tag in old_tags}
|
||||
if len(old_tags_by_id) != len(set(old_tag_ids)):
|
||||
raise RuntimeError("one or more source tags do not exist")
|
||||
|
||||
module = None
|
||||
module_id = media.get("mId")
|
||||
if isinstance(module_id, ObjectId) and module_id != ObjectId("000000000000000000000000"):
|
||||
module = database.module_conf.find_one({"_id": module_id}, {"moduleName": 1, "type": 1}, max_time_ms=5000)
|
||||
if module is None or module.get("type") != 12:
|
||||
raise RuntimeError("target is not attached to the expected short-drama module")
|
||||
|
||||
backup = {
|
||||
"createdAt": datetime.now(timezone.utc),
|
||||
"media": media,
|
||||
"sourceTags": old_tags,
|
||||
"insertedTagIds": [],
|
||||
}
|
||||
backup_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
descriptor = os.open(backup_path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
|
||||
with os.fdopen(descriptor, "w", encoding="utf-8") as backup_file:
|
||||
backup_file.write(json_util.dumps(backup, ensure_ascii=False))
|
||||
|
||||
new_tag_ids = []
|
||||
inserted_tag_ids = []
|
||||
now = datetime.now(timezone.utc)
|
||||
for old_tag_id in old_tag_ids:
|
||||
old_tag = old_tags_by_id[old_tag_id]
|
||||
name = str(old_tag.get("name") or "").strip()
|
||||
if not name:
|
||||
raise RuntimeError("source tag has an empty name")
|
||||
theater_tag = database.media_tag.find_one(
|
||||
{
|
||||
"name": name,
|
||||
"dimension": THEATER_DIMENSION,
|
||||
"type": THEATER_TAG_TYPE,
|
||||
"kind": THEATER_KIND,
|
||||
"isDelete": False,
|
||||
},
|
||||
{"_id": 1},
|
||||
max_time_ms=5000,
|
||||
)
|
||||
if theater_tag is None:
|
||||
result = database.media_tag.insert_one(
|
||||
{
|
||||
"name": name,
|
||||
"dimension": THEATER_DIMENSION,
|
||||
"type": THEATER_TAG_TYPE,
|
||||
"kind": THEATER_KIND,
|
||||
"active": True,
|
||||
"isDiscovery": False,
|
||||
"library": False,
|
||||
"sort": 0,
|
||||
"isDelete": False,
|
||||
"updatedAct": "codex-test-repair",
|
||||
"createdAt": now,
|
||||
"updateTime": now,
|
||||
}
|
||||
)
|
||||
theater_tag = {"_id": result.inserted_id}
|
||||
inserted_tag_ids.append(result.inserted_id)
|
||||
new_tag_ids.append(theater_tag["_id"])
|
||||
|
||||
update = {
|
||||
"kind": THEATER_KIND,
|
||||
"tags": new_tag_ids,
|
||||
"moduleName": module["moduleName"],
|
||||
"sId": ObjectId("000000000000000000000000"),
|
||||
"sectionName": "",
|
||||
"sectionSort": 0,
|
||||
"updatedAct": "codex-test-repair",
|
||||
"updateTime": now,
|
||||
}
|
||||
result = database.media.update_one(
|
||||
{"_id": media_id, "mediaType": "drama", "updatedAct": "codex-test-seed"},
|
||||
{"$set": update},
|
||||
)
|
||||
if result.matched_count != 1 or result.modified_count != 1:
|
||||
raise RuntimeError("target changed before repair completed")
|
||||
|
||||
if inserted_tag_ids:
|
||||
backup["insertedTagIds"] = inserted_tag_ids
|
||||
temporary_path = backup_path.with_suffix(backup_path.suffix + ".tmp")
|
||||
descriptor = os.open(temporary_path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
|
||||
with os.fdopen(descriptor, "w", encoding="utf-8") as backup_file:
|
||||
backup_file.write(json_util.dumps(backup, ensure_ascii=False))
|
||||
os.replace(temporary_path, backup_path)
|
||||
|
||||
print(
|
||||
f"repaired=1 media_id={media_id} tags={len(new_tag_ids)} "
|
||||
f"created_tags={len(inserted_tag_ids)} backup={backup_path}"
|
||||
)
|
||||
client.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,161 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
const mongoURI = "mongodb://admin:TESTwfeZYP0kjBdy8DAUdwU@172.104.181.99:57017,172.104.181.99:57018,172.104.181.99:57019/91porn?replicaSet=yctest&maxPoolSize=200&connectTimeout=30&authSource=admin"
|
||||
|
||||
// SeedCheckin 为指定用户生成N天连续签到数据,使得今天是第N+1天签到
|
||||
// userIds: 用户ID列表 (uint64, 对应本项目的 UID)
|
||||
// n: 已签到天数(生成过去N天的记录,今天为第N+1天)
|
||||
func SeedCheckin(ctx context.Context, db *mongo.Database, userIds []uint64, n int) error {
|
||||
prizeColl := db.Collection("checkin_prize")
|
||||
checkinColl := db.Collection("user_checkin")
|
||||
|
||||
// 东八区
|
||||
loc, _ := time.LoadLocation("Asia/Shanghai")
|
||||
now := time.Now().In(loc)
|
||||
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, loc)
|
||||
|
||||
fmt.Printf("Now: %v\n", now)
|
||||
fmt.Printf("Today (CST 00:00): %v\n", today)
|
||||
fmt.Printf("生成 %d 天签到记录,今天签到为第 %d 天\n", n, n+1)
|
||||
|
||||
// 1. 查奖励配置(非大奖)
|
||||
cursor, err := prizeColl.Find(ctx, bson.M{
|
||||
"status": true,
|
||||
"checkinType": int64(1),
|
||||
}, options.Find().SetSort(bson.M{"checkinDays": 1}))
|
||||
if err != nil {
|
||||
return fmt.Errorf("query prizes failed: %w", err)
|
||||
}
|
||||
var prizeConfigs []bson.M
|
||||
if err := cursor.All(ctx, &prizeConfigs); err != nil {
|
||||
return fmt.Errorf("decode prizes failed: %w", err)
|
||||
}
|
||||
|
||||
// 按天数映射奖品ID(本项目用 ObjectID)
|
||||
dayPrizes := make(map[int64][]primitive.ObjectID)
|
||||
for _, p := range prizeConfigs {
|
||||
days := toInt64(p["checkinDays"])
|
||||
bigPrize, _ := p["bigPrize"].(bool)
|
||||
if !bigPrize {
|
||||
if oid, ok := p["_id"].(primitive.ObjectID); ok {
|
||||
dayPrizes[days] = append(dayPrizes[days], oid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 删除这些用户的所有签到记录
|
||||
delResult, err := checkinColl.DeleteMany(ctx, bson.M{
|
||||
"userId": bson.M{"$in": userIds},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("delete failed: %w", err)
|
||||
}
|
||||
fmt.Printf("\nDeleted %d existing records\n", delResult.DeletedCount)
|
||||
|
||||
// 3. 插入过去N天的签到记录(东八区时间)
|
||||
for _, userId := range userIds {
|
||||
fmt.Printf("\n--- userId=%d ---\n", userId)
|
||||
for day := n; day >= 1; day-- {
|
||||
date := today.AddDate(0, 0, -day)
|
||||
continuouslyDays := int64(n - day + 1)
|
||||
cumulativeDays := continuouslyDays
|
||||
prizes := dayPrizes[continuouslyDays]
|
||||
|
||||
doc := bson.M{
|
||||
"_id": primitive.NewObjectID(),
|
||||
"date": date,
|
||||
"userId": userId,
|
||||
"prizes": prizes,
|
||||
"gave": true,
|
||||
"vipPrizeGave": false,
|
||||
"continuouslyDays": continuouslyDays,
|
||||
"cumulativeDays": cumulativeDays,
|
||||
"isReset": false,
|
||||
"createdAt": date.Add(10 * time.Hour),
|
||||
}
|
||||
|
||||
_, err := checkinColl.InsertOne(ctx, doc)
|
||||
if err != nil {
|
||||
log.Printf(" Insert failed: %v", err)
|
||||
continue
|
||||
}
|
||||
fmt.Printf(" date=%s contDays=%d prizes=%v ✓\n",
|
||||
date.Format("2006-01-02 15:04:05 MST"),
|
||||
continuouslyDays, prizes)
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 验证
|
||||
fmt.Println("\n=== Verification ===")
|
||||
loc2, _ := time.LoadLocation("Asia/Shanghai")
|
||||
for _, userId := range userIds {
|
||||
cur, _ := checkinColl.Find(ctx, bson.M{
|
||||
"userId": userId,
|
||||
}, options.Find().SetSort(bson.M{"date": -1}))
|
||||
var records []bson.M
|
||||
_ = cur.All(ctx, &records)
|
||||
fmt.Printf("userId=%d: %d records, 今天签到为第%d天\n", userId, len(records), len(records)+1)
|
||||
for _, r := range records {
|
||||
var t time.Time
|
||||
switch d := r["date"].(type) {
|
||||
case primitive.DateTime:
|
||||
t = d.Time().In(loc2)
|
||||
case time.Time:
|
||||
t = d.In(loc2)
|
||||
}
|
||||
fmt.Printf(" date=%s contDays=%v prizes=%v\n",
|
||||
t.Format("2006-01-02 15:04:05 MST"),
|
||||
r["continuouslyDays"], r["prizes"])
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("\nDone!")
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
client, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoURI))
|
||||
if err != nil {
|
||||
log.Fatal("connect failed:", err)
|
||||
}
|
||||
defer client.Disconnect(ctx)
|
||||
|
||||
db := client.Database("91porn")
|
||||
|
||||
// ====== 在这里修改参数 ======
|
||||
userIds := []uint64{303458}
|
||||
n := 6 // 生成6天签到记录,今天签到为第7天
|
||||
// ===========================
|
||||
|
||||
if err := SeedCheckin(ctx, db, userIds, n); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func toInt64(v interface{}) int64 {
|
||||
switch n := v.(type) {
|
||||
case int64:
|
||||
return n
|
||||
case int32:
|
||||
return int64(n)
|
||||
case float64:
|
||||
return int64(n)
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,404 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
const mongoURI = "mongodb://admin:TESTwfeZYP0kjBdy8DAUdwU@172.104.181.99:57017,172.104.181.99:57018,172.104.181.99:57019/91porn?replicaSet=yctest&maxPoolSize=200&connectTimeout=30&authSource=admin"
|
||||
|
||||
// PrizeType 与 prizemod.PrizeType 保持一致
|
||||
const (
|
||||
PrizeTypeCurrentValue = 1 // 活跃值
|
||||
PrizeTypeGold = 2 // 金币
|
||||
PrizeTypeVIPCard = 5 // 会员卡
|
||||
PrizeTypeIntegral = 8 // 积分
|
||||
PrizeTypeAIUndress = 9 // AI脱衣
|
||||
PrizeTypeAIChangeFace = 10 // AI换脸
|
||||
PrizeTypeVideoCoupon = 11 // 观影券
|
||||
)
|
||||
|
||||
const CheckinTypeContinuously = 1
|
||||
|
||||
// activityPrizeDef 定义基础奖品
|
||||
type activityPrizeDef struct {
|
||||
Name string
|
||||
Type int
|
||||
Count int32
|
||||
Price int64
|
||||
Desc string
|
||||
}
|
||||
|
||||
// checkinPrizeDef 定义签到奖品配置
|
||||
type checkinPrizeDef struct {
|
||||
Title string
|
||||
CheckinDays int64
|
||||
BigPrize bool
|
||||
PrizeName string
|
||||
PrizeRef string // 引用 activityPrizeDef 的 key
|
||||
}
|
||||
|
||||
func main() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||
defer cancel()
|
||||
|
||||
client, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoURI))
|
||||
if err != nil {
|
||||
log.Fatal("connect failed:", err)
|
||||
}
|
||||
defer client.Disconnect(ctx)
|
||||
|
||||
db := client.Database("91porn")
|
||||
now := time.Now()
|
||||
|
||||
// ========================
|
||||
// Step 0: 清理上次脚本插入的旧数据
|
||||
// ========================
|
||||
fmt.Println("=== Step 0: 清理旧签到配置数据 ===")
|
||||
db.Collection("checkin_config").DeleteMany(ctx, bson.M{})
|
||||
db.Collection("checkin_prize").DeleteMany(ctx, bson.M{})
|
||||
// 清理上次脚本创建的 activity_prize(按名称前缀匹配)
|
||||
delRes, _ := db.Collection("activity_prize").DeleteMany(ctx, bson.M{
|
||||
"desc": bson.M{"$regex": "^签到奖励|^VIP签到"},
|
||||
})
|
||||
fmt.Printf(" 清理 activity_prize: %d, checkin_prize: all, checkin_config: all\n\n", delRes.DeletedCount)
|
||||
|
||||
// ========================
|
||||
// Step 1: 创建 activity_prize 基础奖品
|
||||
// ========================
|
||||
fmt.Println("=== Step 1: 创建 activity_prize 基础奖品 ===")
|
||||
|
||||
activityPrizeColl := db.Collection("activity_prize")
|
||||
|
||||
// 定义所有需要的奖品规格
|
||||
prizeDefs := map[string]activityPrizeDef{
|
||||
// 积分类
|
||||
"integral_3": {Name: "签到积分x3", Type: PrizeTypeIntegral, Count: 3, Price: 3, Desc: "签到奖励3积分"},
|
||||
"integral_5": {Name: "签到积分x5", Type: PrizeTypeIntegral, Count: 5, Price: 5, Desc: "签到奖励5积分"},
|
||||
"integral_8": {Name: "签到积分x8", Type: PrizeTypeIntegral, Count: 8, Price: 8, Desc: "签到奖励8积分"},
|
||||
"integral_10": {Name: "签到积分x10", Type: PrizeTypeIntegral, Count: 10, Price: 10, Desc: "签到奖励10积分"},
|
||||
"integral_12": {Name: "签到积分x12", Type: PrizeTypeIntegral, Count: 12, Price: 12, Desc: "签到奖励12积分"},
|
||||
"integral_15": {Name: "签到积分x15", Type: PrizeTypeIntegral, Count: 15, Price: 15, Desc: "签到奖励15积分"},
|
||||
"integral_18": {Name: "签到积分x18", Type: PrizeTypeIntegral, Count: 18, Price: 18, Desc: "签到奖励18积分"},
|
||||
"integral_20": {Name: "签到积分x20", Type: PrizeTypeIntegral, Count: 20, Price: 20, Desc: "签到奖励20积分"},
|
||||
"integral_25": {Name: "签到积分x25", Type: PrizeTypeIntegral, Count: 25, Price: 25, Desc: "签到奖励25积分"},
|
||||
"integral_30": {Name: "签到积分x30", Type: PrizeTypeIntegral, Count: 30, Price: 30, Desc: "签到奖励30积分"},
|
||||
"integral_50": {Name: "签到积分x50", Type: PrizeTypeIntegral, Count: 50, Price: 50, Desc: "签到奖励50积分"},
|
||||
"integral_80": {Name: "签到积分x80", Type: PrizeTypeIntegral, Count: 80, Price: 80, Desc: "签到奖励80积分"},
|
||||
"integral_100": {Name: "签到积分x100", Type: PrizeTypeIntegral, Count: 100, Price: 100, Desc: "签到奖励100积分"},
|
||||
// 金币类
|
||||
"gold_50": {Name: "签到金币x50", Type: PrizeTypeGold, Count: 50, Price: 50, Desc: "签到奖励50金币"},
|
||||
"gold_100": {Name: "签到金币x100", Type: PrizeTypeGold, Count: 100, Price: 100, Desc: "签到奖励100金币"},
|
||||
"gold_200": {Name: "签到金币x200", Type: PrizeTypeGold, Count: 200, Price: 200, Desc: "签到奖励200金币"},
|
||||
"gold_500": {Name: "签到金币x500", Type: PrizeTypeGold, Count: 500, Price: 500, Desc: "签到奖励500金币"},
|
||||
// 观影券
|
||||
"video_coupon_1": {Name: "观影券x1", Type: PrizeTypeVideoCoupon, Count: 1, Price: 1, Desc: "签到奖励1张观影券"},
|
||||
"video_coupon_2": {Name: "观影券x2", Type: PrizeTypeVideoCoupon, Count: 2, Price: 2, Desc: "签到奖励2张观影券"},
|
||||
"video_coupon_3": {Name: "观影券x3", Type: PrizeTypeVideoCoupon, Count: 3, Price: 3, Desc: "签到奖励3张观影券"},
|
||||
// AI
|
||||
"ai_undress_1": {Name: "AI脱衣x1", Type: PrizeTypeAIUndress, Count: 1, Price: 1, Desc: "签到奖励AI脱衣1次"},
|
||||
"ai_undress_2": {Name: "AI脱衣x2", Type: PrizeTypeAIUndress, Count: 2, Price: 2, Desc: "签到奖励AI脱衣2次"},
|
||||
"ai_changeface_1": {Name: "AI换脸x1", Type: PrizeTypeAIChangeFace, Count: 1, Price: 1, Desc: "签到奖励AI换脸1次"},
|
||||
"ai_changeface_2": {Name: "AI换脸x2", Type: PrizeTypeAIChangeFace, Count: 2, Price: 2, Desc: "签到奖励AI换脸2次"},
|
||||
// VIP专属奖品
|
||||
"vip_integral_5": {Name: "VIP积分x5", Type: PrizeTypeIntegral, Count: 5, Price: 5, Desc: "VIP签到额外5积分"},
|
||||
"vip_integral_8": {Name: "VIP积分x8", Type: PrizeTypeIntegral, Count: 8, Price: 8, Desc: "VIP签到额外8积分"},
|
||||
"vip_integral_10": {Name: "VIP积分x10", Type: PrizeTypeIntegral, Count: 10, Price: 10, Desc: "VIP签到额外10积分"},
|
||||
"vip_integral_15": {Name: "VIP积分x15", Type: PrizeTypeIntegral, Count: 15, Price: 15, Desc: "VIP签到额外15积分"},
|
||||
"vip_integral_20": {Name: "VIP积分x20", Type: PrizeTypeIntegral, Count: 20, Price: 20, Desc: "VIP签到额外20积分"},
|
||||
"vip_integral_30": {Name: "VIP积分x30", Type: PrizeTypeIntegral, Count: 30, Price: 30, Desc: "VIP签到额外30积分"},
|
||||
"vip_integral_50": {Name: "VIP积分x50", Type: PrizeTypeIntegral, Count: 50, Price: 50, Desc: "VIP签到额外50积分"},
|
||||
"vip_gold_100": {Name: "VIP金币x100", Type: PrizeTypeGold, Count: 100, Price: 100, Desc: "VIP签到额外100金币"},
|
||||
"vip_gold_200": {Name: "VIP金币x200", Type: PrizeTypeGold, Count: 200, Price: 200, Desc: "VIP签到额外200金币"},
|
||||
"vip_gold_500": {Name: "VIP金币x500", Type: PrizeTypeGold, Count: 500, Price: 500, Desc: "VIP签到额外500金币"},
|
||||
"vip_video_coupon_1": {Name: "VIP观影券x1", Type: PrizeTypeVideoCoupon, Count: 1, Price: 1, Desc: "VIP签到额外1张观影券"},
|
||||
"vip_video_coupon_2": {Name: "VIP观影券x2", Type: PrizeTypeVideoCoupon, Count: 2, Price: 2, Desc: "VIP签到额外2张观影券"},
|
||||
"vip_ai_undress_1": {Name: "VIP AI脱衣x1", Type: PrizeTypeAIUndress, Count: 1, Price: 1, Desc: "VIP签到额外AI脱衣1次"},
|
||||
"vip_ai_undress_2": {Name: "VIP AI脱衣x2", Type: PrizeTypeAIUndress, Count: 2, Price: 2, Desc: "VIP签到额外AI脱衣2次"},
|
||||
"vip_ai_changeface_1": {Name: "VIP AI换脸x1", Type: PrizeTypeAIChangeFace, Count: 1, Price: 1, Desc: "VIP签到额外AI换脸1次"},
|
||||
"vip_ai_changeface_2": {Name: "VIP AI换脸x2", Type: PrizeTypeAIChangeFace, Count: 2, Price: 2, Desc: "VIP签到额外AI换脸2次"},
|
||||
}
|
||||
|
||||
prizeIdMap := make(map[string]primitive.ObjectID)
|
||||
for key, def := range prizeDefs {
|
||||
doc := bson.M{
|
||||
"name": def.Name, "type": def.Type, "count": def.Count, "price": def.Price,
|
||||
"sort": 0, "desc": def.Desc, "value": 0, "weights": "0", "level": 1,
|
||||
"status": true, "validityTime": 0, "image": "",
|
||||
"updateTime": now, "createTimt": now,
|
||||
}
|
||||
result, err := activityPrizeColl.InsertOne(ctx, doc)
|
||||
if err != nil {
|
||||
log.Printf(" [WARN] 插入 '%s' 失败: %v", key, err)
|
||||
continue
|
||||
}
|
||||
prizeIdMap[key] = result.InsertedID.(primitive.ObjectID)
|
||||
}
|
||||
fmt.Printf(" 共创建 %d 个 activity_prize\n\n", len(prizeIdMap))
|
||||
|
||||
// ========================
|
||||
// Step 2: 创建 checkin_config
|
||||
// ========================
|
||||
fmt.Println("=== Step 2: 配置 checkin_config ===")
|
||||
configColl := db.Collection("checkin_config")
|
||||
configDoc := bson.M{
|
||||
"enable": true,
|
||||
"description": "每日签到领好礼!连续签到天数越多奖励越丰厚,第7天可获得双倍奖励!月卡VIP用户每天额外领取专属奖品。坚持签到31天有超级大礼!",
|
||||
"backgroundImage": "",
|
||||
"rewardBgVideos": []bson.M{
|
||||
{"prizeType": PrizeTypeIntegral, "bgMediaUrl": ""},
|
||||
{"prizeType": PrizeTypeGold, "bgMediaUrl": ""},
|
||||
{"prizeType": PrizeTypeVideoCoupon, "bgMediaUrl": ""},
|
||||
{"prizeType": PrizeTypeAIUndress, "bgMediaUrl": ""},
|
||||
{"prizeType": PrizeTypeAIChangeFace, "bgMediaUrl": ""},
|
||||
},
|
||||
"integerExchangeList": []bson.M{
|
||||
{"name": "月卡VIP", "icon": ""},
|
||||
{"name": "观影券x10", "icon": ""},
|
||||
{"name": "AI脱衣x5", "icon": ""},
|
||||
{"name": "AI换脸x5", "icon": ""},
|
||||
},
|
||||
}
|
||||
configColl.InsertOne(ctx, configDoc)
|
||||
fmt.Println(" checkin_config 已创建(enable=true)\n")
|
||||
|
||||
// ========================
|
||||
// Step 3: 创建 checkin_prize 31天连续签到(普通 + VIP)
|
||||
// ========================
|
||||
fmt.Println("=== Step 3: 创建 checkin_prize 31天签到奖品 ===")
|
||||
checkinPrizeColl := db.Collection("checkin_prize")
|
||||
|
||||
// 31天普通奖品配置(奖励递增,每周有节奏感)
|
||||
normalPrizes := []checkinPrizeDef{
|
||||
// 第1周:基础奖励
|
||||
{Title: "第1天", CheckinDays: 1, PrizeName: "积分x3", PrizeRef: "integral_3"},
|
||||
{Title: "第2天", CheckinDays: 2, PrizeName: "积分x5", PrizeRef: "integral_5"},
|
||||
{Title: "第3天", CheckinDays: 3, PrizeName: "积分x5", PrizeRef: "integral_5"},
|
||||
{Title: "第4天", CheckinDays: 4, PrizeName: "积分x8", PrizeRef: "integral_8"},
|
||||
{Title: "第5天", CheckinDays: 5, PrizeName: "积分x8", PrizeRef: "integral_8"},
|
||||
{Title: "第6天", CheckinDays: 6, PrizeName: "积分x10", PrizeRef: "integral_10"},
|
||||
{Title: "第7天", CheckinDays: 7, PrizeName: "观影券x1", PrizeRef: "video_coupon_1"},
|
||||
// 第2周:稳步提升
|
||||
{Title: "第8天", CheckinDays: 8, PrizeName: "积分x10", PrizeRef: "integral_10"},
|
||||
{Title: "第9天", CheckinDays: 9, PrizeName: "积分x10", PrizeRef: "integral_10"},
|
||||
{Title: "第10天", CheckinDays: 10, PrizeName: "积分x12", PrizeRef: "integral_12"},
|
||||
{Title: "第11天", CheckinDays: 11, PrizeName: "积分x12", PrizeRef: "integral_12"},
|
||||
{Title: "第12天", CheckinDays: 12, PrizeName: "积分x15", PrizeRef: "integral_15"},
|
||||
{Title: "第13天", CheckinDays: 13, PrizeName: "金币x50", PrizeRef: "gold_50"},
|
||||
{Title: "第14天", CheckinDays: 14, PrizeName: "观影券x1", PrizeRef: "video_coupon_1"},
|
||||
// 第3周:中期奖励
|
||||
{Title: "第15天", CheckinDays: 15, PrizeName: "积分x15", PrizeRef: "integral_15"},
|
||||
{Title: "第16天", CheckinDays: 16, PrizeName: "积分x18", PrizeRef: "integral_18"},
|
||||
{Title: "第17天", CheckinDays: 17, PrizeName: "积分x18", PrizeRef: "integral_18"},
|
||||
{Title: "第18天", CheckinDays: 18, PrizeName: "积分x20", PrizeRef: "integral_20"},
|
||||
{Title: "第19天", CheckinDays: 19, PrizeName: "AI脱衣x1", PrizeRef: "ai_undress_1"},
|
||||
{Title: "第20天", CheckinDays: 20, PrizeName: "积分x20", PrizeRef: "integral_20"},
|
||||
{Title: "第21天", CheckinDays: 21, PrizeName: "金币x100", PrizeRef: "gold_100"},
|
||||
// 第4周:高级奖励
|
||||
{Title: "第22天", CheckinDays: 22, PrizeName: "积分x25", PrizeRef: "integral_25"},
|
||||
{Title: "第23天", CheckinDays: 23, PrizeName: "积分x25", PrizeRef: "integral_25"},
|
||||
{Title: "第24天", CheckinDays: 24, PrizeName: "AI换脸x1", PrizeRef: "ai_changeface_1"},
|
||||
{Title: "第25天", CheckinDays: 25, PrizeName: "积分x30", PrizeRef: "integral_30"},
|
||||
{Title: "第26天", CheckinDays: 26, PrizeName: "观影券x2", PrizeRef: "video_coupon_2"},
|
||||
{Title: "第27天", CheckinDays: 27, PrizeName: "积分x30", PrizeRef: "integral_30"},
|
||||
{Title: "第28天", CheckinDays: 28, PrizeName: "金币x200", PrizeRef: "gold_200"},
|
||||
// 最后冲刺
|
||||
{Title: "第29天", CheckinDays: 29, PrizeName: "积分x50", PrizeRef: "integral_50"},
|
||||
{Title: "第30天", CheckinDays: 30, PrizeName: "观影券x3", PrizeRef: "video_coupon_3"},
|
||||
{Title: "第31天", CheckinDays: 31, PrizeName: "积分x100", PrizeRef: "integral_100"},
|
||||
}
|
||||
|
||||
// 31天VIP专属奖品配置
|
||||
vipPrizes := []checkinPrizeDef{
|
||||
// 第1周
|
||||
{Title: "VIP第1天", CheckinDays: 1, BigPrize: true, PrizeName: "VIP积分x5", PrizeRef: "vip_integral_5"},
|
||||
{Title: "VIP第2天", CheckinDays: 2, BigPrize: true, PrizeName: "VIP积分x5", PrizeRef: "vip_integral_5"},
|
||||
{Title: "VIP第3天", CheckinDays: 3, BigPrize: true, PrizeName: "VIP积分x8", PrizeRef: "vip_integral_8"},
|
||||
{Title: "VIP第4天", CheckinDays: 4, BigPrize: true, PrizeName: "VIP积分x8", PrizeRef: "vip_integral_8"},
|
||||
{Title: "VIP第5天", CheckinDays: 5, BigPrize: true, PrizeName: "VIP积分x10", PrizeRef: "vip_integral_10"},
|
||||
{Title: "VIP第6天", CheckinDays: 6, BigPrize: true, PrizeName: "VIP积分x10", PrizeRef: "vip_integral_10"},
|
||||
{Title: "VIP第7天", CheckinDays: 7, BigPrize: true, PrizeName: "VIP观影券x1", PrizeRef: "vip_video_coupon_1"},
|
||||
// 第2周
|
||||
{Title: "VIP第8天", CheckinDays: 8, BigPrize: true, PrizeName: "VIP积分x10", PrizeRef: "vip_integral_10"},
|
||||
{Title: "VIP第9天", CheckinDays: 9, BigPrize: true, PrizeName: "VIP积分x10", PrizeRef: "vip_integral_10"},
|
||||
{Title: "VIP第10天", CheckinDays: 10, BigPrize: true, PrizeName: "VIP积分x15", PrizeRef: "vip_integral_15"},
|
||||
{Title: "VIP第11天", CheckinDays: 11, BigPrize: true, PrizeName: "VIP积分x15", PrizeRef: "vip_integral_15"},
|
||||
{Title: "VIP第12天", CheckinDays: 12, BigPrize: true, PrizeName: "VIP金币x100", PrizeRef: "vip_gold_100"},
|
||||
{Title: "VIP第13天", CheckinDays: 13, BigPrize: true, PrizeName: "VIP积分x15", PrizeRef: "vip_integral_15"},
|
||||
{Title: "VIP第14天", CheckinDays: 14, BigPrize: true, PrizeName: "VIP AI脱衣x1", PrizeRef: "vip_ai_undress_1"},
|
||||
// 第3周
|
||||
{Title: "VIP第15天", CheckinDays: 15, BigPrize: true, PrizeName: "VIP积分x15", PrizeRef: "vip_integral_15"},
|
||||
{Title: "VIP第16天", CheckinDays: 16, BigPrize: true, PrizeName: "VIP积分x20", PrizeRef: "vip_integral_20"},
|
||||
{Title: "VIP第17天", CheckinDays: 17, BigPrize: true, PrizeName: "VIP积分x20", PrizeRef: "vip_integral_20"},
|
||||
{Title: "VIP第18天", CheckinDays: 18, BigPrize: true, PrizeName: "VIP观影券x1", PrizeRef: "vip_video_coupon_1"},
|
||||
{Title: "VIP第19天", CheckinDays: 19, BigPrize: true, PrizeName: "VIP积分x20", PrizeRef: "vip_integral_20"},
|
||||
{Title: "VIP第20天", CheckinDays: 20, BigPrize: true, PrizeName: "VIP AI换脸x1", PrizeRef: "vip_ai_changeface_1"},
|
||||
{Title: "VIP第21天", CheckinDays: 21, BigPrize: true, PrizeName: "VIP金币x200", PrizeRef: "vip_gold_200"},
|
||||
// 第4周
|
||||
{Title: "VIP第22天", CheckinDays: 22, BigPrize: true, PrizeName: "VIP积分x20", PrizeRef: "vip_integral_20"},
|
||||
{Title: "VIP第23天", CheckinDays: 23, BigPrize: true, PrizeName: "VIP积分x30", PrizeRef: "vip_integral_30"},
|
||||
{Title: "VIP第24天", CheckinDays: 24, BigPrize: true, PrizeName: "VIP AI脱衣x2", PrizeRef: "vip_ai_undress_2"},
|
||||
{Title: "VIP第25天", CheckinDays: 25, BigPrize: true, PrizeName: "VIP积分x30", PrizeRef: "vip_integral_30"},
|
||||
{Title: "VIP第26天", CheckinDays: 26, BigPrize: true, PrizeName: "VIP观影券x2", PrizeRef: "vip_video_coupon_2"},
|
||||
{Title: "VIP第27天", CheckinDays: 27, BigPrize: true, PrizeName: "VIP积分x30", PrizeRef: "vip_integral_30"},
|
||||
{Title: "VIP第28天", CheckinDays: 28, BigPrize: true, PrizeName: "VIP金币x500", PrizeRef: "vip_gold_500"},
|
||||
// 最后冲刺
|
||||
{Title: "VIP第29天", CheckinDays: 29, BigPrize: true, PrizeName: "VIP积分x50", PrizeRef: "vip_integral_50"},
|
||||
{Title: "VIP第30天", CheckinDays: 30, BigPrize: true, PrizeName: "VIP AI换脸x2", PrizeRef: "vip_ai_changeface_2"},
|
||||
{Title: "VIP第31天", CheckinDays: 31, BigPrize: true, PrizeName: "VIP积分x50", PrizeRef: "vip_integral_50"},
|
||||
}
|
||||
|
||||
allCheckinPrizes := append(normalPrizes, vipPrizes...)
|
||||
insertedCount := 0
|
||||
|
||||
for _, cp := range allCheckinPrizes {
|
||||
prizeId, ok := prizeIdMap[cp.PrizeRef]
|
||||
if !ok {
|
||||
log.Printf(" [WARN] 找不到奖品引用 '%s',跳过", cp.PrizeRef)
|
||||
continue
|
||||
}
|
||||
doc := bson.M{
|
||||
"title": cp.Title,
|
||||
"image": "",
|
||||
"checkinDays": cp.CheckinDays,
|
||||
"checkinType": int64(CheckinTypeContinuously),
|
||||
"prizeId": prizeId,
|
||||
"prizeName": cp.PrizeName,
|
||||
"status": true,
|
||||
"bigPrize": cp.BigPrize,
|
||||
"createdAt": now,
|
||||
"updatedAt": now,
|
||||
}
|
||||
_, err := checkinPrizeColl.InsertOne(ctx, doc)
|
||||
if err != nil {
|
||||
log.Printf(" [WARN] 插入 '%s' 失败: %v", cp.Title, err)
|
||||
continue
|
||||
}
|
||||
tag := ""
|
||||
if cp.BigPrize {
|
||||
tag = " [VIP]"
|
||||
}
|
||||
fmt.Printf(" Day%-2d %-16s => activity_prize(%s)%s\n",
|
||||
cp.CheckinDays, cp.PrizeName, prizeId.Hex(), tag)
|
||||
insertedCount++
|
||||
}
|
||||
fmt.Printf(" 共创建 %d 个 checkin_prize(普通%d + VIP%d)\n\n", insertedCount, len(normalPrizes), len(vipPrizes))
|
||||
|
||||
// ========================
|
||||
// Step 4: 生成测试用户签到记录
|
||||
// ========================
|
||||
fmt.Println("=== Step 4: 生成测试用户签到记录 ===")
|
||||
|
||||
// ====== 在这里修改参数 ======
|
||||
userIds := []uint64{300001}
|
||||
n := 6 // 生成6天签到记录,今天签到为第7天(触发双倍奖励)
|
||||
// ===========================
|
||||
|
||||
SeedCheckin(ctx, db, userIds, n)
|
||||
|
||||
fmt.Println("\n=== 全部配置完成 ===")
|
||||
fmt.Println("数据关联关系:")
|
||||
fmt.Println(" checkin_config (enable=true)")
|
||||
fmt.Println(" checkin_prize (31天普通 + 31天VIP) --[prizeId]--> activity_prize")
|
||||
fmt.Println(" user_checkin --[prizes]--> checkin_prize._id")
|
||||
fmt.Printf(" 测试用户: %v (已签到%d天,今天第%d天)\n", userIds, n, n+1)
|
||||
}
|
||||
|
||||
// SeedCheckin 为指定用户生成N天连续签到数据
|
||||
func SeedCheckin(ctx context.Context, db *mongo.Database, userIds []uint64, n int) {
|
||||
prizeColl := db.Collection("checkin_prize")
|
||||
checkinColl := db.Collection("user_checkin")
|
||||
|
||||
loc, _ := time.LoadLocation("Asia/Shanghai")
|
||||
now := time.Now().In(loc)
|
||||
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, loc)
|
||||
|
||||
fmt.Printf(" Now: %v\n", now)
|
||||
fmt.Printf(" Today (CST 00:00): %v\n", today)
|
||||
fmt.Printf(" 生成 %d 天签到记录,今天签到为第 %d 天\n", n, n+1)
|
||||
|
||||
// 查奖励配置(非大奖)
|
||||
cursor, err := prizeColl.Find(ctx, bson.M{
|
||||
"status": true,
|
||||
"checkinType": int64(1),
|
||||
}, options.Find().SetSort(bson.M{"checkinDays": 1}))
|
||||
if err != nil {
|
||||
log.Printf(" query prizes failed: %v", err)
|
||||
return
|
||||
}
|
||||
var prizeConfigs []bson.M
|
||||
if err := cursor.All(ctx, &prizeConfigs); err != nil {
|
||||
log.Printf(" decode prizes failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
dayPrizes := make(map[int64][]primitive.ObjectID)
|
||||
for _, p := range prizeConfigs {
|
||||
days := toInt64(p["checkinDays"])
|
||||
bigPrize, _ := p["bigPrize"].(bool)
|
||||
if !bigPrize {
|
||||
if oid, ok := p["_id"].(primitive.ObjectID); ok {
|
||||
dayPrizes[days] = append(dayPrizes[days], oid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delResult, err := checkinColl.DeleteMany(ctx, bson.M{
|
||||
"userId": bson.M{"$in": userIds},
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf(" delete failed: %v", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf(" Deleted %d existing records\n", delResult.DeletedCount)
|
||||
|
||||
for _, userId := range userIds {
|
||||
fmt.Printf("\n --- userId=%d ---\n", userId)
|
||||
for day := n; day >= 1; day-- {
|
||||
date := today.AddDate(0, 0, -day)
|
||||
continuouslyDays := int64(n - day + 1)
|
||||
cumulativeDays := continuouslyDays
|
||||
prizes := dayPrizes[continuouslyDays]
|
||||
|
||||
doc := bson.M{
|
||||
"_id": primitive.NewObjectID(),
|
||||
"date": date,
|
||||
"userId": userId,
|
||||
"prizes": prizes,
|
||||
"gave": true,
|
||||
"vipPrizeGave": false,
|
||||
"continuouslyDays": continuouslyDays,
|
||||
"cumulativeDays": cumulativeDays,
|
||||
"isReset": false,
|
||||
"createdAt": date.Add(10 * time.Hour),
|
||||
}
|
||||
_, err := checkinColl.InsertOne(ctx, doc)
|
||||
if err != nil {
|
||||
log.Printf(" Insert failed: %v", err)
|
||||
continue
|
||||
}
|
||||
fmt.Printf(" date=%s contDays=%d prizes=%v\n",
|
||||
date.Format("2006-01-02"), continuouslyDays, prizes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func toInt64(v interface{}) int64 {
|
||||
switch n := v.(type) {
|
||||
case int64:
|
||||
return n
|
||||
case int32:
|
||||
return int64(n)
|
||||
case float64:
|
||||
return int64(n)
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user