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",
|
"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",
|
||||||
|
|
|
||||||
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 { 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,19 +27,19 @@ 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],
|
||||||
})
|
})
|
||||||
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 { 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()}`;
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,30 @@ 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(
|
||||||
private readonly githubService: GitHubService,
|
private readonly githubService: GitHubService,
|
||||||
private readonly userService: UserService,
|
private readonly userService: UserService,
|
||||||
) { }
|
) {}
|
||||||
|
|
||||||
@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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,30 @@ 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(
|
||||||
private readonly larkService: LarkService,
|
private readonly larkService: LarkService,
|
||||||
private readonly userService: UserService,
|
private readonly userService: UserService,
|
||||||
) { }
|
) {}
|
||||||
|
|
||||||
@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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
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 { 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) {
|
||||||
|
|
|
||||||
|
|
@ -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';
|
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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue