| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103 |
- import {
- BadRequestException,
- Body,
- Controller,
- Headers,
- HttpCode,
- Logger,
- Post,
- Req,
- } from '@nestjs/common';
- import {
- ApiBody,
- ApiHeaders,
- ApiOperation,
- ApiResponse,
- ApiTags,
- ApiBadRequestResponse,
- ApiProperty,
- } 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';
- export class AdClickRequestDto {
- @ApiProperty({ description: '用户唯一设备ID', example: 'xxxxxx' })
- @IsString()
- uid!: string;
- @ApiProperty({ description: '渠道ID', example: 'AAA' })
- @IsString()
- channelId!: string;
- @ApiProperty({
- description: '广告 Mongo ObjectId',
- example: '64b7c2f967ce4d6799c047d1',
- })
- @IsString()
- adsId!: string; // ✅ required
- }
- @ApiTags('Ads Stats')
- @Controller('stats')
- export class AdsStatsController {
- private readonly logger = new Logger(AdsStatsController.name);
- constructor(private readonly publisher: StatsAdClickPublisherService) {}
- @Post('ad-click')
- @HttpCode(202)
- @ApiOperation({
- summary: '广告点击上报',
- description: '用于广告点击统计。uid、channelId、adsId 必填。',
- })
- @ApiBody({
- type: AdClickRequestDto,
- description: 'uid、channelId、adsId 必填(adsId 为 Mongo ObjectId)',
- examples: {
- example: {
- summary: '示例',
- value: {
- uid: 'xxxxxx',
- channelId: 'AAA',
- adsId: '64b7c2f967ce4d6799c047d1',
- },
- },
- },
- })
- @ApiResponse({
- status: 202,
- description: '已接收(异步处理)',
- schema: { example: { ok: true } },
- })
- @ApiBadRequestResponse({
- description: '参数错误(缺少 uid/channelId/adsId)',
- })
- publishAdClick(
- @Req() req: Request,
- @Body() body: AdClickRequestDto,
- ): { ok: true } {
- if (!body?.uid || !body?.channelId || !body?.adsId) {
- throw new BadRequestException('uid, channelId and adsId are required');
- }
- this.publisher.publishAdClick({
- uid: body.uid,
- channelId: body.channelId,
- adsId: body.adsId,
- headers: req.headers,
- clientIp: extractClientIp(req),
- });
- 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;
- }
- }
|