소스 검색

refactor: remove deprecated methods and implement new channel ID generation logic

Dave 1 개월 전
부모
커밋
2c4a8eee0b
2개의 변경된 파일39개의 추가작업 그리고 221개의 파일을 삭제
  1. 0 137
      apps/box-app-api/src/feature/ads/ad.controller.ts
  2. 39 84
      apps/box-mgnt-api/src/mgnt-backend/feature/channel/channel.service.ts

+ 0 - 137
apps/box-app-api/src/feature/ads/ad.controller.ts

@@ -78,143 +78,6 @@ export class AdController {
     return response;
   }
 
-  /**
-   * GET /ads/:id/url
-   *
-   * Protected endpoint that requires JWT authentication.
-   * Returns the ad URL for the given ad ID and publishes an ADS_CLICK event.
-   *
-   * Example: GET /ads/507f1f77bcf86cd799439011/url
-   */
-  // @Get(':id/url')
-  // @UseGuards(JwtAuthGuard)
-  // @ApiBearerAuth()
-  // @ApiOperation({
-  //   summary: '获取广告URL',
-  //   description:
-  //     '通过广告ID获取广告链接。需要JWT认证。返回广告URL并记录点击事件。',
-  // })
-  // @ApiResponse({
-  //   status: 200,
-  //   description: '成功返回广告URL',
-  //   type: AdUrlResponseDto,
-  // })
-  // @ApiResponse({ status: 401, description: '未授权 - 需要JWT token' })
-  // @ApiResponse({ status: 404, description: '广告不存在或已过期' })
-  // async getAdUrl(
-  //   @Param('id') adsId: string,
-  //   @Req() req: RequestWithUser,
-  // ): Promise<AdUrlResponseDto> {
-  //   const uid = req.user?.uid;
-
-  //   if (!uid) {
-  //     this.logger.error('JWT payload missing uid');
-  //     throw new NotFoundException('User not authenticated');
-  //   }
-
-  //   // Extract client IP
-  //   const ip =
-  //     (req.headers['x-forwarded-for'] as string)?.split(',')[0]?.trim() ||
-  //     (req.headers['x-real-ip'] as string) ||
-  //     req.ip ||
-  //     'unknown';
-
-  //   // Extract optional headers
-  //   const userAgent = req.headers['user-agent'];
-  //   const appVersion = req.headers['app-version'] as string | undefined;
-  //   const os = req.headers['os'] as string | undefined;
-
-  //   // Delegate to service for business logic
-  //   return this.adService.getAdUrlAndPublishClick(
-  //     adsId,
-  //     uid,
-  //     ip,
-  //     userAgent,
-  //     appVersion,
-  //     os,
-  //   );
-  // }
-
-  /**
-   * POST /ads/click
-   *
-   * Record ad click event for analytics.
-   * Protected endpoint that requires JWT authentication.
-   */
-  // @Post('click')
-  // // @UseGuards(JwtAuthGuard)
-  // // @ApiBearerAuth()
-  // @ApiOperation({
-  //   summary: '广告点击事件上报',
-  //   description:
-  //     '记录广告点击事件(uid/IP/UA 由服务端填充,不接受客户端时间戳)。',
-  // })
-  // @ApiResponse({
-  //   status: 200,
-  //   description: '成功',
-  //   schema: { example: { status: 1, code: 'OK' } },
-  // })
-  // @ApiResponse({ status: 401, description: '未授权 - 需要JWT token' })
-  // async recordAdClick(
-  //   @Body() body: AdClickDto,
-  //   @Req() req: RequestWithUser,
-  // ): Promise<{ status: number; code: string }> {
-  //   const uid = req.user?.uid;
-
-  //   // if (!uid) {
-  //   //   this.logger.error('JWT payload missing uid');
-  //   //   throw new UnauthorizedException('Missing uid in JWT payload');
-  //   // }
-
-  //   const ip = this.getClientIp(req);
-  //   const userAgent = this.getUserAgent(req);
-
-  //   // Fire-and-forget: don't await for immediate response
-  //   this.adService.recordAdClick(uid, body, ip, userAgent);
-
-  //   return { status: 1, code: 'OK' };
-  // }
-
-  /**
-   * POST /ads/impression
-   *
-   * Record ad impression event for analytics.
-   * Protected endpoint that requires JWT authentication.
-   */
-  // @Post('impression')
-  // @UseGuards(JwtAuthGuard)
-  // @ApiBearerAuth()
-  // @ApiOperation({
-  //   summary: '广告曝光事件上报',
-  //   description:
-  //     '记录广告曝光事件(uid/IP/UA 由服务端填充,不接受客户端时间戳)。',
-  // })
-  // @ApiResponse({
-  //   status: 200,
-  //   description: '成功',
-  //   schema: { example: { status: 1, code: 'OK' } },
-  // })
-  // @ApiResponse({ status: 401, description: '未授权 - 需要JWT token' })
-  // async recordAdImpression(
-  //   @Body() body: AdImpressionDto,
-  //   @Req() req: RequestWithUser,
-  // ): Promise<{ status: number; code: string }> {
-  //   const uid = req.user?.uid;
-
-  //   if (!uid) {
-  //     this.logger.error('JWT payload missing uid');
-  //     throw new UnauthorizedException('Missing uid in JWT payload');
-  //   }
-
-  //   const ip = this.getClientIp(req);
-  //   const userAgent = this.getUserAgent(req);
-
-  //   // Fire-and-forget: don't await for immediate response
-  //   this.adService.recordAdImpression(uid, body, ip, userAgent);
-
-  //   return { status: 1, code: 'OK' };
-  // }
-
   private getClientIp(req: Request): string {
     return (
       (req.headers['x-forwarded-for'] as string)?.split(',')[0]?.trim() ||

+ 39 - 84
apps/box-mgnt-api/src/mgnt-backend/feature/channel/channel.service.ts

@@ -67,87 +67,47 @@ export class ChannelService {
     }));
   }
 
