feat: 添加swagger文档支持
This commit is contained in:
parent
54486e8074
commit
1fe53ab9de
|
|
@ -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` 表中。
|
||||
|
|
@ -38,6 +38,8 @@
|
|||
"bcryptjs": "^2.4.3",
|
||||
"body-parser": "^1.20.3",
|
||||
"cache-manager": "4.1.0",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.2",
|
||||
"dotenv": "^16.4.7",
|
||||
"install": "^0.13.0",
|
||||
"mysql2": "^3.12.0",
|
||||
|
|
|
|||
8590
pnpm-lock.yaml
8590
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
|
@ -5,11 +5,11 @@ import { AppController } from './app.controller';
|
|||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import { AppService } from './app.service';
|
||||
import { UserModule } from './user/user.module';
|
||||
import { User } from './user/entities/user.entity';
|
||||
import { AuthModule } from './auth/auth.module';
|
||||
import { LarkModule } from './lark/lark.module';
|
||||
import { GitHubModule } from './github/github.module';
|
||||
import { DeepseekModule } from './deepseek/deepseek.module';
|
||||
import { TaskModule } from './task/task.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
|
|
@ -27,19 +27,19 @@ import { DeepseekModule } from './deepseek/deepseek.module';
|
|||
username: configService.get('SERVER_USER'),
|
||||
password: configService.get('PASSWORD'),
|
||||
database: 'auth_db',
|
||||
entities: [User],
|
||||
entities: [__dirname + '/**/*.entity{.ts,.js}'],
|
||||
synchronize: true,
|
||||
}),
|
||||
inject: [ConfigService],
|
||||
}),
|
||||
TypeOrmModule.forFeature([User]),
|
||||
UserModule,
|
||||
AuthModule,
|
||||
LarkModule,
|
||||
GitHubModule,
|
||||
DeepseekModule,
|
||||
TaskModule,
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
})
|
||||
export class AppModule { }
|
||||
export class AppModule {}
|
||||
|
|
|
|||
|
|
@ -2,18 +2,27 @@ import { Controller, Get, Req, Res, UseGuards } from '@nestjs/common';
|
|||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { UserService } from '../user/user.service';
|
||||
import { Response } from 'express';
|
||||
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
|
||||
|
||||
@ApiTags('认证模块')
|
||||
@Controller('auth')
|
||||
export class AuthController {
|
||||
constructor(private readonly userService: UserService) { }
|
||||
constructor(private readonly userService: UserService) {}
|
||||
@Get('github')
|
||||
@UseGuards(AuthGuard('github'))
|
||||
@ApiOperation({ summary: 'Github 登录' })
|
||||
@ApiResponse({ status: 302, description: '重定向到 Github 授权页面' })
|
||||
async githubLogin() {
|
||||
// GitHub 登录重定向
|
||||
}
|
||||
|
||||
@Get('github/callback')
|
||||
@UseGuards(AuthGuard('github'))
|
||||
@ApiOperation({ summary: 'Github 登录回调' })
|
||||
@ApiResponse({
|
||||
status: 302,
|
||||
description: '重定向到前端页面,并携带 token',
|
||||
})
|
||||
async githubCallback(@Req() req: any, @Res() res: Response) {
|
||||
const oauthUser = req.user;
|
||||
|
||||
|
|
@ -27,7 +36,7 @@ export class AuthController {
|
|||
email: oauthUser.email,
|
||||
provider: oauthUser.provider,
|
||||
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()}`;
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ export class JwtAuthGuard extends AuthGuard('jwt') {
|
|||
|
||||
handleRequest(err: any, user: any) {
|
||||
if (err || !user) {
|
||||
throw new UnauthorizedException('请先登录');
|
||||
throw new UnauthorizedException('请先登录');
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,26 @@ import { Body, Controller, Post } from '@nestjs/common';
|
|||
import { DeepseekService } from './deepseek.service';
|
||||
import { Public } from 'src/auth/decorators/public.decorator';
|
||||
import { PROVIDER_TYPE } from 'src/constants/providerType';
|
||||
import { ApiBody, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
|
||||
@ApiTags('Deepseek 模块')
|
||||
@Controller('deepSeek')
|
||||
export class DeepseekController {
|
||||
constructor(private readonly deepseekService: DeepseekService) { }
|
||||
constructor(private readonly deepseekService: DeepseekService) {}
|
||||
|
||||
@Public()
|
||||
@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 }) {
|
||||
const response = await this.deepseekService.chatRequest(
|
||||
body.message,
|
||||
|
|
@ -19,6 +32,17 @@ export class DeepseekController {
|
|||
|
||||
@Public()
|
||||
@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 }) {
|
||||
const response = await this.deepseekService.chatRequest(
|
||||
body.message,
|
||||
|
|
@ -29,6 +53,17 @@ export class DeepseekController {
|
|||
|
||||
@Public()
|
||||
@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 }) {
|
||||
const response = await this.deepseekService.chatRequest(
|
||||
body.message,
|
||||
|
|
|
|||
|
|
@ -3,16 +3,30 @@ import { GitHubService } from './github.service';
|
|||
import { UserService } from '../user/user.service';
|
||||
import { Response } from 'express';
|
||||
import { Public } from '../auth/decorators/public.decorator';
|
||||
import {
|
||||
ApiTags,
|
||||
ApiOperation,
|
||||
ApiResponse,
|
||||
ApiQuery,
|
||||
} from '@nestjs/swagger';
|
||||
|
||||
@ApiTags('Github 模块')
|
||||
@Controller('github')
|
||||
export class GitHubController {
|
||||
constructor(
|
||||
private readonly githubService: GitHubService,
|
||||
private readonly userService: UserService,
|
||||
) { }
|
||||
) {}
|
||||
|
||||
@Get('login')
|
||||
@Public()
|
||||
@ApiOperation({ summary: 'Github 登录' })
|
||||
@ApiQuery({
|
||||
name: 'redirectUri',
|
||||
description: '登录成功后的重定向地址',
|
||||
required: true,
|
||||
})
|
||||
@ApiResponse({ status: 302, description: '重定向到 Github 授权页面' })
|
||||
async login(@Query('redirectUri') redirectUri: string, @Res() res: Response) {
|
||||
const loginUrl = await this.githubService.getLoginUrl(redirectUri);
|
||||
return res.redirect(loginUrl);
|
||||
|
|
@ -20,6 +34,12 @@ export class GitHubController {
|
|||
|
||||
@Get('callback')
|
||||
@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(
|
||||
@Query('code') code: string,
|
||||
@Query('state') _state: string,
|
||||
|
|
@ -33,10 +53,15 @@ export class GitHubController {
|
|||
const tokenData = await this.githubService.getAccessToken(code);
|
||||
|
||||
if (tokenData && tokenData.access_token) {
|
||||
const userInfo = await this.githubService.getUserInfo(tokenData.access_token);
|
||||
const userInfo = await this.githubService.getUserInfo(
|
||||
tokenData.access_token,
|
||||
);
|
||||
|
||||
// 检查 GitHub 用户是否已存在
|
||||
let user = await this.userService.findUserByProvider('github', userInfo.id.toString());
|
||||
let user = await this.userService.findUserByProvider(
|
||||
'github',
|
||||
userInfo.id.toString(),
|
||||
);
|
||||
|
||||
if (!user) {
|
||||
// 创建新的 OAuth 用户
|
||||
|
|
@ -44,7 +69,7 @@ export class GitHubController {
|
|||
'github',
|
||||
userInfo.id.toString(),
|
||||
userInfo.login,
|
||||
userInfo.email || `${userInfo.login}@github.local`
|
||||
userInfo.email || `${userInfo.login}@github.local`,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -58,17 +83,18 @@ export class GitHubController {
|
|||
username: user.username,
|
||||
email: user.email,
|
||||
provider: user.provider,
|
||||
providerUsername: user.providerUsername
|
||||
providerUsername: user.providerUsername,
|
||||
},
|
||||
...jwtTokenData
|
||||
...jwtTokenData,
|
||||
});
|
||||
|
||||
} else {
|
||||
return res.status(400).send('Failed to retrieve access token');
|
||||
}
|
||||
} catch (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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,16 +3,30 @@ import { LarkService } from './lark.service';
|
|||
import { UserService } from '../user/user.service';
|
||||
import { Response } from 'express';
|
||||
import { Public } from '../auth/decorators/public.decorator';
|
||||
import {
|
||||
ApiTags,
|
||||
ApiOperation,
|
||||
ApiQuery,
|
||||
ApiResponse,
|
||||
} from '@nestjs/swagger';
|
||||
|
||||
@ApiTags('Lark 模块')
|
||||
@Controller('lark')
|
||||
export class LarkController {
|
||||
constructor(
|
||||
private readonly larkService: LarkService,
|
||||
private readonly userService: UserService,
|
||||
) { }
|
||||
) {}
|
||||
|
||||
@Get('login')
|
||||
@Public()
|
||||
@ApiOperation({ summary: 'Lark 登录' })
|
||||
@ApiQuery({
|
||||
name: 'redirectUri',
|
||||
description: '登录成功后的重定向地址',
|
||||
required: true,
|
||||
})
|
||||
@ApiResponse({ status: 302, description: '重定向到 Lark 授权页面' })
|
||||
async login(@Query('redirectUri') redirectUri: string, @Res() res: Response) {
|
||||
const loginUrl = await this.larkService.getLoginUrl(redirectUri);
|
||||
return res.redirect(loginUrl);
|
||||
|
|
@ -20,6 +34,11 @@ export class LarkController {
|
|||
|
||||
@Get('callback')
|
||||
@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) {
|
||||
if (!code) {
|
||||
return res.status(400).send('Authorization code is missing');
|
||||
|
|
@ -33,7 +52,10 @@ export class LarkController {
|
|||
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) {
|
||||
// 创建新的 OAuth 用户
|
||||
|
|
@ -41,7 +63,7 @@ export class LarkController {
|
|||
'lark',
|
||||
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,
|
||||
email: user.email,
|
||||
provider: user.provider,
|
||||
providerUsername: user.providerUsername
|
||||
providerUsername: user.providerUsername,
|
||||
},
|
||||
...jwtTokenData
|
||||
...jwtTokenData,
|
||||
});
|
||||
} else {
|
||||
return res.status(400).send('Failed to retrieve access token');
|
||||
}
|
||||
} catch (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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
34
src/main.ts
34
src/main.ts
|
|
@ -6,6 +6,8 @@ import * as dotenv from 'dotenv';
|
|||
import { HttpExceptionFilter } from './common/filters/http-exception.filter';
|
||||
import { TransformInterceptor } from './common/interceptors/transform.interceptor';
|
||||
import * as bodyParser from 'body-parser';
|
||||
import { ValidationPipe } from '@nestjs/common';
|
||||
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
|
|
@ -44,10 +46,17 @@ async function bootstrap() {
|
|||
const configService = app.get(ConfigService); // 获取 ConfigService 实例
|
||||
|
||||
app.enableCors({
|
||||
origin: ['http://108.171.193.155:8000', 'http://localhost:8000'], // 指定允许的前端地址
|
||||
origin: true, // 允许所有来源
|
||||
credentials: true, // 允许携带凭证
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH'], // 允许的 HTTP 方法
|
||||
allowedHeaders: ['Content-Type', 'Authorization', 'Accept', 'Origin', 'Referer', 'User-Agent'], // 允许的请求头
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'], // 明确指定允许的 HTTP 方法
|
||||
allowedHeaders: [
|
||||
'Content-Type',
|
||||
'Authorization',
|
||||
'Accept',
|
||||
'Origin',
|
||||
'Referer',
|
||||
'User-Agent',
|
||||
], // 允许的请求头
|
||||
});
|
||||
|
||||
// 注册全局异常过滤器
|
||||
|
|
@ -56,10 +65,29 @@ async function bootstrap() {
|
|||
// 注册全局响应转换拦截器
|
||||
app.useGlobalInterceptors(new TransformInterceptor());
|
||||
|
||||
// 注册全局验证管道
|
||||
app.useGlobalPipes(
|
||||
new ValidationPipe({
|
||||
whitelist: true, // 自动删除非 DTO 定义的属性
|
||||
forbidNonWhitelisted: true, // 如果有非白名单属性,抛出错误
|
||||
transform: true, // 自动转换类型
|
||||
}),
|
||||
);
|
||||
|
||||
// 使用 NestJS 内置方法设置限制
|
||||
app.use(bodyParser.json({ limit: '50mb' }));
|
||||
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);
|
||||
console.log('✅ 应用启动成功,监听端口 3030');
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
import { PartialType } from '@nestjs/swagger';
|
||||
import { CreateTaskDto } from './create-task.dto';
|
||||
|
||||
export class UpdateTaskDto extends PartialType(CreateTaskDto) {}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {}
|
||||
|
|
@ -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' };
|
||||
}
|
||||
}
|
||||
|
|
@ -1,35 +1,49 @@
|
|||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
|
||||
|
||||
@Entity()
|
||||
export class User {
|
||||
@PrimaryGeneratedColumn()
|
||||
@ApiProperty({ description: '用户ID' })
|
||||
id: number;
|
||||
|
||||
@Column({ unique: true })
|
||||
@ApiProperty({ description: '用户名' })
|
||||
username: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@ApiProperty({ description: '密码', required: false })
|
||||
password: string;
|
||||
|
||||
@Column()
|
||||
@ApiProperty({ description: '邮箱' })
|
||||
email: string;
|
||||
|
||||
@Column({ default: true })
|
||||
@ApiProperty({ description: '是否激活', default: true })
|
||||
isActive: boolean;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@ApiProperty({ description: '刷新令牌', required: false })
|
||||
refreshToken: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@ApiProperty({ description: '刷新令牌过期时间', required: false })
|
||||
refreshTokenExpires: Date;
|
||||
|
||||
// OAuth 相关字段
|
||||
@Column({ nullable: true })
|
||||
provider: string; // 'local', 'github', 'lark'
|
||||
@ApiProperty({
|
||||
description: "认证提供商,如 'local', 'github', 'lark'",
|
||||
required: false,
|
||||
})
|
||||
provider: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
providerId: string; // 第三方平台的用户ID
|
||||
@ApiProperty({ description: '第三方平台的用户ID', required: false })
|
||||
providerId: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
providerUsername: string; // 第三方平台的用户名
|
||||
@ApiProperty({ description: '第三方平台的用户名', required: false })
|
||||
providerUsername: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,13 +7,35 @@ import {
|
|||
} from '@nestjs/common';
|
||||
import { UserService } from '../user/user.service';
|
||||
import { Public } from '../auth/decorators/public.decorator';
|
||||
import {
|
||||
ApiTags,
|
||||
ApiOperation,
|
||||
ApiResponse,
|
||||
ApiBody,
|
||||
} from '@nestjs/swagger';
|
||||
import { User } from './entities/user.entity';
|
||||
|
||||
@ApiTags('用户模块')
|
||||
@Controller('user')
|
||||
export class UserController {
|
||||
constructor(private readonly userService: UserService) {}
|
||||
|
||||
@Public()
|
||||
@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(
|
||||
@Body() body: { username: string; password: string; email: string },
|
||||
) {
|
||||
|
|
@ -32,6 +54,19 @@ export class UserController {
|
|||
|
||||
@Public()
|
||||
@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 }) {
|
||||
const { username, password } = body;
|
||||
const user = await this.userService.findUserByUsername(username);
|
||||
|
|
@ -55,6 +90,17 @@ export class UserController {
|
|||
|
||||
@Public()
|
||||
@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 }) {
|
||||
return this.userService.refreshAccessToken(body.refresh_token);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue