Explorar el Código

feat(ad-click): add optional IPv4 hint to AdClickRequestDto and update client IP extraction logic

Dave hace 1 mes
padre
commit
cda8b499ea

+ 1 - 0
.gitignore

@@ -78,3 +78,4 @@ libs/core/media-manager/CONTRACT.md
 scripts/*
 box-nestjs-monorepo-init.md
 box-nestjs-monorepo-init.md
+box-nestjs-monorepo-init.md

+ 26 - 11
apps/box-stats-api/src/feature/stats-events/ads-stats.controller.ts

@@ -10,17 +10,18 @@ import {
 } from '@nestjs/common';
 import {
   ApiBody,
-  ApiHeaders,
   ApiOperation,
   ApiResponse,
   ApiTags,
   ApiBadRequestResponse,
   ApiProperty,
+  ApiPropertyOptional,
 } from '@nestjs/swagger';
 import { Request } from 'express';
 import { StatsAdClickPublisherService } from './stats-ad-click.publisher.service';
 import { extractClientIp } from '../../utils/client-ip.util';
-import { IsString } from 'class-validator';
+import { Transform } from 'class-transformer';
+import { IsOptional, IsString, Matches } from 'class-validator';
 
 export class AdClickRequestDto {
   @ApiProperty({ description: '用户唯一设备ID', example: 'xxxxxx' })
@@ -37,6 +38,23 @@ export class AdClickRequestDto {
   })
   @IsString()
   adsId!: string; // ✅ required
+
+  @ApiPropertyOptional({
+    description: 'Optional IPv4 hint (empty string/null accepted).',
+    example: '1.2.3.4',
+  })
+  @Transform(({ value }) => {
+    if (value === '' || value === null || value === undefined) {
+      return undefined;
+    }
+    return typeof value === 'string' ? value.trim() : value;
+  })
+  @IsOptional()
+  @Matches(
+    /^(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)){3}$/,
+    { message: 'ipv4 must be a valid IPv4 address' },
+  )
+  ipv4?: string;
 }
 
 @ApiTags('Ads Stats')
@@ -82,22 +100,19 @@ export class AdsStatsController {
       throw new BadRequestException('uid, channelId and adsId are required');
     }
 
+    const clientIp =
+      // `ipv4` is a frontend-provided hint, used only when present & validated;
+      // backend extraction remains the fallback.
+      body.ipv4 || extractClientIp(req);
+
     this.publisher.publishAdClick({
       uid: body.uid,
       channelId: body.channelId,
       adsId: body.adsId,
       headers: req.headers,
-      clientIp: extractClientIp(req),
+      clientIp,
     });
 
     return { ok: true };
   }
-
-  private extractForwardedFor(headers: Request['headers']): string | undefined {
-    const header = headers?.['x-forwarded-for'];
-    if (!header) return undefined;
-    const value = Array.isArray(header) ? header[0] : header;
-    const first = value.split(',')[0]?.trim();
-    return first || undefined;
-  }
 }

+ 4 - 0
box-nestjs-monorepo-init.md

@@ -688,3 +688,7 @@ After seeding, login with:
 **Prisma Version**: 5.15.0  
 **NestJS Version**: 10.3.8  
 **Node Version**: 20+
+
+## 🧭 Ads stats hints (box-stats-api)
+
+- `POST /stats/api/v1/stats/ad-click` accepts optional `ipv4` (must be valid IPv4); the backend uses it when present and falls back to extracted client IP otherwise.