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

22 KiB
Raw Blame History

<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>

  1. <EFBFBD>𡁶鍂閫<EFBFBD><EFBFBD>
  2. TypeScript閫<EFBFBD><EFBFBD>
  3. React閫<EFBFBD><EFBFBD>
  4. Node.js<6A>𡒊垢閫<E59EA2><E996AB>
  5. <EFBFBD><EFBFBD><EFBFBD><EFBFBD>
  6. 瘜券<EFBFBD><EFBFBD><EFBFBD>
  7. 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>唳旿摨𤘪䰻霂<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>