feat: 添加swagger文档支持

This commit is contained in:
lichao 2025-10-25 17:55:05 +08:00
parent 54486e8074
commit 1fe53ab9de
18 changed files with 4400 additions and 4649 deletions

63
database-flow.md Normal file
View File

@ -0,0 +1,63 @@
# NestJS & TypeORM 数据写入流程图
这是一个描述在您的项目中,从 NestJS 应用启动到通过 TypeOrm 将用户数据写入数据库的完整流程图。
```mermaid
graph TD
subgraph "1. 应用启动 (Application Bootstrap)"
A[NestJS App Starts] --> B[加载根模块 AppModule];
B --> C{"配置全局数据库连接"};
D[.env 文件] --> E[ConfigService];
E -- 提供配置 --> C;
C --> F["创建全局数据库连接池<br/>(Global DB Connection Pool)"];
end
subgraph "2. 模块和依赖加载 (Module & Dependency Loading)"
B -- 导入 --> G[加载功能模块 UserModule];
G --> H{"为 User 实体注册 Repository"};
F -- 从连接池获取 --> H;
H --> I["在 UserModule 作用域内<br/>注册 User Repository"];
end
subgraph "3. HTTP 请求处理 (HTTP Request Handling)"
J["HTTP Request<br/>(e.g., POST /users)"] --> K[UserController];
K -- 调用方法 --> L[UserService];
I -- 依赖注入 (Inject) --> L["UserService<br/>(constructor receives userRepository)"];
end
subgraph "4. 数据库操作 (Database Operation)"
L -- 1. 调用 --> M["userService.createUser(...)"];
M -- 2. 执行 --> N["this.userRepository.create(data)<br/>(在内存中创建实体)"];
N -- 3. 传递实体 --> O["this.userRepository.save(entity)<br/>(生成 INSERT SQL 语句)"];
O -- 4. 通过连接池发送 SQL --> P[(MySQL 数据库)];
P -- 5. 成功写入 --> Q[user 表];
end
%% Styling
style F fill:#f9f,stroke:#333,stroke-width:2px
style I fill:#ccf,stroke:#333,stroke-width:2px
style P fill:#bbf,stroke:#333,stroke-width:2px
style Q fill:#9f9,stroke:#333,stroke-width:2px
```
### 流程图解读
1. **应用启动**:
* NestJS 应用启动时,首先加载根模块 `AppModule`
* `AppModule` 中的 `TypeOrmModule.forRootAsync` 会利用 `ConfigService` 读取 `.env` 文件中的数据库配置信息。
* 基于这些配置TypeORM 创建一个全局的、可供整个应用使用的数据库连接池。
2. **模块和依赖加载**:
* `AppModule` 加载 `UserModule`
* `UserModule` 中的 `TypeOrmModule.forFeature([User])` 指令会从全局连接池中为 `User` 这个实体获取一个 Repository可以理解为数据表的操作句柄
* 这个 `User` Repository 被注册在 `UserModule` 的依赖注入容器中,等待被使用。
3. **HTTP 请求处理**:
* 当一个外部 HTTP 请求到达 `UserController` 的某个路由时,该路由会调用 `UserService` 中对应的服务方法。
* NestJS 的依赖注入系统会自动将第 2 步注册的 `User` Repository 实例注入到 `UserService` 的构造函数中。
4. **数据库操作**:
* `UserService` 中的方法(如 `createUser`)被执行。
* 代码首先调用 `userRepository.create()` 在内存中创建一个与数据表结构对应的实体对象。
* 紧接着,`userRepository.save()` 方法被调用TypeORM 将这个内存中的对象转换成一条 SQL `INSERT` 语句。
* 这条 SQL 语句通过全局连接池被发送到 MySQL 数据库执行,最终将数据写入 `user` 表中。

View File

@ -38,6 +38,8 @@
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"body-parser": "^1.20.3", "body-parser": "^1.20.3",
"cache-manager": "4.1.0", "cache-manager": "4.1.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.2",
"dotenv": "^16.4.7", "dotenv": "^16.4.7",
"install": "^0.13.0", "install": "^0.13.0",
"mysql2": "^3.12.0", "mysql2": "^3.12.0",

File diff suppressed because it is too large Load Diff

