|
|
@@ -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;
|
|
|
- }
|
|
|
}
|