更新服务器地址和项目名称,移除不必要的客户端日志相关代码,添加阻止交易模型,调整端口号及相关文档内容

This commit is contained in:
richarjiang
2025-08-13 17:31:21 +08:00
parent 4f9d648a50
commit 3b1af29c63
20 changed files with 138 additions and 786 deletions

View File

@@ -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<CreateClientLogDto, 'userId'>[];
}
// 查询客户端日志请求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<ClientLogResponseDto> {}
// 批量创建客户端日志响应DTO
export interface CreateBatchClientLogResponseDto extends BaseResponseDto<ClientLogResponseDto[]> {}
// 获取客户端日志列表响应DTO
export interface GetClientLogsResponseDto extends BaseResponseDto<{
total: number;
list: ClientLogResponseDto[];
page: number;
pageSize: number;
}> {}

View File

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

View File

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

View File

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

View File

@@ -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<UserRelationInfo> {
code: ResponseCode;
message: string;
data: UserRelationInfo;
}

View File

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

View File

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

View File

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

View File

@@ -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<UserRelationInfoResponseDto> {
return this.usersService.getRelationInfo(userId);
}
// 创建客户端日志
@UseGuards(JwtAuthGuard)
@Post('logs')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: '创建客户端日志' })
@ApiBody({ type: CreateClientLogDto })
async createClientLog(@Body() createClientLogDto: CreateClientLogDto): Promise<CreateClientLogResponseDto> {
return this.usersService.createClientLog(createClientLogDto);
}
// 批量创建客户端日志
@UseGuards(JwtAuthGuard)
@Post('logs/batch')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: '批量创建客户端日志' })
@ApiBody({ type: CreateBatchClientLogDto })
async createBatchClientLog(@Body() createBatchClientLogDto: CreateBatchClientLogDto): Promise<CreateBatchClientLogResponseDto> {
return this.usersService.createBatchClientLog(createBatchClientLogDto);
}
// Apple 登录
@Public()
@Post('auth/apple/login')

View File

@@ -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' },

View File

@@ -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<UserRelationInfoResponseDto> {
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<UserRelationInfoResponseDto> {
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<CreateClientLogResponseDto> {
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<CreateBatchClientLogResponseDto> {
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 {