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%)
22 KiB
隞<EFBFBD><EFBFBD>閫<EFBFBD><EFBFBD>
*<EFBFBD><EFBFBD>𧋦嚗? v1.0
*<EFBFBD>𥕦遣<EFBFBD>交<EFBFBD>嚗? 2025-10-10
*<EFBFBD><EFBFBD>鍂<EFBFBD><EFBFBD>凒嚗? <20>滨垢嚗㇌eact/TypeScript嚗? <20>𡒊垢嚗𠃊ode.js/TypeScript嚗?
<EFBFBD><EFBFBD> <20>桀<EFBFBD>
- <EFBFBD>𡁶鍂閫<EFBFBD><EFBFBD>
- TypeScript閫<EFBFBD><EFBFBD>
- React閫<EFBFBD><EFBFBD>
- Node.js<6A>𡒊垢閫<E59EA2><E996AB>
- <EFBFBD>賢<EFBFBD>閫<EFBFBD><EFBFBD>
- 瘜券<EFBFBD>閫<EFBFBD><EFBFBD>
- Git<EFBFBD>𣂷漱閫<EFBFBD><EFBFBD>
<EFBFBD><EFBFBD> 撟喳蝱<E596B3>賢<EFBFBD>雿輻鍂閫<E98D82><E996AB>嚗?025-11-16 <20>啣<EFBFBD>嚗?
潃?<3F>滩<EFBFBD><E6BBA9>鞟內嚗𡁜像<EFBFBD>啣歇<EFBFBD>𣂷<EFBFBD>摰峕㟲<EFBFBD><EFBFBD>抅蝖<EFBFBD>霈暹鴌<EFBFBD>滚𦛚
**霂衣<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)
敹<EFBFBD>◆憭滨鍂<EFBFBD><EFBFBD>像<EFBFBD>唳<EFBFBD><EFBFBD>?
*銝𡁜𦛚璅∪<EFBFBD>嚗㇁SL/AIA/PKB/DC蝑㚁<E89D91>蝳<EFBFBD>迫<EFBFBD>滚<EFBFBD>摰䂿緵隞乩<E99A9E><E4B9A9>蠘<EFBFBD>嚗?
| <EFBFBD>滚𦛚 | 撖澆<EFBFBD><EFBFBD>孵<EFBFBD> | <EFBFBD>券<EFBFBD>? |
|---|---|---|
| 摮睃<EFBFBD><EFBFBD>滚𦛚 | import { storage } from '@/common/storage' |
<EFBFBD><EFBFBD>辣銝𠹺<EFBFBD>銝贝蝸 |
| <EFBFBD>亙<EFBFBD>蝟餌<EFBFBD> | import { logger } from '@/common/logging' |
<EFBFBD><EFBFBD><EFBFBD><EFBFBD>𡝗𠯫敹? |
| 撘<EFBFBD>郊隞餃𦛚 | import { jobQueue } from '@/common/jobs' |
<EFBFBD>踵𧒄<EFBFBD>港遙<EFBFBD>? |
| 蝻枏<EFBFBD><EFBFBD>滚𦛚 | import { cache } from '@/common/cache' |
<EFBFBD><EFBFBD><EFBFBD>撘讐<EFBFBD>摮? |
| *<EFBFBD>唳旿摨? | import { prisma } from '@/config/database' |
<EFBFBD>唳旿摨𤘪<EFBFBD>雿? |
| LLM<EFBFBD>賢<EFBFBD> | import { LLMFactory } from '@/common/llm' |
LLM靚<EFBFBD>鍂 |
<EFBFBD>?甇<>&蝷箔<E89DB7>嚗帋蝙<E5B88B>典像<E585B8>唳<EFBFBD><E594B3>?
// 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>
}
}
<EFBFBD>?<3F>躰秤蝷箔<E89DB7>嚗𡁻<E59A97>憭滚<E686AD><E6BB9A>啣像<E595A3>啗<EFBFBD><E59597>?
// <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()
}
銝箔<EFBFBD>銋<EFBFBD><EFBFBD>霂荔<EFBFBD>
- <EFBFBD>?<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>嚗?
<EFBFBD><EFBFBD>辣銝𠹺<EFBFBD>閫<EFBFBD><EFBFBD>
// <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>
撘<EFBFBD>郊隞餃𦛚閫<EFBFBD><EFBFBD>
// <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>
})
<EFBFBD>唳旿摨栞<EFBFBD><EFBFBD>亥<EFBFBD><EFBFBD>?
// <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()
}
<EFBFBD>亙<EFBFBD>閫<EFBFBD><EFBFBD>
// <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/ <0A>鎿<EFBFBD><E98EBF><EFBFBD> components/ <0A>? <20>鎿<EFBFBD><E98EBF><EFBFBD> Button/ <0A>? <20>? <20>鎿<EFBFBD><E98EBF><EFBFBD> Button.tsx <0A>? <20>? <20>鎿<EFBFBD><E98EBF><EFBFBD> Button.test.tsx <0A>? <20>? <20>鎿<EFBFBD><E98EBF><EFBFBD> Button.styles.ts <0A>? <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>嚗?
// 銝滩<E98A9D>雿輻鍂any
function process(data: any) { // <20>? // ...
}
// 摨磰砲<E7A3B0>𡒊&蝐餃<E89D90>
function process(data: ProcessData) { // <20>? // ...
}
蝐餃<EFBFBD>撖澆<EFBFBD>撖澆枂
// 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'
銝交聢璅∪<EFBFBD>
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true
}
}
React閫<EFBFBD><EFBFBD>
蝏<EFBFBD>辣摰帋<EFBFBD>
<EFBFBD>?<3F>刻<EFBFBD>嚗𡁜遆<F0A1819C>啁<EFBFBD>隞?+ Hooks
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>辣
// <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>
)
}
蝏<EFBFBD>辣蝏<EFBFBD><EFBFBD>
// <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>
)
}
<EFBFBD>∩辣皜脫<EFBFBD>
*<EFBFBD>?<3F>刻<EFBFBD>嚗?
// 蝞<><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>嚗?
// <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>
<EFBFBD><EFBFBD>辣蝏<EFBFBD><EFBFBD>
backend/src/
<0A>鎿<EFBFBD><E98EBF><EFBFBD> routes/ # 頝舐眏摰帋<E691B0>
<0A>? <20>鎿<EFBFBD><E98EBF><EFBFBD> auth.routes.ts
<0A>? <20>婙<EFBFBD><E5A999><EFBFBD> project.routes.ts
<0A>鎿<EFBFBD><E98EBF><EFBFBD> services/ # 銝𡁜𦛚<F0A1819C>餉<EFBFBD>
<0A>? <20>鎿<EFBFBD><E98EBF><EFBFBD> auth.service.ts
<0A>? <20>婙<EFBFBD><E5A999><EFBFBD> project.service.ts
<0A>鎿<EFBFBD><E98EBF><EFBFBD> controllers/ # <20>批<EFBFBD><E689B9>剁<EFBFBD><E58981>舫<EFBFBD>㚁<EFBFBD>
<0A>鎿<EFBFBD><E98EBF><EFBFBD> models/ # Prisma璅∪<E79285>
<0A>鎿<EFBFBD><E98EBF><EFBFBD> utils/ # 撌亙<E6928C><E4BA99>賣㺭
<0A>鎿<EFBFBD><E98EBF><EFBFBD> config/ # <20>滨蔭<E6BBA8>㰘蝸
<0A>鎿<EFBFBD><E98EBF><EFBFBD> types/ # 蝐餃<E89D90>摰帋<E691B0>
<0A>婙<EFBFBD><E5A999><EFBFBD> server.ts # <20>亙藁<E4BA99><E89781>辣
頝舐眏摰帋<EFBFBD>
// 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撅?
// 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()
<EFBFBD>躰秤憭<EFBFBD><EFBFBD>
// 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
}
<EFBFBD>躰秤憭<EFBFBD><EFBFBD>銝剝𡢿隞?
// 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(),
})
}
<EFBFBD>賢<EFBFBD>閫<EFBFBD><EFBFBD>
<EFBFBD><EFBFBD>辣<EFBFBD>賢<EFBFBD>
| 蝐餃<EFBFBD> | <EFBFBD>賢<EFBFBD><EFBFBD>孵<EFBFBD> | 蝷箔<EFBFBD> |
|---|---|---|
| React蝏<EFBFBD>辣 | PascalCase | Button.tsx, ProjectList.tsx |
| Hooks | camelCase + use<73>滨<EFBFBD> | useProjects.ts, useAuth.ts |
| 撌亙<EFBFBD><EFBFBD>賣㺭 | camelCase | formatDate.ts, api.ts |
| 蝐餃<EFBFBD>摰帋<EFBFBD> | camelCase + .types | user.types.ts, api.types.ts |
| 撣賊<EFBFBD> | camelCase + .constants | routes.constants.ts |
| 瘚贝<EFBFBD><EFBFBD><EFBFBD>辣 | <EFBFBD>峕<EFBFBD><EFBFBD><EFBFBD>辣 + .test | Button.test.tsx |
<EFBFBD>㗛<EFBFBD><EFBFBD>賢<EFBFBD>
// <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>
瘜券<EFBFBD>閫<EFBFBD><EFBFBD>
JSDoc瘜券<EFBFBD>
/**
* <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> {
// 摰䂿緵...
}
隞<EFBFBD><EFBFBD>瘜券<EFBFBD>
// <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<EFBFBD>𣂷漱閫<EFBFBD><EFBFBD>
Commit Message<67>澆<EFBFBD>
<type>(<scope>): <subject>
<body>
<footer>
Type蝐餃<EFBFBD>
| 蝐餃<EFBFBD> | 霂湔<EFBFBD> |
|---|---|
| feat | <EFBFBD>啣<EFBFBD><EFBFBD>? |
| fix | Bug靽桀<EFBFBD> |
| docs | <EFBFBD><EFBFBD>﹝<EFBFBD>湔鰵 |
| style | 隞<EFBFBD><EFBFBD><EFBFBD>澆<EFBFBD>嚗<EFBFBD><EFBFBD>敶勗<EFBFBD><EFBFBD>蠘<EFBFBD>嚗? |
| refactor | <EFBFBD>齿<EFBFBD> |
| perf | <EFBFBD>扯<EFBFBD>隡睃<EFBFBD> |
| test | 瘚贝<EFBFBD><EFBFBD>詨<EFBFBD> |
| chore | <EFBFBD><EFBFBD>遣/撌亙<E6928C><E4BA99>睃𢆡 |
蝷箔<EFBFBD>
# 憟賜<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<EFBFBD>滨蔭
{
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 100,
"arrowParens": "avoid"
}
隞<EFBFBD><EFBFBD>Review璉<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>臬炏<EFBFBD>栞粉<EFBFBD>梶<EFBFBD>閫?- [ ] <20>臬炏<E887AC>厰<EFBFBD>憭滢誨<E6BBA2>?- [ ] <20>賣㺭<E8B3A3>臬炏餈<E7828F>鵭嚗<E9B5AD>遣霈?50銵䕘<E98AB5>
- <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>唳旿摨𤘪䰻霂X糓<EFBFBD>虫<EFBFBD><EFBFBD>?
摰匧<EFBFBD>
- <EFBFBD>臬炏<EFBFBD>农QL瘜典<EFBFBD>憌𡡞埯
- <EFBFBD>臬炏<EFBFBD>华SS憌𡡞埯
- <EFBFBD><EFBFBD><EFBFBD>撉諹<EFBFBD><EFBFBD>臬炏摰<EFBFBD><EFBFBD>
瘚贝<EFBFBD>
- <EFBFBD>臬炏<EFBFBD>匧<EFBFBD><EFBFBD><EFBFBD><EFBFBD>霂?- [ ] 瘚贝<E7989A>閬<EFBFBD><E996AC><EFBFBD><EFBFBD>糓<EFBFBD>西雲憭?
*<EFBFBD><EFBFBD>﹝蝏湔擪嚗? 閫<><E996AB><EFBFBD>湔鰵<E6B994><E9B0B5><EFBFBD>峕郊甇斗<E79487>獢?
<EFBFBD><EFBFBD><EFBFBD>擧凒<EFBFBD>堆<EFBFBD> 2025-10-10
蝏湔擪<EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD>航<EFBFBD>韐<EFBFBD>犖