seed-ads.ts 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. import {
  2. PrismaClient,
  3. AdType,
  4. ImageSource,
  5. Prisma,
  6. } from '@prisma/mongo/client';
  7. export function nowSecBigInt(): bigint {
  8. return BigInt(Math.floor(Date.now() / 1000));
  9. }
  10. const prisma = new PrismaClient();
  11. type SeedArgs = {
  12. clean: boolean;
  13. perType: number;
  14. };
  15. function parseArgs(argv: string[]): SeedArgs {
  16. const clean =
  17. argv.includes('--clean') ||
  18. argv.includes('-c') ||
  19. process.env.SEED_CLEAN === '1' ||
  20. process.env.SEED_CLEAN === 'true';
  21. const perTypeFromArg = (() => {
  22. const idx = argv.findIndex((a) => a === '--perType');
  23. if (idx >= 0) {
  24. const v = Number(argv[idx + 1]);
  25. if (Number.isFinite(v) && v > 0) return Math.floor(v);
  26. }
  27. const envValue = Number(process.env.SEED_PER_TYPE);
  28. if (Number.isFinite(envValue) && envValue > 0) return Math.floor(envValue);
  29. return 100;
  30. })();
  31. return { clean, perType: perTypeFromArg };
  32. }
  33. function nowEpochSec(): bigint {
  34. return nowSecBigInt();
  35. }
  36. function randInt(min: number, max: number): number {
  37. // inclusive range
  38. return Math.floor(Math.random() * (max - min + 1)) + min;
  39. }
  40. function pick<T>(arr: readonly T[]): T {
  41. return arr[randInt(0, arr.length - 1)];
  42. }
  43. function clampLen(s: string, maxLen: number): string {
  44. return s.length <= maxLen ? s : s.slice(0, maxLen);
  45. }
  46. function makeShortLabel(prefix: string, maxLen = 20): string {
  47. const tail = randInt(1, 9999).toString().padStart(4, '0');
  48. return clampLen(`${prefix}${tail}`, maxLen);
  49. }
  50. function makeCoverKey(adType: AdType, idx: number): string {
  51. const n = String(idx + 1).padStart(3, '0');
  52. return `ads/${adType.toLowerCase()}/img_${n}.png`;
  53. }
  54. function makeAdsUrl(adType: AdType): string {
  55. const id = randInt(100000, 999999);
  56. return `https://example.com/ads/${adType.toLowerCase()}?c=${id}`;
  57. }
  58. function makeContent(adType: AdType): string {
  59. const phrases = [
  60. '限时优惠',
  61. '新人专享',
  62. '立即领取',
  63. '官方推荐',
  64. '热卖爆款',
  65. '今日必看',
  66. '立刻参与',
  67. '福利放送',
  68. '超值精选',
  69. '品质保证',
  70. ] as const;
  71. const base = `${pick(phrases)} · ${adType}`;
  72. const extra = `|${pick(phrases)}|编号${randInt(1000, 9999)}`;
  73. return clampLen(base + extra, 500);
  74. }
  75. function makeTimeWindow(): { startDt: bigint; expiryDt: bigint } {
  76. const now = Number(nowEpochSec());
  77. const startOffsetDays = randInt(-30, 30);
  78. const start = now + startOffsetDays * 86400;
  79. const durationDays = randInt(7, 90);
  80. const expiry = start + durationDays * 86400;
  81. return { startDt: BigInt(start), expiryDt: BigInt(expiry) };
  82. }
  83. async function main() {
  84. const args = parseArgs(process.argv.slice(2));
  85. const tsNow = nowEpochSec();
  86. const adTypes = Object.values(AdType) as AdType[];
  87. if (args.clean) {
  88. await prisma.ads.deleteMany();
  89. console.log('[seed:ads] cleaned ads collection');
  90. }
  91. const advertiserPrefixes = [
  92. '广告商A',
  93. '广告商B',
  94. '广告商C',
  95. '品牌X',
  96. '品牌Y',
  97. '品牌Z',
  98. ] as const;
  99. const titlePrefixes = [
  100. '爆款推荐',
  101. '限时福利',
  102. '新品上线',
  103. '今日热推',
  104. '官方活动',
  105. '专属礼包',
  106. ] as const;
  107. const batch: Prisma.AdsCreateManyInput[] = [];
  108. for (const adType of adTypes) {
  109. for (let idx = 0; idx < args.perType; idx++) {
  110. const { startDt, expiryDt } = makeTimeWindow();
  111. const advertiser = makeShortLabel(pick(advertiserPrefixes), 20);
  112. const title = makeShortLabel(pick(titlePrefixes), 20);
  113. const adsCoverImg =
  114. Math.random() < 0.85 ? makeCoverKey(adType, idx) : null;
  115. const adsUrl = Math.random() < 0.9 ? makeAdsUrl(adType) : null;
  116. const adsContent = Math.random() < 0.8 ? makeContent(adType) : null;
  117. batch.push({
  118. adType,
  119. advertiser,
  120. title,
  121. adsContent,
  122. adsCoverImg,
  123. adsUrl,
  124. imgSource: ImageSource.LOCAL_ONLY,
  125. startDt,
  126. expiryDt,
  127. seq: randInt(0, 9999),
  128. status: Math.random() < 0.9 ? 1 : 0,
  129. createAt: tsNow,
  130. updateAt: tsNow,
  131. });
  132. }
  133. }
  134. const result = await prisma.ads.createMany({ data: batch });
  135. console.log(
  136. `[seed:ads] adTypes=${adTypes.length} perType=${args.perType} inserted=${result.count} clean=${args.clean}`,
  137. );
  138. }
  139. main()
  140. .catch((error) => {
  141. console.error('[seed:ads] failed:', error);
  142. process.exitCode = 1;
  143. })
  144. .finally(async () => {
  145. await prisma.$disconnect();
  146. });