Files
AIclinicalresearch/docs/04-开发规范/05-代码规范.md
HaHafeng 1b53ab9d52 feat(aia): Complete AIA V2.0 with universal streaming capabilities
Major Changes:
- Add StreamingService with OpenAI Compatible format
- Upgrade Chat component V2 with Ant Design X integration
- Implement AIA module with 12 intelligent agents
- Update API routes to unified /api/v1 prefix
- Update system documentation

Backend (~1300 lines):
- common/streaming: OpenAI Compatible adapter
- modules/aia: 12 agents, conversation service, streaming integration
- Update route versions (RVW, PKB to v1)

Frontend (~3500 lines):
- modules/aia: AgentHub + ChatWorkspace (100% prototype restoration)
- shared/Chat: AIStreamChat, ThinkingBlock, useAIStream Hook
- Update API endpoints to v1

Documentation:
- AIA module status guide
- Universal capabilities catalog
- System overview updates
- All module documentation sync

Tested: Stream response verified, authentication working
Status: AIA V2.0 core completed (85%)
2026-01-14 19:15:01 +08:00

950 lines
22 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.
# 隞<><E99A9E><EFBFBD><E996AB>
> **<2A><>𧋦嚗?* v1.0
> **<2A>𥕦遣<F0A595A6><EFBFBD>嚗?* 2025-10-10
> **<2A><><EFBFBD><E98D82>凒嚗?* <20>滨垢嚗㇌eact/TypeScript嚗? <20>𡒊垢嚗𠃊ode.js/TypeScript嚗?
---
## <20><> <20><EFBFBD>
1. [<EFBFBD>𡁶鍂閫<EFBFBD><EFBFBD>](#<23>𡁶鍂閫<E98D82><E996AB>)
2. [TypeScript閫<EFBFBD><EFBFBD>](#typescript閫<74><E996AB>)
3. [React閫<EFBFBD><EFBFBD>](#react閫<74><E996AB>)
4. [Node.js<6A>𡒊垢閫<E59EA2><E996AB>](#nodejs<6A>𡒊垢閫<E59EA2><E996AB>)
5. [<EFBFBD><EFBFBD><EFBFBD><EFBFBD>](#<23><EFBFBD><EFBFBD><E996AB>)
6. [瘜券<EFBFBD><EFBFBD><EFBFBD>](#瘜券<E7989C><EFBFBD><E996AB>)
7. [Git<EFBFBD>𣂷漱閫<EFBFBD><EFBFBD>](#git<69>𣂷漱閫<E6BCB1><E996AB>)
---
## <20><> 撟喳蝱<E596B3><EFBFBD>雿輻鍂閫<E98D82><E996AB>嚗?025-11-16 <20><EFBFBD>嚗?
> **潃?<3F><EFBFBD><E6BBA9>鞟內**嚗𡁜像<F0A1819C>啣歇<E595A3>𣂷<EFBFBD>摰峕㟲<E5B395><E39FB2>抅蝖<E68A85>霈暹鴌<E69AB9>滚𦛚
> **霂衣<E99C82><EFBFBD><E996AB>**嚗靀鈭穃<E988AD><E7A983><EFBFBD><E7AC94>𤏸<EFBFBD><F0A48FB8><EFBFBD>(./08-鈭穃<E988AD><E7A983><EFBFBD><E7AC94>𤏸<EFBFBD><F0A48FB8>?md)
> **霂衣<E99C82><E8A1A3><EFBFBD>﹝**嚗靀撟喳蝱<E596B3><EFBFBD>霈暹鴌閫<E9B48C><E996AB>](../09-<2D><EFBFBD>摰墧鴌/04-撟喳蝱<E596B3><EFBFBD>霈暹鴌閫<E9B48C><E996AB>.md)
### 敹<>◆憭滨鍂<E6BBA8><E98D82><EFBFBD><EFBFBD><E594B3>?
**銝𡁜𦛚璅<E79285>嚗㇁SL/AIA/PKB/DC蝑㚁<E89D91><EFBFBD><EFBFBD><EFBFBD>摰䂿緵隞乩<E99A9E><E4B9A9><EFBFBD>嚗?*
| <20>滚𦛚 | 撖澆<E69296><E6BE86><EFBFBD> | <20><EFBFBD>?|
|------|---------|------|
| **摮睃<E691AE><E79D83>滚𦛚** | `import { storage } from '@/common/storage'` | <20><>辣銝𠹺<E98A9D>銝贝蝸 |
| **<EFBFBD><EFBFBD>蝟餌<EFBFBD>** | `import { logger } from '@/common/logging'` | <20><><EFBFBD><EFBFBD>𡝗𠯫敹?|
| **撘<>郊隞餃𦛚** | `import { jobQueue } from '@/common/jobs'` | <20>踵𧒄<E8B8B5>港遙<E6B8AF>?|
| **蝻枏<E89DBB><E69E8F>滚𦛚** | `import { cache } from '@/common/cache'` | <20><><EFBFBD>撘讐<E69298>摮?|
| **<EFBFBD>唳旿摨?* | `import { prisma } from '@/config/database'` | <20>唳旿摨𤘪<E691A8>雿?|
| **LLM<4C><EFBFBD>** | `import { LLMFactory } from '@/common/llm'` | LLM靚<4D>鍂 |
---
### <20>?甇<>蝷箔<E89DB7>嚗帋蝙<E5B88B>典像<E585B8><EFBFBD><E594B3>?
```typescript
// backend/src/modules/asl/services/literatureService.ts
import { storage } from '@/common/storage'
import { logger } from '@/common/logging'
import { jobQueue } from '@/common/jobs'
import { cache } from '@/common/cache'
import { prisma } from '@/config/database'
export class LiteratureService {
async uploadPDF(projectId: string, pdfBuffer: Buffer) {
// 1. 雿輻鍂撟喳蝱摮睃<E691AE><E79D83>滚𦛚
const key = `asl/projects/${projectId}/pdfs/${Date.now()}.pdf`
const url = await storage.upload(key, pdfBuffer)
// 2. 雿輻鍂撟喳蝱<E596B3><EFBFBD>蝟餌<E89D9F>
logger.info('PDF uploaded', { projectId, url })
// 3. 雿輻鍂撟喳蝱<E596B3>唳旿摨? const literature = await prisma.aslLiterature.create({
data: { projectId, pdfUrl: url, pdfFileSize: pdfBuffer.length }
})
// 4. 雿輻鍂撟喳蝱蝻枏<E89DBB>
await cache.set(`literature:${literature.id}`, literature, 3600)
return literature
}
async startScreening(projectId: string, literatureIds: string[]) {
// 5. 雿輻鍂撟喳蝱撘<E89DB1>郊隞餃𦛚嚗<F0A69B9A><EFBFBD>園𡢿隞餃𦛚敹<F0A69B9A>◆撘<E29786>郊嚗? const job = await jobQueue.push('asl:screening', {
projectId,
literatureIds
})
logger.info('Screening job created', { jobId: job.id })
return { jobId: job.id } // 蝡见朖餈𥪜<E9A488>
}
}
```
---
### <20>?<3F>躰秤蝷箔<E89DB7>嚗𡁻<E59A97>憭滚<E686AD><E6BB9A>啣像<E595A3><EFBFBD><E59597>?
```typescript
// <20>?<3F>躰秤嚗𡁜銁銝𡁜𦛚璅<E79285>銝剛䌊撌勗<E6928C><E58B97><EFBFBD><E595A3>?// backend/src/modules/asl/storage/LocalStorage.ts <20>?銝滚<E98A9D>霂亙<E99C82><E4BA99><EFBFBD>
import fs from 'fs'
export class LocalStorage {
async upload(file: Buffer) {
await fs.writeFile('./uploads/file.pdf', file) // <20>?<3F><EFBFBD>摰䂿緵
return '/uploads/file.pdf'
}
}
// <20>?<3F>躰秤嚗𡁜銁銝𡁜𦛚璅<E79285>銝剛䌊撌勗<E6928C><E58B97>唳𠯫敹?// backend/src/modules/asl/logger/logger.ts <20>?銝滚<E98A9D>霂亙<E99C82><E4BA99><EFBFBD>
import winston from 'winston'
export const logger = winston.createLogger({...}) // <20>?<3F><EFBFBD>摰䂿緵
// <20>?<3F>躰秤嚗𡁏<E59A97>甈⊥鰵撱箸㺭<E7AEB8><EFBFBD>餈墧𦻖
import { PrismaClient } from '@prisma/client'
export function getUser() {
const prisma = new PrismaClient() // <20>?餈墧𦻖瘜<F0A6BB96><E7989C>
return prisma.user.findMany()
}
```
**銝箔<E98A9D><EFBFBD><E98A8B>霂荔<E99C82>**
- <20>?<3F><EFBFBD><EFBFBD><E99A9E>嚗屸𠗕隞亦輕<E4BAA6>?- <20>?銝滚<E98A9D><E79285>摰䂿緵銝滢<E98A9D><E6BBA2>?- <20>?<3F><EFBFBD>蝏煺<E89D8F><E785BA><EFBFBD><EFBFBD><EFBFBD><EFBFBD>𧋦<EFBFBD>?鈭𤑳垢嚗?- <20>?瘚芾晶撘<E699B6><E69298>烐𧒄<E78390>?- <20>?鈭𤑳垢<F0A491B3>函蔡隡𡁜仃韐伐<E99F90>Serverless<73>𣂼<EFBFBD>嚗?
---
### <20><>辣銝𠹺<E98A9D><EFBFBD><E996AB>
```typescript
// <20>?甇<>嚗帋蝙<E5B88B><EFBFBD><E585B8>冽𡂝鞊<E99E8A>
const url = await storage.upload('asl/pdf/123.pdf', buffer)
// <20>?<3F>躰秤嚗𡁶凒<F0A181B6><EFBFBD>雿𨀣<E99BBF>隞嗥頂蝏?fs.writeFileSync('./uploads/123.pdf', buffer) // Serverless摰孵膥<E5ADB5>滚鍳隡帋腺憭?
// <20>?<3F>躰秤嚗𡁶蝻𣇉<E89DBB>摮睃<E691AE>頝臬<E9A09D>
const filePath = 'D:/uploads/123.pdf' // Windows頝臬<E9A09D>嚗𥴰inux<75><EFBFBD>餈鞱<E9A488>
```
---
### 撘<>郊隞餃𦛚閫<F0A69B9A><E996AB>
```typescript
// <20>?甇<>嚗𡁻鵭<F0A181BB>園𡢿隞餃𦛚嚗?10蝘𡜐<E89D98><EFBFBD>◆撘<E29786>郊憭<E9838A><E686AD>
app.post('/screening/start', async (req, res) => {
const job = await jobQueue.push('asl:screening', data)
res.send({ jobId: job.id }) // 蝡见朖餈𥪜<E9A488>嚗䔶<E59A97>蝑匧<E89D91>摰峕<E691B0>
})
// <20>亥砭餈𥕦漲
app.get('/screening/jobs/:id', async (req, res) => {
const job = await jobQueue.getJob(req.params.id)
res.send({ status: job.status, progress: job.progress })
})
// <20>?<3F>躰秤嚗𡁜<E59A97>甇亦<E79487><EFBFBD><EFBFBD>園𡢿隞餃𦛚
app.post('/screening/start', async (req, res) => {
const results = await processAllLiteratures(data) // <20><EFBFBD><E888AA><EFBFBD>閬?0<><30><EFBFBD>
res.send({ results }) // Serverless 30蝘坿<E89D98><E59DBF><EFBFBD>
})
```
---
### <20>唳旿摨栞<E691A8><E6A09E><EFBFBD><E4BAA5>?
```typescript
// <20>?甇<>嚗帋蝙<E5B88B><EFBFBD><EFBFBD>Prisma摰硺<E691B0>
import { prisma } from '@/config/database'
export async function getUsers() {
return await prisma.user.findMany()
}
// <20>?<3F>躰秤嚗𡁏<E59A97>甈⊥鰵撱箏<E692B1>靘?export async function getUsers() {
const prisma = new PrismaClient() // 餈墧𦻖<E5A2A7><EFBFBD>堒偷嚗? return await prisma.user.findMany()
}
```
---
### <20><EFBFBD><EFBFBD><E996AB>
```typescript
// <20>?甇<>嚗帋蝙<E5B88B>典像<E585B8>唳𠯫敹㛖頂蝏?import { logger } from '@/common/logging'
logger.info('Operation successful', { userId, action: 'upload' })
logger.error('Operation failed', { error: err.message, userId })
// <20>?<3F>躰秤嚗帋蝙<E5B88B>牢onsole.log
console.log('Operation successful') // <20><EFBFBD><E4ADBE><EFBFBD><EFBFBD><EFBFBD>嚗屸𠗕隞交䰻霂?
// <20>?<3F>躰秤嚗𡁜<E59A97><F0A1819C>砍𧑐<E7A08D><EFBFBD><E4BA99><EFBFBD>
fs.appendFileSync('./app.log', 'Operation successful') // Serverless銝齿𣈲<E9BDBF>?```
---
## <EFBFBD>𡁶<EFBFBD><EFBFBD>
### <EFBFBD><EFBFBD>
- <EFBFBD>?ESLint<EFBFBD>rettier蝏煺<EFBFBD><EFBFBD><EFBFBD>
- <EFBFBD>?<EFBFBD>?<EFBFBD>?- <EFBFBD>?<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>?`'`
- <EFBFBD>?<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>?- <EFBFBD>?<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>100
- <EFBFBD>?<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
### <EFBFBD><EFBFBD><EFBFBD><EFBFBD>
- <EFBFBD>?<EFBFBD><EFBFBD><EFBFBD><EFBFBD>??- <EFBFBD>?<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
- <EFBFBD>?barrel exports嚗ǎndex.ts嚗?- <EFBFBD>?<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>𣬚𤌍?
```
src/
<EFBFBD><EFBFBD><EFBFBD><EFBFBD> components/
<EFBFBD>? <20><EFBFBD><E98EBF><EFBFBD> Button/
<EFBFBD>? <20>? <20><EFBFBD><E98EBF><EFBFBD> Button.tsx
<EFBFBD>? <20>? <20><EFBFBD><E98EBF><EFBFBD> Button.test.tsx
<EFBFBD>? <20>? <20><EFBFBD><E98EBF><EFBFBD> Button.styles.ts
<EFBFBD>? <20>? <20><EFBFBD><E5A999><EFBFBD> index.ts # export { Button } from './Button'
```
### 隞<><E99A9E>瘜券<E7989C>
- <20>?憭齿<E686AD><E9BDBF><EFBFBD><EFBFBD>◆瘜券<E7989C>
- <20>?<3F><EFBFBD>API敹<49>◆瘜券<E7989C>
- <20>?<3F><EFBFBD><E8B8B9>删鍂瘜券<E7989C>
- <20>?雿輻鍂JSDoc<6F><EFBFBD>
---
## TypeScript閫<74><E996AB>
### 蝐餃<E89D90>摰帋<E691B0>
**<2A>?<3F><EFBFBD>嚗?*
```typescript
// 雿輻鍂interface摰帋<E691B0>撖寡情蝏𤘪<E89D8F>
interface User {
id: string
email: string
name?: string
}
// 雿輻鍂type摰帋<E691B0><E5B88B>𥪜<EFBFBD>蝐餃<E89D90>
type Status = 'active' | 'inactive' | 'suspended'
// 雿輻鍂enum摰帋<E691B0>撣賊<E692A3><E8B38A><EFBFBD><EFBFBD>
enum UserRole {
USER = 'user',
ADMIN = 'admin',
}
```
**<EFBFBD>?<3F><EFBFBD>嚗?*
```typescript
// 銝滩<E98A9D>雿輻鍂any
function process(data: any) { // <20>? // ...
}
// 摨磰砲<E7A3B0>𡒊蝐餃<E89D90>
function process(data: ProcessData) { // <20>? // ...
}
```
### 蝐餃<E89D90>撖澆<E69296>撖澆枂
```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'
```
### 銝交聢璅<E79285>
```json
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true
}
}
```
---
## React閫<74><E996AB>
### 蝏<>辣摰帋<E691B0>
**<EFBFBD>?<3F><EFBFBD>嚗𡁜遆<F0A1819C><EFBFBD>隞?+ 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>
)
}
```
**<EFBFBD>?<3F><EFBFBD>嚗𡁶掩蝏<E68EA9>辣**
```tsx
// <20><EFBFBD><E697A5>厩鸌畾𢠃<E795BE><EFBFBD><E79899><EFBFBD><EFBFBD>銝滢蝙<E6BBA2>函掩蝏<E68EA9>
class Button extends React.Component { ... } // <20>?```
### Hooks閫<EFBFBD><EFBFBD>
**<EFBFBD>?<EFBFBD><EFBFBD><EFBFBD>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>
)
}
```
### 蝏<>辣蝏<E8BEA3><E89D8F>
```tsx
// <20>?<3F><EFBFBD><E588BB><EFBFBD><EFBFBD>隞嗥<E99A9E><E597A5>?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摰帋<E691B0>
}
export function Component({ prop1, prop2 }: ComponentProps) {
// 1. Hooks
const navigate = useNavigate()
const [state, setState] = useState()
const { data } = useCustomHook()
// 2. 瘣曄<E798A3><E69B84><EFBFBD><E59786><EFBFBD>useMemo嚗? const computedValue = useMemo(() => {
return heavyComputation(data)
}, [data])
// 3. 鈭衤辣憭<E8BEA3><E686AD>嚗óseCallback嚗? const handleClick = useCallback(() => {
// 憭<><E686AD><EFBFBD><EFBFBD>
}, [])
// 4. Effects
useEffect(() => {
// <20><EFBFBD><E887AD>? }, [])
// 5. <20><EFBFBD>餈𥪜<E9A488>嚗𡿨oading/Error嚗? if (!data) return <Loading />
// 6. 皜脫<E79A9C>
return (
<div>
{/* JSX */}
</div>
)
}
```
### <20>∩辣皜脫<E79A9C>
**<EFBFBD>?<3F><EFBFBD>嚗?*
```tsx
// 蝞<><E89D9E>閙辺隞塚<E99A9E>雿輻鍂 &&
{isLoggedIn && <UserMenu />}
// if-else嚗帋蝙<E5B88B><EFBFBD><E585B6><EFBFBD><EFBFBD>蝞㛖泵
{isLoggedIn ? <UserMenu /> : <LoginButton />}
// 憭𡁏辺隞塚<E99A9E><E5A19A>𣂼<EFBFBD>銝箏遆<E7AE8F><EFBFBD><EFBFBD>
function renderContent() {
if (loading) return <Loading />
if (error) return <Error />
if (data.length === 0) return <Empty />
return <DataList data={data} />
}
return <div>{renderContent()}</div>
```
**<EFBFBD>?<3F><EFBFBD>嚗?*
```tsx
// <20><EFBFBD>憭齿<E686AD><E9BDBF><EFBFBD><EFBFBD>憟𦯀<E6869F><F0A6AF80><EFBFBD><EFBFBD>蝞㛖泵
{condition1 ? (
condition2 ? <A /> : <B />
) : (
condition3 ? <C /> : <D />
)} // <20>?<3F>曆誑<E69B86><E8AA91>
```
---
## Node.js<6A>𡒊垢閫<E59EA2><E996AB>
### <20><>辣蝏<E8BEA3><E89D8F>
```
backend/src/
<EFBFBD><EFBFBD><EFBFBD><EFBFBD> routes/ # 頝舐眏摰帋<E691B0>
<EFBFBD>? <20><EFBFBD><E98EBF><EFBFBD> auth.routes.ts
<EFBFBD>? <20><EFBFBD><E5A999><EFBFBD> project.routes.ts
<EFBFBD><EFBFBD><EFBFBD><EFBFBD> services/ # 銝𡁜𦛚<F0A1819C><EFBFBD>
<EFBFBD>? <20><EFBFBD><E98EBF><EFBFBD> auth.service.ts
<EFBFBD>? <20><EFBFBD><E5A999><EFBFBD> project.service.ts
<EFBFBD><EFBFBD><EFBFBD><EFBFBD> controllers/ # <20><EFBFBD><E689B9><EFBFBD><E58981><EFBFBD><EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD> models/ # Prisma璅<E79285>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD> utils/ # 撌亙<E6928C><E4BA99>賣㺭
<EFBFBD><EFBFBD><EFBFBD><EFBFBD> config/ # <20>滨蔭<E6BBA8>㰘蝸
<EFBFBD><EFBFBD><EFBFBD><EFBFBD> types/ # 蝐餃<E89D90>摰帋<E691B0>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD> server.ts # <20>亙藁<E4BA99><E89781>
```
### 頝舐眏摰帋<E691B0>
```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) {
// <20><EFBFBD>憿寧𤌍<E5AFA7>𡑒”
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,
})
}
)
// <20>𥕦遣憿寧𤌍
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 {
/**
* <20><EFBFBD><E79195><EFBFBD><E586BD><EFBFBD><EFBFBD><EFBFBD>銵? */
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,
},
}
}
/**
* <20>𥕦遣憿寧𤌍
*/
async createProject(userId: string, data: CreateProjectDto) {
return prisma.project.create({
data: {
userId,
name: data.name,
description: data.description,
},
})
}
/**
* <20>湔鰵憿寧𤌍
*/
async updateProject(
userId: string,
projectId: string,
data: UpdateProjectDto
) {
// 撉諹<E69289><E8ABB9><EFBFBD><EFBFBD>
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,
})
}
/**
* <20>𣳇膄憿寧𤌍
*/
async deleteProject(userId: string, projectId: string) {
// 撉諹<E69289><E8ABB9><EFBFBD><EFBFBD>
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()
```
### <20>躰秤憭<E7A7A4><E686AD>
```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
}
```
### <20>躰秤憭<E7A7A4><E686AD>銝剝𡢿隞?
```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
) {
// 霈啣<E99C88><E595A3>躰秤
request.log.error(error)
// <20><EFBFBD>銋厰<E98A8B>霂? 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<6D>躰秤
if (error.name === 'PrismaClientKnownRequestError') {
// 憭<><E686AD>Prisma<6D><EFBFBD><E5ADB5>躰秤
return reply.code(400).send({
success: false,
error: {
code: 'DATABASE_ERROR',
message: 'Database operation failed',
},
timestamp: new Date().toISOString(),
})
}
// 暺䁅恕<E48185>躰秤
return reply.code(500).send({
success: false,
error: {
code: 'INTERNAL_ERROR',
message: 'Internal server error',
},
timestamp: new Date().toISOString(),
})
}
```
---
## <20><EFBFBD><EFBFBD><E996AB>
### <20><><EFBFBD><EFBFBD>
| 蝐餃<E89D90> | <20><EFBFBD><E8B3A2><EFBFBD> | 蝷箔<E89DB7> |
|------|---------|------|
| React蝏<74>辣 | PascalCase | `Button.tsx`, `ProjectList.tsx` |
| Hooks | camelCase + use<73><EFBFBD> | `useProjects.ts`, `useAuth.ts` |
| 撌亙<E6928C><E4BA99>賣㺭 | camelCase | `formatDate.ts`, `api.ts` |
| 蝐餃<E89D90>摰帋<E691B0> | camelCase + .types | `user.types.ts`, `api.types.ts` |
| 撣賊<E692A3> | camelCase + .constants | `routes.constants.ts` |
| 瘚贝<E7989A><E8B49D><EFBFBD>辣 | <20><EFBFBD><E5B395><EFBFBD>辣 + .test | `Button.test.tsx` |
### <20><EFBFBD><E3979B><EFBFBD>
```typescript
// <20>?<3F><EFBFBD>
const userName = 'John' // camelCase
const USER_ROLE = 'admin' // 撣賊<E692A3><E8B38A>沃PPER_SNAKE_CASE
const isLoading = false // 撣<><E692A3><EFBFBD>潛鍂is/has/can<61><EFBFBD>
const hasPermission = true
const canEdit = false
// <20>?<3F><EFBFBD>
const user_name = 'John' // 銝滨鍂snake_case
const loading = false // 撣<><E692A3><EFBFBD>潛撩撠魀s<E9AD80><EFBFBD>
const x = 10 // <20><EFBFBD>銋厩<E98A8B><E58EA9><EFBFBD><E3979B>?```
### <EFBFBD><EFBFBD><EFBFBD>
```typescript
// <20>?<3F><EFBFBD>
function getUser() { } // get: <20><EFBFBD><E79195>唳旿
function fetchProjects() { } // fetch: 撘<><EFBFBD><EFBFBD>
function createProject() { } // create: <20>𥕦遣
function updateProject() { } // update: <20>湔鰵
function deleteProject() { } // delete: <20>𣳇膄
function handleClick() { } // handle: 鈭衤辣憭<E8BEA3><E686AD>
function validateEmail() { } // validate: 撉諹<E69289>
function formatDate() { } // format: <20><EFBFBD><E6BE86>?
// <20>?<3F><EFBFBD>
function data() { } // 銝齿<E98A9D>璆𡁜<E79286><F0A1819C>?function doSomething() { } // 憭芣芋蝟?function process() { } // 銝齿<E98A9D>蝖?```
### <EFBFBD><EFBFBD><EFBFBD>
```typescript
// <20>?<3F><EFBFBD>
<Button /> // <20><EFBFBD><EFBFBD>
<UserProfile /> // 銝𡁜𦛚蝏<F0A69B9A>
<ProjectList /> // <20>𡑒”蝏<E2809D>
<CreateProjectModal /> // 撘寧<E69298><EFBFBD>
// <20>?<3F><EFBFBD>
<button /> // 銝滨鍂撠誩<E692A0>
<user_profile /> // 銝滨鍂snake_case
<ListProjects /> // <20><EFBFBD>銝滩<E98A9D><E6BBA9><EFBFBD>
```
---
## 瘜券<E7989C><EFBFBD><E996AB>
### JSDoc瘜券<E7989C>
```typescript
/**
* <20>𥕦遣<F0A595A6>圈★<E59C88>? * @param userId - <20><EFBFBD>ID
* @param data - 憿寧𤌍<E5AFA7>唳旿
* @returns <20>𥕦遣<F0A595A6><E981A3><EFBFBD>桀笆鞊? * @throws {ValidationError} 敶𤘪㺭<F0A498AA><EFBFBD><EFBFBD>仃韐交𧒄
*/
async function createProject(
userId: string,
data: CreateProjectDto
): Promise<Project> {
// 摰䂿緵...
}
```
### 隞<><E99A9E>瘜券<E7989C>
```typescript
// <20>?憟賜<E6869F>瘜券<E7989C>嚗朞圾<E69C9E>𠹺蛹隞<E89BB9>銋?// 雿輻鍂setTimeout<75><EFBFBD><E8B8B9><EFBFBD>UI皜脫<E79A9C>
setTimeout(() => {
processLargeData()
}, 0)
// 蝑匧<E89D91>Dify憭<79><E686AD><EFBFBD><EFBFBD>﹝嚗峕<E59A97>憭𡁻<E686AD>霂?0甈?for (let i = 0; i < 10; i++) {
const status = await checkStatus()
if (status === 'completed') break
await sleep(2000)
}
// <20>?<3F><EFBFBD>瘜券<E7989C>嚗𡁻<E59A97>憭滢誨<E6BBA2>?// 霈曄蔭loading銝演rue
setLoading(true)
// 靚<>鍂API
await api.getData()
```
---
## Git<69>𣂷漱閫<E6BCB1><E996AB>
### Commit Message<67><EFBFBD>
```
<type>(<scope>): <subject>
<body>
<footer>
```
### Type蝐餃<E89D90>
| 蝐餃<E89D90> | 霂湔<E99C82> |
|------|------|
| feat | <20><EFBFBD><E595A3>?|
| fix | Bug靽桀<E99DBD> |
| docs | <20><><EFBFBD>湔鰵 |
| style | 隞<><E99A9E><EFBFBD><EFBFBD><EFBFBD><E59A97>敶勗<E695B6><E58B97><EFBFBD>嚗?|
| refactor | <20>齿<EFBFBD> |
| perf | <20><EFBFBD>隡睃<E99AA1> |
| test | 瘚贝<E7989A><E8B49D><EFBFBD> |
| chore | <20><>遣/撌亙<E6928C><E4BA99>睃𢆡 |
### 蝷箔<E89DB7>
```bash
# 憟賜<E6869F><E8B39C>𣂷漱
git commit -m "feat(auth): 摰䂿緵<E482BF><EFBFBD><E586BD><EFBFBD><E9A483><EFBFBD>"
git commit -m "fix(project): 靽桀<E99DBD>憿寧𤌍<E5AFA7>𣳇膄<F0A3B387><E88684><EFBFBD><EFBFBD><EFBFBD>"
git commit -m "docs(api): <20>湔鰵API<50><49>﹝"
git commit -m "refactor(chat): 隡睃<E99AA1><EFBFBD><E798A8><EFBFBD>辣蝏𤘪<E89D8F>"
# 銝滚末<E6BB9A><E69CAB><EFBFBD>鈭?git commit -m "update" # <20>?憭芣芋蝟?git commit -m "fix bug" # <20>?瘝⊥<E7989D>霂湔<E99C82><E6B994><EFBFBD>銋Ê̄ug
git commit -m "摰峕<E691B0><E5B395><EFBFBD>" # <20>?瘝⊥<E7989D>霂湔<E99C82><E6B994><EFBFBD><EFBFBD><E98A8B><EFBFBD>?```
---
## ESLint<6E>滨蔭
```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<65>滨蔭
```json
{
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 100,
"arrowParens": "avoid"
}
```
---
## 隞<><E99A9E>Review璉<77><E79289><EFBFBD><E4BAA4>?
### <20><EFBFBD>
- [ ] <20><EFBFBD><E8A098>臬炏摰峕㟲摰䂿緵
- [ ] <20>臬炏<E887AC><EFBFBD>瞍讐<E79E8D>颲寧<E9A2B2><E5AFA7><EFBFBD><EFBFBD>
- [ ] <20>躰秤憭<E7A7A4><E686AD><EFBFBD>臬炏摰<E7828F><E691B0>
### 隞<><E99A9E>韐券<E99F90>
- [ ]<><E99A9E><EFBFBD>臬炏<E887AC>栞粉<E6A09E><EFBFBD>閫?- [ ] <20>臬炏<E887AC><EFBFBD>憭滢誨<E6BBA2>?- [ ] <20>賣㺭<E8B3A3>臬炏餈<E7828F>鵭嚗<E9B5AD>遣霈?50銵䕘<E98AB5>
- [ ] <20>臬炏<E887AC><EFBFBD><E89084><EFBFBD><EFBFBD><E996AB>
### <20><EFBFBD>
- [ ] <20>臬炏<E887AC><EFBFBD><EFBFBD><E689AF><EFBFBD>
- [ ] <20>臬炏<E887AC><EFBFBD><EFBFBD><E695B9><EFBFBD><EFBFBD><EFBFBD>皜脫<E79A9C>
- [ ] <20>唳旿摨𤘪䰻霂<EFBCB8><EFBFBD><E899AB>?
### 摰匧<E691B0>
- [ ] <20>臬炏<E887AC>农QL瘜典<E7989C>憌𡡞埯
- [ ] <20>臬炏<E887AC>华SS憌𡡞埯
- [ ] <20><><EFBFBD>撉諹<E69289><E8ABB9>臬炏摰<E7828F><E691B0>
### 瘚贝<E7989A>
- [ ] <20>臬炏<E887AC><EFBFBD><E58CA7><EFBFBD><EFBFBD>霂?- [ ] 瘚贝<E7989A><EFBFBD><E996AC><EFBFBD><EFBFBD><EFBFBD>西雲憭?
---
**<EFBFBD><EFBFBD>﹝蝏湔擪嚗?* 閫<><E996AB><EFBFBD>湔鰵<E6B994><E9B0B5><EFBFBD>峕郊甇斗<E79487>獢?
**<EFBFBD><EFBFBD><EFBFBD>擧凒<EFBFBD><EFBFBD>** 2025-10-10
**蝏湔擪<E6B994><E693AA><EFBFBD>** <20><><EFBFBD><EFBFBD><EFBFBD>