Bläddra i källkod

feat: add new seed scripts for ads and update AdsModule schema

Dave 1 månad sedan
förälder
incheckning
a5d8e7601a
4 ändrade filer med 173 tillägg och 6 borttagningar
  1. 5 1
      package.json
  2. 0 1
      prisma/mongo/schema/ads-module.prisma
  3. 1 4
      prisma/mongo/schema/ads.prisma
  4. 167 0
      prisma/mongo/seed-ads.ts

+ 5 - 1
package.json

@@ -20,6 +20,10 @@
     "prisma:seed:box-admin:admin": "dotenv -e .env.mgnt -- ts-node -P tsconfig.seed.json prisma/mongo/seed-admin.ts",
     "prisma:seed:box-admin": "dotenv -e .env.mgnt -- ts-node -P tsconfig.seed.json prisma/mongo/seed.ts",
     "prisma:seed:box-stats": "dotenv -e .env.mgnt -- ts-node -P tsconfig.seed.json prisma/mongo-stats/seed.ts",
+    "prisma:seed:box-admin:ads": "dotenv -e .env.mgnt -- ts-node -T prisma/mongo/seed-ads.ts",
+    "prisma:seed:box-admin:ads:clean": "dotenv -e .env.mgnt -- ts-node -T prisma/mongo/seed-ads.ts --clean",
+    "seed:ads": "dotenv -e .env -- ts-node -T prisma/seed-ads.ts",
+    "seed:ads:clean": "dotenv -e .env -- ts-node -T prisma/seed-ads.ts --clean",
     "typecheck": "tsc --noEmit --project tsconfig.base.json",
     "typecheck:watch": "tsc --noEmit --watch --project tsconfig.base.json",
     "lint": "eslint \"{apps,libs}/**/*.ts\" --max-warnings 0",
@@ -122,4 +126,4 @@
     "tsx": "^4.20.6",
     "typescript": "^5.4.5"
   }
-}
+}

+ 0 - 1
prisma/mongo/schema/ads-module.prisma

@@ -25,7 +25,6 @@ model AdsModule {
   adsModule   String   @unique     /// 展示名称 (e.g. 启动页 / 轮播 / 弹窗-图标)
   moduleDesc  String?              /// 模块简介 + 比例 (例如: 启动页(10:21))
   seq         Int      @default(0)
-  ads         Ads[]
 
   @@map("adsModule")
 }

+ 1 - 4
prisma/mongo/schema/ads.prisma