View File

@ -5,11 +5,11 @@ import { AppController } from './app.controller';
import { ConfigModule, ConfigService } from '@nestjs/config'; import { ConfigModule, ConfigService } from '@nestjs/config';
import { AppService } from './app.service'; import { AppService } from './app.service';
import { UserModule } from './user/user.module'; import { UserModule } from './user/user.module';
import { User } from './user/entities/user.entity';
import { AuthModule } from './auth/auth.module'; import { AuthModule } from './auth/auth.module';
import { LarkModule } from './lark/lark.module'; import { LarkModule } from './lark/lark.module';
import { GitHubModule } from './github/github.module'; import { GitHubModule } from './github/github.module';
import { DeepseekModule } from './deepseek/deepseek.module'; import { DeepseekModule } from './deepseek/deepseek.module';
import { TaskModule } from './task/task.module';
@Module({ @Module({
imports: [ imports: [
@ -27,17 +27,17 @@ import { DeepseekModule } from './deepseek/deepseek.module';
username: configService.get('SERVER_USER'), username: configService.get('SERVER_USER'),
password: configService.get('PASSWORD'), password: configService.get('PASSWORD'),
database: 'auth_db', database: 'auth_db',
entities: [User], entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true, synchronize: true,
}), }),
inject: [ConfigService], inject: [ConfigService],
}), }),
TypeOrmModule.forFeature([User]),
UserModule, UserModule,
AuthModule, AuthModule,
LarkModule, LarkModule,
GitHubModule, GitHubModule,
DeepseekModule, DeepseekModule,
TaskModule,
], ],
controllers: [AppController], controllers: [AppController],
providers: [AppService], providers: [AppService],

View File

