Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
赵月辉
/
fastgpt-migrated
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Unverified
Commit
c547b434
authored
Jan 27, 2026
by
Archer
Committed by
GitHub
Jan 27, 2026
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
pr skill (#6326)
* pr skill * doc
parent
ee69cecf
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
3010 additions
and
0 deletions
+3010
-0
.claude/skills/common/skills/api-development/SKILL.md
+757
-0
.claude/skills/pr-review/SKILL.md
+261
-0
.claude/skills/pr-review/code-quality-standards.md
+612
-0
.claude/skills/pr-review/common-issues-checklist.md
+833
-0
.claude/skills/pr-review/fastgpt-style-guide.md
+547
-0
No files found.
.claude/skills/common/skills/api-development/SKILL.md
0 → 100644
View file @
c547b434
---
name
:
api-development
description
:
FastGPT API 开发规范。重点强调使用 zod schema 定义入参和出参,在 API 文档中声明路由信息,编写对应的 OpenAPI 文档,以及在 API 路由中使用 schema.parse 进行验证。
---
# FastGPT API 开发规范
> FastGPT 项目 API 路由开发的标准化指南,确保 API 的一致性、类型安全和文档完整性。
## 何时使用此技能
-
开发新的 Next.js API 路由
-
修改现有 API 的入参或出参
-
需要 API 类型定义和文档
-
审查 API 相关代码
## 核心原则
### 🔴 必须遵守的规则
1.
**所有 API 必须使用 zod schema 定义入参和出参**
2.
**必须导出 schema 的 TypeScript 类型**
3.
**必须在 schema 文件头部声明 API 信息(路由、方法、描述、标签)**
4.
**入参必须使用 schema.parse() 验证**
5.
**函数返回值必须使用 schema.parse() 验证**
6.
**必须编写完整的 OpenAPI 文档**
## 开发流程
### 步骤 1: 定义 Zod Schema 并声明 API
**文件位置**
:
`packages/global/openapi/[module]/[api].ts`
**文件头部必须声明 API 信息**
:
```
typescript
import
{
z
}
from
'zod'
;
/* ============================================================================
* API: 获取应用对话日志列表
* Route: POST /api/core/app/logs/list
* Method: POST
* Description: 获取指定应用的对话日志列表,支持分页和多种筛选条件
* Tags: ['App', 'Log', 'Read']
* ============================================================================ */
// 入参 Schema
export
const
GetAppChatLogsBodySchema
=
PaginationSchema
.
extend
({
appId
:
z
.
string
().
meta
({
example
:
'68ad85a7463006c963799a05'
,
description
:
'应用 ID'
}),
dateStart
:
z
.
union
([
z
.
string
(),
z
.
date
()]).
meta
({
example
:
'2024-01-01T00:00:00.000Z'
,
description
:
'开始时间'
}),
dateEnd
:
z
.
union
([
z
.
string
(),
z
.
date
()]).
meta
({
example
:
'2024-12-31T23:59:59.999Z'
,
description
:
'结束时间'
}),
sources
:
z
.
array
(
z
.
nativeEnum
(
ChatSourceEnum
)).
optional
().
meta
({
example
:
[
ChatSourceEnum
.
api
,
ChatSourceEnum
.
online
],
description
:
'对话来源筛选'
})
});
// 导出入参类型
export
type
getAppChatLogsBody
=
z
.
infer
<
typeof
GetAppChatLogsBodySchema
>
;
// 出参 Schema
export
const
GetAppChatLogsResponseSchema
=
z
.
object
({
total
:
z
.
number
().
meta
({
example
:
100
,
description
:
'总记录数'
}),
list
:
z
.
array
(
ChatLogItemSchema
)
});
// 导出出参类型
export
type
getAppChatLogsResponseType
=
z
.
infer
<
typeof
GetAppChatLogsResponseSchema
>
;
```
**API 声明规范**
:
```
typescript
/**
* 每个 API 文件必须在文件头部声明以下信息:
*
* 1. API 名称 (API): 简短的功能描述
* 2. 路由 (Route): 完整的 API 路径
* 3. 方法 (Method): HTTP 方法 (GET/POST/PUT/DELETE)
* 4. 描述 (Description): API 的详细功能说明
* 5. 标签 (Tags): API 的分类标签数组
*
* 标签示例:
* - 'App': 应用相关 API
* - 'User': 用户相关 API
* - 'Log': 日志相关 API
* - 'Read': 只读操作
* - 'Write': 写入操作
* - 'Delete': 删除操作
*/
```
**Schema 定义规范**
:
#### ✅ 字段定义规范
```
typescript
// ✅ 好的实践: 完整的 meta 信息
export
const
GetUserSchema
=
z
.
object
({
userId
:
z
.
string
().
meta
({
example
:
'68ad85a7463006c963799a05'
,
description
:
'用户 ID'
}),
email
:
z
.
string
().
email
().
meta
({
example
:
'user@example.com'
,
description
:
'用户邮箱'
}),
age
:
z
.
number
().
int
().
positive
().
meta
({
example
:
25
,
description
:
'用户年龄'
}),
status
:
z
.
enum
([
'active'
,
'inactive'
]).
meta
({
example
:
'active'
,
description
:
'用户状态'
})
});
// ❌ 不好的实践: 缺少 meta 信息
export
const
GetUserSchemaBad
=
z
.
object
({
userId
:
z
.
string
(),
email
:
z
.
string
(),
age
:
z
.
number
(),
status
:
z
.
string
()
});
```
#### ✅ 嵌套对象定义
```
typescript
// 嵌套对象应该定义为独立的 Schema
export
const
AddressSchema
=
z
.
object
({
street
:
z
.
string
().
meta
({
description
:
'街道地址'
}),
city
:
z
.
string
().
meta
({
description
:
'城市'
}),
country
:
z
.
string
().
meta
({
description
:
'国家'
})
});
export
const
CreateUserSchema
=
z
.
object
({
name
:
z
.
string
().
meta
({
description
:
'用户名'
}),
address
:
AddressSchema
.
meta
({
description
:
'地址信息'
})
});
```
#### ✅ 数组定义
```
typescript
export
const
GetUserListResponseSchema
=
z
.
object
({
total
:
z
.
number
().
meta
({
example
:
100
,
description
:
'总数'
}),
list
:
z
.
array
(
z
.
object
({
id
:
z
.
string
().
meta
({
description
:
'用户 ID'
}),
name
:
z
.
string
().
meta
({
description
:
'用户名'
})
})
).
meta
({
description
:
'用户列表'
})
});
```
#### ✅ 可选字段
```
typescript
export
const
UpdateUserSchema
=
z
.
object
({
userId
:
z
.
string
().
meta
({
description
:
'用户 ID'
}),
// 可选字段使用 .optional()
name
:
z
.
string
().
optional
().
meta
({
description
:
'用户名'
}),
// 或使用 .nullish() 允许 null 和 undefined
email
:
z
.
string
().
email
().
nullish
().
meta
({
description
:
'用户邮箱'
})
});
```
#### ✅ 分页 Schema
```
typescript
import
{
PaginationSchema
}
from
'@fastgpt/global/openapi/api'
;
// 继承分页 Schema
export
const
GetUserListSchema
=
PaginationSchema
.
extend
({
// 添加额外的筛选字段
keyword
:
z
.
string
().
optional
().
meta
({
description
:
'搜索关键词'
}),
status
:
z
.
enum
([
'active'
,
'inactive'
]).
optional
().
meta
({
description
:
'状态筛选'
})
});
```
#### ✅ 多个 API 的 Schema 文件
```
typescript
/* ============================================================================
* API: 获取日志键
* Route: GET /api/core/app/logs/keys
* Method: GET
* Description: 获取应用的日志配置键列表
* Tags: ['App', 'Log', 'Read']
* ============================================================================ */
export
const
GetLogKeysQuerySchema
=
z
.
object
({
appId
:
z
.
string
().
meta
({
description
:
'应用 ID'
})
});
export
const
GetLogKeysResponseSchema
=
z
.
object
({
logKeys
:
z
.
array
(
AppLogKeysSchema
).
meta
({
description
:
'日志键列表'
})
});
/* ============================================================================
* API: 更新日志键
* Route: POST /api/core/app/logs/keys
* Method: POST
* Description: 更新应用的日志配置键
* Tags: ['App', 'Log', 'Write']
* ============================================================================ */
export
const
UpdateLogKeysBodySchema
=
z
.
object
({
appId
:
z
.
string
().
meta
({
description
:
'应用 ID'
}),
logKeys
:
z
.
array
(
AppLogKeysSchema
).
meta
({
description
:
'日志键列表'
})
});
```
### 步骤 2: 实现 API 路由
**文件位置**
:
`projects/app/src/pages/api/[path]/[route].ts`
**标准实现模板**
:
```
typescript
import
type
{
NextApiResponse
}
from
'next'
;
import
{
NextAPI
}
from
'@/service/middleware/entry'
;
import
type
{
ApiRequestProps
}
from
'@fastgpt/service/type/next'
;
import
{
GetAppChatLogsBodySchema
,
GetAppChatLogsResponseSchema
,
type
getAppChatLogsResponseType
}
from
'@fastgpt/global/openapi/...'
;
async
function
handler
(
req
:
ApiRequestProps
,
_res
:
NextApiResponse
):
Promise
<
getAppChatLogsResponseType
>
{
// 🔴 步骤 1: 使用 schema.parse() 验证入参
const
{
appId
,
dateStart
,
dateEnd
,
sources
}
=
GetAppChatLogsBodySchema
.
parse
(
req
.
body
);
// 或对于 query 参数
// const { param1, param2 } = YourAPIQuerySchema.parse(req.query);
// 🔴 步骤 2: 业务逻辑处理
const
result
=
await
yourBusinessLogic
({
appId
,
dateStart
,
dateEnd
,
sources
});
// 🔴 步骤 3: 使用 schema.parse() 验证出参
return
GetAppChatLogsResponseSchema
.
parse
({
list
:
result
.
list
,
total
:
result
.
total
});
}
export
default
NextAPI
(
handler
);
```
**完整示例**
:
```
typescript
import
type
{
NextApiResponse
}
from
'next'
;
import
type
{
ApiRequestProps
}
from
'@fastgpt/service/type/next'
;
import
{
NextAPI
}
from
'@/service/middleware/entry'
;
import
{
authApp
}
from
'@fastgpt/service/support/permission/app/auth'
;
import
{
GetAppChatLogsBodySchema
,
GetAppChatLogsResponseSchema
,
type
getAppChatLogsResponseType
}
from
'@fastgpt/global/openapi/core/app/log/api'
;
async
function
handler
(
req
:
ApiRequestProps
,
_res
:
NextApiResponse
):
Promise
<
getAppChatLogsResponseType
>
{
// 🔴 1. 验证入参
const
{
appId
,
dateStart
,
dateEnd
,
sources
}
=
GetAppChatLogsBodySchema
.
parse
(
req
.
body
);
// 2. 权限验证 (如果需要)
await
authApp
({
req
,
authToken
:
true
,
appId
,
per
:
AppReadChatLogPerVal
});
// 3. 业务逻辑
const
{
list
,
total
}
=
await
getChatLogsFromDB
({
appId
,
dateStart
,
dateEnd
,
sources
});
// 🔴 4. 验证出参
return
GetAppChatLogsResponseSchema
.
parse
({
list
,
total
});
}
export
default
NextAPI
(
handler
);
```
### 步骤 3: 权限验证 (如需要)
**使用 `authApp` 或其他权限验证函数**
:
```
typescript
import
{
authApp
}
from
'@fastgpt/service/support/permission/app/auth'
;
import
{
AppWritePerVal
}
from
'@fastgpt/global/support/permission/app/constant'
;
async
function
handler
(
req
:
ApiRequestProps
,
res
:
NextApiResponse
)
{
const
{
appId
}
=
YourAPIBodySchema
.
parse
(
req
.
body
);
// 权限验证
await
authApp
({
req
,
authToken
:
true
,
appId
,
per
:
AppWritePerVal
// 权限常量
});
// 继续处理...
}
```
### 步骤 4: 错误处理
**使用统一的错误处理**
:
```
typescript
import
{
APIError
}
from
'@fastgpt/service/core/error/controller'
;
import
{
CommonErrEnum
}
from
'@fastgpt/global/common/error/code/common'
;
async
function
handler
(
req
:
ApiRequestProps
,
res
:
NextApiResponse
)
{
try
{
const
{
appId
}
=
YourAPIBodySchema
.
parse
(
req
.
body
);
if
(
!
appId
)
{
return
Promise
.
reject
(
CommonErrEnum
.
missingParams
);
}
// 业务逻辑...
}
catch
(
error
)
{
// 统一错误处理
return
APIError
(
error
)(
req
,
res
);
}
}
```
## 完整开发示例
### 场景: 创建用户 API
**1. 定义 Schema**
(
`packages/global/openapi/core/user/api.ts`
):
```
typescript
import
{
z
}
from
'zod'
;
/* ============================================================================
* API: 创建用户
* Route: POST /api/core/user/create
* Method: POST
* Description: 创建新用户,返回创建的用户信息
* Tags: ['User', 'Write']
* ============================================================================ */
// 入参
export
const
CreateUserBodySchema
=
z
.
object
({
name
:
z
.
string
().
min
(
2
).
max
(
50
).
meta
({
example
:
'Alice'
,
description
:
'用户名 (2-50 字符)'
}),
email
:
z
.
string
().
email
().
meta
({
example
:
'alice@example.com'
,
description
:
'用户邮箱'
}),
age
:
z
.
number
().
int
().
positive
().
optional
().
meta
({
example
:
25
,
description
:
'用户年龄'
}),
avatar
:
z
.
string
().
url
().
optional
().
meta
({
example
:
'https://example.com/avatar.jpg'
,
description
:
'头像 URL'
})
});
export
type
createUserBodyType
=
z
.
infer
<
typeof
CreateUserBodySchema
>
;
// 出参
export
const
CreateUserResponseSchema
=
z
.
object
({
userId
:
z
.
string
().
meta
({
example
:
'68ad85a7463006c963799a05'
,
description
:
'用户 ID'
}),
name
:
z
.
string
().
meta
({
example
:
'Alice'
,
description
:
'用户名'
}),
email
:
z
.
string
().
meta
({
example
:
'alice@example.com'
,
description
:
'用户邮箱'
}),
createdAt
:
z
.
date
().
meta
({
example
:
'2024-01-01T00:00:00.000Z'
,
description
:
'创建时间'
})
});
export
type
createUserResponseType
=
z
.
infer
<
typeof
CreateUserResponseSchema
>
;
```
**2. 实现 API**
(
`projects/app/src/pages/api/core/user/create.ts`
):
```
typescript
import
type
{
NextApiResponse
}
from
'next'
;
import
{
NextAPI
}
from
'@/service/middleware/entry'
;
import
type
{
ApiRequestProps
}
from
'@fastgpt/service/type/next'
;
import
{
MongoUser
}
from
'@fastgpt/service/core/user/schema'
;
import
{
CreateUserBodySchema
,
CreateUserResponseSchema
,
type
createUserResponseType
}
from
'@fastgpt/global/openapi/core/user/api'
;
async
function
handler
(
req
:
ApiRequestProps
,
_res
:
NextApiResponse
):
Promise
<
createUserResponseType
>
{
// 🔴 验证入参
const
{
name
,
email
,
age
,
avatar
}
=
CreateUserBodySchema
.
parse
(
req
.
body
);
// 检查邮箱是否已存在
const
existingUser
=
await
MongoUser
.
findOne
({
email
});
if
(
existingUser
)
{
return
Promise
.
reject
(
'Email already exists'
);
}
// 创建用户
const
user
=
await
MongoUser
.
create
({
name
,
email
,
age
,
avatar
,
createdAt
:
new
Date
()
});
// 🔴 验证出参
return
CreateUserResponseSchema
.
parse
({
userId
:
user
.
_id
.
toString
(),
name
:
user
.
name
,
email
:
user
.
email
,
createdAt
:
user
.
createdAt
});
}
export
default
NextAPI
(
handler
);
```
## 审查检查清单
### 🔴 必须检查项 (阻塞性)
**Schema 文件**
(
`packages/global/openapi/.../api.ts`
):
-
[
]
**API 声明**
: 文件头部有 API 信息(路由、方法、描述、标签)
-
[
]
**Schema 定义**
: 入参和出参都使用 zod 定义
-
[
]
**类型导出**
: 导出
`z.infer<typeof Schema>`
类型
-
[
]
**Meta 信息**
: 所有字段都有
`description`
和
`example`
**API 路由文件**
(
`projects/app/src/pages/api/.../route.ts`
):
-
[
]
**入参验证**
: 使用
`Schema.parse(req.body)`
或
`parse(req.query)`
-
[
]
**出参验证**
: 使用
`Schema.parse(responseData)`
-
[
]
**函数返回类型**
: 函数返回值声明为导出的类型
-
[
]
**权限验证**
: API 路由有相应的权限检查 (如需要)
### 🟡 推荐检查项 (建议性)
-
[
]
**错误处理**
: 使用
`APIError`
统一错误处理
-
[
]
**字段验证**
: 使用 zod 的验证方法 (.min(), .max(), .email() 等)
-
[
]
**可空字段**
: 正确使用
`.optional()`
或
`.nullish()`
-
[
]
**复用 Schema**
: 相同结构抽取为独立 Schema
-
[
]
**分页支持**
: 列表 API 继承
`PaginationSchema`
### 🟢 可选检查项 (优化性)
-
[
]
**字段顺序**
: 字段按重要性排序
-
[
]
**Schema 复用**
: 复用现有 Schema 减少重复
-
[
]
**注释**
: 复杂逻辑添加注释
## 常见问题和解决方案
### 问题 1: 缺少 API 声明
**错误示例**
:
```
typescript
// ❌ 错误: 缺少 API 声明
import
{
z
}
from
'zod'
;
export
const
GetUserSchema
=
z
.
object
({
id
:
z
.
string
()
});
```
**正确做法**
:
```
typescript
// ✅ 正确: 包含完整的 API 声明
import
{
z
}
from
'zod'
;
/* ============================================================================
* API: 获取用户信息
* Route: GET /api/core/user/detail
* Method: GET
* Description: 根据 userId 获取用户详细信息
* Tags: ['User', 'Read']
* ============================================================================ */
export
const
GetUserSchema
=
z
.
object
({
id
:
z
.
string
().
meta
({
example
:
'68ad85a7463006c963799a05'
,
description
:
'用户 ID'
})
});
```
### 问题 2: 类型不匹配
**错误示例**
:
```
typescript
// ❌ 错误: 函数返回类型未声明
async
function
handler
(
req
:
ApiRequestProps
,
res
:
NextApiResponse
)
{
const
data
=
YourAPIBodySchema
.
parse
(
req
.
body
);
return
{
success
:
true
,
data
};
// 类型未声明
}
```
**正确做法**
:
```
typescript
// ✅ 正确: 声明返回类型
async
function
handler
(
req
:
ApiRequestProps
,
_res
:
NextApiResponse
):
Promise
<
yourAPIResponseType
>
{
const
data
=
YourAPIBodySchema
.
parse
(
req
.
body
);
return
YourAPIResponseSchema
.
parse
({
success
:
true
,
data
});
}
```
### 问题 3: 缺少 Meta 信息
**错误示例**
:
```
typescript
// ❌ 错误: 缺少 meta 信息
export
const
UserSchema
=
z
.
object
({
id
:
z
.
string
(),
name
:
z
.
string
(),
email
:
z
.
string
()
});
```
**正确做法**
:
```
typescript
// ✅ 正确: 完整的 meta 信息
export
const
UserSchema
=
z
.
object
({
id
:
z
.
string
().
meta
({
example
:
'68ad85a7463006c963799a05'
,
description
:
'用户 ID'
}),
name
:
z
.
string
().
meta
({
example
:
'Alice'
,
description
:
'用户名'
}),
email
:
z
.
string
().
email
().
meta
({
example
:
'alice@example.com'
,
description
:
'用户邮箱'
})
});
```
### 问题 4: 未验证出参
**错误示例**
:
```
typescript
// ❌ 错误: 直接返回数据
async
function
handler
(
req
:
ApiRequestProps
,
res
:
NextApiResponse
)
{
const
{
appId
}
=
YourAPIBodySchema
.
parse
(
req
.
body
);
const
result
=
await
getData
(
appId
);
return
result
;
// 未验证出参结构
}
```
**正确做法**
:
```
typescript
// ✅ 正确: 验证出参
async
function
handler
(
req
:
ApiRequestProps
,
res
:
NextApiResponse
)
{
const
{
appId
}
=
YourAPIBodySchema
.
parse
(
req
.
body
);
const
result
=
await
getData
(
appId
);
return
YourAPIResponseSchema
.
parse
(
result
);
}
```
### 问题 5: Schema 复用不当
**不好做法**
:
```
typescript
// ❌ 重复定义相同的结构
export
const
Schema1
=
z
.
object
({
id
:
z
.
string
(),
name
:
z
.
string
(),
email
:
z
.
string
()
});
export
const
Schema2
=
z
.
object
({
id
:
z
.
string
(),
name
:
z
.
string
(),
email
:
z
.
string
()
});
```
**正确做法**
:
```
typescript
// ✅ 抽取公共 Schema
export
const
BaseUserSchema
=
z
.
object
({
id
:
z
.
string
().
meta
({
description
:
'ID'
}),
name
:
z
.
string
().
meta
({
description
:
'名称'
}),
email
:
z
.
string
().
email
().
meta
({
description
:
'邮箱'
})
});
export
const
Schema1
=
z
.
object
({
user
:
BaseUserSchema
});
export
const
Schema2
=
z
.
object
({
users
:
z
.
array
(
BaseUserSchema
)
});
```
## 快速参考
### API 声明模板
```
typescript
/* ============================================================================
* API: [简短功能描述]
* Route: [HTTP 方法] [完整路由路径]
* Method: [GET/POST/PUT/DELETE]
* Description: [详细功能说明]
* Tags: [['模块', '子模块', '操作类型']]
* ============================================================================ */
```
### 常用标签
-
**模块标签**
:
`App`
,
`User`
,
`Chat`
,
`Workflow`
,
`Dataset`
-
**操作类型**
:
`Read`
,
`Write`
,
`Delete`
,
`Update`
-
**其他**
:
`Admin`
,
`Public`
,
`Internal`
### 常用 Zod 验证方法
```
typescript
// 字符串
z
.
string
()
// 字符串
.
min
(
2
)
// 最小长度
.
max
(
50
)
// 最大长度
.
email
()
// 邮箱格式
.
url
()
// URL 格式
.
uuid
()
// UUID 格式
// 数字
z
.
number
()
// 数字
.
int
()
// 整数
.
positive
()
// 正数
.
min
(
0
)
// 最小值
.
max
(
100
)
// 最大值
// 布尔
z
.
boolean
()
// 布尔值
// 日期
z
.
date
()
// 日期对象
.
or
(
z
.
string
())
// 或日期字符串
// 枚举
z
.
enum
([
'active'
,
'inactive'
])
// 枚举值
z
.
nativeEnum
(
MyEnum
)
// TypeScript 枚举
// 数组
z
.
array
(
z
.
string
())
// 字符串数组
.
min
(
1
)
// 最小长度
.
max
(
10
)
// 最大长度
// 可选
z
.
string
().
optional
()
// 可选 (undefined)
z
.
string
().
nullish
()
// 可空 (undefined | null)
// 对象
z
.
object
({
// 对象
name
:
z
.
string
(),
age
:
z
.
number
()
})
// 继承
PaginationSchema
.
extend
({
// 扩展
keyword
:
z
.
string
()
})
// 联合类型
z
.
union
([
z
.
string
(),
z
.
number
()])
// 字符串或数字
z
.
discriminator
(
'type'
,
{
// 判别联合
type1
:
Type1Schema
,
type2
:
Type2Schema
})
```
### Meta 字段说明
```
typescript
z
.
string
().
meta
({
example
:
'value'
,
// 示例值 (必填)
description
:
'字段说明'
// 字段描述 (必填)
})
```
### TypeScript 类型导出
```
typescript
// Schema 定义
export
const
UserSchema
=
z
.
object
({
id
:
z
.
string
(),
name
:
z
.
string
()
});
// 导出类型 (命名规范: camelCase)
export
type
userType
=
z
.
infer
<
typeof
UserSchema
>
;
// 或使用 PascalCase
export
type
UserType
=
z
.
infer
<
typeof
UserSchema
>
;
```
## 参考资源
### 项目内示例
-
**API Schema 示例**
:
`/Volumes/code/fastgpt-pro/FastGPT/packages/global/openapi/core/app/log/api.ts`
-
**API 实现示例**
:
`/Volumes/code/fastgpt-pro/FastGPT/projects/app/src/pages/api/core/app/logs/list.ts`
-
**分页 Schema**
:
`packages/global/openapi/api.ts`
### 相关文档
-
**Zod 官方文档**
: https://zod.dev/
-
**FastGPT API 规范**
:
`.claude/skills/pr-review/fastgpt-style-guide.md`
-
**PR Review 审查维度**
:
`.claude/skills/pr-review/code-quality-standards.md`
---
**Version**
: 1.0
**Last Updated**
: 2026-01-27
**Maintainer**
: FastGPT Development Team
.claude/skills/pr-review/SKILL.md
0 → 100644
View file @
c547b434
---
name
:
pr-review
description
:
进行 Pull Request 代码审查,包括代码质量、安全性、性能、架构合理性等方面的全面评估。当用户要求审查 PR 或提到 "review pr"、"检查 PR" 等关键词时激活。
---
# PR Review 代码审查技能
> 全面审查 Pull Request 的代码质量、安全性、性能和架构设计,提供专业的改进建议
## 快速开始
```
bash
# 审查当前分支的 PR
gh pr view
# 审查指定 PR
gh pr view 6324
# 查看变更内容
gh pr diff 6324
```
## 工具集成
### 使用 gh CLI 加速审查
```
bash
# 查看并审查 PR
gh pr view <number>
&&
gh pr diff <number>
# 添加审查评论
gh pr review <number>
--comment
-b
"我的审查意见"
# 批准 PR
gh pr review <number>
--approve
# 请求修改
gh pr review <number>
--request-changes
```
### 本地测试 PR
```
bash
# 检出 PR 分支到本地
gh pr checkout <number>
# 运行测试
pnpm
test
# 运行 lint
pnpm lint
# 类型检查
pnpm tsc
--noEmit
# 启动开发服务器验证
pnpm dev
```
### 常见命令参考
```
bash
# PR 信息查看
gh pr view
--json
title,body,author,state,files,additions,deletions
# PR diff 查看
gh pr diff
gh pr diff <number>
>
/tmp/pr.diff
# 保存到文件
# PR commits 查看
gh pr view
--json
commits
--jq
'.commits[].messageHeadline'
# PR checks 状态
gh pr checks
# PR 评论
gh pr comment <number>
--body
"评论内容"
# PR 审查提交
gh pr review <number>
--approve
gh pr review <number>
--request-changes
gh pr review <number>
--comment
-b
"评论内容"
# PR 操作
gh pr merge <number>
--squash
# Squash merge
gh pr close <number>
# 关闭 PR
```
## 审查流程
### 1. 信息收集阶段
自动执行以下步骤:
```
bash
# 1. 获取 PR 基本信息
gh pr view
--json
title,body,author,state,headRefName,baseRefName,additions,deletions,files
# 2. 获取 PR 变更 diff
gh pr diff
# 3. 获取 PR 的 commit 历史
gh pr view
--json
commits
# 4. 检查 CI/CD 状态
gh pr checks
```
### 2. 多维度代码审查
按照以下三个维度进行系统性审查:
#### 维度 1: 代码质量标准 📐
通用的代码质量标准,适用于所有项目:
-
**安全性**
: 输入验证、权限检查、注入防护、敏感信息保护
-
**正确性**
: 错误处理、边界条件、类型安全
-
**性能**
: 算法复杂度、数据库优化、内存管理
-
**可测试性**
: 测试覆盖、测试质量、Mock 使用
📖
**详细指南**
:
[
code-quality-standards.md
](
./code-quality-standards.md
)
#### 维度 2: FastGPT 风格规范 🎨
FastGPT 项目特定的代码规范和约定:
-
**工作流节点开发**
: 类型定义、节点枚举、执行逻辑、isEntry 管理
-
**API 路由开发**
: 路由定义、权限验证、错误处理
-
**前端组件开发**
: TypeScript + React、Chakra UI、状态管理
-
**数据库操作**
: Model 定义、查询优化、索引设计
-
**包结构与依赖**
: 依赖方向、导入规范、类型导出
📖
**详细指南**
:
[
fastgpt-style-guide.md
](
./fastgpt-style-guide.md
)
#### 维度 3: 常见问题检查清单 🔍
快速识别和修复常见问题模式:
-
**TypeScript 问题**
: any 类型滥用、类型定义不完整、不安全断言
-
**异步错误处理**
: 未处理 Promise、错误信息丢失、静默失败
-
**React 性能**
: 不必要的重渲染、渲染中创建对象、缺少 memoization
-
**工作流节点**
: isEntry 未重置、交互历史未清理、白名单遗漏
-
**安全漏洞**
: 注入攻击、XSS、文件上传漏洞
📖
**详细清单**
:
[
common-issues-checklist.md
](
./common-issues-checklist.md
)
### 3. 生成并提交审查报告
PR 审查输出分为两个部分:
1.
**整体审查报告**
: 提交为 PR 顶部的总体评论
2.
**行级代码评论**
: 直接在代码行的位置添加具体评论
#### 步骤 1: 分析代码并准备评论
在审查过程中,需要为每个问题记录:
-
**文件路径**
: 如
`packages/service/core/workflow/dispatch.ts`
-
**行号**
: 如
`L142-L150`
-
**问题类型**
: 🔴严重 / 🟡改进 / 🟢优化
-
**评论内容**
: 具体的问题描述和建议
#### 步骤 2: 添加行级代码评论
GitHub CLI 支持在特定行添加评论。评论数据格式为 JSON:
```
bash
# 1. 准备行级评论 JSON 文件
cat
>
/tmp/line-comments.json
<<
'
EOF
'
{
"body": "行级代码审查评论",
"event": "COMMENT",
"comments": [
{
"path": "packages/service/core/workflow/dispatch.ts",
"line": 142,
"body": "🔴 **严重问题**: 这里缺少错误处理,如果 runtimeNode 为 null 会导致运行时错误。\n\n**建议**:\n```typescript\nif (!runtimeNode) {\n throw new Error(`Runtime node not found:
${
nodeId
}
`);\n}\n```"
},
{
"path": "packages/service/core/workflow/dispatch.ts",
"line": 150,
"body": "🟡 **性能优化**: 建议将此正则表达式编译提取到函数外部,避免每次调用都重新编译。\n\n**建议**:\n```typescript\nconst NODE_ID_PATTERN = /^node_([a-f0-9]+)
$/
; // 在模块顶部定义\n```"
}
]
}
EOF
# 2. 提交整体审查报告和行级评论
gh pr review <number>
--body-file
/tmp/pr-review.md
--json
>
/tmp/review-result.json
```
#### 步骤 3: 生成整体审查报告
```
markdown
# PR Review: {PR Title}
## 📊 变更概览
-
**PR 编号**
: #{number}
-
**作者**
: @author
-
**分支**
: {baseRefName} ← {headRefName}
-
**变更统计**
: +{additions} -{deletions} 行
-
**涉及文件**
: {files.length} 个文件
## ✅ 优点
{列出做得好的地方}
## ⚠️ 问题汇总
### 🔴 严重问题 ({count} 个,必须修复)
{简要列出每个严重问题,并在下方添加行级评论}
### 🟡 建议改进 ({count} 个)
{简要列出每个建议}
### 🟢 可选优化 ({count} 个)
{简要列出优化建议}
## 🧪 测试建议
{建议的测试方法}
## 💬 总体评价
-
**代码质量**
: ⭐⭐⭐⭐☆ (4/5)
-
**安全性**
: ⭐⭐⭐⭐⭐ (5/5)
-
**性能**
: ⭐⭐⭐⭐☆ (4/5)
-
**可维护性**
: ⭐⭐⭐⭐☆ (4/5)
## 🚀 审查结论
{建议: 通过/需修改/拒绝}
---
## 📍 详细代码评论
已在以下位置添加了具体的行级评论:
{列出所有添加了行级评论的位置}
```
#### 步骤 4: 提交整体审查报告
通过 GitHub CLI 提交整体审查报告到评论区。
#### 审查命令快速参考:
| 场景 | 命令 |
|------|------|
| 批准 PR |
`gh pr review <number> --approve`
|
| 请求修改 |
`gh pr review <number> --request-changes`
|
| 一般评论 |
`gh pr review <number> --comment`
|
| 从文件提交 |
`gh pr review <number> --body-file /tmp/review.md`
|
| 添加普通评论 |
`gh pr comment <number> --body "内容"`
|
| 撤销审查 |
`gh pr review <number> --dismiss`
|
## 参考文档
### 核心审查文档
-
**维度 1**
:
[
code-quality-standards.md
](
./code-quality-standards.md
)
- 通用代码质量标准
-
**维度 2**
:
[
fastgpt-style-guide.md
](
./fastgpt-style-guide.md
)
- FastGPT 项目规范
-
**维度 3**
:
[
common-issues-checklist.md
](
./common-issues-checklist.md
)
- 常见问题清单
.claude/skills/pr-review/code-quality-standards.md
0 → 100644
View file @
c547b434
# 维度 1: 代码质量标准
> 通用的代码质量标准,适用于所有项目。这些标准关注代码的正确性、安全性、性能和可维护性。
## 目录
-
[
1. 安全性标准
](
#1-安全性标准
)
-
[
2. 正确性标准
](
#2-正确性标准
)
-
[
3. 性能标准
](
#3-性能标准
)
-
[
4. 可测试性标准
](
#4-可测试性标准
)
-
[
5. 可维护性标准
](
#5-可维护性标准
)
-
[
6. 文档标准
](
#6-文档标准
)
---
## 1. 安全性标准
### 1.1 输入验证 🔴 **必须检查**
**原则**
: 永远不要信任用户输入,所有输入必须验证
**检查清单**
:
-
[
]
所有用户输入都经过验证和清理
-
[
]
文件上传验证类型、大小、扩展名
-
[
]
URL 参数和查询参数验证
-
[
]
使用白名单而不是黑名单
-
[
]
数组/对象参数验证长度和结构
**示例**
:
```
typescript
// ❌ 不安全: 直接使用用户输入
async
function
searchUsers
(
query
:
string
)
{
return
await
db
.
users
.
find
({
name
:
query
});
}
// ✅ 安全: 验证和清理输入
async
function
searchUsers
(
query
:
string
):
Promise
<
User
[]
>
{
// 验证输入
if
(
!
query
||
query
.
length
>
100
)
{
throw
new
Error
(
'Invalid query parameter'
);
}
// 清理输入: 移除特殊字符
const
sanitizedQuery
=
query
.
replace
(
/
[^\w\s]
/g
,
''
);
return
await
db
.
users
.
find
({
name
:
{
$regex
:
sanitizedQuery
,
$options
:
'i'
}
})
.
limit
(
10
)
// 限制结果数量
.
toArray
();
}
```
### 1.2 权限检查 🔴 **必须检查**
**原则**
: 所有需要授权的操作都必须验证用户权限
**检查清单**
:
-
[
]
所有 API 路由都有权限验证
-
[
]
验证用户对资源的所有权
-
[
]
敏感操作需要额外验证 (2FA, 确认密码)
-
[
]
遵循最小权限原则
**示例**
:
```
typescript
// ❌ 不安全: 没有权限验证
export
default
async
function
handler
(
req
:
NextAPIRequest
,
res
:
NextAPIResponse
)
{
const
userId
=
req
.
body
.
userId
;
const
user
=
await
db
.
users
.
findById
(
userId
);
res
.
json
(
user
);
}
// ✅ 安全: 验证权限
import
{
parseHeaderCert
}
from
'@fastgpt/global/support/permission/controller'
;
export
default
async
function
handler
(
req
:
NextAPIRequest
,
res
:
NextAPIResponse
)
{
// 1. 验证身份
const
{
userId
:
authUserId
}
=
await
parseHeaderCert
(
req
);
// 2. 验证权限 (只能访问自己的数据)
const
requestedUserId
=
req
.
body
.
userId
;
// 管理员可以访问所有用户,普通用户只能访问自己
if
(
authUserId
!==
requestedUserId
&&
!
isAdmin
(
authUserId
))
{
throw
new
Error
(
'Permission denied'
);
}
const
user
=
await
db
.
users
.
findById
(
requestedUserId
);
// 3. 过滤敏感字段
const
{
password
,
...
safeUser
}
=
user
;
res
.
json
(
safeUser
);
}
```
### 1.3 注入防护 🔴 **必须检查**
**原则**
: 防止 SQL/NoSQL 注入、命令注入、XSS 等攻击
**检查清单**
:
-
[
]
使用参数化查询,不拼接字符串
-
[
]
避免直接使用
`eval`
或
`Function`
构造函数
-
[
]
对用户输出进行 HTML 转义
-
[
]
使用 DOMPurify 等库清理 HTML
**示例**
:
```
typescript
// ❌ NoSQL 注入风险
async
function
findUser
(
query
:
any
)
{
return
await
db
.
users
.
findOne
(
query
);
// 如果 query = { "$gt": "" }, 会返回所有用户
}
// ✅ 使用参数化和验证
async
function
findUser
(
email
:
string
):
Promise
<
User
|
null
>
{
// 验证 email 格式
if
(
!
/^
[^\s
@
]
+@
[^\s
@
]
+
\.[^\s
@
]
+$/
.
test
(
email
))
{
throw
new
Error
(
'Invalid email format'
);
}
return
await
db
.
users
.
findOne
({
email
});
}
```
### 1.4 敏感信息保护 🔴 **必须检查**
**原则**
: 不要在代码中硬编码敏感信息,不要在日志中暴露敏感数据
**检查清单**
:
-
[
]
无硬编码的密钥、token、密码
-
[
]
敏感信息使用环境变量
-
[
]
错误日志不包含敏感信息
-
[
]
API 响应过滤敏感字段
-
[
]
密码使用哈希存储
**示例**
:
```
typescript
// ❌ 不安全: 硬编码密钥
const
API_KEY
=
'sk-1234567890abcdef'
;
const
DB_PASSWORD
=
'mypassword'
;
// ✅ 安全: 使用环境变量
const
API_KEY
=
process
.
env
.
OPENAI_API_KEY
;
if
(
!
API_KEY
)
{
throw
new
Error
(
'OPENAI_API_KEY is required'
);
}
// ❌ 不安全: 日志包含敏感信息
console
.
log
(
'User logged in:'
,
{
userId
:
user
.
id
,
email
:
user
.
email
,
password
:
user
.
password
// 密码被记录!
});
// ✅ 安全: 过滤敏感字段
const
{
password
,
...
safeUser
}
=
user
;
console
.
log
(
'User logged in:'
,
{
userId
:
safeUser
.
id
,
email
:
safeUser
.
email
});
```
---
## 2. 正确性标准
### 2.1 错误处理 🔴 **必须检查**
**原则**
: 所有可能失败的操作都必须有错误处理
**检查清单**
:
-
[
]
所有 async/await 都有 try-catch
-
[
]
Promise 都有 .catch() 处理
-
[
]
错误信息清晰且有用
-
[
]
区分业务错误和系统错误
-
[
]
错误日志包含上下文信息
**示例**
:
```
typescript
// ❌ 不好的错误处理
async
function
deleteUser
(
userId
:
string
)
{
await
db
.
users
.
deleteOne
({
id
:
userId
});
// 没有错误处理,如果用户不存在会怎样?
}
// ✅ 好的错误处理
async
function
deleteUser
(
userId
:
string
):
Promise
<
void
>
{
try
{
const
result
=
await
db
.
users
.
deleteOne
({
id
:
userId
});
if
(
result
.
deletedCount
===
0
)
{
throw
new
Error
(
`User not found:
${
userId
}
`
);
}
console
.
log
(
`User
${
userId
}
deleted successfully`
);
}
catch
(
error
)
{
if
(
error
instanceof
Error
)
{
console
.
error
(
`Failed to delete user
${
userId
}
:`
,
error
);
throw
new
Error
(
`Delete user failed:
${
error
.
message
}
`
);
}
throw
error
;
}
}
```
### 2.2 类型安全 🟡 **推荐检查**
**原则**
: 充分利用 TypeScript 类型系统,避免类型错误
**检查清单**
:
-
[
]
避免使用
`any`
类型
-
[
]
函数参数和返回值有明确的类型
-
[
]
复杂类型使用 interface 或 type 定义
-
[
]
使用类型守卫而不是类型断言
-
[
]
启用 strict 模式
**示例**
:
```
typescript
// ❌ 不好的类型使用
async
function
fetchData
(
id
:
any
):
any
{
const
result
:
any
=
await
db
.
collection
(
'data'
).
findOne
({
id
});
return
result
;
}
// ✅ 好的类型使用
interface
UserData
{
id
:
string
;
name
:
string
;
email
:
string
;
createdAt
:
Date
;
}
async
function
fetchData
(
id
:
string
):
Promise
<
UserData
|
null
>
{
const
result
=
await
db
.
collection
<
UserData
>
(
'data'
).
findOne
({
id
});
return
result
;
}
```
### 2.3 边界条件 🟡 **推荐检查**
**原则**
: 考虑边界情况和异常输入
**检查清单**
:
-
[
]
空值处理 (null, undefined, '')
-
[
]
空数组/空对象处理
-
[
]
极限值处理 (0, 最大值, 最小值)
-
[
]
并发和竞争条件
-
[
]
资源耗尽情况
**示例**
:
```
typescript
// ❌ 未处理边界条件
function
getFirstItem
<
T
>
(
items
:
T
[]):
T
{
return
items
[
0
];
// 如果数组为空会返回 undefined
}
// ✅ 处理边界条件
function
getFirstItem
<
T
>
(
items
:
T
[]):
T
|
undefined
{
if
(
items
.
length
===
0
)
{
return
undefined
;
}
return
items
[
0
];
}
// 或使用可选链
function
getFirstItem
<
T
>
(
items
:
T
[]):
T
|
undefined
{
return
items
[
0
];
}
// 使用时
const
first
=
getFirstItem
(
items
);
if
(
first
)
{
// 安全使用 first
}
```
---
## 3. 性能标准
### 3.1 算法复杂度 🟡 **推荐检查**
**原则**
: 避免不必要的嵌套循环,使用合适的数据结构
**检查清单**
:
-
[
]
避免嵌套循环 (O(n²) 或更差)
-
[
]
大数据集使用合适的算法
-
[
]
使用 Set/Map 优化查找操作
-
[
]
分页处理大数据集
**示例**
:
```
typescript
// ❌ 性能问题: O(n²)
function
findDuplicates
(
arr
:
string
[]):
string
[]
{
const
duplicates
:
string
[]
=
[];
for
(
let
i
=
0
;
i
<
arr
.
length
;
i
++
)
{
for
(
let
j
=
i
+
1
;
j
<
arr
.
length
;
j
++
)
{
if
(
arr
[
i
]
===
arr
[
j
])
{
duplicates
.
push
(
arr
[
i
]);
}
}
}
return
duplicates
;
}
// ✅ 优化后: O(n)
function
findDuplicates
(
arr
:
string
[]):
string
[]
{
const
seen
=
new
Set
<
string
>
();
const
duplicates
:
string
[]
=
[];
for
(
const
item
of
arr
)
{
if
(
seen
.
has
(
item
))
{
duplicates
.
push
(
item
);
}
else
{
seen
.
add
(
item
);
}
}
return
duplicates
;
}
```
### 3.2 数据库查询 🟡 **推荐检查**
**原则**
: 避免 N+1 查询,使用索引优化查询
**检查清单**
:
-
[
]
避免 N+1 查询问题
-
[
]
使用索引优化查询
-
[
]
只查询需要的字段
-
[
]
使用分页 (skip + limit)
-
[
]
批量操作使用 bulkWrite
**示例**
:
```
typescript
// ❌ N+1 查询问题
const
users
=
await
db
.
users
.
find
({}).
toArray
();
for
(
const
user
of
users
)
{
const
posts
=
await
db
.
posts
.
find
({
userId
:
user
.
id
}).
toArray
();
user
.
posts
=
posts
;
}
// ✅ 使用 $in 操作
const
users
=
await
db
.
users
.
find
({}).
toArray
();
const
userIds
=
users
.
map
(
u
=>
u
.
id
);
const
posts
=
await
db
.
posts
.
find
({
userId
:
{
$in
:
userIds
}
}).
toArray
();
// 构建映射
const
postsByUser
=
new
Map
<
string
,
Post
[]
>
();
posts
.
forEach
(
post
=>
{
if
(
!
postsByUser
.
has
(
post
.
userId
))
{
postsByUser
.
set
(
post
.
userId
,
[]);
}
postsByUser
.
get
(
post
.
userId
)
!
.
push
(
post
);
});
// 关联数据
users
.
forEach
(
user
=>
{
user
.
posts
=
postsByUser
.
get
(
user
.
id
)
||
[];
});
```
### 3.3 内存管理 🟢 **可选检查**
**原则**
: 避免内存泄漏,及时清理资源
**检查清单**
:
-
[
]
避免内存泄漏 (事件监听器、定时器)
-
[
]
及时清理不再使用的大对象
-
[
]
使用流处理大文件
-
[
]
避免不必要的闭包
---
## 4. 可测试性标准
### 4.1 测试覆盖 🟡 **推荐检查**
**原则**
: 新功能必须有测试,核心功能要有充分测试
**检查清单**
:
-
[
]
新功能有对应的单元测试
-
[
]
核心业务逻辑有集成测试
-
[
]
关键路径有 E2E 测试
-
[
]
测试覆盖主要场景 (正常和异常)
**示例**
:
```
typescript
describe
(
'UserService'
,
()
=>
{
describe
(
'createUser'
,
()
=>
{
it
(
'should create user successfully with valid data'
,
async
()
=>
{
// Arrange
const
userData
=
{
name
:
'Test User'
,
email
:
'test@example.com'
};
// Act
const
user
=
await
createUser
(
userData
);
// Assert
expect
(
user
).
toBeDefined
();
expect
(
user
.
id
).
toBeDefined
();
expect
(
user
.
name
).
toBe
(
userData
.
name
);
});
it
(
'should throw error with duplicate email'
,
async
()
=>
{
// Arrange
const
userData
=
{
name
:
'Test User'
,
email
:
'existing@example.com'
};
await
createUser
(
userData
);
// Act & Assert
await
expect
(
createUser
(
userData
)).
rejects
.
toThrow
(
'Duplicate email'
);
});
it
(
'should throw error with invalid email'
,
async
()
=>
{
// Arrange
const
userData
=
{
name
:
'Test User'
,
email
:
'invalid-email'
};
// Act & Assert
await
expect
(
createUser
(
userData
)).
rejects
.
toThrow
(
'Invalid email'
);
});
});
});
```
### 4.2 测试质量 🟢 **可选检查**
**原则**
: 测试应该是独立的、可重复的、快速的
**检查清单**
:
-
[
]
测试用例独立,不依赖执行顺序
-
[
]
测试用例可重复执行
-
[
]
测试运行快速 (隔离慢操作)
-
[
]
测试命名清晰描述测试意图
-
[
]
使用 AAA 模式 (Arrange, Act, Assert)
---
## 5. 可维护性标准
### 5.1 代码组织 🟡 **推荐检查**
**原则**
: 代码应该易于理解、修改和扩展
**检查清单**
:
-
[
]
函数/模块职责单一
-
[
]
代码重复已抽取
-
[
]
函数长度合理 (一般 < 50 行)
-
[
]
文件结构清晰
-
[
]
命名清晰表达意图
**示例**
:
```
typescript
// ❌ 职责不单一,函数过长
async
function
processUser
(
userId
:
string
)
{
const
user
=
await
db
.
users
.
findById
(
userId
);
if
(
!
user
)
throw
new
Error
(
'User not found'
);
const
orders
=
await
db
.
orders
.
find
({
userId
}).
toArray
();
const
totalAmount
=
orders
.
reduce
((
sum
,
order
)
=>
sum
+
order
.
amount
,
0
);
const
recommendations
=
await
generateRecommendations
(
user
);
const
notifications
=
await
buildNotifications
(
user
,
recommendations
);
await
sendEmail
(
user
.
email
,
notifications
);
await
updateLastLogin
(
userId
);
return
{
user
,
orders
,
totalAmount
,
recommendations
};
}
// ✅ 职责单一,易于测试
async
function
getUserProfile
(
userId
:
string
)
{
const
user
=
await
db
.
users
.
findById
(
userId
);
if
(
!
user
)
throw
new
Error
(
'User not found'
);
return
user
;
}
async
function
getUserOrders
(
userId
:
string
)
{
return
await
db
.
orders
.
find
({
userId
}).
toArray
();
}
async
function
calculateTotalAmount
(
orders
:
Order
[])
{
return
orders
.
reduce
((
sum
,
order
)
=>
sum
+
order
.
amount
,
0
);
}
async
function
processUser
(
userId
:
string
)
{
const
user
=
await
getUserProfile
(
userId
);
const
orders
=
await
getUserOrders
(
userId
);
const
totalAmount
=
calculateTotalAmount
(
orders
);
return
{
user
,
orders
,
totalAmount
};
}
```
### 5.2 命名规范 🟢 **可选检查**
**原则**
: 命名应该清晰表达意图,遵循团队约定
**检查清单**
:
-
[
]
变量名清晰表达用途
-
[
]
函数名使用动词开头
-
[
]
布尔值变量使用 is/has/should 前缀
-
[
]
常量使用 UPPER_SNAKE_CASE
-
[
]
类/接口使用 PascalCase
**示例**
:
```
typescript
// ❌ 不好的命名
const
d
=
new
Date
();
const
temp1
=
getUser
();
const
flag
=
checkUser
();
// ✅ 好的命名
const
currentDate
=
new
Date
();
const
currentUser
=
getUser
();
const
isAuthenticated
=
checkUser
();
```
---
## 6. 文档标准
### 6.1 注释质量 🟢 **可选检查**
**原则**
: 注释应该解释"为什么"而不是"是什么"
**检查清单**
:
-
[
]
复杂逻辑有清晰注释
-
[
]
注释解释设计决策
-
[
]
公共 API 有 JSDoc 注释
-
[
]
TODO/FIXME 有跟踪 issue
**示例**
:
```
typescript
// ❌ 不好的注释: 重复代码
// 获取用户
const
user
=
await
getUser
(
userId
);
// ✅ 好的注释: 解释原因
// 使用缓存避免重复查询数据库
const
user
=
await
getUserWithCache
(
userId
);
// ❌ 不好的注释: 没有解释
// 重试 3 次
for
(
let
i
=
0
;
i
<
3
;
i
++
)
{
try
{
return
await
operation
();
}
catch
(
error
)
{
// 继续尝试
}
}
// ✅ 好的注释: 解释设计决策
// 重试 3 次处理临时网络故障
// 使用指数退避避免服务器过载
for
(
let
attempt
=
1
;
attempt
<=
3
;
attempt
++
)
{
try
{
return
await
operation
();
}
catch
(
error
)
{
if
(
attempt
===
3
)
throw
error
;
await
sleep
(
Math
.
pow
(
2
,
attempt
)
*
1000
);
}
}
```
### 6.2 API 文档 🔴 **必选检查**
所有修改到的 API 都需要用 zod 来进行类型声明以及编写对应的 OpenAPI 文档。
参考:
[
api-development.md
](
../common/skills/api-development/SKILL.md
)
---
## 快速检查表
### 🔴 必须检查项 (阻塞性)
-
[
]
**输入验证**
: 所有用户输入都经过验证
-
[
]
**权限验证**
: API 路由都有权限检查
-
[
]
**注入防护**
: 使用参数化查询
-
[
]
**敏感信息**
: 无硬编码密钥
-
[
]
**错误处理**
: 所有异步操作有错误处理
### 🟡 推荐检查项 (建议性)
-
[
]
**类型安全**
: 避免使用
`any`
-
[
]
**边界条件**
: 处理空值和边界情况
-
[
]
**算法复杂度**
: 避免嵌套循环
-
[
]
**数据库查询**
: 避免 N+1 查询
-
[
]
**测试覆盖**
: 新功能有测试
-
[
]
**代码组织**
: 职责单一,无重复代码
### 🟢 可选检查项 (优化性)
-
[
]
**命名规范**
: 命名清晰表达意图
-
[
]
**注释质量**
: 复杂逻辑有注释
-
[
]
**API 文档**
: 公共 API 有 JSDoc
-
[
]
**内存管理**
: 避免内存泄漏
---
**Version**
: 1.0
**Last Updated**
: 2026-01-27
**Maintainer**
: FastGPT Development Team
.claude/skills/pr-review/common-issues-checklist.md
0 → 100644
View file @
c547b434
# 维度 3: 常见问题检查清单
> 快速识别和修复常见问题模式。这个清单帮助审查者快速发现代码中的典型问题和反模式。
## 目录
-
[
1. TypeScript 问题
](
#1-typescript-问题
)
-
[
2. 异步错误处理问题
](
#2-异步错误处理问题
)
-
[
3. React 性能问题
](
#3-react-性能问题
)
-
[
4. 工作流节点问题
](
#4-工作流节点问题
)
-
[
5. 安全漏洞问题
](
#5-安全漏洞问题
)
-
[
6. 代码重复问题
](
#6-代码重复问题
)
-
[
7. 环境配置问题
](
#7-环境配置问题
)
---
## 1. TypeScript 问题
### 🔴 1.1 滥用 any 类型
**问题识别**
:
-
变量声明为
`any`
类型
-
函数参数或返回值使用
`any`
-
类型断言过度使用
**快速修复**
:
```
typescript
// ❌ 问题代码
async
function
fetchData
(
id
:
any
):
any
{
const
result
:
any
=
await
db
.
collection
(
'data'
).
findOne
({
id
});
return
result
;
}
// ✅ 修复方案
interface
UserData
{
id
:
string
;
name
:
string
;
email
:
string
;
}
async
function
fetchData
(
id
:
string
):
Promise
<
UserData
|
null
>
{
const
result
=
await
db
.
collection
<
UserData
>
(
'data'
).
findOne
({
id
});
return
result
;
}
```
**审查建议**
: 🔴 严重问题,必须修复
---
### 🟡 1.2 类型定义不完整
**问题识别**
:
-
使用
`object`
作为类型
-
参数结构不明确
-
缺少必要的类型定义
**快速修复**
:
```
typescript
// ❌ 问题代码
function
updateUser
(
id
:
string
,
data
:
object
)
{
return
db
.
users
.
updateOne
({
id
},
{
$set
:
data
});
}
// ✅ 修复方案
type
UpdateUserData
=
{
name
?:
string
;
email
?:
string
;
avatar
?:
string
;
};
function
updateUser
(
id
:
string
,
data
:
UpdateUserData
)
{
return
db
.
users
.
updateOne
({
id
},
{
$set
:
data
});
}
```
**审查建议**
: 🟡 建议改进
---
### 🟡 1.3 不安全的类型断言
**问题识别**
:
-
双重断言 (
`as any as Type`
)
-
断言后没有验证
-
过度依赖类型断言
**快速修复**
:
```
typescript
// ❌ 问题代码
const
value
=
data
as
any
as
User
;
// ✅ 修复方案 1: 类型守卫
function
isUser
(
value
:
unknown
):
value
is
User
{
return
(
typeof
value
===
'object'
&&
value
!==
null
&&
'id'
in
value
&&
'name'
in
value
);
}
if
(
isUser
(
data
))
{
// 安全使用 data 作为 User
}
// ✅ 修复方案 2: 使用 zod 验证
import
{
z
}
from
'zod'
;
const
UserSchema
=
z
.
object
({
id
:
z
.
string
(),
name
:
z
.
string
()
});
const
result
=
UserSchema
.
parse
(
data
);
```
**审查建议**
: 🟡 建议改进
---
## 2. 异步错误处理问题
### 🔴 2.1 未处理的 Promise rejection
**问题识别**
:
-
async 函数没有 try-catch
-
没有 .catch() 处理
-
错误可能静默失败
**快速修复**
:
```
typescript
// ❌ 问题代码
async
function
fetchUserData
(
userId
:
string
)
{
const
response
=
await
fetch
(
`/api/users/
${
userId
}
`
);
const
data
=
await
response
.
json
();
return
data
;
}
// ✅ 修复方案
async
function
fetchUserData
(
userId
:
string
):
Promise
<
UserData
>
{
try
{
const
response
=
await
fetch
(
`/api/users/
${
userId
}
`
);
if
(
!
response
.
ok
)
{
throw
new
Error
(
`HTTP error! status:
${
response
.
status
}
`
);
}
const
data
=
await
response
.
json
();
return
data
;
}
catch
(
error
)
{
if
(
error
instanceof
Error
)
{
console
.
error
(
`Failed to fetch user
${
userId
}
:`
,
error
);
throw
new
Error
(
`User fetch failed:
${
error
.
message
}
`
);
}
throw
error
;
}
}
```
**审查建议**
: 🔴 严重问题,必须修复
---
### 🟡 2.2 错误信息丢失
**问题识别**
:
-
catch 中创建新的错误但不保留原始错误
-
错误日志信息不完整
-
难以调试和追踪问题
**快速修复**
:
```
typescript
// ❌ 问题代码
async
function
saveUser
(
user
:
User
)
{
try
{
await
db
.
users
.
insertOne
(
user
);
}
catch
(
error
)
{
throw
new
Error
(
'Save failed'
);
// 原始错误丢失
}
}
// ✅ 修复方案
async
function
saveUser
(
user
:
User
)
{
try
{
await
db
.
users
.
insertOne
(
user
);
}
catch
(
error
)
{
if
(
error
instanceof
Error
)
{
console
.
error
(
'Database error:'
,
error
);
throw
new
Error
(
`Save user failed:
${
error
.
message
}
`
,
{
cause
:
error
});
}
throw
error
;
}
}
```
**审查建议**
: 🟡 建议改进
---
### 🟡 2.3 静默忽略错误
**问题识别**
:
-
空的 catch 块
-
使用 void 忽略 Promise
-
没有说明原因的忽略
**快速修复**
:
```
typescript
// ❌ 问题代码
async
function
cleanup
()
{
try
{
await
deleteTempFiles
();
}
catch
(
error
)
{
// 空的 catch,错误被忽略
}
}
// ✅ 修复方案
async
function
cleanup
()
{
try
{
await
deleteTempFiles
();
}
catch
(
error
)
{
// 至少记录错误日志
console
.
error
(
'Cleanup failed:'
,
error
);
// 如果确实需要忽略,添加注释说明原因
// 错误被忽略是因为清理失败不应影响主流程
}
}
```
**审查建议**
: 🟡 建议改进 (必须有明确的注释说明)
---
## 3. React 性能问题
### 🟢 3.1 不必要的组件重渲染
**问题识别**
:
-
父组件状态变化导致子组件不必要的重渲染
-
子组件是昂贵的计算或渲染
-
没有使用 React.memo
**快速修复**
:
```
typescript
// ❌ 问题代码
const
Parent
=
({
items
}:
{
items
:
Item
[]
})
=>
{
const
[
count
,
setCount
]
=
useState
(
0
);
return
(
<>
<
button
onClick
=
{()
=>
setCount
(
count
+
1
)}
>
Count
:
{
count
}
<
/button
>
{
items
.
map
(
item
=>
(
<
ExpensiveChild
data
=
{
item
}
key
=
{
item
.
id
}
/
>
))}
<
/
>
);
};
// ✅ 修复方案
const
ExpensiveChild
=
React
.
memo
(
function
ExpensiveChild
({
data
}:
{
data
:
Item
})
{
// 昂贵的计算或渲染
return
<
div
>
{
/* ... */
}
<
/div>
;
});
const
Parent
=
({
items
}:
{
items
:
Item
[]
})
=>
{
const
[
count
,
setCount
]
=
useState
(
0
);
return
(
<>
<
button
onClick
=
{()
=>
setCount
(
count
+
1
)}
>
Count
:
{
count
}
<
/button
>
{
items
.
map
(
item
=>
(
<
ExpensiveChild
data
=
{
item
}
key
=
{
item
.
id
}
/
>
))}
<
/
>
);
};
```
**审查建议**
: 🟢 可选优化
---
### 🟡 3.2 渲染中创建新对象/函数
**问题识别**
:
-
JSX 中使用箭头函数
-
JSX 中创建对象字面量
-
导致子组件不必要的重渲染
**快速修复**
:
```
typescript
// ❌ 问题代码
const
MyComponent
=
({
items
}:
{
items
:
Item
[]
})
=>
{
return
(
<>
{
items
.
map
(
item
=>
(
<
Child
key
=
{
item
.
id
}
data
=
{
item
}
onClick
=
{()
=>
handleClick
(
item
.
id
)}
// 每次渲染创建新函数
options
=
{{
enable
:
true
,
mode
:
'edit'
}}
// 每次渲染创建新对象
/
>
))}
<
/
>
);
};
// ✅ 修复方案
const
MyComponent
=
({
items
}:
{
items
:
Item
[]
})
=>
{
const
handleClick
=
useCallback
((
id
:
string
)
=>
{
// 处理逻辑
},
[]);
const
options
=
useMemo
(()
=>
({
enable
:
true
,
mode
:
'edit'
}),
[]);
return
(
<>
{
items
.
map
(
item
=>
(
<
Child
key
=
{
item
.
id
}
data
=
{
item
}
onClick
=
{()
=>
handleClick
(
item
.
id
)}
options
=
{
options
}
/
>
))}
<
/
>
);
};
```
**审查建议**
: 🟡 建议改进
---
### 🟡 3.3 昂贵计算未缓存
**问题识别**
:
-
复杂的数组操作 (sort, filter, map 链式调用)
-
每次渲染都重新计算
-
计算结果在渲染间不变
**快速修复**
:
```
typescript
// ❌ 问题代码
const
ExpensiveList
=
({
items
}:
{
items
:
Item
[]
})
=>
{
// 每次渲染都重新计算
const
sortedItems
=
items
.
sort
((
a
,
b
)
=>
a
.
value
-
b
.
value
);
const
filteredItems
=
sortedItems
.
filter
(
item
=>
item
.
active
);
return
<
ul
>
{
filteredItems
.
map
(
item
=>
<
li
key
=
{
item
.
id
}
>
{
item
.
name
}
<
/li>
)
}</
ul
>
;
};
// ✅ 修复方案
const
ExpensiveList
=
({
items
}:
{
items
:
Item
[]
})
=>
{
const
sortedItems
=
useMemo
(()
=>
[...
items
].
sort
((
a
,
b
)
=>
a
.
value
-
b
.
value
),
[
items
]
);
const
filteredItems
=
useMemo
(()
=>
sortedItems
.
filter
(
item
=>
item
.
active
),
[
sortedItems
]
);
return
<
ul
>
{
filteredItems
.
map
(
item
=>
<
li
key
=
{
item
.
id
}
>
{
item
.
name
}
<
/li>
)
}</
ul
>
;
};
```
**审查建议**
: 🟡 建议改进
---
## 4. 工作流节点问题
### 🔴 4.1 isEntry 标志未重置
**问题识别**
:
-
交互节点执行逻辑中第二阶段没有设置
`node.isEntry = false`
-
节点可能重复执行
-
交互节点功能异常
**快速修复**
:
```
typescript
// ❌ 问题代码
export
const
dispatchInteractiveNode
=
async
(
props
:
Props
)
=>
{
const
{
isEntry
}
=
props
.
node
;
if
(
!
isEntry
)
{
return
{
interactive
:
{
...
}
};
}
// 处理用户输入
return
{
data
:
{
...
}
};
// 忘记重置 isEntry!
};
// ✅ 修复方案
export
const
dispatchInteractiveNode
=
async
(
props
:
Props
)
=>
{
const
{
node
,
lastInteractive
}
=
props
;
const
{
isEntry
}
=
node
;
// 第一阶段: 返回交互请求
if
(
!
isEntry
||
lastInteractive
?.
type
!==
'interactiveType'
)
{
return
{
[
DispatchNodeResponseKeyEnum
.
interactive
]:
{
type
:
'interactiveType'
,
params
:
{
/* ... */
}
}
};
}
// 第二阶段: 处理用户输入
node
.
isEntry
=
false
;
// 🔴 必须: 重置入口标志
return
{
data
:
{
/* ... */
},
[
DispatchNodeResponseKeyEnum
.
rewriteHistories
]:
histories
.
slice
(
0
,
-
2
)
};
};
```
**审查建议**
: 🔴 严重问题,必须修复
---
### 🔴 4.2 交互历史未清理
**问题识别**
:
-
交互节点返回值中没有
`rewriteHistories`
-
用户会看到交互过程中产生的临时消息
**快速修复**
:
```
typescript
// ❌ 问题代码
export
const
dispatchInteractiveNode
=
async
(
props
:
Props
)
=>
{
// 处理用户输入后
return
{
data
:
{
result
:
userInput
}
// 忘记清理交互对话的历史记录
};
};
// ✅ 修复方案
export
const
dispatchInteractiveNode
=
async
(
props
:
Props
)
=>
{
const
{
histories
}
=
props
;
// 处理用户输入后
return
{
data
:
{
result
:
userInput
},
// 移除交互对话的历史记录 (用户问题 + 系统响应 = 2条)
[
DispatchNodeResponseKeyEnum
.
rewriteHistories
]:
histories
.
slice
(
0
,
-
2
)
};
};
```
**审查建议**
: 🔴 严重问题,必须修复
---
### 🔴 4.3 isEntry 白名单遗漏
**问题识别**
:
-
新增交互节点但未更新 isEntry 白名单
-
节点在恢复时 isEntry 被重置,导致流程错误
**快速修复**
:
```
typescript
// ❌ 问题代码
// packages/service/core/workflow/dispatch/index.ts
runtimeNodes
.
forEach
((
item
)
=>
{
if
(
item
.
flowNodeType
!==
FlowNodeTypeEnum
.
userSelect
&&
item
.
flowNodeType
!==
FlowNodeTypeEnum
.
formInput
// 新的交互节点类型未添加到白名单
)
{
item
.
isEntry
=
false
;
}
});
// ✅ 修复方案
runtimeNodes
.
forEach
((
item
)
=>
{
if
(
item
.
flowNodeType
!==
FlowNodeTypeEnum
.
userSelect
&&
item
.
flowNodeType
!==
FlowNodeTypeEnum
.
formInput
&&
item
.
flowNodeType
!==
FlowNodeTypeEnum
.
yourNodeType
// 新增
)
{
item
.
isEntry
=
false
;
}
});
```
**审查建议**
: 🔴 严重问题,必须修复
---
## 5. 安全漏洞问题
### 🔴 5.1 SQL/NoSQL 注入
**问题识别**
:
-
用户输入直接用于数据库查询
-
没有输入验证和清理
-
使用字符串拼接构建查询
**快速修复**
:
```
typescript
// ❌ 问题代码
async
function
searchUsers
(
query
:
string
)
{
return
await
db
.
users
.
find
({
name
:
query
});
// 如果 query = { "$gt": "" },会返回所有用户
}
// ✅ 修复方案
async
function
searchUsers
(
query
:
string
):
Promise
<
User
[]
>
{
if
(
!
query
||
query
.
length
>
100
)
{
throw
new
Error
(
'Invalid query'
);
}
const
sanitizedQuery
=
query
.
replace
(
/
[^\w\s]
/g
,
''
);
return
await
db
.
users
.
find
({
name
:
{
$regex
:
sanitizedQuery
,
$options
:
'i'
}
}).
limit
(
10
).
toArray
();
}
```
**审查建议**
: 🔴 严重问题,必须修复
---
### 🔴 5.2 XSS 攻击
**问题识别**
:
-
使用
`dangerouslySetInnerHTML`
-
用户输入直接渲染到 HTML
-
没有 HTML 转义
**快速修复**
:
```
typescript
// ❌ 问题代码
const
UserProfile
=
({
user
}:
{
user
:
User
})
=>
{
return
(
<
div
>
<
h1
>
{
user
.
name
}
<
/h1
>
<
p
dangerouslySetInnerHTML
=
{{
__html
:
user
.
bio
}}
/
>
<
/div
>
);
};
// ✅ 修复方案
import
DOMPurify
from
'dompurify'
;
const
UserProfile
=
({
user
}:
{
user
:
User
})
=>
{
const
cleanBio
=
DOMPurify
.
sanitize
(
user
.
bio
);
return
(
<
div
>
<
h1
>
{
user
.
name
}
<
/h1
>
<
p
dangerouslySetInnerHTML
=
{{
__html
:
cleanBio
}}
/
>
<
/div
>
);
};
// 或更安全的方案
const
UserProfile
=
({
user
}:
{
user
:
User
})
=>
{
return
(
<
div
>
<
h1
>
{
user
.
name
}
<
/h1
>
<
p
>
{
user
.
bio
}
<
/p> /
/
React
自动转义
<
/div
>
);
};
```
**审查建议**
: 🔴 严重问题,必须修复
---
### 🔴 5.3 文件上传漏洞
**问题识别**
:
-
没有文件类型验证
-
没有文件大小限制
-
没有扩展名白名单
**快速修复**
:
```
typescript
// ❌ 问题代码
app
.
post
(
'/upload'
,
async
(
req
,
res
)
=>
{
const
file
=
req
.
body
.
file
;
await
fs
.
writeFile
(
`/uploads/
${
file
.
name
}
`
,
file
.
data
);
res
.
json
({
success
:
true
});
});
// ✅ 修复方案
import
{
extname
}
from
'path'
;
const
ALLOWED_EXTENSIONS
=
[
'.jpg'
,
'.jpeg'
,
'.png'
,
'.gif'
,
'.pdf'
];
const
ALLOWED_MIMES
=
[
'image/jpeg'
,
'image/png'
,
'image/gif'
,
'application/pdf'
];
const
MAX_FILE_SIZE
=
5
*
1024
*
1024
;
// 5MB
app
.
post
(
'/upload'
,
async
(
req
,
res
)
=>
{
const
file
=
req
.
body
.
file
;
// 验证文件大小
if
(
file
.
size
>
MAX_FILE_SIZE
)
{
return
res
.
status
(
400
).
json
({
error
:
'File too large'
});
}
// 验证 MIME 类型
if
(
!
ALLOWED_MIMES
.
includes
(
file
.
mimetype
))
{
return
res
.
status
(
400
).
json
({
error
:
'Invalid file type'
});
}
// 验证扩展名
const
ext
=
extname
(
file
.
name
).
toLowerCase
();
if
(
!
ALLOWED_EXTENSIONS
.
includes
(
ext
))
{
return
res
.
status
(
400
).
json
({
error
:
'Invalid file extension'
});
}
const
safeName
=
`
${
Date
.
now
()}
-
${
Math
.
random
().
toString
(
36
).
substr
(
2
)}${
ext
}
`
;
await
fs
.
writeFile
(
`/uploads/
${
safeName
}
`
,
file
.
data
);
res
.
json
({
success
:
true
,
filename
:
safeName
});
});
```
**审查建议**
: 🔴 严重问题,必须修复
---
## 6. 代码重复问题
### 🟡 6.1 重复的逻辑
**问题识别**
:
-
相同或相似的代码出现在多处
-
复制粘贴的代码
-
修改 bug 时需要改多处
**快速修复**
:
```
typescript
// ❌ 问题代码
function
validateEmail1
(
email
:
string
):
boolean
{
return
/^
[^\s
@
]
+@
[^\s
@
]
+
\.[^\s
@
]
+$/
.
test
(
email
);
}
function
validateEmail2
(
email
:
string
):
boolean
{
return
/^
[^\s
@
]
+@
[^\s
@
]
+
\.[^\s
@
]
+$/
.
test
(
email
);
}
// ✅ 修复方案
const
EMAIL_REGEX
=
/^
[^\s
@
]
+@
[^\s
@
]
+
\.[^\s
@
]
+$/
;
function
validateEmail
(
email
:
string
):
boolean
{
return
EMAIL_REGEX
.
test
(
email
);
}
```
**审查建议**
: 🟡 建议改进
---
### 🟡 6.2 重复的组件结构
**问题识别**
:
-
多个组件有相似的结构和布局
-
只有细微差别
-
可以抽取共享逻辑或样式
**快速修复**
:
```
typescript
// ❌ 问题代码
const
UserList1
=
({
users
}:
{
users
:
User
[]
})
=>
{
return
(
<
Box
p
=
{
4
}
borderWidth
=
"1px"
borderRadius
=
"md"
>
<
VStack
spacing
=
{
3
}
>
{
users
.
map
(
user
=>
(
<
Box
key
=
{
user
.
id
}
p
=
{
3
}
bg
=
"gray.100"
>
<
Text
>
{
user
.
name
}
<
/Text
>
<
/Box
>
))}
<
/VStack
>
<
/Box
>
);
};
// ✅ 修复方案
interface
ListProps
<
T
>
{
items
:
T
[];
renderItem
:
(
item
:
T
)
=>
React
.
ReactNode
;
}
const
GenericList
=
<
T
,
>
({
items
,
renderItem
}:
ListProps
<
T
>
)
=>
{
return
(
<
Box
p
=
{
4
}
borderWidth
=
"1px"
borderRadius
=
"md"
>
<
VStack
spacing
=
{
3
}
>
{
items
.
map
((
item
,
index
)
=>
(
<
Box
key
=
{
index
}
p
=
{
3
}
bg
=
"gray.100"
>
{
renderItem
(
item
)}
<
/Box
>
))}
<
/VStack
>
<
/Box
>
);
};
const
UserList
=
({
users
}:
{
users
:
User
[]
})
=>
{
return
(
<
GenericList
items
=
{
users
}
renderItem
=
{(
user
)
=>
<
Text
>
{
user
.
name
}
<
/Text>
}
/>
);
};
```
**审查建议**
: 🟡 建议改进
---
## 7. 环境配置问题
### 🔴 7.1 硬编码配置
**问题识别**
:
-
配置值直接写在代码中
-
密钥、token 硬编码
-
不同环境无法灵活配置
**快速修复**
:
```
typescript
// ❌ 问题代码
const
API_KEY
=
'sk-1234567890abcdef'
;
const
DB_URL
=
'mongodb://localhost:27017/myapp'
;
// ✅ 修复方案
const
API_KEY
=
process
.
env
.
OPENAI_API_KEY
;
const
DB_URL
=
process
.
env
.
MONGODB_URL
;
if
(
!
API_KEY
)
{
throw
new
Error
(
'OPENAI_API_KEY is required'
);
}
```
**审查建议**
: 🔴 严重问题 (特别是敏感信息),必须修复
---
### 🟡 7.2 环境变量未验证
**问题识别**
:
-
直接使用环境变量而不验证
-
没有默认值或类型转换
-
缺少必需的环境变量检查
**快速修复**
:
```
typescript
// ❌ 问题代码
const
config
=
{
apiKey
:
process
.
env
.
API_KEY
,
port
:
parseInt
(
process
.
env
.
PORT
),
debug
:
process
.
env
.
DEBUG
===
'true'
};
// ✅ 修复方案
const
getConfig
=
()
=>
{
const
apiKey
=
process
.
env
.
API_KEY
;
if
(
!
apiKey
)
{
throw
new
Error
(
'API_KEY environment variable is required'
);
}
const
port
=
parseInt
(
process
.
env
.
PORT
||
'3000'
,
10
);
if
(
isNaN
(
port
))
{
throw
new
Error
(
'PORT must be a valid number'
);
}
return
{
apiKey
,
port
,
debug
:
process
.
env
.
DEBUG
===
'true'
};
};
const
config
=
getConfig
();
```
**审查建议**
: 🟡 建议改进
---
## 快速识别检查表
### 🔴 严重问题 (必须修复)
-
[
]
滥用
`any`
类型
-
[
]
未处理的 Promise rejection
-
[
]
工作流节点
`isEntry`
未重置
-
[
]
硬编码敏感信息
-
[
]
SQL/NoSQL 注入漏洞
-
[
]
XSS 攻击漏洞
-
[
]
文件上传无验证
### 🟡 建议改进 (推荐修复)
-
[
]
类型定义不完整
-
[
]
错误信息丢失
-
[
]
React 不必要的重渲染
-
[
]
环境变量未验证
-
[
]
代码重复
### 🟢 可选优化 (锦上添花)
-
[
]
进一步性能优化
-
[
]
代码简化
-
[
]
类型守卫优化
---
**Version**
: 1.0
**Last Updated**
: 2026-01-27
**Maintainer**
: FastGPT Development Team
.claude/skills/pr-review/fastgpt-style-guide.md
0 → 100644
View file @
c547b434
# 维度 2: FastGPT 风格规范
> FastGPT 项目特定的代码规范和约定。这些规范关注项目特定的开发模式和架构要求。
本文档详细说明 FastGPT 项目中各类代码开发的特定规范和审查要点,确保代码符合项目的架构模式和最佳实践。
## 目录
-
[
1. 工作流节点开发规范
](
#1-工作流节点开发规范
)
-
[
2. API 路由开发规范
](
#2-api-路由开发规范
)
-
[
3. 前端组件开发规范
](
#3-前端组件开发规范
)
-
[
4. 数据库操作规范
](
#4-数据库操作规范
)
-
[
5. 包结构与依赖规范
](
#5-包结构与依赖规范
)
---
## 1. 工作流节点开发规范
工作流节点是 FastGPT 的核心组件,开发时需要严格遵循架构要求。
### 1.1 类型定义
**文件位置**
:
`packages/global/core/workflow/template/system/interactive/type.d.ts`
**审查要点**
:
-
✅ 新节点类型定义在
`type.d.ts`
中
-
✅ 使用
`type`
而不是
`interface`
(项目约定)
-
✅ 类型定义包含所有必要的字段
-
✅ 导出类型供其他模块使用
**示例**
:
```
typescript
// 定义交互节点响应类型
export
type
YourInteractiveNode
=
InteractiveNodeType
&
{
type
:
'yourNodeType'
;
params
:
{
description
:
string
;
yourField
:
YourItemType
[];
submitted
?:
boolean
;
};
};
// 添加到联合类型
export
type
InteractiveNodeResponseType
=
|
UserSelectInteractive
|
UserInputInteractive
|
YourInteractiveNode
// 新增
|
ChildrenInteractive
;
```
### 1.2 节点枚举
**文件位置**
:
`packages/global/core/workflow/node/constant.ts`
**审查要点**
:
-
✅ 新节点类型添加到
`FlowNodeTypeEnum`
-
✅ 枚举值使用 camelCase
-
✅ 枚举值清晰表达节点用途
**示例**
:
```
typescript
export
enum
FlowNodeTypeEnum
{
// ... 现有类型
yourNodeType
=
'yourNodeType'
,
// 新增
}
```
### 1.3 节点模板
**文件位置**
:
`packages/global/core/workflow/template/system/interactive/yourNode.ts`
**审查要点**
:
-
✅ 使用
`FlowNodeTemplateType`
类型
-
✅ 设置
`templateType`
为正确的类型
-
✅ 使用
`i18nT`
进行国际化
-
✅ 定义清晰的输入输出结构
-
✅
`isTool`
标记正确 (工具节点设为 true)
**示例**
:
```
typescript
export
const
YourNode
:
FlowNodeTemplateType
=
{
id
:
FlowNodeTypeEnum
.
yourNodeType
,
templateType
:
FlowNodeTemplateTypeEnum
.
interactive
,
flowNodeType
:
FlowNodeTypeEnum
.
yourNodeType
,
showSourceHandle
:
true
,
showTargetHandle
:
true
,
avatar
:
'core/workflow/template/yourNode'
,
name
:
i18nT
(
'app:workflow.your_node'
),
intro
:
i18nT
(
'app:workflow.your_node_tip'
),
isTool
:
true
,
// 工具节点
inputs
:
[
{
key
:
NodeInputKeyEnum
.
description
,
renderTypeList
:
[
FlowNodeInputTypeEnum
.
textarea
],
valueType
:
WorkflowIOValueTypeEnum
.
string
,
label
:
i18nT
(
'app:workflow.node_description'
),
placeholder
:
i18nT
(
'app:workflow.your_node_placeholder'
)
}
],
outputs
:
[
{
id
:
NodeOutputKeyEnum
.
yourResult
,
key
:
NodeOutputKeyEnum
.
yourResult
,
required
:
true
,
label
:
i18nT
(
'workflow:your_result'
),
valueType
:
WorkflowIOValueTypeEnum
.
object
,
type
:
FlowNodeOutputTypeEnum
.
static
}
]
};
```
### 1.4 节点执行逻辑
**文件位置**
:
`packages/service/core/workflow/dispatch/interactive/yourNode.ts`
**审查要点**
:
-
✅ 函数签名使用
`ModuleDispatchProps`
泛型
-
✅ 返回类型使用
`DispatchNodeResultType`
-
✅ 两阶段执行: 第一次返回 interactive,第二次处理用户输入
-
✅
**重要**
: 第二阶段必须设置
`node.isEntry = false`
-
✅ 使用
`rewriteHistories`
清理交互历史
-
✅ 错误处理完善
**关键模式**
:
```
typescript
export
const
dispatchYourNode
=
async
(
props
:
Props
):
Promise
<
YourNodeResponse
>
=>
{
const
{
histories
,
node
,
params
:
{
description
,
yourField
},
query
}
=
props
;
const
{
isEntry
}
=
node
;
// 第一阶段: 非入口或交互类型不匹配,返回交互请求
if
(
!
isEntry
||
lastInteractive
?.
type
!==
'yourNodeType'
)
{
return
{
[
DispatchNodeResponseKeyEnum
.
interactive
]:
{
type
:
'yourNodeType'
,
params
:
{
description
,
yourField
}
}
};
}
// 第二阶段: 处理用户提交的数据
node
.
isEntry
=
false
;
// 🔴 必须: 重置入口标志
// 处理用户输入...
const
userInput
=
parseUserInput
(
query
);
return
{
data
:
{
[
NodeOutputKeyEnum
.
yourResult
]:
userInput
},
// 移除交互对话的历史记录 (最后2条)
[
DispatchNodeResponseKeyEnum
.
rewriteHistories
]:
histories
.
slice
(
0
,
-
2
),
[
DispatchNodeResponseKeyEnum
.
toolResponses
]:
userInput
,
[
DispatchNodeResponseKeyEnum
.
nodeResponse
]:
{
yourResult
:
userInput
}
};
};
```
### 1.5 回调注册
**文件位置**
:
`packages/service/core/workflow/dispatch/constants.ts`
**审查要点**
:
-
✅ 在
`callbackMap`
中注册节点
-
✅ 导入执行函数
-
✅ 确保枚举值匹配
**示例**
:
```
typescript
import
{
dispatchYourNode
}
from
'./interactive/yourNode'
;
export
const
callbackMap
:
Record
<
FlowNodeTypeEnum
,
any
>
=
{
// ... 现有节点
[
FlowNodeTypeEnum
.
yourNodeType
]:
dispatchYourNode
,
};
```
### 1.6 isEntry 白名单
**文件位置**
:
`packages/service/core/workflow/dispatch/index.ts`
(约 1012-1019 行)
**审查要点**
:
-
✅ 交互节点类型添加到 isEntry 白名单
-
✅ 这些节点的 isEntry 标志不会被自动重置
**示例**
:
```
typescript
// 交互节点不会自动重置 isEntry 标志 (因为需要根据 isEntry 字段来判断是首次进入还是流程进入)
runtimeNodes
.
forEach
((
item
)
=>
{
if
(
item
.
flowNodeType
!==
FlowNodeTypeEnum
.
userSelect
&&
item
.
flowNodeType
!==
FlowNodeTypeEnum
.
formInput
&&
item
.
flowNodeType
!==
FlowNodeTypeEnum
.
agent
&&
item
.
flowNodeType
!==
FlowNodeTypeEnum
.
yourNodeType
// 新增
)
{
item
.
isEntry
=
false
;
}
});
```
### 1.7 前端组件
**文件位置**
:
-
聊天组件:
`projects/app/src/components/core/chat/components/Interactive/InteractiveComponents.tsx`
-
工作流编辑器:
`projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeYourNode.tsx`
**审查要点**
:
-
✅ 使用 React.memo 包裹组件
-
✅ 使用 useForm 管理表单状态
-
✅ 支持禁用状态 (submitted)
-
✅ 使用 Chakra UI 组件
-
✅ 响应式设计
### 1.8 国际化
**文件位置**
:
`packages/web/i18n/`
(zh-CN, en, zh-Hant)
**审查要点**
:
-
✅ 所有语言的翻译文件都更新
-
✅ key 使用有意义的命名
-
✅ 使用命名空间
`workflow:`
或
`app:`
**示例**
:
```
json
{
"workflow"
:
{
"your_node"
:
"你的节点名称"
,
"your_node_tip"
:
"节点功能说明"
,
"your_result"
:
"节点输出结果"
}
}
```
---
## 2. API 路由开发规范
FastGPT 使用 Next.js API Routes,需要遵循特定的开发模式。
### 2.1 路由定义
**文件位置**
:
`projects/app/src/pages/api/`
**审查要点**
:
-
✅ 路由文件使用命名导出,不支持默认导出
-
✅ 使用
`NextAPIRequest`
和
`NextAPIResponse`
类型
-
✅ 支持的 HTTP 方法明确 (
`GET`
,
`POST`
,
`PUT`
,
`DELETE`
)
-
✅ 返回统一的响应格式
**示例**
:
```
typescript
import
type
{
NextAPIRequest
,
NextAPIResponse
}
from
'@fastgpt/service/type/next'
;
import
{
APIError
}
from
'@fastgpt/service/core/error/controller'
;
export
default
async
function
handler
(
req
:
NextAPIRequest
,
res
:
NextAPIResponse
)
{
try
{
if
(
req
.
method
!==
'POST'
)
{
throw
new
Error
(
'Method not allowed'
);
}
// 处理逻辑...
const
result
=
await
processData
(
req
.
body
);
res
.
json
(
result
);
}
catch
(
error
)
{
APIError
(
error
)(
req
,
res
);
}
}
```
### 2.2 类型合约
**文件位置**
:
`packages/global/openapi/`
**审查要点**
:
-
✅ API 合约定义在 OpenAPI 规范文件中
-
✅ 请求参数有完整的类型定义
-
✅ 响应格式有完整的类型定义
-
✅ 错误响应有说明
### 2.3 业务逻辑
**文件位置**
:
-
通用逻辑:
`packages/service/`
-
项目特定逻辑:
`projects/app/src/service/`
**审查要点**
:
-
✅ 业务逻辑与 API 路由分离
-
✅ 服务函数有明确的类型定义
-
✅ 错误处理统一
### 2.4 权限验证
**审查要点**
:
-
✅ 所有 API 路由都有权限验证 (除了公开端点)
-
✅ 使用
`parseHeaderCert`
解析认证头
-
✅ 验证用户对资源的所有权
-
✅ 敏感操作需要额外验证
**示例**
:
```
typescript
import
{
parseHeaderCert
}
from
'@fastgpt/global/support/permission/controller'
;
export
default
async
function
handler
(
req
:
NextAPIRequest
,
res
:
NextAPIResponse
)
{
try
{
// 解析认证头
const
{
userId
,
teamId
}
=
await
parseHeaderCert
(
req
);
// 验证权限
const
resource
=
await
Resource
.
findById
(
resourceId
);
if
(
!
resource
||
resource
.
userId
!==
userId
)
{
throw
new
Error
(
'Permission denied'
);
}
// 继续处理...
}
catch
(
error
)
{
APIError
(
error
)(
req
,
res
);
}
}
```
### 2.5 错误处理
**审查要点**
:
-
✅ 使用 try-catch 包裹所有异步操作
-
✅ 使用
`APIError`
统一错误响应
-
✅ 错误信息不暴露敏感数据
-
✅ HTTP 状态码正确
---
## 3. 前端组件开发规范
FastGPT 使用 React + TypeScript + Chakra UI。
### 3.1 组件结构
**审查要点**
:
-
✅ 使用函数式组件和 Hooks
-
✅ 组件使用
`React.memo`
优化性能
-
✅ Props 有明确的类型定义
-
✅ 使用 TypeScript type 而不是 interface (项目约定)
**示例**
:
```
typescript
import
React
from
'react'
;
import
{
Box
,
Button
}
from
'@chakra-ui/react'
;
type
YourComponentProps
=
{
title
:
string
;
onClick
:
()
=>
void
;
disabled
?:
boolean
;
};
export
const
YourComponent
=
React
.
memo
(
function
YourComponent
({
title
,
onClick
,
disabled
=
false
}:
YourComponentProps
)
{
return
(
<
Box
>
<
Button
onClick
=
{
onClick
}
isDisabled
=
{
disabled
}
>
{
title
}
<
/Button
>
<
/Box
>
);
});
```
### 3.2 状态管理
**审查要点**
:
-
✅ 本地状态使用
`useState`
-
✅ 全局状态使用 Zustand store
-
✅ 表单状态使用
`useForm`
(react-hook-form)
-
✅ 复杂状态逻辑使用
`useReducer`
### 3.3 样式规范
**审查要点**
:
-
✅ 优先使用 Chakra UI props
-
✅ 响应式设计使用 Chakra UI 的断点系统
-
✅ 自定义样式放在
`styles/theme.ts`
-
✅ 避免内联样式
**示例**
:
```
typescript
// ❌ 不好的实践
<
Box
style
=
{{
backgroundColor
:
'blue'
,
padding
:
'16px'
}}
>
// ✅ 好的实践
<
Box
bg
=
"blue.500"
p
=
{
4
}
>
```
### 3.4 国际化
**审查要点**
:
-
✅ 所有用户可见文本使用
`i18nT`
-
✅ 翻译 key 使用命名空间
-
✅ 动态文本使用插值
**示例**
:
```
typescript
import
{
i18nT
}
from
'@fastgpt/web/i18n/utils'
;
const
message
=
i18nT
(
'user:welcome'
,
{
name
:
userName
});
```
### 3.5 性能优化
**审查要点**
:
-
✅ 列表渲染使用 key
-
✅ 大列表使用虚拟化
-
✅ 避免在渲染中创建新对象/函数
-
✅ 使用
`useMemo`
缓存计算结果
-
✅ 使用
`useCallback`
缓存函数
---
## 4. 数据库操作规范
FastGPT 使用 MongoDB (Mongoose) 和 PostgreSQL。
### 4.1 Model 定义
**文件位置**
:
`packages/service/common/mongo/schema/`
**审查要点**
:
-
✅ Schema 定义使用 TypeScript 泛型
-
✅ 必要的字段添加索引
-
✅ 敏感字段加密存储
-
✅ 定义虚拟字段和实例方法
**示例**
:
```
typescript
import
{
mongoose
,
Schema
}
from
'@fastgpt/service/common/mongo'
;
const
UserSchema
=
new
Schema
({
username
:
{
type
:
String
,
required
:
true
,
unique
:
true
},
password
:
{
type
:
String
,
required
:
true
,
select
:
false
},
// 默认不查询
email
:
{
type
:
String
,
required
:
true
},
createdAt
:
{
type
:
Date
,
default
:
Date
.
now
}
});
// 索引
UserSchema
.
index
({
username
:
1
});
UserSchema
.
index
({
email
:
1
});
// 虚拟字段
UserSchema
.
virtual
(
'fullName'
).
get
(
function
()
{
return
`
${
this
.
firstName
}
${
this
.
lastName
}
`
;
});
export
const
User
=
mongoose
.
model
(
'User'
,
UserSchema
);
```
### 4.2 查询操作
**审查要点**
:
-
✅ 使用参数化查询防止注入
-
✅ 避免 N+1 查询
-
✅ 使用 projection 只查询需要的字段
-
✅ 大结果集使用分页
-
✅ 异步操作有错误处理
**示例**
:
```
typescript
// ❌ 不好的实践
const
users
=
await
User
.
find
({}).
toArray
();
// 可能返回大量数据
// ✅ 好的实践
const
users
=
await
User
.
find
({})
.
project
({
username
:
1
,
email
:
1
})
// 只查询需要的字段
.
limit
(
20
)
// 限制结果数量
.
skip
(
page
*
20
)
.
toArray
();
```
### 4.3 错误处理
**审查要点**
:
-
✅ 数据库操作使用 try-catch
-
✅ 处理重复键错误 (code 11000)
-
✅ 处理连接错误
-
✅ 错误日志包含上下文信息
---
## 5. 包结构与依赖规范
FastGPT 是一个 monorepo,使用 pnpm workspaces。
### 5.1 包结构
```
packages/
├── global/ # 类型、常量、工具函数 (无运行时依赖)
├── service/ # 后端服务、数据库模型 (依赖 global)
└── web/ # 前端组件、样式、i18n (依赖 global)
projects/
├── app/ # NextJS 应用 (依赖所有 packages)
├── sandbox/ # NestJS 沙箱服务 (独立应用)
└── mcp_server/ # MCP 服务器 (独立应用)
```
### 5.2 依赖规则
**审查要点**
:
-
✅
`packages/global/`
无任何运行时依赖
-
✅
`packages/service/`
只依赖
`packages/global/`
-
✅
`packages/web/`
只依赖
`packages/global/`
-
✅
`projects/app/`
可以依赖所有 packages
-
✅ 独立项目 (sandbox, mcp_server) 最小化依赖
### 5.3 导入规范
**审查要点**
:
-
✅ 使用项目别名导入:
`@fastgpt/global`
,
`@fastgpt/service`
,
`@fastgpt/web`
-
✅ 避免相对路径导入跨包的文件
-
✅ 导入路径使用 index 简化
**示例**
:
```
typescript
// ❌ 不好的导入
import
{
UserType
}
from
..
/
..
/
..
/
..
/
..
/
packages
/
global
/
core
/
user
/
type
.
d
.
ts
;
// ✅ 好的导入
import
{
UserType
}
from
'@fastgpt/global/core/user/type'
;
```
### 5.4 类型导出
**审查要点**
:
-
✅ 公共类型必须导出
-
✅ 类型文件使用
`.d.ts`
扩展名
-
✅ 复杂类型放在独立的类型文件
-
✅ 使用
`export type`
导出类型
---
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment