Files
plates-server/src/users/users.controller.ts

203 lines
7.3 KiB
TypeScript

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<UserResponseDto> {
this.logger.log(`get profile: ${JSON.stringify(user)}`);
return this.usersService.getProfile(user);
}
// 更新用户昵称、头像
@UseGuards(JwtAuthGuard)
@Put('update')
async updateUser(@Body() updateUserDto: UpdateUserDto): Promise<UpdateUserResponseDto> {
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<AppleLoginResponseDto> {
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<RefreshTokenResponseDto> {
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<DeleteAccountResponseDto> {
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<GuestLoginResponseDto> {
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<RefreshGuestTokenResponseDto> {
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<ProcessNotificationResponseDto> {
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<RestorePurchaseResponseDto> {
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);
}
}