@ -2,18 +2,27 @@ import { Controller, Get, Req, Res, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport'; import { AuthGuard } from '@nestjs/passport';
import { UserService } from '../user/user.service'; import { UserService } from '../user/user.service';
import { Response } from 'express'; import { Response } from 'express';
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
@ApiTags('认证模块')
@Controller('auth') @Controller('auth')
export class AuthController { export class AuthController {
constructor(private readonly userService: UserService) {} constructor(private readonly userService: UserService) {}
@Get('github') @Get('github')
@UseGuards(AuthGuard('github')) @UseGuards(AuthGuard('github'))
@ApiOperation({ summary: 'Github 登录' })
@ApiResponse({ status: 302, description: '重定向到 Github 授权页面' })
async githubLogin() { async githubLogin() {
// GitHub 登录重定向 // GitHub 登录重定向
} }
@Get('github/callback') @Get('github/callback')
@UseGuards(AuthGuard('github')) @UseGuards(AuthGuard('github'))
@ApiOperation({ summary: 'Github 登录回调' })
@ApiResponse({
status: 302,
description: '重定向到前端页面,并携带 token',
})
async githubCallback(@Req() req: any, @Res() res: Response) { async githubCallback(@Req() req: any, @Res() res: Response) {
const oauthUser = req.user; const oauthUser = req.user;
@ -27,7 +36,7 @@ export class AuthController {
email: oauthUser.email, email: oauthUser.email,
provider: oauthUser.provider, provider: oauthUser.provider,
providerUsername: oauthUser.providerUsername || oauthUser.username, providerUsername: oauthUser.providerUsername || oauthUser.username,
access_token: jwtTokenData.access_token access_token: jwtTokenData.access_token,
}); });
const frontendUrl = `http://localhost:8000/test-github-sso.html?${params.toString()}`; const frontendUrl = `http://localhost:8000/test-github-sso.html?${params.toString()}`;

View File

@ -2,13 +2,26 @@ import { Body, Controller, Post } from '@nestjs/common';
import { DeepseekService } from './deepseek.service'; import { DeepseekService } from './deepseek.service';
import { Public } from 'src/auth/decorators/public.decorator'; import { Public } from 'src/auth/decorators/public.decorator';
import { PROVIDER_TYPE } from 'src/constants/providerType'; import { PROVIDER_TYPE } from 'src/constants/providerType';
import { ApiBody, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
@ApiTags('Deepseek 模块')
@Controller('deepSeek') @Controller('deepSeek')
export class DeepseekController { export class DeepseekController {
constructor(private readonly deepseekService: DeepseekService) {} constructor(private readonly deepseekService: DeepseekService) {}
@Public() @Public()
@Post('chat-flow') @Post('chat-flow')
@ApiOperation({ summary: '与 Flow 模型聊天' })
@ApiBody({
schema: {
type: 'object',
properties: {
message: { type: 'string', description: '发送给模型的消息' },
},
required: ['message'],
},
})
@ApiResponse({ status: 200, description: '成功返回模型的响应' })
async chatFlow(@Body() body: { message: string }) { async chatFlow(@Body() body: { message: string }) {
const response = await this.deepseekService.chatRequest( const response = await this.deepseekService.chatRequest(
body.message, body.message,
@ -19,6 +32,17 @@ export class DeepseekController {
@Public() @Public()
@Post('chat-deep') @Post('chat-deep')
@ApiOperation({ summary: '与 Deep 模型聊天' })
@ApiBody({
schema: {
type: 'object',
properties: {
message: { type: 'string', description: '发送给模型的消息' },
},
required: ['message'],
},
})
@ApiResponse({ status: 200, description: '成功返回模型的响应' })
async chatDeep(@Body() body: { message: string }) { async chatDeep(@Body() body: { message: string }) {
const response = await this.deepseekService.chatRequest( const response = await this.deepseekService.chatRequest(
body.message, body.message,
@ -29,6 +53,17 @@ export class DeepseekController {
@Public() @Public()
@Post('chat-grok') @Post('chat-grok')
@ApiOperation({ summary: '与 Grok 模型聊天' })
@ApiBody({
schema: {
type: 'object',
properties: {
message: { type: 'string', description: '发送给模型的消息' },
},
required: ['message'],
},
})
@ApiResponse({ status: 200, description: '成功返回模型的响应' })
async chatGrok(@Body() body: { message: string }) { async chatGrok(@Body() body: { message: string }) {
const response = await this.deepseekService.chatRequest( const response = await this.deepseekService.chatRequest(
body.message, body.message,

View File

@ -3,7 +3,14 @@ import { GitHubService } from './github.service';
import { UserService } from '../user/user.service'; import { UserService } from '../user/user.service';
import { Response } from 'express'; import { Response } from 'express';
import { Public } from '../auth/decorators/public.decorator'; import { Public } from '../auth/decorators/public.decorator';
import {
ApiTags,
ApiOperation,
ApiResponse,
ApiQuery,
} from '@nestjs/swagger';
@ApiTags('Github 模块')
@Controller('github') @Controller('github')
export class GitHubController { export class GitHubController {
constructor( constructor(
@ -13,6 +20,13 @@ export class GitHubController {
@Get('login') @Get('login')
@Public() @Public()
@ApiOperation({ summary: 'Github 登录' })
@ApiQuery({
name: 'redirectUri',
description: '登录成功后的重定向地址',
required: true,
})
@ApiResponse({ status: 302, description: '重定向到 Github 授权页面' })
async login(@Query('redirectUri') redirectUri: string, @Res() res: Response) { async login(@Query('redirectUri') redirectUri: string, @Res() res: Response) {
const loginUrl = await this.githubService.getLoginUrl(redirectUri); const loginUrl = await this.githubService.getLoginUrl(redirectUri);
return res.redirect(loginUrl); return res.redirect(loginUrl);
@ -20,6 +34,12 @@ export class GitHubController {
@Get('callback') @Get('callback')
@Public() @Public()
@ApiOperation({ summary: 'Github 登录回调' })
@ApiQuery({ name: 'code', description: 'Github 返回的授权码' })
@ApiQuery({ name: 'state', description: 'Github 返回的状态' })
@ApiResponse({ status: 200, description: '登录成功,返回用户信息和 token' })
@ApiResponse({ status: 400, description: '授权码缺失' })
@ApiResponse({ status: 500, description: 'Github 认证失败' })
async callback( async callback(
@Query('code') code: string, @Query('code') code: string,
@Query('state') _state: string, @Query('state') _state: string,
@ -33,10 +53,15 @@ export class GitHubController {
const tokenData = await this.githubService.getAccessToken(code); const tokenData = await this.githubService.getAccessToken(code);
if (tokenData && tokenData.access_token) { if (tokenData && tokenData.access_token) {
const userInfo = await this.githubService.getUserInfo(tokenData.access_token); const userInfo = await this.githubService.getUserInfo(
tokenData.access_token,
);
// 检查 GitHub 用户是否已存在 // 检查 GitHub 用户是否已存在
let user = await this.userService.findUserByProvider('github', userInfo.id.toString()); let user = await this.userService.findUserByProvider(
'github',
userInfo.id.toString(),
);
if (!user) { if (!user) {
// 创建新的 OAuth 用户 // 创建新的 OAuth 用户
@ -44,7 +69,7 @@ export class GitHubController {
'github', 'github',
userInfo.id.toString(), userInfo.id.toString(),
userInfo.login, userInfo.login,
userInfo.email || `${userInfo.login}@github.local` userInfo.email || `${userInfo.login}@github.local`,
); );
} }
@ -58,17 +83,18 @@ export class GitHubController {
username: user.username, username: user.username,
email: user.email, email: user.email,
provider: user.provider, provider: user.provider,
providerUsername: user.providerUsername providerUsername: user.providerUsername,
}, },
...jwtTokenData ...jwtTokenData,
}); });
} else { } else {
return res.status(400).send('Failed to retrieve access token'); return res.status(400).send('Failed to retrieve access token');
} }
} catch (error) { } catch (error) {
console.error('GitHub OAuth callback error:', error); console.error('GitHub OAuth callback error:', error);
return res.status(500).send('Internal server error during GitHub authentication'); return res
.status(500)
.send('Internal server error during GitHub authentication');
} }
} }
} }

View File

@ -3,7 +3,14 @@ import { LarkService } from './lark.service';
import { UserService } from '../user/user.service'; import { UserService } from '../user/user.service';
import { Response } from 'express'; import { Response } from 'express';
import { Public } from '../auth/decorators/public.decorator'; import { Public } from '../auth/decorators/public.decorator';
import {
ApiTags,
ApiOperation,
ApiQuery,
ApiResponse,
} from '@nestjs/swagger';
@ApiTags('Lark 模块')
@Controller('lark') @Controller('lark')
export class LarkController { export class LarkController {
constructor( constructor(
@ -13,6 +20,13 @@ export class LarkController {
@Get('login') @Get('login')
@Public() @Public()
@ApiOperation({ summary: 'Lark 登录' })
@ApiQuery({
name: 'redirectUri',
description: '登录成功后的重定向地址',
required: true,
})
@ApiResponse({ status: 302, description: '重定向到 Lark 授权页面' })
async login(@Query('redirectUri') redirectUri: string, @Res() res: Response) { async login(@Query('redirectUri') redirectUri: string, @Res() res: Response) {
const loginUrl = await this.larkService.getLoginUrl(redirectUri); const loginUrl = await this.larkService.getLoginUrl(redirectUri);
return res.redirect(loginUrl); return res.redirect(loginUrl);
@ -20,6 +34,11 @@ export class LarkController {
@Get('callback') @Get('callback')
@Public() @Public()
@ApiOperation({ summary: 'Lark 登录回调' })
@ApiQuery({ name: 'code', description: 'Lark 返回的授权码' })
@ApiResponse({ status: 200, description: '登录成功,返回用户信息和 token' })
@ApiResponse({ status: 400, description: '授权码缺失' })
@ApiResponse({ status: 500, description: 'Lark 认证失败' })
async callback(@Query('code') code: string, @Res() res: Response) { async callback(@Query('code') code: string, @Res() res: Response) {
if (!code) { if (!code) {
return res.status(400).send('Authorization code is missing'); return res.status(400).send('Authorization code is missing');
@ -33,7 +52,10 @@ export class LarkController {
const userInfo = await this.larkService.getUserInfo(accessToken); const userInfo = await this.larkService.getUserInfo(accessToken);
// 检查飞书用户是否已存在 // 检查飞书用户是否已存在
let user = await this.userService.findUserByProvider('lark', userInfo.user_id); let user = await this.userService.findUserByProvider(
'lark',
userInfo.user_id,
);
if (!user) { if (!user) {
// 创建新的 OAuth 用户 // 创建新的 OAuth 用户
@ -41,7 +63,7 @@ export class LarkController {
'lark', 'lark',
userInfo.user_id, userInfo.user_id,
userInfo.name || userInfo.user_id, userInfo.name || userInfo.user_id,
userInfo.email || `${userInfo.user_id}@feishu.local` userInfo.email || `${userInfo.user_id}@feishu.local`,
); );
} }
@ -55,16 +77,18 @@ export class LarkController {
username: user.username, username: user.username,
email: user.email, email: user.email,
provider: user.provider, provider: user.provider,
providerUsername: user.providerUsername providerUsername: user.providerUsername,
}, },
...jwtTokenData ...jwtTokenData,
}); });
} else { } else {
return res.status(400).send('Failed to retrieve access token'); return res.status(400).send('Failed to retrieve access token');
} }
} catch (error) { } catch (error) {
console.error('Lark OAuth callback error:', error); console.error('Lark OAuth callback error:', error);
return res.status(500).send('Internal server error during Lark authentication'); return res
.status(500)
.send('Internal server error during Lark authentication');
} }
} }
} }

View File

@ -6,6 +6,8 @@ import * as dotenv from 'dotenv';
import { HttpExceptionFilter } from './common/filters/http-exception.filter'; import { HttpExceptionFilter } from './common/filters/http-exception.filter';
import { TransformInterceptor } from './common/interceptors/transform.interceptor'; import { TransformInterceptor } from './common/interceptors/transform.interceptor';
import * as bodyParser from 'body-parser'; import * as bodyParser from 'body-parser';
import { ValidationPipe } from '@nestjs/common';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
dotenv.config(); dotenv.config();
@ -44,10 +46,17 @@ async function bootstrap() {
const configService = app.get(ConfigService); // 获取 ConfigService 实例 const configService = app.get(ConfigService); // 获取 ConfigService 实例
app.enableCors({ app.enableCors({
origin: ['http://108.171.193.155:8000', 'http://localhost:8000'], // 指定允许的前端地址 origin: true, // 允许所有来源
credentials: true, // 允许携带凭证 credentials: true, // 允许携带凭证
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH'], // 允许的 HTTP 方法 methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'], // 明确指定允许的 HTTP 方法
allowedHeaders: ['Content-Type', 'Authorization', 'Accept', 'Origin', 'Referer', 'User-Agent'], // 允许的请求头 allowedHeaders: [
'Content-Type',
'Authorization',
'Accept',
'Origin',
'Referer',
'User-Agent',
], // 允许的请求头
}); });
// 注册全局异常过滤器 // 注册全局异常过滤器
@ -56,10 +65,29 @@ async function bootstrap() {
// 注册全局响应转换拦截器 // 注册全局响应转换拦截器
app.useGlobalInterceptors(new TransformInterceptor()); app.useGlobalInterceptors(new TransformInterceptor());
// 注册全局验证管道
app.useGlobalPipes(
new ValidationPipe({
whitelist: true, // 自动删除非 DTO 定义的属性
forbidNonWhitelisted: true, // 如果有非白名单属性,抛出错误
transform: true, // 自动转换类型
}),
);
// 使用 NestJS 内置方法设置限制 // 使用 NestJS 内置方法设置限制
app.use(bodyParser.json({ limit: '50mb' })); app.use(bodyParser.json({ limit: '50mb' }));
app.use(bodyParser.urlencoded({ limit: '50mb', extended: true })); app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));
// Swagger 文档配置
const config = new DocumentBuilder()
.setTitle('我的应用 API')
.setDescription('这是我的应用程序的 API 文档')
.setVersion('1.0')
.addBearerAuth()
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document);
await app.listen(3030); await app.listen(3030);
console.log('✅ 应用启动成功,监听端口 3030'); console.log('✅ 应用启动成功,监听端口 3030');
} catch (error) { } catch (error) {

View File

@ -0,0 +1,19 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNotEmpty, IsBoolean, IsOptional } from 'class-validator';
export class CreateTaskDto {
@ApiProperty({ description: '任务标题' })
@IsString()
@IsNotEmpty()
title: string;
@ApiProperty({ description: '任务描述', required: false })
@IsString()
@IsOptional()
description?: string;
@ApiProperty({ description: '是否完成', default: false, required: false })
@IsBoolean()
@IsOptional()
isCompleted?: boolean;
}

View File

@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/swagger';
import { CreateTaskDto } from './create-task.dto';
export class UpdateTaskDto extends PartialType(CreateTaskDto) {}

View File

@ -0,0 +1,21 @@
import { ApiProperty } from '@nestjs/swagger';
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class Task {
@PrimaryGeneratedColumn()
@ApiProperty({ description: '任务ID' })
id: number;
@Column()
@ApiProperty({ description: '任务标题' })
title: string;
@Column({ nullable: true })
@ApiProperty({ description: '任务描述', required: false })
description?: string;
@Column({ default: false })
@ApiProperty({ description: '是否完成', default: false })
isCompleted: boolean;
}

View File

@ -0,0 +1,63 @@
import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
} from '@nestjs/common';
import { TaskService } from './task.service';
import { CreateTaskDto } from './dto/create-task.dto';
import { UpdateTaskDto } from './dto/update-task.dto';
import {
ApiTags,
ApiOperation,
ApiResponse,
ApiParam,
} from '@nestjs/swagger';
import { Task } from './entities/task.entity';
@ApiTags('任务模块')
@Controller('task')
export class TaskController {
constructor(private readonly taskService: TaskService) {}
@Post()
@ApiOperation({ summary: '创建任务' })
@ApiResponse({ status: 201, description: '创建成功', type: Task })
create(@Body() createTaskDto: CreateTaskDto) {
return this.taskService.create(createTaskDto);
}
@Get()
@ApiOperation({ summary: '获取所有任务' })
@ApiResponse({ status: 200, description: '获取成功', type: [Task] })
findAll() {
return this.taskService.findAll();
}
@Get(':id')
@ApiOperation({ summary: '获取单个任务' })
@ApiParam({ name: 'id', description: '任务ID' })
@ApiResponse({ status: 200, description: '获取成功', type: Task })
findOne(@Param('id') id: string) {
return this.taskService.findOne(+id);
}
@Patch(':id')
@ApiOperation({ summary: '更新任务' })
@ApiParam({ name: 'id', description: '任务ID' })
@ApiResponse({ status: 200, description: '更新成功', type: Task })
update(@Param('id') id: string, @Body() updateTaskDto: UpdateTaskDto) {
return this.taskService.update(+id, updateTaskDto);
}
@Delete(':id')
@ApiOperation({ summary: '删除任务' })
@ApiParam({ name: 'id', description: '任务ID' })
@ApiResponse({ status: 200, description: '删除成功' })
remove(@Param('id') id: string) {
return this.taskService.remove(+id);
}
}

12
src/task/task.module.ts Normal file
View File

@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { TaskService } from './task.service';
import { TaskController } from './task.controller';
import { Task } from './entities/task.entity';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [TypeOrmModule.forFeature([Task])],
controllers: [TaskController],
providers: [TaskService],
})
export class TaskModule {}

37
src/task/task.service.ts Normal file
View File

@ -0,0 +1,37 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Task } from './entities/task.entity';
import { CreateTaskDto } from './dto/create-task.dto';
import { UpdateTaskDto } from './dto/update-task.dto';
@Injectable()
export class TaskService {
constructor(
@InjectRepository(Task)
private taskRepository: Repository<Task>,
) {}
create(createTaskDto: CreateTaskDto) {
const task = this.taskRepository.create(createTaskDto);
return this.taskRepository.save(task);
}
findAll() {
return this.taskRepository.find();
}
findOne(id: number) {
return this.taskRepository.findOneBy({ id });
}
async update(id: number, updateTaskDto: UpdateTaskDto) {
await this.taskRepository.update(id, updateTaskDto);
return this.taskRepository.findOneBy({ id });
}
async remove(id: number) {
await this.taskRepository.delete(id);
return { message: 'Deleted successfully' };
}
}

View File

@ -1,35 +1,49 @@
import { ApiProperty } from '@nestjs/swagger';
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'; import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity() @Entity()
export class User { export class User {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
@ApiProperty({ description: '用户ID' })
id: number; id: number;
@Column({ unique: true }) @Column({ unique: true })
@ApiProperty({ description: '用户名' })
username: string; username: string;
@Column({ nullable: true }) @Column({ nullable: true })
@ApiProperty({ description: '密码', required: false })
password: string; password: string;
@Column() @Column()
@ApiProperty({ description: '邮箱' })
email: string; email: string;
@Column({ default: true }) @Column({ default: true })
@ApiProperty({ description: '是否激活', default: true })
isActive: boolean; isActive: boolean;
@Column({ nullable: true }) @Column({ nullable: true })
@ApiProperty({ description: '刷新令牌', required: false })
refreshToken: string; refreshToken: string;
@Column({ nullable: true }) @Column({ nullable: true })
@ApiProperty({ description: '刷新令牌过期时间', required: false })
refreshTokenExpires: Date; refreshTokenExpires: Date;
// OAuth 相关字段 // OAuth 相关字段
@Column({ nullable: true }) @Column({ nullable: true })
provider: string; // 'local', 'github', 'lark' @ApiProperty({
description: "认证提供商,如 'local', 'github', 'lark'",
required: false,
})
provider: string;
@Column({ nullable: true }) @Column({ nullable: true })
providerId: string; // 第三方平台的用户ID @ApiProperty({ description: '第三方平台的用户ID', required: false })
providerId: string;
@Column({ nullable: true }) @Column({ nullable: true })
providerUsername: string; // 第三方平台的用户名 @ApiProperty({ description: '第三方平台的用户名', required: false })
providerUsername: string;
} }

View File

@ -7,13 +7,35 @@ import {
} from '@nestjs/common'; } from '@nestjs/common';
import { UserService } from '../user/user.service'; import { UserService } from '../user/user.service';
import { Public } from '../auth/decorators/public.decorator'; import { Public } from '../auth/decorators/public.decorator';
import {
ApiTags,
ApiOperation,
ApiResponse,
ApiBody,
} from '@nestjs/swagger';
import { User } from './entities/user.entity';
@ApiTags('用户模块')
@Controller('user') @Controller('user')
export class UserController { export class UserController {
constructor(private readonly userService: UserService) {} constructor(private readonly userService: UserService) {}
@Public() @Public()
@Post('register') @Post('register')
@ApiOperation({ summary: '用户注册' })
@ApiBody({
schema: {
type: 'object',
properties: {
username: { type: 'string', description: '用户名' },
password: { type: 'string', description: '密码' },
email: { type: 'string', description: '邮箱' },
},
required: ['username', 'password', 'email'],
},
})
@ApiResponse({ status: 201, description: '注册成功', type: User })
@ApiResponse({ status: 400, description: '参数错误' })
async register( async register(
@Body() body: { username: string; password: string; email: string }, @Body() body: { username: string; password: string; email: string },
) { ) {
@ -32,6 +54,19 @@ export class UserController {
@Public() @Public()
@Post('login') @Post('login')
@ApiOperation({ summary: '用户登录' })
@ApiBody({
schema: {
type: 'object',
properties: {
username: { type: 'string', description: '用户名' },
password: { type: 'string', description: '密码' },
},
required: ['username', 'password'],
},
})
@ApiResponse({ status: 200, description: '登录成功' })
@ApiResponse({ status: 401, description: '认证失败' })
async login(@Body() body: { username: string; password: string }) { async login(@Body() body: { username: string; password: string }) {
const { username, password } = body; const { username, password } = body;
const user = await this.userService.findUserByUsername(username); const user = await this.userService.findUserByUsername(username);
@ -55,6 +90,17 @@ export class UserController {
@Public() @Public()
@Post('refresh-token') @Post('refresh-token')
@ApiOperation({ summary: '刷新-token' })
@ApiBody({
schema: {
type: 'object',
properties: {
refresh_token: { type: 'string', description: 'Refresh Token' },
},
required: ['refresh_token'],
},
})
@ApiResponse({ status: 200, description: '刷新成功' })
async refreshToken(@Body() body: { refresh_token: string }) { async refreshToken(@Body() body: { refresh_token: string }) {
return this.userService.refreshAccessToken(body.refresh_token); return this.userService.refreshAccessToken(body.refresh_token);
} }