import { Controller, Get, Post, Body, Param, HttpCode, HttpStatus, Put, Logger, UseGuards, Inject, Req, } from '@nestjs/common'; import { Request } from 'express'; import { WINSTON_MODULE_PROVIDER } from 'nest-winston'; import { Logger as WinstonLogger } from 'winston'; import { UsersService } from './users.service'; import { CreateUserDto } from './dto/create-user.dto'; import { UserResponseDto } from './dto/user-response.dto'; import { ApiOperation, ApiBody, ApiResponse, ApiTags } from '@nestjs/swagger'; import { UpdateUserDto, UpdateUserResponseDto } from './dto/update-user.dto'; import { AppleLoginDto, AppleLoginResponseDto, RefreshTokenDto, RefreshTokenResponseDto } from './dto/apple-login.dto'; import { DeleteAccountDto, DeleteAccountResponseDto } from './dto/delete-account.dto'; import { GuestLoginDto, GuestLoginResponseDto, RefreshGuestTokenDto, RefreshGuestTokenResponseDto } from './dto/guest-login.dto'; import { AppStoreServerNotificationDto, ProcessNotificationResponseDto } from './dto/app-store-notification.dto'; import { RestorePurchaseDto, RestorePurchaseResponseDto } from './dto/restore-purchase.dto'; import { Public } from '../common/decorators/public.decorator'; import { CurrentUser } from '../common/decorators/current-user.decorator'; import { AccessTokenPayload } from './services/apple-auth.service'; import { JwtAuthGuard } from 'src/common/guards/jwt-auth.guard'; import { ResponseCode } from 'src/base.dto'; @ApiTags('users') @Controller('users') export class UsersController { private readonly logger = new Logger(UsersController.name); constructor( private readonly usersService: UsersService, @Inject(WINSTON_MODULE_PROVIDER) private readonly winstonLogger: WinstonLogger, ) { } @UseGuards(JwtAuthGuard) @Get('/info') @HttpCode(HttpStatus.OK) @ApiOperation({ summary: '创建用户' }) @ApiBody({ type: CreateUserDto }) @ApiResponse({ type: UserResponseDto }) async getProfile(@CurrentUser() user: AccessTokenPayload): Promise { this.logger.log(`get profile: ${JSON.stringify(user)}`); return this.usersService.getProfile(user); } // 更新用户昵称、头像 @UseGuards(JwtAuthGuard) @Put('update') async updateUser(@Body() updateUserDto: UpdateUserDto): Promise { return this.usersService.updateUser(updateUserDto); } // Apple 登录 @Public() @Post('auth/apple/login') @HttpCode(HttpStatus.OK) @ApiOperation({ summary: 'Apple 登录验证' }) @ApiBody({ type: AppleLoginDto }) @ApiResponse({ type: AppleLoginResponseDto }) async appleLogin(@Body() appleLoginDto: AppleLoginDto): Promise { return this.usersService.appleLogin(appleLoginDto); } // 刷新访问令牌 @Public() @Post('auth/refresh') @HttpCode(HttpStatus.OK) @ApiOperation({ summary: '刷新访问令牌' }) @ApiBody({ type: RefreshTokenDto }) @ApiResponse({ type: RefreshTokenResponseDto }) async refreshToken(@Body() refreshTokenDto: RefreshTokenDto): Promise { return this.usersService.refreshToken(refreshTokenDto); } // 删除用户账号 @UseGuards(JwtAuthGuard) @Post('delete-account') @HttpCode(HttpStatus.OK) @ApiOperation({ summary: '删除用户账号' }) @ApiBody({ type: DeleteAccountDto }) @ApiResponse({ type: DeleteAccountResponseDto }) async deleteAccount(@CurrentUser() user: AccessTokenPayload): Promise { const deleteAccountDto = { userId: user.sub, }; return this.usersService.deleteAccount(deleteAccountDto); } // 游客登录 @Public() @Post('auth/guest/login') @HttpCode(HttpStatus.OK) @ApiOperation({ summary: '游客登录' }) @ApiBody({ type: GuestLoginDto }) @ApiResponse({ type: GuestLoginResponseDto }) async guestLogin(@Body() guestLoginDto: GuestLoginDto): Promise { return this.usersService.guestLogin(guestLoginDto); } // 刷新游客令牌 @Public() @Post('auth/guest/refresh') @HttpCode(HttpStatus.OK) @ApiOperation({ summary: '刷新游客访问令牌' }) @ApiBody({ type: RefreshGuestTokenDto }) @ApiResponse({ type: RefreshGuestTokenResponseDto }) async refreshGuestToken(@Body() refreshGuestTokenDto: RefreshGuestTokenDto): Promise { return this.usersService.refreshGuestToken(refreshGuestTokenDto); } // App Store 服务器通知接收接口 @Public() @Post('app-store-notifications') @HttpCode(HttpStatus.OK) @ApiOperation({ summary: '接收App Store服务器通知' }) @ApiBody({ type: AppStoreServerNotificationDto }) @ApiResponse({ type: ProcessNotificationResponseDto }) async handleAppStoreNotification(@Body() notificationDto: AppStoreServerNotificationDto): Promise { this.logger.log(`收到App Store服务器通知`); return this.usersService.processAppStoreNotification(notificationDto); } // RevenueCat Webhook @Public() @Post('revenuecat-webhook') @HttpCode(HttpStatus.OK) @ApiOperation({ summary: '接收 RevenueCat webhook' }) async handleRevenueCatWebhook(@Body() webhook) { // 使用结构化日志记录webhook接收 this.winstonLogger.info('RevenueCat webhook received', { context: 'UsersController', eventType: webhook.event?.type, eventId: webhook.event?.id, appUserId: webhook.event?.app_user_id, timestamp: new Date().toISOString(), webhookData: { apiVersion: webhook.api_version, eventTimestamp: webhook.event?.event_timestamp_ms } }); try { await this.usersService.handleRevenueCatWebhook(webhook); this.winstonLogger.info('RevenueCat webhook processed successfully', { context: 'UsersController', eventType: webhook.event?.type, eventId: webhook.event?.id, appUserId: webhook.event?.app_user_id }); return { code: ResponseCode.SUCCESS, message: 'success' }; } catch (error) { this.winstonLogger.error('RevenueCat webhook processing failed', { context: 'UsersController', eventType: webhook.event?.type, eventId: webhook.event?.id, appUserId: webhook.event?.app_user_id, error: error.message, stack: error.stack }); return { code: ResponseCode.ERROR, message: error.message }; } } // 恢复购买 @UseGuards(JwtAuthGuard) @Post('restore-purchase') async restorePurchase( @Body() restorePurchaseDto: RestorePurchaseDto, @CurrentUser() user: AccessTokenPayload, @Req() request: Request ): Promise { const clientIp = request.ip || request.connection.remoteAddress || 'unknown'; const userAgent = request.get('User-Agent') || 'unknown'; this.logger.log(`恢复购买请求 - 用户ID: ${user.sub}, IP: ${clientIp}`); // 记录安全相关信息 this.winstonLogger.info('Purchase restore request', { context: 'UsersController', userId: user.sub, clientIp, userAgent, originalAppUserId: restorePurchaseDto.customerInfo?.originalAppUserId, entitlementsCount: Object.keys(restorePurchaseDto.customerInfo?.activeEntitlements || {}).length, nonSubTransactionsCount: restorePurchaseDto.customerInfo?.nonSubscriptionTransactions?.length || 0 }); return this.usersService.restorePurchase(restorePurchaseDto, user.sub, clientIp, userAgent); } }