diff --git a/DEPLOY.md b/DEPLOY.md index 76b88c6..b67ca11 100644 --- a/DEPLOY.md +++ b/DEPLOY.md @@ -25,7 +25,7 @@ ## 服务器配置 -- **服务器地址**: 119.91.211.52 +- **服务器地址**: 129.204.155.94 - **用户**: root(可在脚本中修改) - **部署目录**: /usr/local/web/pilates-server @@ -38,7 +38,7 @@ ssh-keygen -t rsa -b 4096 -C "your_email@example.com" # 将公钥复制到服务器 -ssh-copy-id root@119.91.211.52 +ssh-copy-id root@129.204.155.94 ``` ### 2. 服务器环境要求 @@ -141,16 +141,16 @@ ssh-copy-id root@119.91.211.52 ```bash # 查看服务状态 -ssh root@119.91.211.52 'cd /usr/local/web/pilates-server && pm2 status' +ssh root@129.204.155.94 'cd /usr/local/web/pilates-server && pm2 status' # 查看日志 -ssh root@119.91.211.52 'cd /usr/local/web/pilates-server && pm2 logs' +ssh root@129.204.155.94 'cd /usr/local/web/pilates-server && pm2 logs' # 重启服务 -ssh root@119.91.211.52 'cd /usr/local/web/pilates-server && pm2 restart ecosystem.config.js' +ssh root@129.204.155.94 'cd /usr/local/web/pilates-server && pm2 restart ecosystem.config.js' # 停止服务 -ssh root@119.91.211.52 'cd /usr/local/web/pilates-server && pm2 stop ecosystem.config.js' +ssh root@129.204.155.94 'cd /usr/local/web/pilates-server && pm2 stop ecosystem.config.js' ``` ## 自定义配置 @@ -158,7 +158,7 @@ ssh root@119.91.211.52 'cd /usr/local/web/pilates-server && pm2 stop ecosystem.c 如需修改服务器配置,请编辑脚本文件中的以下变量: ```bash -SERVER_HOST="119.91.211.52" # 服务器地址 +SERVER_HOST="129.204.155.94" # 服务器地址 SERVER_USER="root" # SSH用户名 SERVER_PATH="/usr/local/web/pilates-server" # 部署目录 ``` @@ -211,7 +211,7 @@ SERVER_PATH="/usr/local/web/pilates-server" # 部署目录 ```bash # 登录服务器 -ssh root@119.91.211.52 +ssh root@129.204.155.94 # 进入项目目录 cd /usr/local/web/pilates-server diff --git a/README.md b/README.md index 93ac685..3d9e7f0 100644 --- a/README.md +++ b/README.md @@ -204,7 +204,7 @@ yarn start:dev ### 服务器配置 -- **服务器地址**: 119.91.211.52 +- **服务器地址**: 129.204.155.94 - **用户**: root - **部署目录**: /usr/local/web/pilates-server diff --git a/deploy-optimized.sh b/deploy-optimized.sh index b928a1c..4c9f895 100755 --- a/deploy-optimized.sh +++ b/deploy-optimized.sh @@ -1,9 +1,9 @@ #!/bin/bash # 优化版发布脚本 - 只上传源码,服务器端构建 -SERVER_HOST="119.91.211.52" +SERVER_HOST="129.204.155.94" SERVER_USER="root" -SERVER_PATH="/usr/local/web/love-tips-server" +SERVER_PATH="/usr/local/web/pilates-server" # 定义颜色 GREEN='\033[0;32m' diff --git a/deploy-simple.sh b/deploy-simple.sh index 375561c..60e8c80 100755 --- a/deploy-simple.sh +++ b/deploy-simple.sh @@ -1,9 +1,9 @@ #!/bin/bash # 简化版发布脚本 -SERVER_HOST="119.91.211.52" +SERVER_HOST="129.204.155.94" SERVER_USER="root" -SERVER_PATH="/usr/local/web/love-tips-server" +SERVER_PATH="/usr/local/web/pilates-server" echo "🚀 开始部署到服务器..." diff --git a/deploy.sh b/deploy.sh index 606483d..69c58c3 100755 --- a/deploy.sh +++ b/deploy.sh @@ -1,10 +1,10 @@ #!/bin/bash # 发布脚本配置 -SERVER_HOST="119.91.211.52" +SERVER_HOST="129.204.155.94" SERVER_USER="root" # 根据实际情况修改用户名 -SERVER_PATH="/usr/local/web/love-tips-server" -PROJECT_NAME="love-tips-server" +SERVER_PATH="/usr/local/web/pilates-server" +PROJECT_NAME="pilates-server" # 定义颜色 GREEN='\033[0;32m' diff --git a/docs/winston-logger-guide.md b/docs/winston-logger-guide.md index 715eb7b..77af128 100644 --- a/docs/winston-logger-guide.md +++ b/docs/winston-logger-guide.md @@ -138,7 +138,7 @@ export class UsersController { NODE_ENV=production # 自定义日志目录 (可选) -LOG_DIR=/var/log/love-tips-server +LOG_DIR=/var/log/pilates-server ``` ## 最佳实践 diff --git a/ecosystem.config.js b/ecosystem.config.js index 707b3bf..5cca40b 100644 --- a/ecosystem.config.js +++ b/ecosystem.config.js @@ -15,7 +15,7 @@ module.exports = { merge_logs: true, env_production: { NODE_ENV: 'production', - PORT: 3000 + PORT: 3002 }, env_development: { NODE_ENV: 'development', diff --git a/package-lock.json b/package-lock.json index 99030a7..9b2f289 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "love-tips-server", + "name": "pilates-server", "version": "0.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "love-tips-server", + "name": "pilates-server", "version": "0.0.1", "license": "UNLICENSED", "dependencies": { @@ -13525,4 +13525,4 @@ } } } -} +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index d7a5b98..439dc13 100644 --- a/src/main.ts +++ b/src/main.ts @@ -43,15 +43,15 @@ async function bootstrap() { // swigger const config = new DocumentBuilder() - .setTitle('Love Tips API') - .setDescription('Love Tips API description') + .setTitle('Pilates API') + .setDescription('Pilates API description') .setVersion('1.0') .build(); const documentFactory = () => SwaggerModule.createDocument(app, config); SwaggerModule.setup('api/docs', app, documentFactory); - const port = process.env.PORT ?? 3000; + const port = process.env.PORT ?? 3002; await app.listen(port); const appLogger = new Logger('Bootstrap'); diff --git a/src/users/dto/client-log.dto.ts b/src/users/dto/client-log.dto.ts deleted file mode 100644 index 9b23969..0000000 --- a/src/users/dto/client-log.dto.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { IsString, IsNotEmpty, IsOptional, IsEnum, IsDateString, IsNumber, Min } from 'class-validator'; -import { LogLevel } from '../models/client-log.model'; -import { BaseResponseDto } from '../../base.dto'; - -// 创建客户端日志请求DTO -export class CreateClientLogDto { - @IsString() - @IsNotEmpty() - userId: string; - - @IsString() - @IsNotEmpty() - logContent: string; - - @IsOptional() - @IsEnum(LogLevel) - logLevel?: LogLevel; - - @IsOptional() - @IsString() - clientVersion?: string; - - @IsOptional() - @IsString() - deviceModel?: string; - - @IsOptional() - @IsString() - iosVersion?: string; - - @IsOptional() - @IsDateString() - clientTimestamp?: string; -} - -// 批量创建客户端日志请求DTO -export class CreateBatchClientLogDto { - @IsString() - @IsNotEmpty() - userId: string; - - logs: Omit[]; -} - -// 查询客户端日志请求DTO -export class GetClientLogsDto { - @IsString() - @IsNotEmpty() - userId: string; - - @IsOptional() - @IsNumber() - @Min(1) - page?: number; - - @IsOptional() - @IsNumber() - @Min(1) - pageSize?: number; - - @IsOptional() - @IsEnum(LogLevel) - logLevel?: LogLevel; - - @IsOptional() - @IsDateString() - startDate?: string; - - @IsOptional() - @IsDateString() - endDate?: string; -} - -// 客户端日志响应DTO -export class ClientLogResponseDto { - id: number; - userId: string; - logContent: string; - logLevel: LogLevel; - clientVersion?: string; - deviceModel?: string; - iosVersion?: string; - clientTimestamp?: Date; - createdAt: Date; -} - -// 创建客户端日志响应DTO -export interface CreateClientLogResponseDto extends BaseResponseDto {} - -// 批量创建客户端日志响应DTO -export interface CreateBatchClientLogResponseDto extends BaseResponseDto {} - -// 获取客户端日志列表响应DTO -export interface GetClientLogsResponseDto extends BaseResponseDto<{ - total: number; - list: ClientLogResponseDto[]; - page: number; - pageSize: number; -}> {} \ No newline at end of file diff --git a/src/users/dto/encrypted-user.dto.ts b/src/users/dto/encrypted-user.dto.ts deleted file mode 100644 index 37298f4..0000000 --- a/src/users/dto/encrypted-user.dto.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { IsString, IsNotEmpty } from 'class-validator'; -import { ApiProperty } from '@nestjs/swagger'; - -export class EncryptedCreateUserDto { - @IsString({ message: '加密数据必须是字符串' }) - @IsNotEmpty({ message: '加密数据不能为空' }) - @ApiProperty({ - description: '加密的用户数据', - example: 'eyJpdiI6IjEyMzQ1Njc4OTAiLCJ0YWciOiJhYmNkZWZnaCIsImRhdGEiOiIuLi4ifQ==' - }) - encryptedData: string; -} - -export class EncryptedResponseDto { - @ApiProperty({ description: '是否成功', example: true }) - success: boolean; - - @ApiProperty({ description: '响应消息', example: '操作成功' }) - message: string; - - @ApiProperty({ description: '加密的响应数据', required: false }) - encryptedData?: string; -} \ No newline at end of file diff --git a/src/users/dto/topic-favorite.dto.ts b/src/users/dto/topic-favorite.dto.ts deleted file mode 100644 index 0dad3bf..0000000 --- a/src/users/dto/topic-favorite.dto.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { IsNumber, IsBoolean } from 'class-validator'; -import { ApiProperty } from '@nestjs/swagger'; -import { ResponseCode } from 'src/base.dto'; - -// 收藏话题请求DTO -export class FavoriteTopicDto { - @ApiProperty({ description: '话题ID' }) - @IsNumber() - topicId: number; -} - -// 取消收藏话题请求DTO -export class UnfavoriteTopicDto { - @ApiProperty({ description: '话题ID' }) - @IsNumber() - topicId: number; -} - -// 收藏操作响应DTO -export class FavoriteResponseDto { - @ApiProperty({ description: '响应代码' }) - code: ResponseCode; - - @ApiProperty({ description: '响应消息' }) - message: string; - - @ApiProperty({ description: '操作结果' }) - data: { - success: boolean; - isFavorited: boolean; - topicId: number; - }; -} - -// 扩展的话题响应DTO(包含收藏状态) -export class TopicWithFavoriteDto { - @ApiProperty({ description: '话题ID' }) - id: number; - - @ApiProperty({ description: '话题标题' }) - topic: string; - - @ApiProperty({ description: '开场白' }) - opening: string | object; - - @ApiProperty({ description: '脚本类型' }) - scriptType: string; - - @ApiProperty({ description: '脚本话题' }) - scriptTopic: string; - - @ApiProperty({ description: '关键词' }) - keywords: string; - - @ApiProperty({ description: '是否已收藏' }) - @IsBoolean() - isFavorited: boolean; - - @ApiProperty({ description: '创建时间' }) - createdAt: Date; - - @ApiProperty({ description: '更新时间' }) - updatedAt: Date; -} - -// 话题列表响应DTO(包含收藏状态) -export class TopicListWithFavoriteResponseDto { - @ApiProperty({ description: '响应代码' }) - code: ResponseCode; - - @ApiProperty({ description: '响应消息' }) - message: string; - - @ApiProperty({ description: '话题列表数据' }) - data: { - list: TopicWithFavoriteDto[]; - total: number; - page: number; - pageSize: number; - }; -} \ No newline at end of file diff --git a/src/users/dto/topic-library.dto.ts b/src/users/dto/topic-library.dto.ts deleted file mode 100644 index 90332f1..0000000 --- a/src/users/dto/topic-library.dto.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { IsNumber, IsOptional, IsString, Min } from 'class-validator'; -import { ResponseCode } from 'src/base.dto'; -import { TopicLibrary } from '../models/topic-library.model'; -import { ApiProperty } from '@nestjs/swagger'; -import { TopicCategory } from '../models/topic-category.model'; -export class TopicLibraryResponseDto { - code: ResponseCode; - message: string; - data: TopicLibrary | TopicLibrary[] | null; -} - -export class GetTopicLibraryRequestDto { - // 分页相关 - @IsOptional() - @IsNumber() - @Min(1) - @ApiProperty({ description: '页码', example: 1 }) - page?: number; - @IsOptional() - @IsNumber() - @Min(1) - @ApiProperty({ description: '每页条数', example: 10 }) - pageSize?: number; - // 话题筛选 - @IsString() - @ApiProperty({ description: '话题', example: '话题' }) - topic: string; - - // 用户ID,用于查询用户自己的话题 - @IsString() - @IsOptional() - @ApiProperty({ description: '用户ID', example: '123' }) - userId: string; - - @IsOptional() - @IsString() - @ApiProperty({ description: '加密参数', example: '加密参数' }) - encryptedParameters?: string; -} - -export class GetTopicLibraryResponseDto { - code: ResponseCode; - message: string; - data: TopicLibrary | TopicLibrary[] | null; -} - -export class GenerateTopicRequestDto { - @ApiProperty({ - description: '话题', - example: '话题', - required: false, - }) - @IsOptional() - topic: string; -} - -export class GenerateTopicResponseDto { - code: ResponseCode; - message: string; - data: TopicLibrary | TopicLibrary[] | null; -} - -export class GetTopicCategoryResponseDto { - code: ResponseCode; - message: string; - data: TopicCategory | TopicCategory[] | null; -} - -export class DeleteTopicRequestDto { - @ApiProperty({ description: '话题ID', example: 1 }) - @IsNumber() - topicId: number; -} - -export class DeleteTopicResponseDto { - @ApiProperty({ description: '响应码', example: 200 }) - code: ResponseCode; - - @ApiProperty({ description: '响应消息', example: '删除成功' }) - message: string; - - @ApiProperty({ - description: '响应数据', - type: 'object', - properties: { - success: { type: 'boolean', example: true } - } - }) - data: { success: boolean }; -} - -export class DislikeTopicRequestDto { - @ApiProperty({ description: '话题ID', example: 1 }) - @IsNumber() - topicId: number; -} - -export class DislikeTopicResponseDto { - @ApiProperty({ description: '响应码', example: 200 }) - code: ResponseCode; - - @ApiProperty({ description: '响应消息', example: '不喜欢成功' }) - message: string; - - @ApiProperty({ description: '响应数据', example: true }) - data: boolean; -} - diff --git a/src/users/dto/user-relation-info.dto.ts b/src/users/dto/user-relation-info.dto.ts deleted file mode 100644 index 1a639b3..0000000 --- a/src/users/dto/user-relation-info.dto.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { IsString, IsBoolean, IsOptional, IsNumber } from 'class-validator'; -import { BaseResponseDto, ResponseCode } from 'src/base.dto'; -import { UserRelationInfo } from '../models/user-relation-info.model'; - -export class UserRelationInfoDto { - @IsString() - userId: string; - - @IsOptional() - @IsString() - myOccupation?: string; - - @IsOptional() - @IsString() - myInterests?: string; - - @IsOptional() - @IsString() - myCity?: string; - - @IsOptional() - @IsString() - myCharacteristics?: string; - - @IsOptional() - @IsString() - theirName?: string; - - @IsOptional() - @IsString() - theirOccupation?: string; - - @IsOptional() - @IsString() - theirBirthday?: string; - - @IsOptional() - @IsString() - theirInterests?: string; - - @IsOptional() - @IsString() - theirCity?: string; - - @IsOptional() - @IsString() - currentStage?: string; - - @IsOptional() - @IsBoolean() - isLongDistance?: boolean; - - @IsOptional() - @IsString() - additionalDescription?: string; -} - -export class UserRelationInfoResponseDto implements BaseResponseDto { - code: ResponseCode; - message: string; - data: UserRelationInfo; -} diff --git a/src/users/models/blocked-transaction.model.ts b/src/users/models/blocked-transaction.model.ts new file mode 100644 index 0000000..21dcb09 --- /dev/null +++ b/src/users/models/blocked-transaction.model.ts @@ -0,0 +1,97 @@ +import { Column, Model, Table, DataType } from 'sequelize-typescript'; + +export enum BlockReason { + FRAUD_DETECTED = 'FRAUD_DETECTED', + DUPLICATE_USAGE = 'DUPLICATE_USAGE', + SUSPICIOUS_ACTIVITY = 'SUSPICIOUS_ACTIVITY', + MANUAL_BLOCK = 'MANUAL_BLOCK', + SYSTEM_SECURITY = 'SYSTEM_SECURITY' +} + +@Table({ + tableName: 't_blocked_transactions', + underscored: true, + indexes: [ + { + unique: true, + fields: ['transaction_id'] + }, + { + fields: ['created_at'] + }, + { + fields: ['block_reason'] + } + ] +}) +export class BlockedTransaction extends Model { + @Column({ + type: DataType.UUID, + defaultValue: DataType.UUIDV4, + primaryKey: true, + }) + declare id: string; + + @Column({ + type: DataType.STRING, + allowNull: false, + unique: true, + comment: '被阻止的交易ID' + }) + transactionId: string; + + @Column({ + type: DataType.STRING, + allowNull: true, + comment: '产品ID' + }) + productId: string; + + @Column({ + type: DataType.STRING, + allowNull: false, + comment: '阻止原因' + }) + blockReason: BlockReason; + + @Column({ + type: DataType.TEXT, + allowNull: true, + comment: '详细说明' + }) + description: string; + + @Column({ + type: DataType.STRING, + allowNull: true, + comment: '操作员ID(如果是手动阻止)' + }) + operatorId: string; + + @Column({ + type: DataType.DATE, + allowNull: true, + comment: '阻止到期时间(null表示永久阻止)' + }) + expiresAt: Date | null; + + @Column({ + type: DataType.BOOLEAN, + allowNull: false, + defaultValue: true, + comment: '是否激活' + }) + isActive: boolean; + + @Column({ + type: DataType.DATE, + defaultValue: DataType.NOW, + }) + declare createdAt: Date; + + @Column({ + type: DataType.DATE, + defaultValue: DataType.NOW, + }) + declare updatedAt: Date; +} \ No newline at end of file diff --git a/src/users/models/client-log.model.ts b/src/users/models/client-log.model.ts deleted file mode 100644 index 1d93cd6..0000000 --- a/src/users/models/client-log.model.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { Column, Model, Table, DataType, ForeignKey, BelongsTo } from 'sequelize-typescript'; -import { User } from './user.model'; - -export enum LogLevel { - DEBUG = 'debug', - INFO = 'info', - WARN = 'warn', - ERROR = 'error', -} - -@Table({ - tableName: 't_client_logs', - underscored: true, -}) -export class ClientLog extends Model { - @Column({ - type: DataType.INTEGER, - autoIncrement: true, - primaryKey: true, - }) - declare id: number; - - @ForeignKey(() => User) - @Column({ - type: DataType.STRING, - allowNull: false, - comment: '用户ID', - }) - declare userId: string; - - @Column({ - type: DataType.TEXT, - allowNull: false, - comment: '日志内容', - }) - declare logContent: string; - - @Column({ - type: DataType.STRING, - allowNull: true, - defaultValue: LogLevel.INFO, - comment: '日志级别', - }) - declare logLevel: LogLevel; - - @Column({ - type: DataType.STRING, - allowNull: true, - comment: '客户端版本', - }) - declare clientVersion: string; - - @Column({ - type: DataType.STRING, - allowNull: true, - comment: '设备型号', - }) - declare deviceModel: string; - - @Column({ - type: DataType.STRING, - allowNull: true, - comment: 'iOS版本', - }) - declare iosVersion: string; - - @Column({ - type: DataType.DATE, - allowNull: true, - comment: '客户端时间戳', - }) - declare clientTimestamp: Date; - - @Column({ - type: DataType.DATE, - defaultValue: DataType.NOW, - comment: '服务器接收时间', - }) - declare createdAt: Date; - - @Column({ - type: DataType.DATE, - defaultValue: DataType.NOW, - }) - declare updatedAt: Date; - - @BelongsTo(() => User) - user: User; -} \ No newline at end of file diff --git a/src/users/models/user.model.ts b/src/users/models/user.model.ts index 0244efc..57509a5 100644 --- a/src/users/models/user.model.ts +++ b/src/users/models/user.model.ts @@ -110,15 +110,6 @@ export class User extends Model { }) declare deviceName: string; - @Column({ - type: DataType.STRING, - allowNull: true, - comment: '用户过去生成的话题 id 列表', - }) - declare lastTopicIds: string; - - declare isNew?: boolean; - get isVip(): boolean { return this.membershipExpiration ? dayjs(this.membershipExpiration).isAfter(dayjs()) : false; } diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index 13d4c08..a60f04d 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -18,15 +18,8 @@ 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 { UserRelationInfoDto, UserRelationInfoResponseDto } from './dto/user-relation-info.dto'; import { ApiOperation, ApiBody, ApiResponse, ApiTags } from '@nestjs/swagger'; import { UpdateUserDto, UpdateUserResponseDto } from './dto/update-user.dto'; -import { - CreateClientLogDto, - CreateBatchClientLogDto, - CreateClientLogResponseDto, - CreateBatchClientLogResponseDto, -} from './dto/client-log.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'; @@ -49,7 +42,7 @@ export class UsersController { ) { } @UseGuards(JwtAuthGuard) - @Get() + @Get('/info') @HttpCode(HttpStatus.OK) @ApiOperation({ summary: '创建用户' }) @ApiBody({ type: CreateUserDto }) @@ -67,40 +60,6 @@ export class UsersController { return this.usersService.updateUser(updateUserDto); } - - // 获取用户关系 - @UseGuards(JwtAuthGuard) - @Put('relations') - async updateOrCreateRelationInfo(@Body() relationInfoDto: UserRelationInfoDto) { - return this.usersService.updateOrCreateRelationInfo(relationInfoDto); - } - - @UseGuards(JwtAuthGuard) - @Get('relations/:userId') - async getRelationInfo(@Param('userId') userId: string): Promise { - return this.usersService.getRelationInfo(userId); - } - - // 创建客户端日志 - @UseGuards(JwtAuthGuard) - @Post('logs') - @HttpCode(HttpStatus.OK) - @ApiOperation({ summary: '创建客户端日志' }) - @ApiBody({ type: CreateClientLogDto }) - async createClientLog(@Body() createClientLogDto: CreateClientLogDto): Promise { - return this.usersService.createClientLog(createClientLogDto); - } - - // 批量创建客户端日志 - @UseGuards(JwtAuthGuard) - @Post('logs/batch') - @HttpCode(HttpStatus.OK) - @ApiOperation({ summary: '批量创建客户端日志' }) - @ApiBody({ type: CreateBatchClientLogDto }) - async createBatchClientLog(@Body() createBatchClientLogDto: CreateBatchClientLogDto): Promise { - return this.usersService.createBatchClientLog(createBatchClientLogDto); - } - // Apple 登录 @Public() @Post('auth/apple/login') diff --git a/src/users/users.module.ts b/src/users/users.module.ts index 2305bd0..7814144 100644 --- a/src/users/users.module.ts +++ b/src/users/users.module.ts @@ -7,9 +7,20 @@ import { ApplePurchaseService } from "./services/apple-purchase.service"; import { EncryptionService } from "../common/encryption.service"; import { AppleAuthService } from "./services/apple-auth.service"; import { JwtModule } from '@nestjs/jwt'; +import { BlockedTransaction } from "./models/blocked-transaction.model"; +import { UserPurchase } from "./models/user-purchase.model"; +import { PurchaseRestoreLog } from "./models/purchase-restore-log.model"; +import { RevenueCatEvent } from "./models/revenue-cat-event.model"; + @Module({ imports: [ - SequelizeModule.forFeature([User]), + SequelizeModule.forFeature([ + User, + BlockedTransaction, + UserPurchase, + PurchaseRestoreLog, + RevenueCatEvent, + ]), JwtModule.register({ secret: process.env.JWT_ACCESS_SECRET || 'your-access-token-secret-key', signOptions: { expiresIn: '30d' }, diff --git a/src/users/users.service.ts b/src/users/users.service.ts index c9d1d97..df282c2 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -12,32 +12,18 @@ import { InjectModel, InjectConnection } from '@nestjs/sequelize'; import { Gender, User } from './models/user.model'; import { UserResponseDto } from './dto/user-response.dto'; import { ResponseCode } from 'src/base.dto'; -import { UserRelationInfo } from './models/user-relation-info.model'; -import { UserRelationInfoDto, UserRelationInfoResponseDto } from './dto/user-relation-info.dto'; -import { TopicLibrary } from './models/topic-library.model'; import { Transaction, Op } from 'sequelize'; import { Sequelize } from 'sequelize-typescript'; import { UpdateUserDto, UpdateUserResponseDto } from './dto/update-user.dto'; -import { TopicCategory } from './models/topic-category.model'; import { UserPurchase, PurchaseType, PurchaseStatus, PurchasePlatform } from './models/user-purchase.model'; import { ApplePurchaseService } from './services/apple-purchase.service'; import * as dayjs from 'dayjs'; -import { UpdateMembershipDto, UpdateMembershipResponseDto } from './dto/membership.dto'; -import { ClientLog } from './models/client-log.model'; -import { - CreateClientLogDto, - CreateBatchClientLogDto, - CreateClientLogResponseDto, - CreateBatchClientLogResponseDto, -} from './dto/client-log.dto'; -import { EncryptionService } from 'src/common/encryption.service'; import { AccessTokenPayload, AppleAuthService, AppleTokenPayload } from './services/apple-auth.service'; 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, NotificationType } from './dto/app-store-notification.dto'; -import { TopicService } from './topic.service'; import { RevenueCatEvent } from './models/revenue-cat-event.model'; import { RevenueCatWebhookDto, RevenueCatEventType } from './dto/revenue-cat-webhook.dto'; import { RestorePurchaseDto, RestorePurchaseResponseDto, RestoredPurchaseInfo, ActiveEntitlement, NonSubscriptionTransaction } from './dto/restore-purchase.dto'; @@ -53,26 +39,16 @@ export class UsersService { @Inject(WINSTON_MODULE_PROVIDER) private readonly winstonLogger: WinstonLogger, @InjectModel(User) private userModel: typeof User, - @InjectModel(UserRelationInfo) - private userRelationInfoModel: typeof UserRelationInfo, - @InjectModel(TopicLibrary) - private topicLibraryModel: typeof TopicLibrary, - @InjectModel(TopicCategory) - private topicCategoryModel: typeof TopicCategory, @InjectModel(UserPurchase) private userPurchaseModel: typeof UserPurchase, - @InjectModel(ClientLog) - private clientLogModel: typeof ClientLog, @InjectModel(RevenueCatEvent) private revenueCatEventModel: typeof RevenueCatEvent, @InjectModel(PurchaseRestoreLog) private purchaseRestoreLogModel: typeof PurchaseRestoreLog, - @InjectModel(BlockedTransaction) - private blockedTransactionModel: typeof BlockedTransaction, - private encryptionService: EncryptionService, private appleAuthService: AppleAuthService, private applePurchaseService: ApplePurchaseService, - private topicService: TopicService, + @InjectModel(BlockedTransaction) + private blockedTransactionModel: typeof BlockedTransaction, @InjectConnection() private sequelize: Sequelize, ) { } @@ -101,13 +77,9 @@ export class UsersService { }; } - // 查询已收藏的话题数量 - const favoriteTopicCount = await this.topicService.getFavoriteTopicCount(existingUser.id); - const returnData = { ...existingUser.toJSON(), maxUsageCount: DEFAULT_FREE_USAGE_COUNT, - favoriteTopicCount, isVip: existingUser.isVip, } @@ -176,192 +148,6 @@ export class UsersService { }; } - async updateOrCreateRelationInfo(relationInfoDto: UserRelationInfoDto): Promise { - try { - this.logger.log(`updateOrCreateRelationInfo: ${JSON.stringify(relationInfoDto, null, 2)}`); - // 检查userId是否存在于用户表中 - const user = await this.userModel.findByPk(relationInfoDto.userId); - if (!user) { - return { - code: ResponseCode.ERROR, - message: `ID为${relationInfoDto.userId}的用户不存在`, - data: null as any, - }; - } - - // 查找是否已存在关系信息 - const existingInfo = await this.userRelationInfoModel.findOne({ - where: { userId: relationInfoDto.userId }, - }); - - if (existingInfo) { - // 存在则更新 - await existingInfo.update(relationInfoDto, { - where: { userId: relationInfoDto.userId }, - }); - return { - code: ResponseCode.SUCCESS, - message: 'success', - data: existingInfo, - }; - } else { - // 不存在则创建 - const newRelationInfo = await this.userRelationInfoModel.create({ - ...relationInfoDto, - }); - return { - code: ResponseCode.SUCCESS, - message: 'success', - data: newRelationInfo, - }; - } - } catch (error) { - this.logger.error(`更新或创建关系信息失败: ${error instanceof Error ? error.message : '未知错误'}`); - if (error instanceof NotFoundException) { - throw error; - } - return { - code: ResponseCode.ERROR, - message: `更新或创建关系信息失败: ${error instanceof Error ? error.message : '未知错误'}`, - data: null as any, - }; - } - } - - async getRelationInfo(userId: string): Promise { - try { - const relationInfos = await this.userRelationInfoModel.findAll({ - where: { userId }, - }); - - if (!relationInfos.length) { - const newRelationInfo = await this.userRelationInfoModel.create({ - userId, - }); - return { - code: ResponseCode.ERROR, - message: '关系信息不存在', - data: newRelationInfo, - }; - } - - return { - code: ResponseCode.SUCCESS, - message: 'success', - data: relationInfos[0], - }; - } catch (error) { - this.logger.error(`获取关系信息失败: ${error instanceof Error ? error.message : '未知错误'}`); - return { - code: ResponseCode.ERROR, - message: `获取关系信息失败: ${error instanceof Error ? error.message : '未知错误'}`, - data: null as any, - }; - } - } - - // 内部方法:计算订阅过期时间, 根据会员当前的有效期累加新的有效期 - private calculateExpirationDate(purchaseType: PurchaseType, currentExpiration: Date): Date { - this.logger.log(`calculateExpirationDate purchaseType: ${purchaseType}, currentExpiration: ${currentExpiration}`); - switch (purchaseType) { - case PurchaseType.WEEKLY: - return dayjs(currentExpiration ?? new Date()).add(7, 'day').toDate(); - case PurchaseType.QUARTERLY: - return dayjs(currentExpiration ?? new Date()).add(90, 'day').toDate(); - case PurchaseType.LIFETIME: - return dayjs(currentExpiration ?? new Date()).add(365 * 100, 'day').toDate(); - default: - throw new BadRequestException('无效的购买类型'); - } - } - - // 创建客户端日志 - async createClientLog(createClientLogDto: CreateClientLogDto): Promise { - try { - this.logger.log(`createClientLog: ${JSON.stringify(createClientLogDto, null, 2)}`); - - const { userId, logContent, logLevel, clientVersion, deviceModel, iosVersion, clientTimestamp } = createClientLogDto; - - // 检查用户是否存在 - const user = await this.userModel.findByPk(userId); - if (!user) { - return { - code: ResponseCode.ERROR, - message: `ID为${userId}的用户不存在`, - data: null as any, - }; - } - - // 创建日志记录 - const clientLog = await this.clientLogModel.create({ - userId, - logContent, - logLevel, - clientVersion, - deviceModel, - iosVersion, - clientTimestamp: clientTimestamp ? new Date(clientTimestamp) : null, - }); - - return { - code: ResponseCode.SUCCESS, - message: 'success', - data: clientLog.toJSON(), - }; - } catch (error) { - this.logger.error(`创建客户端日志失败: ${error instanceof Error ? error.message : '未知错误'}`); - return { - code: ResponseCode.ERROR, - message: `创建客户端日志失败: ${error instanceof Error ? error.message : '未知错误'}`, - data: null as any, - }; - } - } - - // 批量创建客户端日志 - async createBatchClientLog(createBatchClientLogDto: CreateBatchClientLogDto): Promise { - try { - this.logger.log(`createBatchClientLog: ${JSON.stringify(createBatchClientLogDto, null, 2)}`); - - const { userId, logs } = createBatchClientLogDto; - - // 检查用户是否存在 - const user = await this.userModel.findByPk(userId); - if (!user) { - return { - code: ResponseCode.ERROR, - message: `ID为${userId}的用户不存在`, - data: null as any, - }; - } - - // 批量创建日志记录 - const clientLogs = await this.clientLogModel.bulkCreate( - logs.map(log => ({ - userId, - logContent: log.logContent, - logLevel: log.logLevel, - clientVersion: log.clientVersion, - deviceModel: log.deviceModel, - iosVersion: log.iosVersion, - clientTimestamp: log.clientTimestamp ? new Date(log.clientTimestamp) : null, - })) - ); - - return { - code: ResponseCode.SUCCESS, - message: 'success', - data: clientLogs.map(log => log.toJSON()), - }; - } catch (error) { - this.logger.error(`批量创建客户端日志失败: ${error instanceof Error ? error.message : '未知错误'}`); - return { - code: ResponseCode.ERROR, - message: `批量创建客户端日志失败: ${error instanceof Error ? error.message : '未知错误'}`, - data: null as any, - }; - } - } /** * Apple 登录 @@ -406,8 +192,6 @@ export class UsersService { isNewUser = true; this.logger.log(`创建新的Apple用户: ${userId}`); - // 创建三条话题,不消耗次数 - await this.topicService.generateTopic('初识破冰', userId, false, true, 3); } else { // 更新现有用户的登录时间 user.lastLogin = new Date(); @@ -415,8 +199,6 @@ export class UsersService { this.logger.log(`Apple用户登录: ${userId}`); } - const favoriteTopicCount = await this.topicService.getFavoriteTopicCount(userId); - // 生成访问令牌和刷新令牌 const accessToken = this.appleAuthService.generateAccessToken(userId, user.mail); const refreshToken = this.appleAuthService.generateRefreshToken(userId); @@ -427,7 +209,6 @@ export class UsersService { isNew: isNewUser, isVip: user.isVip, maxUsageCount: DEFAULT_FREE_USAGE_COUNT, - favoriteTopicCount, }; return { @@ -508,41 +289,19 @@ export class UsersService { } // 开始删除用户相关数据(使用事务确保数据一致性) - // 1. 删除用户关系信息 - await this.userRelationInfoModel.destroy({ - where: { userId }, - transaction, - }); - - // 2. 删除用户购买记录 + // 1. 删除用户购买记录 await this.userPurchaseModel.destroy({ where: { userId }, transaction, }); - // 3. 删除用户客户端日志 - await this.clientLogModel.destroy({ - where: { userId }, - transaction, - }); - - // 4. 删除用户的个人话题 - // 删除收藏 - await this.topicService.deleteFavoriteTopics(userId, transaction); - - await this.topicLibraryModel.destroy({ - where: { userId }, - transaction, - }); - - // 5. 最后删除用户本身 + // 最后删除用户本身 await this.userModel.destroy({ where: { id: userId }, transaction, }); - // 提交事务 await transaction.commit(); @@ -605,8 +364,6 @@ export class UsersService { isNewUser = true; this.logger.log(`创建新的游客用户: ${guestUserId}`); - // 创建三条话题,不消耗次数 - await this.topicService.generateTopic('初识破冰', guestUserId, false, true, 3); } else { // 更新现有游客用户的登录时间和设备信息 user.lastLogin = new Date(); @@ -627,7 +384,6 @@ export class UsersService { isVip: user.membershipExpiration ? dayjs(user.membershipExpiration).isAfter(dayjs()) : false, isGuest: true, maxUsageCount: DEFAULT_FREE_USAGE_COUNT, - favoriteTopicCount: 0, }; return {