-  private async generateNextChannelNo(): Promise<number> {
-    const last = await this.mongoPrismaService.channel.findFirst({
-      where: { channelNo: { isSet: true } },
-      orderBy: { channelNo: 'desc' },
-      select: { channelNo: true },
+  // private async generateNextChannelNo(): Promise<number> {
+  //   const last = await this.mongoPrismaService.channel.findFirst({
+  //     where: { channelNo: { isSet: true } },
+  //     orderBy: { channelNo: 'desc' },
+  //     select: { channelNo: true },
+  //   });
+
+  //   return (last?.channelNo ?? 0) + 1;
+  // }
+
+  private async generateNextChannelIdNo(): Promise<number> {
+    const res = await this.mongoPrismaService.$runCommandRaw({
+      aggregate: 'channel',
+      pipeline: [
+        // If you want extra safety, uncomment this:
+        // { $match: { channelId: { $regex: /^[0-9]+$/ } } },
+        { $match: { channelId: { $type: 'string' } } },
+        { $project: { n: { $toLong: '$channelId' } } },
+        { $group: { _id: null, maxN: { $max: '$n' } } },
+      ],
+      cursor: {},
     });
 
-    return (last?.channelNo ?? 0) + 1;
-  }
-
-  private async backfillIds(): Promise<void> {
-    if (this.isBackfilling) {
-      this.logger.warn('backfill is already running, skipping.');
-      return;
-    }
-
-    this.isBackfilling = true;
-    this.logger.log('Starting backfill of id...');
-    try {
-      const withoutId = await this.mongoPrismaService.channel.findMany({
-        where: {
-          OR: [{ channelNo: { isSet: false } }, { channelNo: null }],
-        },
-        orderBy: { createAt: 'asc' },
-        select: { id: true },
-      });
-
-      if (withoutId.length === 0) {
-        this.logger.log('No channels need backfilling channelNo.');
-        return;
-      }
+    const first = (res as any)?.cursor?.firstBatch?.[0];
+    const maxN = first?.maxN;
 
-      this.logger.log(
-        `Found ${withoutId.length} channels without channelNo. Starting backfill...`,
-      );
+    const maxNum =
+      typeof maxN === 'number'
+        ? maxN
+        : typeof maxN?.toNumber === 'function'
+          ? maxN.toNumber()
+          : Number(maxN ?? 0);
 
-      let nextUid = await this.generateNextChannelNo();
-
-      for (const channel of withoutId) {
-        let assigned = false;
-
-        while (!assigned) {
-          try {
-            await this.mongoPrismaService.channel.update({
-              where: { id: channel.id },
-              data: { channelNo: nextUid },
-            });
-
-            this.logger.log(
-              `Backfilled channelNo ${nextUid} for channel id ${channel.id}`,
-            );
-
-            nextUid += 1;
-            assigned = true;
-          } catch (e: any) {
-            // Unique constraint violation → retry with a fresh number
-            if (e?.code === 'P2002') {
-              this.logger.warn(`Duplicate channelNo ${nextUid}, retrying...`);
-              nextUid = await this.generateNextChannelNo();
-            } else {
-              throw e;
-            }
-          }
-        }
-      }
-
-      this.logger.log(
-        `Backfilled ${withoutId.length} channelNos successfully.`,
-      );
-    } finally {
-      this.isBackfilling = false;
-      this.logger.log('Finished backfilling process.');
+    if (!Number.isFinite(maxNum) || maxNum < 0) {
+      throw new Error(`Invalid max channelId computed: ${String(maxN)}`);
     }
+
+    return maxNum + 1;
   }
 
   async create(dto: CreateChannelDto) {
-    await this.backfillIds().catch((e) => {
-      this.logger.error('Error during backfillIds:', e);
-    });
-
-    // Check for duplicate channel name
     const existingChannel = await this.mongoPrismaService.channel.findFirst({
       where: { name: dto.name },
       select: { id: true },
@@ -171,8 +131,7 @@ export class ChannelService {
     const isDefault = (await this.mongoPrismaService.channel.count()) === 0;
     const now = this.now();
 
-    // Generate and create with retry to avoid duplicate channelNo under concurrency
-    let nextNo = await this.generateNextChannelNo();
+    let nextNo = await this.generateNextChannelIdNo();
     let channel: Awaited<
       ReturnType<typeof this.mongoPrismaService.channel.create>
     > | null = null;
@@ -181,8 +140,7 @@ export class ChannelService {
       try {
         channel = await this.mongoPrismaService.channel.create({
           data: {
-            channelNo: nextNo,
-            channelId: String(nextNo), // channelId mirrors channelNo on create
+            channelId: String(nextNo), // numeric string, generated
             name: dto.name,
             landingUrl: dto.landingUrl,
             videoCdn: this.trimOptional(dto.videoCdn) ?? null,
@@ -204,17 +162,17 @@ export class ChannelService {
           (e instanceof PrismaClientKnownRequestError && e.code === 'P2002');
 
         if (isP2002) {
-          this.logger.warn(`Duplicate channelNo ${nextNo}, retrying...`);
-          nextNo = await this.generateNextChannelNo();
+          this.logger.warn(
+            `Duplicate channelId "${String(nextNo)}", retrying...`,
+          );
+          nextNo = await this.generateNextChannelIdNo();
           continue;
         }
         throw e;
       }
     }
 
-    // Auto-schedule cache refresh
     await this.cacheSyncService.scheduleChannelRefreshAll();
-    // Schedule channel-with-categories rebuild for this channel
     await this.cacheSyncService.scheduleAction({
       entityType: CacheEntityType.CHANNEL,
       operation: CacheOperation.REFRESH,
@@ -301,9 +259,6 @@ export class ChannelService {
   }
 
   async list(dto: ListChannelDto) {
-    await this.backfillIds().catch((e) => {
-      this.logger.error('Error during backfillIds:', e);
-    });
     const where: any = {};
 
     if (dto.name) {