|
|
@@ -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();
|
|
|
+ });
|