更新服务器地址和项目名称,移除不必要的客户端日志相关代码,添加阻止交易模型,调整端口号及相关文档内容
This commit is contained in:
16
DEPLOY.md
16
DEPLOY.md
@@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
## 服务器配置
|
## 服务器配置
|
||||||
|
|
||||||
- **服务器地址**: 119.91.211.52
|
- **服务器地址**: 129.204.155.94
|
||||||
- **用户**: root(可在脚本中修改)
|
- **用户**: root(可在脚本中修改)
|
||||||
- **部署目录**: /usr/local/web/pilates-server
|
- **部署目录**: /usr/local/web/pilates-server
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
|
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. 服务器环境要求
|
### 2. 服务器环境要求
|
||||||
@@ -141,16 +141,16 @@ ssh-copy-id root@119.91.211.52
|
|||||||
|
|
||||||
```bash
|
```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
|
```bash
|
||||||
SERVER_HOST="119.91.211.52" # 服务器地址
|
SERVER_HOST="129.204.155.94" # 服务器地址
|
||||||
SERVER_USER="root" # SSH用户名
|
SERVER_USER="root" # SSH用户名
|
||||||
SERVER_PATH="/usr/local/web/pilates-server" # 部署目录
|
SERVER_PATH="/usr/local/web/pilates-server" # 部署目录
|
||||||
```
|
```
|
||||||
@@ -211,7 +211,7 @@ SERVER_PATH="/usr/local/web/pilates-server" # 部署目录
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 登录服务器
|
# 登录服务器
|
||||||
ssh root@119.91.211.52
|
ssh root@129.204.155.94
|
||||||
|
|
||||||
# 进入项目目录
|
# 进入项目目录
|
||||||
cd /usr/local/web/pilates-server
|
cd /usr/local/web/pilates-server
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ yarn start:dev
|
|||||||
|
|
||||||
### 服务器配置
|
### 服务器配置
|
||||||
|
|
||||||
- **服务器地址**: 119.91.211.52
|
- **服务器地址**: 129.204.155.94
|
||||||
- **用户**: root
|
- **用户**: root
|
||||||
- **部署目录**: /usr/local/web/pilates-server
|
- **部署目录**: /usr/local/web/pilates-server
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# 优化版发布脚本 - 只上传源码,服务器端构建
|
# 优化版发布脚本 - 只上传源码,服务器端构建
|
||||||
SERVER_HOST="119.91.211.52"
|
SERVER_HOST="129.204.155.94"
|
||||||
SERVER_USER="root"
|
SERVER_USER="root"
|
||||||
SERVER_PATH="/usr/local/web/love-tips-server"
|
SERVER_PATH="/usr/local/web/pilates-server"
|
||||||
|
|
||||||
# 定义颜色
|
# 定义颜色
|
||||||
GREEN='\033[0;32m'
|
GREEN='\033[0;32m'
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# 简化版发布脚本
|
# 简化版发布脚本
|
||||||
SERVER_HOST="119.91.211.52"
|
SERVER_HOST="129.204.155.94"
|
||||||
SERVER_USER="root"
|
SERVER_USER="root"
|
||||||
SERVER_PATH="/usr/local/web/love-tips-server"
|
SERVER_PATH="/usr/local/web/pilates-server"
|
||||||
|
|
||||||
echo "🚀 开始部署到服务器..."
|
echo "🚀 开始部署到服务器..."
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# 发布脚本配置
|
# 发布脚本配置
|
||||||
SERVER_HOST="119.91.211.52"
|
SERVER_HOST="129.204.155.94"
|
||||||
SERVER_USER="root" # 根据实际情况修改用户名
|
SERVER_USER="root" # 根据实际情况修改用户名
|
||||||
SERVER_PATH="/usr/local/web/love-tips-server"
|
SERVER_PATH="/usr/local/web/pilates-server"
|
||||||
PROJECT_NAME="love-tips-server"
|
PROJECT_NAME="pilates-server"
|
||||||
|
|
||||||
# 定义颜色
|
# 定义颜色
|
||||||
GREEN='\033[0;32m'
|
GREEN='\033[0;32m'
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ export class UsersController {
|
|||||||
NODE_ENV=production
|
NODE_ENV=production
|
||||||
|
|
||||||
# 自定义日志目录 (可选)
|
# 自定义日志目录 (可选)
|
||||||
LOG_DIR=/var/log/love-tips-server
|
LOG_DIR=/var/log/pilates-server
|
||||||
```
|
```
|
||||||
|
|
||||||
## 最佳实践
|
## 最佳实践
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ module.exports = {
|
|||||||
merge_logs: true,
|
merge_logs: true,
|
||||||
env_production: {
|
env_production: {
|
||||||
NODE_ENV: 'production',
|
NODE_ENV: 'production',
|
||||||
PORT: 3000
|
PORT: 3002
|
||||||
},
|
},
|
||||||
env_development: {
|
env_development: {
|
||||||
NODE_ENV: 'development',
|
NODE_ENV: 'development',
|
||||||
|
|||||||
6
package-lock.json
generated
6
package-lock.json
generated
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "love-tips-server",
|
"name": "pilates-server",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "love-tips-server",
|
"name": "pilates-server",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -13525,4 +13525,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,15 +43,15 @@ async function bootstrap() {
|
|||||||
|
|
||||||
// swigger
|
// swigger
|
||||||
const config = new DocumentBuilder()
|
const config = new DocumentBuilder()
|
||||||
.setTitle('Love Tips API')
|
.setTitle('Pilates API')
|
||||||
.setDescription('Love Tips API description')
|
.setDescription('Pilates API description')
|
||||||
.setVersion('1.0')
|
.setVersion('1.0')
|
||||||
.build();
|
.build();
|
||||||
const documentFactory = () => SwaggerModule.createDocument(app, config);
|
const documentFactory = () => SwaggerModule.createDocument(app, config);
|
||||||
SwaggerModule.setup('api/docs', app, documentFactory);
|
SwaggerModule.setup('api/docs', app, documentFactory);
|
||||||
|
|
||||||
|
|
||||||
const port = process.env.PORT ?? 3000;
|
const port = process.env.PORT ?? 3002;
|
||||||
await app.listen(port);
|
await app.listen(port);
|
||||||
|
|
||||||
const appLogger = new Logger('Bootstrap');
|
const appLogger = new Logger('Bootstrap');
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}> {}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
97
src/users/models/blocked-transaction.model.ts
Normal file
97
src/users/models/blocked-transaction.model.ts
Normal 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;
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -110,15 +110,6 @@ export class User extends Model {
|
|||||||
})
|
})
|
||||||
declare deviceName: string;
|
declare deviceName: string;
|
||||||
|
|
||||||
@Column({
|
|
||||||
type: DataType.STRING,
|
|
||||||
allowNull: true,
|
|
||||||
comment: '用户过去生成的话题 id 列表',
|
|
||||||
})
|
|
||||||
declare lastTopicIds: string;
|
|
||||||
|
|
||||||
declare isNew?: boolean;
|
|
||||||
|
|
||||||
get isVip(): boolean {
|
get isVip(): boolean {
|
||||||
return this.membershipExpiration ? dayjs(this.membershipExpiration).isAfter(dayjs()) : false;
|
return this.membershipExpiration ? dayjs(this.membershipExpiration).isAfter(dayjs()) : false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,15 +18,8 @@ import { Logger as WinstonLogger } from 'winston';
|
|||||||
import { UsersService } from './users.service';
|
import { UsersService } from './users.service';
|
||||||
import { CreateUserDto } from './dto/create-user.dto';
|
import { CreateUserDto } from './dto/create-user.dto';
|
||||||
import { UserResponseDto } from './dto/user-response.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 { ApiOperation, ApiBody, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||||
import { UpdateUserDto, UpdateUserResponseDto } from './dto/update-user.dto';
|
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 { AppleLoginDto, AppleLoginResponseDto, RefreshTokenDto, RefreshTokenResponseDto } from './dto/apple-login.dto';
|
||||||
import { DeleteAccountDto, DeleteAccountResponseDto } from './dto/delete-account.dto';
|
import { DeleteAccountDto, DeleteAccountResponseDto } from './dto/delete-account.dto';
|
||||||
import { GuestLoginDto, GuestLoginResponseDto, RefreshGuestTokenDto, RefreshGuestTokenResponseDto } from './dto/guest-login.dto';
|
import { GuestLoginDto, GuestLoginResponseDto, RefreshGuestTokenDto, RefreshGuestTokenResponseDto } from './dto/guest-login.dto';
|
||||||
@@ -49,7 +42,7 @@ export class UsersController {
|
|||||||
) { }
|
) { }
|
||||||
|
|
||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(JwtAuthGuard)
|
||||||
@Get()
|
@Get('/info')
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@ApiOperation({ summary: '创建用户' })
|
@ApiOperation({ summary: '创建用户' })
|
||||||
@ApiBody({ type: CreateUserDto })
|
@ApiBody({ type: CreateUserDto })
|
||||||
@@ -67,40 +60,6 @@ export class UsersController {
|
|||||||
return this.usersService.updateUser(updateUserDto);
|
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 登录
|
// Apple 登录
|
||||||
@Public()
|
@Public()
|
||||||
@Post('auth/apple/login')
|
@Post('auth/apple/login')
|
||||||
|
|||||||
@@ -7,9 +7,20 @@ import { ApplePurchaseService } from "./services/apple-purchase.service";
|
|||||||
import { EncryptionService } from "../common/encryption.service";
|
import { EncryptionService } from "../common/encryption.service";
|
||||||
import { AppleAuthService } from "./services/apple-auth.service";
|
import { AppleAuthService } from "./services/apple-auth.service";
|
||||||
import { JwtModule } from '@nestjs/jwt';
|
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({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
SequelizeModule.forFeature([User]),
|
SequelizeModule.forFeature([
|
||||||
|
User,
|
||||||
|
BlockedTransaction,
|
||||||
|
UserPurchase,
|
||||||
|
PurchaseRestoreLog,
|
||||||
|
RevenueCatEvent,
|
||||||
|
]),
|
||||||
JwtModule.register({
|
JwtModule.register({
|
||||||
secret: process.env.JWT_ACCESS_SECRET || 'your-access-token-secret-key',
|
secret: process.env.JWT_ACCESS_SECRET || 'your-access-token-secret-key',
|
||||||
signOptions: { expiresIn: '30d' },
|
signOptions: { expiresIn: '30d' },
|
||||||
|
|||||||
@@ -12,32 +12,18 @@ import { InjectModel, InjectConnection } from '@nestjs/sequelize';
|
|||||||
import { Gender, User } from './models/user.model';
|
import { Gender, User } from './models/user.model';
|
||||||
import { UserResponseDto } from './dto/user-response.dto';
|
import { UserResponseDto } from './dto/user-response.dto';
|
||||||
import { ResponseCode } from 'src/base.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 { Transaction, Op } from 'sequelize';
|
||||||
import { Sequelize } from 'sequelize-typescript';
|
import { Sequelize } from 'sequelize-typescript';
|
||||||
import { UpdateUserDto, UpdateUserResponseDto } from './dto/update-user.dto';
|
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 { UserPurchase, PurchaseType, PurchaseStatus, PurchasePlatform } from './models/user-purchase.model';
|
||||||
import { ApplePurchaseService } from './services/apple-purchase.service';
|
import { ApplePurchaseService } from './services/apple-purchase.service';
|
||||||
import * as dayjs from 'dayjs';
|
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 { AccessTokenPayload, AppleAuthService, AppleTokenPayload } from './services/apple-auth.service';
|
||||||
import { AppleLoginDto, AppleLoginResponseDto, RefreshTokenDto, RefreshTokenResponseDto } from './dto/apple-login.dto';
|
import { AppleLoginDto, AppleLoginResponseDto, RefreshTokenDto, RefreshTokenResponseDto } from './dto/apple-login.dto';
|
||||||
import { DeleteAccountDto, DeleteAccountResponseDto } from './dto/delete-account.dto';
|
import { DeleteAccountDto, DeleteAccountResponseDto } from './dto/delete-account.dto';
|
||||||
import { GuestLoginDto, GuestLoginResponseDto, RefreshGuestTokenDto, RefreshGuestTokenResponseDto } from './dto/guest-login.dto';
|
import { GuestLoginDto, GuestLoginResponseDto, RefreshGuestTokenDto, RefreshGuestTokenResponseDto } from './dto/guest-login.dto';
|
||||||
import { AppStoreServerNotificationDto, ProcessNotificationResponseDto, NotificationType } from './dto/app-store-notification.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 { RevenueCatEvent } from './models/revenue-cat-event.model';
|
||||||
import { RevenueCatWebhookDto, RevenueCatEventType } from './dto/revenue-cat-webhook.dto';
|
import { RevenueCatWebhookDto, RevenueCatEventType } from './dto/revenue-cat-webhook.dto';
|
||||||
import { RestorePurchaseDto, RestorePurchaseResponseDto, RestoredPurchaseInfo, ActiveEntitlement, NonSubscriptionTransaction } from './dto/restore-purchase.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,
|
@Inject(WINSTON_MODULE_PROVIDER) private readonly winstonLogger: WinstonLogger,
|
||||||
@InjectModel(User)
|
@InjectModel(User)
|
||||||
private userModel: typeof 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)
|
@InjectModel(UserPurchase)
|
||||||
private userPurchaseModel: typeof UserPurchase,
|
private userPurchaseModel: typeof UserPurchase,
|
||||||
@InjectModel(ClientLog)
|
|
||||||
private clientLogModel: typeof ClientLog,
|
|
||||||
@InjectModel(RevenueCatEvent)
|
@InjectModel(RevenueCatEvent)
|
||||||
private revenueCatEventModel: typeof RevenueCatEvent,
|
private revenueCatEventModel: typeof RevenueCatEvent,
|
||||||
@InjectModel(PurchaseRestoreLog)
|
@InjectModel(PurchaseRestoreLog)
|
||||||
private purchaseRestoreLogModel: typeof PurchaseRestoreLog,
|
private purchaseRestoreLogModel: typeof PurchaseRestoreLog,
|
||||||
@InjectModel(BlockedTransaction)
|
|
||||||
private blockedTransactionModel: typeof BlockedTransaction,
|
|
||||||
private encryptionService: EncryptionService,
|
|
||||||
private appleAuthService: AppleAuthService,
|
private appleAuthService: AppleAuthService,
|
||||||
private applePurchaseService: ApplePurchaseService,
|
private applePurchaseService: ApplePurchaseService,
|
||||||
private topicService: TopicService,
|
@InjectModel(BlockedTransaction)
|
||||||
|
private blockedTransactionModel: typeof BlockedTransaction,
|
||||||
@InjectConnection()
|
@InjectConnection()
|
||||||
private sequelize: Sequelize,
|
private sequelize: Sequelize,
|
||||||
) { }
|
) { }
|
||||||
@@ -101,13 +77,9 @@ export class UsersService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询已收藏的话题数量
|
|
||||||
const favoriteTopicCount = await this.topicService.getFavoriteTopicCount(existingUser.id);
|
|
||||||
|
|
||||||
const returnData = {
|
const returnData = {
|
||||||
...existingUser.toJSON(),
|
...existingUser.toJSON(),
|
||||||
maxUsageCount: DEFAULT_FREE_USAGE_COUNT,
|
maxUsageCount: DEFAULT_FREE_USAGE_COUNT,
|
||||||
favoriteTopicCount,
|
|
||||||
isVip: existingUser.isVip,
|
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 登录
|
* Apple 登录
|
||||||
@@ -406,8 +192,6 @@ export class UsersService {
|
|||||||
isNewUser = true;
|
isNewUser = true;
|
||||||
this.logger.log(`创建新的Apple用户: ${userId}`);
|
this.logger.log(`创建新的Apple用户: ${userId}`);
|
||||||
|
|
||||||
// 创建三条话题,不消耗次数
|
|
||||||
await this.topicService.generateTopic('初识破冰', userId, false, true, 3);
|
|
||||||
} else {
|
} else {
|
||||||
// 更新现有用户的登录时间
|
// 更新现有用户的登录时间
|
||||||
user.lastLogin = new Date();
|
user.lastLogin = new Date();
|
||||||
@@ -415,8 +199,6 @@ export class UsersService {
|
|||||||
this.logger.log(`Apple用户登录: ${userId}`);
|
this.logger.log(`Apple用户登录: ${userId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const favoriteTopicCount = await this.topicService.getFavoriteTopicCount(userId);
|
|
||||||
|
|
||||||
// 生成访问令牌和刷新令牌
|
// 生成访问令牌和刷新令牌
|
||||||
const accessToken = this.appleAuthService.generateAccessToken(userId, user.mail);
|
const accessToken = this.appleAuthService.generateAccessToken(userId, user.mail);
|
||||||
const refreshToken = this.appleAuthService.generateRefreshToken(userId);
|
const refreshToken = this.appleAuthService.generateRefreshToken(userId);
|
||||||
@@ -427,7 +209,6 @@ export class UsersService {
|
|||||||
isNew: isNewUser,
|
isNew: isNewUser,
|
||||||
isVip: user.isVip,
|
isVip: user.isVip,
|
||||||
maxUsageCount: DEFAULT_FREE_USAGE_COUNT,
|
maxUsageCount: DEFAULT_FREE_USAGE_COUNT,
|
||||||
favoriteTopicCount,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -508,41 +289,19 @@ export class UsersService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 开始删除用户相关数据(使用事务确保数据一致性)
|
// 开始删除用户相关数据(使用事务确保数据一致性)
|
||||||
// 1. 删除用户关系信息
|
// 1. 删除用户购买记录
|
||||||
await this.userRelationInfoModel.destroy({
|
|
||||||
where: { userId },
|
|
||||||
transaction,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 2. 删除用户购买记录
|
|
||||||
await this.userPurchaseModel.destroy({
|
await this.userPurchaseModel.destroy({
|
||||||
where: { userId },
|
where: { userId },
|
||||||
transaction,
|
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({
|
await this.userModel.destroy({
|
||||||
where: { id: userId },
|
where: { id: userId },
|
||||||
transaction,
|
transaction,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 提交事务
|
// 提交事务
|
||||||
await transaction.commit();
|
await transaction.commit();
|
||||||
|
|
||||||
@@ -605,8 +364,6 @@ export class UsersService {
|
|||||||
isNewUser = true;
|
isNewUser = true;
|
||||||
this.logger.log(`创建新的游客用户: ${guestUserId}`);
|
this.logger.log(`创建新的游客用户: ${guestUserId}`);
|
||||||
|
|
||||||
// 创建三条话题,不消耗次数
|
|
||||||
await this.topicService.generateTopic('初识破冰', guestUserId, false, true, 3);
|
|
||||||
} else {
|
} else {
|
||||||
// 更新现有游客用户的登录时间和设备信息
|
// 更新现有游客用户的登录时间和设备信息
|
||||||
user.lastLogin = new Date();
|
user.lastLogin = new Date();
|
||||||
@@ -627,7 +384,6 @@ export class UsersService {
|
|||||||
isVip: user.membershipExpiration ? dayjs(user.membershipExpiration).isAfter(dayjs()) : false,
|
isVip: user.membershipExpiration ? dayjs(user.membershipExpiration).isAfter(dayjs()) : false,
|
||||||
isGuest: true,
|
isGuest: true,
|
||||||
maxUsageCount: DEFAULT_FREE_USAGE_COUNT,
|
maxUsageCount: DEFAULT_FREE_USAGE_COUNT,
|
||||||
favoriteTopicCount: 0,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user