Files
AIclinicalresearch/docs/02-开发规范/代码规范.md
AI Clinical Dev Team 9acbb0ae2b feat: complete Dify platform deployment (Day 18)
## Dify 閮ㄧ讲瀹屾垚 鉁?
### 瀹屾垚鐨勫伐浣?1. Docker 闀滃儚鍔犻€熷櫒閰嶇疆
   - 閰嶇疆 5 涓浗鍐呴暅鍍忔簮
   - 澶у箙鎻愬崌涓嬭浇閫熷害鍜屾垚鍔熺巼

2. Dify 闀滃儚鎷夊彇 (鍏?11 涓湇鍔?
   - langgenius/dify-api:1.9.1
   - langgenius/dify-web:1.9.1
   - postgres, redis, weaviate, nginx 绛?   - 鎬诲ぇ灏忕害 2GB锛岃€楁椂绾?15 鍒嗛挓

3. Dify 鏈嶅姟鍚姩
   - 鉁?nginx (80/443)
   - 鉁?api, worker, worker_beat
   - 鉁?web (3000)
   - 鉁?db (PostgreSQL), redis
   - 鉁?weaviate (鍚戦噺鏁版嵁搴?
   - 鉁?sandbox, plugin_daemon, ssrf_proxy

4. Dify 鍒濆鍖栭厤缃?   - 鍒涘缓绠$悊鍛樿处鍙?   - 鍒涘缓搴旂敤: AI Clinical Research
   - 鑾峰彇 API Key: app-VZRn0vMXdmltEJkvatHVGv5j

5. 鍚庣鐜閰嶇疆
   - DIFY_API_URL=http://localhost/v1
   - DIFY_API_KEY 宸查厤缃?
### 鏂囨。鏇存柊
- 鏂板: docs/05-姣忔棩杩涘害/Day18-Dify閮ㄧ讲瀹屾垚.md
- 鏇存柊: docs/04-寮€鍙戣鍒?寮€鍙戦噷绋嬬.md (Day 18 鏍囪涓哄畬鎴?

### 涓嬩竴姝?Day 19-24: 鐭ヨ瘑搴撶郴缁熷紑鍙?- Dify 瀹㈡埛绔皝瑁?- 鐭ヨ瘑搴撶鐞?CRUD
- 鏂囨。涓婁紶涓庡鐞?- @鐭ヨ瘑搴撻泦鎴?- RAG 闂瓟楠岃瘉

---
Progress: 閲岀▼纰?1 (MVP) 85% -> 鐭ヨ瘑搴撶郴缁熷紑鍙戜腑
2025-10-11 08:58:41 +08:00

817 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 代码规范
> **版本:** v1.0
> **创建日期:** 2025-10-10
> **适用范围:** 前端React/TypeScript+ 后端Node.js/TypeScript
---
## 📋 目录
1. [通用规范](#通用规范)
2. [TypeScript规范](#typescript规范)
3. [React规范](#react规范)
4. [Node.js后端规范](#nodejs后端规范)
5. [命名规范](#命名规范)
6. [注释规范](#注释规范)
7. [Git提交规范](#git提交规范)
---
## 通用规范
### 代码风格
- ✅ 使用ESLint和Prettier统一代码风格
- ✅ 缩进2个空格
- ✅ 字符串:优先使用单引号 `'`
- ✅ 行尾:不加分号(除非必要)
- ✅ 单行最大长度100字符
- ✅ 使用尾随逗号(对象、数组)
### 文件组织
- ✅ 一个文件一个组件/类
- ✅ 相关文件放在同一目录
- ✅ 使用barrel exportsindex.ts
- ✅ 测试文件与源文件同目录
```
src/
├── components/
│ ├── Button/
│ │ ├── Button.tsx
│ │ ├── Button.test.tsx
│ │ ├── Button.styles.ts
│ │ └── index.ts # export { Button } from './Button'
```
### 代码注释
- ✅ 复杂逻辑必须注释
- ✅ 公共API必须注释
- ✅ 避免无用注释
- ✅ 使用JSDoc格式
---
## TypeScript规范
### 类型定义
**✅ 推荐:**
```typescript
// 使用interface定义对象结构
interface User {
id: string
email: string
name?: string
}
// 使用type定义联合类型
type Status = 'active' | 'inactive' | 'suspended'
// 使用enum定义常量集合
enum UserRole {
USER = 'user',
ADMIN = 'admin',
}
```
**❌ 避免:**
```typescript
// 不要使用any
function process(data: any) { // ❌
// ...
}
// 应该明确类型
function process(data: ProcessData) { // ✅
// ...
}
```
### 类型导入导出
```typescript
// types.ts
export interface Project {
id: string
name: string
description: string
}
export type ProjectStatus = 'active' | 'archived'
// project.service.ts
import type { Project, ProjectStatus } from './types'
```
### 严格模式
```json
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true
}
}
```
---
## React规范
### 组件定义
**✅ 推荐:函数组件 + Hooks**
```tsx
import { useState } from 'react'
interface ButtonProps {
label: string
onClick: () => void
variant?: 'primary' | 'secondary'
disabled?: boolean
}
export function Button({
label,
onClick,
variant = 'primary',
disabled = false
}: ButtonProps) {
const [isLoading, setIsLoading] = useState(false)
const handleClick = async () => {
setIsLoading(true)
try {
await onClick()
} finally {
setIsLoading(false)
}
}
return (
<button
onClick={handleClick}
disabled={disabled || isLoading}
className={`btn btn-${variant}`}
>
{isLoading ? 'Loading...' : label}
</button>
)
}
```
**❌ 避免:类组件**
```tsx
// 除非有特殊需求,否则不使用类组件
class Button extends React.Component { ... } // ❌
```
### Hooks规范
**✅ 推荐自定义Hooks**
```typescript
// useProjects.ts
import { useState, useEffect } from 'react'
import { projectService } from '@/services'
import type { Project } from '@/types'
export function useProjects() {
const [projects, setProjects] = useState<Project[]>([])
const [loading, setLoading] = useState(false)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
loadProjects()
}, [])
const loadProjects = async () => {
setLoading(true)
setError(null)
try {
const data = await projectService.getProjects()
setProjects(data)
} catch (err) {
setError(err as Error)
} finally {
setLoading(false)
}
}
return { projects, loading, error, reload: loadProjects }
}
// 使用
function ProjectList() {
const { projects, loading, error } = useProjects()
if (loading) return <Loading />
if (error) return <Error message={error.message} />
return (
<ul>
{projects.map(project => (
<li key={project.id}>{project.name}</li>
))}
</ul>
)
}
```
### 组件组织
```tsx
// ✅ 推荐的组件结构
import { useState, useEffect, useMemo, useCallback } from 'react'
import { useNavigate } from 'react-router-dom'
import { SomeComponent } from '@/components'
import { useCustomHook } from '@/hooks'
import type { SomeType } from '@/types'
interface ComponentProps {
// props定义
}
export function Component({ prop1, prop2 }: ComponentProps) {
// 1. Hooks
const navigate = useNavigate()
const [state, setState] = useState()
const { data } = useCustomHook()
// 2. 派生状态useMemo
const computedValue = useMemo(() => {
return heavyComputation(data)
}, [data])
// 3. 事件处理useCallback
const handleClick = useCallback(() => {
// 处理逻辑
}, [])
// 4. Effects
useEffect(() => {
// 副作用
}, [])
// 5. 早期返回Loading/Error
if (!data) return <Loading />
// 6. 渲染
return (
<div>
{/* JSX */}
</div>
)
}
```
### 条件渲染
**✅ 推荐:**
```tsx
// 简单条件:使用 &&
{isLoggedIn && <UserMenu />}
// if-else使用三元运算符
{isLoggedIn ? <UserMenu /> : <LoginButton />}
// 多条件:提取为函数或组件
function renderContent() {
if (loading) return <Loading />
if (error) return <Error />
if (data.length === 0) return <Empty />
return <DataList data={data} />
}
return <div>{renderContent()}</div>
```
**❌ 避免:**
```tsx
// 避免复杂的嵌套三元运算符
{condition1 ? (
condition2 ? <A /> : <B />
) : (
condition3 ? <C /> : <D />
)} // ❌ 难以理解
```
---
## Node.js后端规范
### 文件组织
```
backend/src/
├── routes/ # 路由定义
│ ├── auth.routes.ts
│ └── project.routes.ts
├── services/ # 业务逻辑
│ ├── auth.service.ts
│ └── project.service.ts
├── controllers/ # 控制器(可选)
├── models/ # Prisma模型
├── utils/ # 工具函数
├── config/ # 配置加载
├── types/ # 类型定义
└── server.ts # 入口文件
```
### 路由定义
```typescript
// routes/project.routes.ts
import { FastifyInstance } from 'fastify'
import { projectService } from '../services/project.service'
import { authMiddleware } from '../middleware/auth'
export async function projectRoutes(server: FastifyInstance) {
// 获取项目列表
server.get(
'/api/v1/projects',
{
preHandler: [authMiddleware],
schema: {
querystring: {
type: 'object',
properties: {
page: { type: 'number' },
pageSize: { type: 'number' },
},
},
},
},
async (request, reply) => {
const { page = 1, pageSize = 20 } = request.query as any
const userId = request.user.id
const result = await projectService.getProjects(userId, {
page,
pageSize,
})
return reply.send({
success: true,
data: result,
})
}
)
// 创建项目
server.post(
'/api/v1/projects',
{
preHandler: [authMiddleware],
schema: {
body: {
type: 'object',
required: ['name', 'description'],
properties: {
name: { type: 'string', minLength: 1, maxLength: 200 },
description: { type: 'string', minLength: 1 },
},
},
},
},
async (request, reply) => {
const userId = request.user.id
const data = request.body as CreateProjectDto
const project = await projectService.createProject(userId, data)
return reply.code(201).send({
success: true,
data: project,
})
}
)
}
```
### Service层
```typescript
// services/project.service.ts
import { prisma } from '../lib/prisma'
import type { CreateProjectDto, UpdateProjectDto } from '../types'
export class ProjectService {
/**
* 获取用户的项目列表
*/
async getProjects(userId: string, options: PaginationOptions) {
const { page, pageSize } = options
const [items, total] = await Promise.all([
prisma.project.findMany({
where: { userId },
skip: (page - 1) * pageSize,
take: pageSize,
orderBy: { createdAt: 'desc' },
}),
prisma.project.count({ where: { userId } }),
])
return {
items,
pagination: {
page,
pageSize,
total,
totalPages: Math.ceil(total / pageSize),
hasNext: page * pageSize < total,
hasPrev: page > 1,
},
}
}
/**
* 创建项目
*/
async createProject(userId: string, data: CreateProjectDto) {
return prisma.project.create({
data: {
userId,
name: data.name,
description: data.description,
},
})
}
/**
* 更新项目
*/
async updateProject(
userId: string,
projectId: string,
data: UpdateProjectDto
) {
// 验证权限
const project = await prisma.project.findFirst({
where: { id: projectId, userId },
})
if (!project) {
throw new Error('Project not found or unauthorized')
}
return prisma.project.update({
where: { id: projectId },
data,
})
}
/**
* 删除项目
*/
async deleteProject(userId: string, projectId: string) {
// 验证权限
const project = await prisma.project.findFirst({
where: { id: projectId, userId },
})
if (!project) {
throw new Error('Project not found or unauthorized')
}
await prisma.project.delete({
where: { id: projectId },
})
}
}
export const projectService = new ProjectService()
```
### 错误处理
```typescript
// utils/errors.ts
export class AppError extends Error {
constructor(
public code: string,
public message: string,
public statusCode: number = 400,
public details?: any
) {
super(message)
this.name = 'AppError'
}
}
export class ValidationError extends AppError {
constructor(message: string, details?: any) {
super('VALIDATION_ERROR', message, 422, details)
}
}
export class UnauthorizedError extends AppError {
constructor(message: string = 'Unauthorized') {
super('UNAUTHORIZED', message, 401)
}
}
export class NotFoundError extends AppError {
constructor(resource: string) {
super('NOT_FOUND', `${resource} not found`, 404)
}
}
// 使用
async function getProject(id: string) {
const project = await prisma.project.findUnique({ where: { id } })
if (!project) {
throw new NotFoundError('Project')
}
return project
}
```
### 错误处理中间件
```typescript
// middleware/error-handler.ts
import { FastifyError, FastifyReply, FastifyRequest } from 'fastify'
import { AppError } from '../utils/errors'
export async function errorHandler(
error: FastifyError | AppError,
request: FastifyRequest,
reply: FastifyReply
) {
// 记录错误
request.log.error(error)
// 自定义错误
if (error instanceof AppError) {
return reply.code(error.statusCode).send({
success: false,
error: {
code: error.code,
message: error.message,
details: error.details,
},
timestamp: new Date().toISOString(),
})
}
// Prisma错误
if (error.name === 'PrismaClientKnownRequestError') {
// 处理Prisma特定错误
return reply.code(400).send({
success: false,
error: {
code: 'DATABASE_ERROR',
message: 'Database operation failed',
},
timestamp: new Date().toISOString(),
})
}
// 默认错误
return reply.code(500).send({
success: false,
error: {
code: 'INTERNAL_ERROR',
message: 'Internal server error',
},
timestamp: new Date().toISOString(),
})
}
```
---
## 命名规范
### 文件命名
| 类型 | 命名方式 | 示例 |
|------|---------|------|
| React组件 | PascalCase | `Button.tsx`, `ProjectList.tsx` |
| Hooks | camelCase + use前缀 | `useProjects.ts`, `useAuth.ts` |
| 工具函数 | camelCase | `formatDate.ts`, `api.ts` |
| 类型定义 | camelCase + .types | `user.types.ts`, `api.types.ts` |
| 常量 | camelCase + .constants | `routes.constants.ts` |
| 测试文件 | 同源文件 + .test | `Button.test.tsx` |
### 变量命名
```typescript
// ✅ 推荐
const userName = 'John' // camelCase
const USER_ROLE = 'admin' // 常量用UPPER_SNAKE_CASE
const isLoading = false // 布尔值用is/has/can前缀
const hasPermission = true
const canEdit = false
// ❌ 避免
const user_name = 'John' // 不用snake_case
const loading = false // 布尔值缺少is前缀
const x = 10 // 无意义的变量名
```
### 函数命名
```typescript
// ✅ 推荐
function getUser() { } // get: 获取数据
function fetchProjects() { } // fetch: 异步获取
function createProject() { } // create: 创建
function updateProject() { } // update: 更新
function deleteProject() { } // delete: 删除
function handleClick() { } // handle: 事件处理
function validateEmail() { } // validate: 验证
function formatDate() { } // format: 格式化
// ❌ 避免
function data() { } // 不清楚功能
function doSomething() { } // 太模糊
function process() { } // 不明确
```
### 组件命名
```typescript
// ✅ 推荐
<Button /> // 基础组件
<UserProfile /> // 业务组件
<ProjectList /> // 列表组件
<CreateProjectModal /> // 弹窗组件
// ❌ 避免
<button /> // 不用小写
<user_profile /> // 不用snake_case
<ListProjects /> // 动词不要在前
```
---
## 注释规范
### JSDoc注释
```typescript
/**
* 创建新项目
* @param userId - 用户ID
* @param data - 项目数据
* @returns 创建的项目对象
* @throws {ValidationError} 当数据验证失败时
*/
async function createProject(
userId: string,
data: CreateProjectDto
): Promise<Project> {
// 实现...
}
```
### 代码注释
```typescript
// ✅ 好的注释:解释为什么
// 使用setTimeout避免阻塞UI渲染
setTimeout(() => {
processLargeData()
}, 0)
// 等待Dify处理文档最多重试10次
for (let i = 0; i < 10; i++) {
const status = await checkStatus()
if (status === 'completed') break
await sleep(2000)
}
// ❌ 坏的注释:重复代码
// 设置loading为true
setLoading(true)
// 调用API
await api.getData()
```
---
## Git提交规范
### Commit Message格式
```
<type>(<scope>): <subject>
<body>
<footer>
```
### Type类型
| 类型 | 说明 |
|------|------|
| feat | 新功能 |
| fix | Bug修复 |
| docs | 文档更新 |
| style | 代码格式(不影响功能) |
| refactor | 重构 |
| perf | 性能优化 |
| test | 测试相关 |
| chore | 构建/工具变动 |
### 示例
```bash
# 好的提交
git commit -m "feat(auth): 实现用户登录功能"
git commit -m "fix(project): 修复项目删除权限问题"
git commit -m "docs(api): 更新API文档"
git commit -m "refactor(chat): 优化消息组件结构"
# 不好的提交
git commit -m "update" # ❌ 太模糊
git commit -m "fix bug" # ❌ 没有说明是什么bug
git commit -m "完成功能" # ❌ 没有说明是什么功能
```
---
## ESLint配置
```javascript
// .eslintrc.js
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'prettier',
],
rules: {
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'react/react-in-jsx-scope': 'off',
'react/prop-types': 'off',
},
}
```
---
## Prettier配置
```json
{
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 100,
"arrowParens": "avoid"
}
```
---
## 代码Review检查清单
### 功能
- [ ] 功能是否完整实现
- [ ] 是否有遗漏的边界情况
- [ ] 错误处理是否完善
### 代码质量
- [ ] 代码是否易读易理解
- [ ] 是否有重复代码
- [ ] 函数是否过长(建议<50行
- [ ] 是否遵守命名规范
### 性能
- [ ] 是否有性能问题
- [ ] 是否有不必要的重渲染
- [ ] 数据库查询是否优化
### 安全
- [ ] 是否有SQL注入风险
- [ ] 是否有XSS风险
- [ ] 权限验证是否完善
### 测试
- [ ] 是否有单元测试
- [ ] 测试覆盖率是否足够
---
**文档维护:** 规范更新需同步此文档
**最后更新:** 2025-10-10
**维护者:** 技术负责人