@@ -1,6 +1,6 @@
 model Ads {
   id           String     @id @map("_id") @default(auto()) @db.ObjectId
-  adsModuleId  String     @db.ObjectId       // 广告模块 Id (banner/startup/轮播等)
+  adType       AdType                        // Redis key & module type
   advertiser   String                        // 广告商 (业务上限制 max 20 字符)
   title        String                        // 标题 (业务上限制 max 20 字符)
   adsContent   String?                       // 广告文案 (业务上限制 max 500 字符)
@@ -18,8 +18,5 @@ model Ads {
   createAt     BigInt     @default(0)        // 创建时间
   updateAt     BigInt     @default(0)        // 更新时间
 
-  // Relations
-  adsModule    AdsModule  @relation(fields: [adsModuleId], references: [id])
-
   @@map("ads")  // collection name
 }

+ 167 - 0
prisma/mongo/seed-ads.ts

@@ -0,0 +1,167 @@
+import {
+  PrismaClient,
+  AdType,
+  ImageSource,
+  Prisma,
+} from '@prisma/mongo/client';
+
+const prisma = new PrismaClient();
+
+type SeedArgs = {
+  clean: boolean;
+  perType: number;
+};
+
+function parseArgs(argv: string[]): SeedArgs {
+  const clean =
+    argv.includes('--clean') ||
+    argv.includes('-c') ||
+    process.env.SEED_CLEAN === '1' ||
+    process.env.SEED_CLEAN === 'true';
+
+  const perTypeFromArg = (() => {
+    const idx = argv.findIndex((a) => a === '--perType');
+    if (idx >= 0) {
+      const v = Number(argv[idx + 1]);
+      if (Number.isFinite(v) && v > 0) return Math.floor(v);
+    }
+    const envValue = Number(process.env.SEED_PER_TYPE);
+    if (Number.isFinite(envValue) && envValue > 0) return Math.floor(envValue);
+    return 100;
+  })();
+
+  return { clean, perType: perTypeFromArg };
+}
+
+function nowEpochSec(): bigint {
+  return BigInt(Math.floor(Date.now() / 1000));
+}
+
+function randInt(min: number, max: number): number {
+  // inclusive range
+  return Math.floor(Math.random() * (max - min + 1)) + min;
+}
+
+function pick<T>(arr: readonly T[]): T {
+  return arr[randInt(0, arr.length - 1)];
+}
+
+function clampLen(s: string, maxLen: number): string {
+  return s.length <= maxLen ? s : s.slice(0, maxLen);
+}
+
+function makeShortLabel(prefix: string, maxLen = 20): string {
+  const tail = randInt(1, 9999).toString().padStart(4, '0');
+  return clampLen(`${prefix}${tail}`, maxLen);
+}
+
+function makeCoverKey(adType: AdType, idx: number): string {
+  const n = String(idx + 1).padStart(3, '0');
+  return `ads/${adType.toLowerCase()}/img_${n}.png`;
+}
+
+function makeAdsUrl(adType: AdType): string {
+  const id = randInt(100000, 999999);
+  return `https://example.com/ads/${adType.toLowerCase()}?c=${id}`;
+}
+
+function makeContent(adType: AdType): string {
+  const phrases = [
+    '限时优惠',
+    '新人专享',
+    '立即领取',
+    '官方推荐',
+    '热卖爆款',
+    '今日必看',
+    '立刻参与',
+    '福利放送',
+    '超值精选',
+    '品质保证',
+  ] as const;
+
+  const base = `${pick(phrases)} · ${adType}`;
+  const extra = `|${pick(phrases)}|编号${randInt(1000, 9999)}`;
+  return clampLen(base + extra, 500);
+}
+
+function makeTimeWindow(): { startDt: bigint; expiryDt: bigint } {
+  const now = Number(nowEpochSec());
+  const startOffsetDays = randInt(-30, 30);
+  const start = now + startOffsetDays * 86400;
+  const durationDays = randInt(7, 90);
+  const expiry = start + durationDays * 86400;
+  return { startDt: BigInt(start), expiryDt: BigInt(expiry) };
+}
+
+async function main() {
+  const args = parseArgs(process.argv.slice(2));
+  const tsNow = nowEpochSec();
+  const adTypes = Object.values(AdType) as AdType[];
+
+  if (args.clean) {
+    await prisma.ads.deleteMany();
+    console.log('[seed:ads] cleaned ads collection');
+  }
+
+  const advertiserPrefixes = [
+    '广告商A',
+    '广告商B',
+    '广告商C',
+    '品牌X',
+    '品牌Y',
+    '品牌Z',
+  ] as const;
+
+  const titlePrefixes = [
+    '爆款推荐',
+    '限时福利',
+    '新品上线',
+    '今日热推',
+    '官方活动',
+    '专属礼包',
+  ] as const;
+
+  const batch: Prisma.AdsCreateManyInput[] = [];
+
+  for (const adType of adTypes) {
+    for (let idx = 0; idx < args.perType; idx++) {
+      const { startDt, expiryDt } = makeTimeWindow();
+      const advertiser = makeShortLabel(pick(advertiserPrefixes), 20);
+      const title = makeShortLabel(pick(titlePrefixes), 20);
+      const adsCoverImg =
+        Math.random() < 0.85 ? makeCoverKey(adType, idx) : null;
+      const adsUrl = Math.random() < 0.9 ? makeAdsUrl(adType) : null;
+      const adsContent = Math.random() < 0.8 ? makeContent(adType) : null;
+
+      batch.push({
+        adType,
+        advertiser,
+        title,
+        adsContent,
+        adsCoverImg,
+        adsUrl,
+        imgSource: ImageSource.LOCAL_ONLY,
+        startDt,
+        expiryDt,
+        seq: randInt(0, 9999),
+        status: Math.random() < 0.9 ? 1 : 0,
+        createAt: tsNow,
+        updateAt: tsNow,
+      });
+    }
+  }
+
+  const result = await prisma.ads.createMany({ data: batch });
+  console.log(
+    `[seed:ads] adTypes=${adTypes.length} perType=${args.perType} inserted=${result.count} clean=${args.clean}`,
+  );
+}
+
+main()
+  .catch((error) => {
+    console.error('[seed:ads] failed:', error);
+    process.exitCode = 1;
+  })
+  .finally(async () => {
+    await prisma.$disconnect();
+  });