Files
AIclinicalresearch/docs/04-开发规范/08-云原生开发规范.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

648 lines
18 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.
# 鈭穃<E988AD><E7A983><EFBFBD><E7AC94>𤏸<EFBFBD><F0A48FB8>?
> **<2A><><EFBFBD><EFB99D>𧋦嚗?* V1.1
> **<2A>𥕦遣<F0A595A6><EFBFBD>嚗?* 2025-11-16
> **<2A><><EFBFBD>擧凒<E693A7><EFBFBD>** 2025-12-13 <20><> **Postgres-Only <20><EFBFBD><EFBFBD><E996AB><EFBFBD><EFBFBD>**
> **<2A><>鍂撖寡情嚗?* <20><><EFBFBD><EFBFBD><E58CA7>睲犖<E79DB2>?
> **撘箏<E69298><E7AE8F><EFBFBD>** <20>?敹<><EFBFBD><EFBFBD>
> **蝏湔擪<E6B994><E693AA><EFBFBD>** <20><EFBFBD><E59786><EFBFBD>
---
## <20><> <20><>﹝霂湔<E99C82>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD>銋劐<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>嚗𠄎erverless SAE + RDS + OSS嚗劐<E59A97><E58A90>?*隞<><E99A9E><EFBFBD><E996AB>**嚗峕<E59A97><E5B395><EFBFBD><E58A90>⊥芋<E28AA5><EFBFBD>ASL<53><4C>IA<49><41>KB蝑㚁<E89D91><EFBFBD><EFBFBD><EFBFBD><E89084>?
**<EFBFBD><EFBFBD><EFBFBD>園𡢿**嚗?0 <20><><EFBFBD>
**璉<><E79289>仿<EFBFBD><E4BBBF>?*嚗𡁏<E59A97>甈∩誨<E288A9><E8AAA8><EFBFBD>鈭文<E988AD>
---
## <20><> <20><EFBFBD><E8A9A8><EFBFBD>嚗𡁜<E59A97><F0A1819C>典像<E585B8><EFBFBD><E59597>?
> **潃?<3F><EFBFBD><E6BBA9>鞟內嚗?025-11-16 <20>湔鰵嚗?*嚗𡁜像<F0A1819C>啣歇<E595A3>𣂷<EFBFBD>摰峕㟲<E5B395><E39FB2>抅蝖<E68A85>霈暹鴌<E69AB9>滚𦛚
> **霂衣<E99C82><E8A1A3><EFBFBD>﹝**嚗靀撟喳蝱<E596B3><EFBFBD>霈暹鴌閫<E9B48C><E996AB>](../09-<2D><EFBFBD>摰墧鴌/04-撟喳蝱<E596B3><EFBFBD>霈暹鴌閫<E9B48C><E996AB>.md)
### 撟喳蝱撌脫<E6928C>靘𤤿<E99D98><F0A4A4BF>滚𦛚
**銝𡁜𦛚璅<E79285>嚗㇁SL/AIA/PKB/DC蝑㚁<E89D91>摨磰砲憭滨鍂隞乩<E99A9E>撟喳蝱<E596B3><EFBFBD>嚗𣬚<E59A97><E79487>憭滚<E686AD><E6BB9A><EFBFBD>**
| <20>滚𦛚 | 撖澆<E69296><E6BE86><EFBFBD> | <20><EFBFBD>?| <20><>﹝ |
|------|---------|------|------|
| **摮睃<E691AE><E79D83>滚𦛚** | `import { storage } from '@/common/storage'` | <20><>辣銝𠹺<E98A9D>銝贝蝸 | <20>?撟喳蝱蝥?|
| **<EFBFBD><EFBFBD>蝟餌<EFBFBD>** | `import { logger } from '@/common/logging'` | <20><><EFBFBD><EFBFBD>𡝗𠯫敹?| <20>?撟喳蝱蝥?|
| **撘<>郊隞餃𦛚** | `import { jobQueue } from '@/common/jobs'` | <20>踵𧒄<E8B8B5>港遙<E6B8AF>?| <20>?撟喳蝱蝥?|
| **蝻枏<E89DBB><E69E8F>滚𦛚** | `import { cache } from '@/common/cache'` | <20><><EFBFBD>撘讐<E69298>摮?| <20>?撟喳蝱蝥?|
| **<EFBFBD><EFBFBD> <20><EFBFBD>蝏凋<E89D8F>** | `import { CheckpointService } from '@/common/jobs'` | 隞餃𦛚<E9A483><EFBFBD>蝞∠<E89D9E> | <20>?撟喳蝱蝥改<E89DA5><E694B9><EFBFBD> |
| **<EFBFBD>唳旿摨?* | `import { prisma } from '@/config/database'` | <20>唳旿摨𤘪<E691A8>雿?| <20>?撟喳蝱蝥?|
| **LLM<4C><EFBFBD>** | `import { LLMFactory } from '@/common/llm'` | LLM靚<4D>鍂 | <20>?撟喳蝱蝥?|
### 蝷箔<E89DB7>嚗𡁏迤蝖桐蝙<E6A190>典像<E585B8><EFBFBD><E594B3>?
```typescript
// <20>?甇<>嚗𡁶凒<F0A181B6>亙紡<E4BA99>亙像<E4BA99><EFBFBD><E594B3>?import { storage } from '@/common/storage'
import { logger } from '@/common/logging'
import { jobQueue } from '@/common/jobs'
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 }
})
return literature
}
}
```
### <20>?<3F>躰秤嚗𡁻<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>
export class LocalStorage {
async upload(file: Buffer) {
await fs.writeFile('./uploads/file.pdf', file) // <20>?<3F><EFBFBD>摰䂿緵
}
}
// <20>?<3F>躰秤嚗𡁜銁銝𡁜𦛚璅<E79285>銝剛䌊撌勗<E6928C><E58B97>唳𠯫敹?// backend/src/modules/asl/logger/logger.ts <20>?銝滚<E98A9D>霂亙<E99C82><E4BA99><EFBFBD>
export const logger = winston.createLogger({...}) // <20>?<3F><EFBFBD>摰䂿緵
```
**<EFBFBD><EFBFBD>**嚗?- <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>?<3F><EFBFBD><E588BB>𡁏<EFBFBD>嚗㇄O嚗?
### 1. <20><>辣摮睃<E691AE> <20>?
```typescript
// <20>?甇<>嚗帋蝙<E5B88B><EFBFBD><E585B8>冽𡂝鞊<E99E8A>
import { storage } from '@/common/storage/StorageFactory'
export async function uploadFile(file: Buffer, filename: string) {
const key = `asl/pdfs/${Date.now()}-${filename}`
const url = await storage.upload(key, file)
return url
}
// <20>?甇<>嚗鍃xcel <20>湔𦻖隞𤾸<E99A9E>摮䁅圾<E48185>?import * as xlsx from 'xlsx'
export async function importExcel(buffer: Buffer) {
const workbook = xlsx.read(buffer, { type: 'buffer' }) // <20><><EFBFBD><EFBFBD><E996AB>
const data = xlsx.utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]])
return data
}
```
**<EFBFBD><EFBFBD>眏**嚗?- 摰孵膥<E5ADB5>滚鍳銝滢<E98A9D><EFBCB7><E4BB83>
- <20>砍𧑐撘<F0A79190><E69298><EFBFBD><E7A983>煺漣<E785BA><EFBFBD><EFBFBD><E99A9E><EFBFBD><E98A9D>?- <20>芸𢆡<E88AB8>寞旿<E5AF9E><EFBFBD><E887AC><EFBFBD><E3979B><EFBFBD>揢摮睃<E691AE><E79D83><EFBFBD>
---
### 2. <20>唳旿摨栞<E691A8><E6A09E>?<3F>?
```typescript
// <20>?甇<>嚗帋蝙<E5B88B><EFBFBD><EFBFBD> Prisma Client
import { prisma } from '@/config/database'
export async function createProject(data: any) {
return await prisma.aslScreeningProject.create({ data })
}
// <20>?甇<>嚗𡁏鸌<F0A1818F>𤩺<EFBFBD>雿靝蝙<E99D9D><EFBFBD><E585B6>?export async function importLiteratures(literatures: any[]) {
return await prisma.$transaction(async (tx) => {
return await tx.aslLiterature.createMany({ data: literatures })
})
}
```
**<EFBFBD><EFBFBD>眏**嚗?- <20><EFBFBD>摰硺<E691B0>憭滨鍂餈墧𦻖
- <20><EFBFBD>餈墧𦻖<E5A2A7><EFBFBD>堒偷
- 鈭见𦛚靽肽<E99DBD><E882BD>唳旿銝<E697BF><E98A9D><EFBFBD>?
---
### 3. <20><EFBFBD><E887AC><EFBFBD><E3979B>滨蔭 <20>?
```typescript
// <20>?甇<>嚗𡁶<E59A97><EFBFBD><E98A9D>滨蔭蝞∠<E89D9E>
// backend/src/config/env.ts
export const config = {
llm: {
apiKey: process.env.LLM_API_KEY!,
baseUrl: process.env.LLM_BASE_URL!,
},
oss: {
region: process.env.OSS_REGION!,
bucket: process.env.OSS_BUCKET!,
},
database: {
url: process.env.DATABASE_URL!,
}
}
// <20>?甇<>嚗帋蝙<E5B88B><EFBFBD>蝵桀笆鞊?import { config } from '@/config/env'
const apiKey = config.llm.apiKey
```
**<EFBFBD><EFBFBD>眏**嚗?- <20>滨蔭<E6BBA8><E894AD>葉蝞∠<E89D9E>
- 蝐餃<E89D90>摰匧<E691B0>
- 靘蹂<E99D98><E8B982><EFBFBD><EFBFBD><EFBFBD>
---
### 4. <20>踵𧒄<E8B8B5>港遙<E6B8AF><EFBFBD><E288AA>?<3F>?
```typescript
// <20>?甇<>嚗𡁜<E59A97>甇乩遙<E4B9A9>?+ 餈𥕦漲頧株砭
export async function startScreening(req, res) {
// 1. <20>𥕦遣隞餃𦛚霈啣<E99C88>
const task = await prisma.aslScreeningTask.create({
data: {
projectId: req.params.projectId,
status: 'pending',
totalItems: 100,
}
})
// 2. 蝡见朖餈𥪜<E9A488>隞餃𦛚ID
res.send({ success: true, taskId: task.id })
// 3. <20>𤾸蝱撘<E89DB1><EFBFBD><EFBFBD><EFBFBD><E59A97><EFBFBD><EFBFBD>霂瑟<E99C82>嚗? processScreeningAsync(task.id).catch(err => {
console.error('蝑偦<E89D91>劐遙<E58A90>仃韐?', err)
})
}
// <20>滨垢頧株砭餈𥕦漲
export async function getTaskProgress(req, res) {
const task = await prisma.aslScreeningTask.findUnique({
where: { id: req.params.taskId }
})
res.send({
status: task.status,
progress: Math.round((task.completedItems / task.totalItems) * 100)
})
}
```
**<EFBFBD><EFBFBD>眏**嚗?- <20><EFBFBD>霂瑟<E99C82><EFBFBD>𧒄嚗𠄎AE暺䁅恕30蝘𡜐<E89D98>
- <20><EFBFBD>雿㯄<E99BBF><E3AF84>游末
- <20><EFBFBD><E88880><EFBFBD>隞餃𦛚
**<EFBFBD>?摰峕㟲摰噼殿<E599BC><E6AEBF><EFBFBD>?*嚗?霂西<E99C82> [Postgres-Only撘<79>郊隞餃𦛚憭<F0A69B9A><E686AD><EFBFBD><EFBFBD><EFBFBD>](../02-<2D>𡁶鍂<F0A181B6><EFBFBD>撅?Postgres-Only撘<79>郊隞餃𦛚憭<F0A69B9A><E686AD><EFBFBD><EFBFBD><EFBFBD>.md)嚗<>抅鈭𥟠C Tool C摰峕㟲摰噼殿嚗?
---
### 5. <20><EFBFBD>颲枏枂 <20>?
```typescript
// <20>?甇<>嚗帋蝙<E5B88B>?logger 颲枏枂<E69E8F>?stdout
import pino from 'pino'
const logger = pino({
level: process.env.LOG_LEVEL || 'info'
})
export async function uploadFile(req, res) {
logger.info({
userId: req.userId,
filename: req.file.filename
}, 'File uploaded')
// ... 銝𠹺<E98A9D><F0A0B9BA><EFBFBD>
}
// <20>?甇<>嚗𡁶<E59A97><F0A181B6><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
logger.error({
error: err.message,
stack: err.stack,
userId: req.userId
}, 'Upload failed')
```
**<EFBFBD><EFBFBD>眏**嚗?- SAE <20>芸𢆡<E88AB8><F0A286A1><EFBFBD> stdout <20><EFBFBD>
- 蝏𤘪<E89D8F><F0A498AA>碶噶鈭擧䰻霂<E99C82><EFBCB7>?- <20><><EFBFBD><EFBFBD>嚗䔶<E59A97>隡帋腺憭?
---
### 6. <20>躰秤憭<E7A7A4><E686AD> <20>?
```typescript
// <20>?甇<>嚗𡁶<E59A97><EFBFBD><E98A9D>躰秤憭<E7A7A4><E686AD>
export async function uploadPdf(req, res) {
try {
const file = await req.file()
const buffer = await file.toBuffer()
const url = await storage.upload(key, buffer)
res.send({ success: true, url })
} catch (error) {
logger.error({ error }, 'PDF upload failed')
res.status(500).send({
success: false,
error: {
code: 'UPLOAD_FAILED',
message: '<27><>辣銝𠹺<E98A9D>憭梯揖嚗諹窈<E8ABB9><EFBFBD>'
}
})
}
}
```
**<EFBFBD><EFBFBD>眏**嚗?- <20><EFBFBD><E586BD><EFBFBD><E8A781>见末<E8A781>躰秤靽⊥<E99DBD>
- <20><EFBFBD>霈啣<E99C88>霂衣<E99C82><E8A1A3>躰秤
- 銝齿𠂔<E9BDBF><EFBFBD><E884A3><EFBFBD><E585B8>?
---
### 7. 銝湔𧒄<E6B994><F0A79284>辣憭<E8BEA3><E686AD> <20>?
```typescript
// <20>?甇<>嚗?tmp <20><EFBFBD><E6A180><EFBFBD>蝡见朖<E8A781>𣳇膄
import fs from 'fs/promises'
import path from 'path'
export async function extractPdfText(ossKey: string): Promise<string> {
const tmpPath = path.join('/tmp', `${Date.now()}.pdf`)
try {
// 1. 隞?OSS 銝贝蝸<E8B49D>?/tmp
await storage.download(ossKey, tmpPath)
// 2. <20>𣂼<EFBFBD><F0A382BC><EFBFBD>𧋦
const text = await extractWithNougat(tmpPath)
return text
} finally {
// 3. 蝡见朖<E8A781>𣳇膄銝湔𧒄<E6B994><F0A79284>辣嚗<E8BEA3><E59A97>霈箸<E99C88><E7AEB8>笔仃韐伐<E99F90>
try {
await fs.unlink(tmpPath)
} catch (err) {
logger.warn({ tmpPath }, 'Failed to delete temp file')
}
}
}
```
**<EFBFBD><EFBFBD>眏**嚗?- /tmp 摰寥<E691B0><E5AFA5><EFBFBD>嚗?12MB嚗?- 摰孵膥<E5ADB5>滚鍳隡𡁏<E99AA1>蝛?- <20><EFBFBD><EFBFBD><E89DA4><EFBFBD>䭾說
---
## <20><> Postgres-Only <20><EFBFBD><EFBFBD><E996AB>嚗?025-12-13 <20><EFBFBD>嚗?
### <20><EFBFBD><E8A9A8><EFBFBD>
**Platform-Only 璅<E79285>**嚗𡁏<E59A97><F0A1818F>劐遙<E58A90>∠恣<E288A0><E681A3><EFBFBD><EFBFBD><EFBFBD>摮睃<E691AE><E79D83>?`platform_schema.job.data`嚗䔶<EFBFBD><EFBFBD>∟”<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>∩縑<EFBFBD><EFBFBD>?
### 隞餃𦛚蝞∠<E89D9E><E288A0><EFBFBD>迤蝖桀<E89D96>瘜?
#### <20>?DO: 雿輻鍂 job.data 摮睃<E691AE>隞餃𦛚蝞∠<E89D9E>靽⊥<E99DBD>
```typescript
// <20>?甇<>嚗帋遙<E5B88B><EFBFBD><E28AA5><EFBFBD><EFBFBD><EFBFBD><EFBFBD>靽⊥<E99DBD>摮睃<E691AE><E79D83>?job.data
import { jobQueue } from '@/common/jobs';
import { CheckpointService } from '@/common/jobs/CheckpointService';
// <20><EFBFBD><E588B8><EFBFBD>⊥𧒄嚗<F0A79284><E59A97><EFBFBD><EFBFBD><E680A0>港縑<E6B8AF>?await jobQueue.push('asl:screening:batch', {
// 銝𡁜𦛚靽⊥<E99DBD>
taskId: 'xxx',
projectId: 'yyy',
literatureIds: [...],
// <20>?隞餃𦛚<E9A483><F0A69B9A><EFBFBD>靽⊥<E99DBD><EFBFBD><E59A97><EFBFBD>典銁 job.data嚗? batchIndex: 3,
totalBatches: 20,
startIndex: 150,
endIndex: 200,
// <20>?餈𥕦漲餈質葵
processedCount: 0,
successCount: 0,
failedCount: 0,
});
// Worker 銝凋蝙<E5878B>?CheckpointService
const checkpointService = new CheckpointService(prisma);
// 靽嘥<E99DBD><E598A5><EFBFBD><E58994>?job.data
await checkpointService.saveCheckpoint(job.id, {
currentBatchIndex: 5,
currentIndex: 250,
processedBatches: 5,
totalBatches: 20
});
// <20>㰘蝸<E3B098><EFBFBD>隞?job.data
const checkpoint = await checkpointService.loadCheckpoint(job.id);
if (checkpoint) {
resumeFrom = checkpoint.currentIndex;
}
```
#### <20>?DON'T: <20><EFBFBD><E585B6>∟”銝剖<E98A9D><E58996>其遙<E585B6>∠恣<E288A0><E681A3><EFBFBD>?
```typescript
// <20>?<3F>躰秤嚗𡁜銁銝𡁜𦛚銵函<E98AB5> Schema 銝剜溶<E5899C>牐遙<E78990>∠恣<E288A0><E681A3><EFBFBD>畾?model AslScreeningTask {
id String @id
projectId String
// <20>?銝滩<E98A9D>瘛餃<E7989B>餈嗘<E9A488>摮埈挾嚗? totalBatches Int // <20>?摨磰砲<E7A3B0>?job.data 銝? processedBatches Int // <20>?摨磰砲<E7A3B0>?job.data 銝? currentIndex Int // <20>?摨磰砲<E7A3B0>?job.data 銝? checkpointData Json? // <20>?摨磰砲<E7A3B0>?job.data 銝?}
// <20>?<3F>躰秤嚗朞䌊撌勗<E6928C><E58B97>唳鱏<E594B3><EFBFBD><E5AF9E>?class MyCheckpointService {
async save(taskId: string) {
await prisma.aslScreeningTask.update({
where: { id: taskId },
data: { checkpointData: {...} } // <20>?銝滩<E98A9D>餈蹱甅<E8B9B1>𡄯<EFBFBD>
});
}
}
```
**銝箔<E98A9D><EFBFBD><E98A8B>撖對<E69296>**
- <20>?瘥譍葵璅<E79285><E288AA><EFBFBD>瘛餃<E7989B><E9A483><EFBFBD><E8A9A8><EFBFBD><EFBFBD>畾蛛<E795BE><EFBFBD><E99A9E><EFBFBD><EFBFBD>嚗?- <20>?餈嘥<E9A488> DRY <20><EFBFBD>
- <20>?餈嘥<E9A488> 3 撅<><EFBFBD><E6B2B2><EFBFBD><EFBFBD>?- <20>?蝏湔擪<E6B994>圈𠗕嚗<F0A09795><EFBFBD><EFBFBD><EFBFBD><E9A489><EFBFBD><EFBFBD>㺿憭𡁜<E686AD>嚗?
### <20><EFBFBD><E7AE84><EFBFBD><EFBFBD>澆ế<E6BE86><EFBFBD><EFBFBD><E996AB>
#### <20>?DO: 摰䂿緵<E482BF><EFBFBD><E7AE84>峕芋撘誩<E69298><E8AAA9>?
```typescript
const QUEUE_THRESHOLD = 50; // <20><EFBFBD><E588BB><EFBFBD><EFBFBD>?
export async function startTask(items: any[]) {
const useQueue = items.length >= QUEUE_THRESHOLD;
if (useQueue) {
// <20><EFBFBD><E79285>嚗𡁜之隞餃𦛚嚗<F0A69B9A>竉50<35><EFBFBD>
const chunks = splitIntoChunks(items, 50);
for (const chunk of chunks) {
await jobQueue.push('task:batch', {...});
}
} else {
// <20>湔𦻖璅<E79285>嚗𡁜<E59A97>隞餃𦛚嚗?50<35><EFBFBD>
processDirectly(items); // 敹恍<E695B9><EFBFBD>摨? }
}
```
**銝箔<E98A9D><EFBFBD><E98A8B><EFBFBD><EFBFBD>嚗?*
- <20>?撠譍遙<E8AD8D><E288AA><EFBFBD>摨䈑<E691A8><E48891>𣳇<EFBFBD><F0A3B387>堒辣餈<E8BEA3><E9A488>
- <20>?憭找遙<E689BE><EFBFBD><E28ABF><EFBFBD><EFBFBD>𣈲<EFBFBD><F0A388B2><EFBFBD>寧賒隡𩤃<E99AA1>
- <20>?<3F><EFBFBD>銝𤾸虾<F0A4BEB8><EFBFBD>批像銵?
#### <20>?DON'T: <20><><EFBFBD>劐遙<E58A90><EFBFBD>韏圈<E99F8F><E59C88>?
```typescript
// <20>?<3F>躰秤嚗𡁜朖雿?<3F>∟扇敶蓥<E695B6>雿輻鍂<E8BCBB><EFBFBD>
export async function startTask(items: any[]) {
// <20>㰘捏憭𡁜<E686AD><F0A1819C>唳旿嚗屸<E59A97><E5B1B8><EFBFBD><E588B8><EFBFBD><EFBFBD><EFBFBD>
await jobQueue.push('task:batch', items); // <20>?撠譍遙<E8AD8D><EFBFBD><E288A9>匧辣餈?}
```
**銝箔<E98A9D><EFBFBD><E98A8B>撖對<E69296>**
- <20>?撠譍遙<E8AD8D><EFBFBD>摨娍<E691A8><EFBFBD><E59A97><EFBFBD><EFBFBD>頧株砭<E6A0AA><EFBFBD>嚗?- <20>?瘚芾晶<E88ABE><EFBFBD><EFBFBD><E99F8F>
- <20>?<3F><EFBFBD>雿㯄<E99BBF>撌?
### <20><><EFBFBD>潭綫<E6BDAD>𣂼<EFBFBD>?
| 隞餃𦛚蝐餃<E89D90> | <20><EFBFBD><E588BB><EFBFBD><EFBFBD>?| <20><>眏 |
|---------|---------|------|
| <20><>讃蝑偦<E89D91>?| 50蝭?| <20><EFBFBD>~7蝘𡜐<E89D98>50蝭㻬5<E3BBAC><35><EFBFBD> |
| <20>唳旿<E594B3>𣂼<EFBFBD> | 50<35>?| <20>閙辺~5-10蝘𡜐<E89D98>50<35>﹚5<EFB99A><35><EFBFBD> |
| 蝏蠘恣璅<E79285> | 30銝?| <20>蓥葵~10蝘𡜐<E89D98>30銝泠5<E6B3A0><35><EFBFBD> |
| 暺䁅恕 | 50<35>?| <20>𡁶鍂<F0A181B6><EFBFBD><E588BB>?|
---
## <20>?蝳<><EFBFBD>𡁏<EFBFBD>嚗㇄ON'T嚗?
### 1. <20>砍𧑐<E7A08D><F0A79190>辣摮睃<E691AE> <20>?
```typescript
// <20>?蝳<>迫嚗𡁏𧋦<F0A1818F><EFBFBD>隞嗥頂蝏笔<E89D8F><E7AC94>?import fs from 'fs'
export async function uploadFile(req, res) {
const file = await req.file()
const buffer = await file.toBuffer()
// <20>?<3F>躰秤嚗𡁜捆<F0A1819C><EFBFBD><E588B8>臭腺憭? fs.writeFileSync('./uploads/file.pdf', buffer)
res.send({ url: '/uploads/file.pdf' })
}
// <20>?蝳<>迫嚗帋<E59A97>韏𡝗𧋦<F0A19D97>啗楝敺?const uploadDir = '/var/app/uploads'
const filePath = path.join(uploadDir, filename)
```
**<EFBFBD><EFBFBD>**嚗?- 摰孵膥<E5ADB5>滚鍳<E6BB9A>𡝗<EFBFBD>摰孵<E691B0><E5ADB5><EFBFBD>辣銝
- 憭𡁜<E686AD>靘钅𡢿<E99285><EFBFBD><E4ADBE>曹澈<E69BB9><E6BE88>
-<><E89DA4>蝛粹𡢿<E7B2B9><EFBFBD>
**甇<><EFBFBD>𡁏<EFBFBD>**嚗帋蝙<E5B88B>?`storage.upload()` 銝𠹺<E98A9D><F0A0B9BA>?OSS
---
### 2. <20><><EFBFBD>蝻枏<E89DBB> <20>?
```typescript
// <20>?蝳<>迫嚗𡁜<E59A97>摮条<E691AE>摮矋<E691AE>憭𡁜<E686AD>靘衤<E99D98><E8A1A4>曹澈嚗?const cache = new Map<string, any>()
export async function getProject(id: string) {
// <20>?<3F>躰秤嚗𡁏<E59A97>摰孵<E691B0><E5ADB5><EFBFBD>摰硺<E691B0>霂颱<E99C82><E9A2B1><EFBFBD>摮? if (cache.has(id)) {
return cache.get(id)
}
const project = await prisma.aslScreeningProject.findUnique({ where: { id } })
cache.set(id, project)
return project
}
// <20>?蝳<>迫嚗𡁜<E59A97><EFBFBD><E69285><EFBFBD>摮睃<E691AE><E79D83><EFBFBD>?let taskStatus = {} // 憭𡁜<E686AD>靘衤<E99D98><E8A1A4>峕郊
```
**<EFBFBD><EFBFBD>**嚗?- 憭𡁜<E686AD>靘钅𡢿<E99285>唳旿銝滚<E98A9D>甇?- <20>拙捆<E68B99>𡒊<EFBFBD>摮睃仃<E79D83>?- <20><><EFBFBD><EFBFBD>删鍂銝滚虾<E6BB9A>?
**甇<><EFBFBD>𡁏<EFBFBD>**嚗帋蝙<E5B88B>?Redis <20>𡝗㺭<F0A19D97><EFBFBD>
---
### 3. 蝖祉<E89D96><E7A589><EFBFBD><EFBFBD>蝵?<3F>?
```typescript
// <20>?蝳<>迫嚗𡁶蝻𣇉<E89DBB>
const LLM_API_KEY = 'sk-xxx'
const DB_HOST = '192.168.1.100'
const OSS_BUCKET = 'my-bucket'
// <20>?蝳<>迫嚗𡁜<E59A97>甇餌垢<E9A48C>?app.listen(3001)
// <20>?蝳<>迫嚗𡁜<E59A97>甇餃<E79487><E9A483>?const baseUrl = 'https://api.example.com'
```
**<EFBFBD><EFBFBD>**嚗?- <20><EFBFBD><E4ADBE><EFBFBD><EFBFBD><EFBFBD>
- 摰匧<E691B0>憌𡡞埯嚗<E59FAF><E59A97><EFBFBD><EFBFBD><E4BAA4><EFBFBD>
- <20>函蔡<E587BD>圈𠗕
**甇<><EFBFBD>𡁏<EFBFBD>**嚗?```typescript
// <20>?雿輻鍂<E8BCBB><EFBFBD><E887AC><EFBFBD>
const apiKey = process.env.LLM_API_KEY
const port = process.env.PORT || 3001
app.listen(port)
```
---
### 4. <20>峕郊<E5B395>蹂遙<E8B982>?<3F>?
```typescript
// <20>?蝳<>迫嚗𡁜<E59A97>甇亙<E79487><E4BA99><EFBFBD>鵭隞餃𦛚
export async function screenLiteratures(req, res) {
const literatures = await prisma.aslLiterature.findMany({...})
// <20>?<3F>躰秤嚗?00蝭<30><EFBFBD><EFBFBD>餈?0蝘? for (const lit of literatures) {
await llmScreening(lit) // 瘥讐<E798A5>2-3蝘? }
res.send({ success: true }) // <20><EFBFBD>撌脩<E6928C><EFBFBD>𧒄
}
// <20>?蝳<>迫嚗𡁏瓷<F0A1818F><EFBFBD><E39591><EFBFBD><E597A1>?const result = await axios.get(url) // <20><EFBFBD>瘞訾<E7989E>蝑匧<E89D91>
```
**<2A><EFBFBD>**嚗?- SAE 霂瑟<E99C82><EFBFBD>𧒄 30 蝘?- <20>滨垢蝑匧<E89D91><E58CA7>園𡢿餈<F0A1A2BF>
- <20><EFBFBD><E4ADBE>曄內餈𥕦漲
**甇<><EFBFBD>𡁏<EFBFBD>**嚗𡁜<E59A97>甇乩遙<E4B9A9>?+ 餈𥕦漲頧株砭嚗<E7A0AD><E59A97> DO 蝚?<3F><EFBFBD>
---
### 5. <20>砍𧑐<E7A08D><EFBFBD><E4BA99><EFBFBD><20>?
```typescript
// <20>?蝳<>迫嚗𡁜<E59A97><F0A1819C>交𧋦<E4BAA4><EFBFBD>隞?import fs from 'fs'
export function logError(error: Error) {
// <20>?<3F>躰秤嚗𡁜捆<F0A1819C><EFBFBD><E588B8>臭腺憭梧<E686AD><E6A2A7><EFBFBD><E4ADBE><EFBFBD><EFBFBD><EFBFBD>
fs.appendFileSync('/var/log/app.log', error.message + '\n')
}
// <20>?蝳<>迫嚗帋蝙<E5B88B>?console.log <20><EFBFBD><E494B6>?logger
console.log('User logged in') // <20><EFBFBD><E588A0><EFBFBD><EFBFBD>嚗屸𠗕隞交䰻霂?```
**<2A><EFBFBD>**嚗?- 摰孵膥<E5ADB5>滚鍳<E6BB9A><EFBFBD>
- 憭𡁜<E686AD>靘𧢲𠯫敹堒<E695B9><E5A092>?- <20><EFBFBD><E4ADBE><EFBFBD><EFBFBD><E89189><EFBFBD>
**甇<><EFBFBD>𡁏<EFBFBD>**嚗?```typescript
// <20>?颲枏枂<E69E8F>?stdout嚗䔶蝙<E494B6>?logger
logger.info({ userId, action: 'login' }, 'User logged in')
```
---
### 6. <20>啣遣<E595A3>唳旿摨栞<E691A8><E6A09E>?<3F>?
```typescript
// <20>?蝳<>迫嚗𡁏<E59A97>甈∟窈瘙<E7AA88>鰵撱箄<E692B1><E7AE84>?import { PrismaClient } from '@prisma/client'
export async function getProjects(req, res) {
// <20>?<3F>躰秤嚗𡁏<E59A97>甈⊥鰵撱綽<E692B1>餈墧𦻖<E5A2A7>唳𠂔憓? const prisma = new PrismaClient()
const projects = await prisma.aslScreeningProject.findMany()
await prisma.$disconnect()
res.send(projects)
}
// <20>?蝳<>迫嚗𡁶凒<F0A181B6>乩蝙<E4B9A9>?pg <20><EFBFBD>隞㚚店<E39A9A>?import { Pool } from 'pg'
const pool = new Pool({ connectionString: process.env.DATABASE_URL })
```
**<2A><EFBFBD>**嚗?- 餈墧𦻖<E5A2A7>啣翰<E595A3><EFBFBD>堒偷嚗㇌DS<44>𣂼<EFBFBD> 400 餈墧𦻖嚗?- <20><EFBFBD>雿𦒘<E99BBF><EFBFBD><E59A97><EFBFBD>亙遣蝡贝<E89DA1>埈𧒄嚗?- 韏<><E99F8F>瘚芾晶
**甇<><EFBFBD>𡁏<EFBFBD>**嚗?```typescript
// <20>?雿輻鍂<E8BCBB><EFBFBD> Prisma Client
import { prisma } from '@/config/database'
const projects = await prisma.aslScreeningProject.findMany()
```
---
### 7. 敹賜裦<E8B39C>躰秤 <20>?
```typescript
// <20>?蝳<>迫嚗𡁶征<F0A181B6>?catch
try {
await storage.upload(key, buffer)
} catch (error) {
// <20>?<3F>躰秤鋡怠<E98BA1><E680A0><EFBFBD><E39A81><EFBFBD><E4ADBE>埝䰻
}
// <20>?蝳<>迫嚗帋<E59A97><EFBFBD><E686AD> Promise rejection
processAsync(taskId) // 瘝⊥<E7989D> .catch()
// <20>?蝳<>迫嚗朞<E59A97><E69C9E>墧芋蝟𢠃<E89D9F>霂?catch (error) {
res.status(500).send({ error: 'Something went wrong' })
// <20><EFBFBD>銝滨䰻<E6BBA8><EFBFBD><EFBFBD><E98A8B><EFBFBD><E988AD><EFBFBD><E68692><EFBFBD><E996AB>
}
```
**<2A><EFBFBD>**嚗?- <20>躰秤<E8BAB0><EFBFBD>餈質葵
- <20><EFBFBD>雿㯄<E99BBF>撌?- <20>埝䰻<E59F9D>圈𠗕
**甇<><EFBFBD>𡁏<EFBFBD>**嚗?```typescript
// <20>?霈啣<E99C88><E595A3><EFBFBD> + <20>见末<E8A781>躰秤靽⊥<E99DBD>
try {
await storage.upload(key, buffer)
} catch (error) {
logger.error({ error, key }, 'Upload failed')
res.status(500).send({
success: false,
error: {
code: 'UPLOAD_FAILED',
message: '<27><>辣銝𠹺<E98A9D>憭梯揖嚗諹窈璉<E7AA88><E79289><EFBFBD>蝏𨅯<E89D8F><F0A885AF><EFBFBD>'
}
})
}
```
---
## <20><><><E99A9E>摰⊥䰻璉<E4B0BB><E79289><EFBFBD><E4BAA4>?
<EFBFBD>?*<2A>𣂷漱隞<E6BCB1><E99A9E><EFBFBD>?*嚗諹窈<E8ABB9>鞾★璉<E29885><E79289><EFBFBD>
### <20><>辣摮睃<E691AE>
- [ ] <20>臬炏雿輻鍂 `storage.upload()` <20><EFBFBD> `fs.writeFile()`嚗?- [ ] Excel <20>臬炏隞𤾸<E99A9E>摮䁅圾<E48185><EFBFBD><E7909C><EFBFBD>靽嘥<E99DBD><E598A5>唳𧋦<E594B3><EFBFBD>
- [ ] PDF <20>𣂼<EFBFBD><F0A382BC>擧糓<E693A7><EFBFBD><E8A1A3><EFBFBD><E596B3>支葩<E694AF><EFBFBD>隞塚<E99A9E>
### <20>唳旿摨?- [ ] <20>臬炏雿輻鍂<E8BCBB><EFBFBD> `prisma` 摰硺<E691B0>嚗?- [ ] <20>臬炏<E887AC><EFBFBD><E8B8B9>典儐<E585B8>臭葉<E887AD><EFBFBD><E689AF>閙辺<E99699>亥砭嚗<E7A0AD><E59A97>摨磰砲<E7A3B0><EFBFBD><E5AFA5><EFBFBD>嚗?- [ ] <20><EFBFBD><E5AFA5><EFBFBD><E6BBA2>臬炏雿輻鍂鈭见𦛚嚗?
### <20>滨蔭蝞∠<E89D9E>
- [ ] <20>臬炏<E887AC><E7828F><EFBFBD><EFBFBD>蝵桅<E89DB5>隞?`process.env` 霂餃<E99C82>嚗?- [ ] <20>臬炏瘝⊥<E7989D>蝖祉<E89D96><E7A589><EFBFBD><EFBFBD> IP<49><50><EFBFBD><EFBFBD><EFBFBD><E6BABB><EFBFBD><EFBFBD><EFBFBD>
- [ ] `.env.example` <20>臬炏撌脫凒<E884AB><EFBFBD>
### <20>踵𧒄<E8B8B5>港遙<E6B8AF>?- [ ] 頞<><E9A09E> 10 蝘垍<E89D98>隞餃𦛚<E9A483>臬炏<E887AC>嫣蛹撘<E89BB9>郊嚗?- [ ] <20>臬炏<E887AC>𣂷<EFBFBD><EFBFBD><E988AD>摨行䰻霂𦻖<EFBCB8><F0A6BB96><EFBFBD>
- [ ] <20>滨垢<E6BBA8>臬炏<E887AC>㕑蔭霂<E99C82> WebSocket <20><EFBFBD>餈𥕦漲嚗?
### <20><EFBFBD>
- [ ] <20>臬炏雿輻鍂 `logger` <20><EFBFBD> `console.log`嚗?- [ ] <20><EFBFBD><E4BA99>臬炏蝏𤘪<E89D8F><F0A498AA><EFBFBD>JSON<4F><EFBFBD>嚗㚁<E59A97>
- [ ] <20>臬炏霈啣<E99C88><EFBFBD><E988AD><EFBFBD><EFBFBD>雿頣<E99BBF>userId<49><64>ction嚗㚁<E59A97>
### <20>躰秤憭<E7A7A4><E686AD>
- [ ] <20><><EFBFBD>?async <20>賣㺭<E8B3A3>臬炏<E887AC>?try-catch嚗?- [ ] <20>臬炏霈啣<E99C88><EFBFBD>祕蝏<E7A595><E89D8F>霂舀𠯫敹梹<E695B9>
- [ ] <20>臬炏餈𥪜<E9A488><EFBFBD><E988AD>憟賜<E6869F><E8B39C>躰秤靽⊥<E99DBD>嚗?
### 銝湔𧒄<E6B994><F0A79284>
- [ ] `/tmp` <20><EFBFBD>雿輻鍂<E8BCBB>擧糓<E693A7><EFBFBD><E8A1A3><EFBFBD><E596B3><EFBFBD>
- [ ] <20>臬炏<E887AC>?`finally` <20>𦯀葉皜<E89189><E79A9C>嚗?- [ ] <20>臬炏<E887AC><EFBFBD><E8B8B9><EFBFBD>靘肽<E99D98> `/tmp`嚗?
---
## <20>㴓 敹恍<E695B9>蠘䌊璉<E48C8A>嚗?<3F><><EFBFBD>嚗?
**餈鞱<E9A488>隞乩<E99A9E><E4B9A9>賭誘嚗峕<E59A97><E5B395>乩誨<E4B9A9><E8AAA8><EFBFBD>臬炏<E887AC><EFBFBD>閫?*嚗?
```bash
# 璉<><E79289>交糓<E4BAA4><EFBFBD><E8A18C>砍𧑐<E7A08D><F0A79190>辣摮睃<E691AE>
grep -r "fs.writeFile\|fs.appendFile" backend/src/modules/
# 璉<><E79289>交糓<E4BAA4><EFBFBD>蝖祉<E89D96><E7A589><EFBFBD><EFBFBD>蝵?grep -r "sk-\|http://\|192.168" backend/src/modules/
# 璉<><E79289>交糓<E4BAA4><EFBFBD><E8A18C>啣遣 Prisma 餈墧𦻖
grep -r "new PrismaClient" backend/src/modules/
# 璉<><E79289>交糓<E4BAA4><EFBFBD> console.log
grep -r "console.log" backend/src/modules/
```
**憸<><E686B8>蝏𤘪<E89D8F>**嚗𡁏<E59A97><F0A1818F><EFBFBD><E39787><EFBFBD>霂亥<E99C82><E4BAA5>?**0 銝芸龪<E88AB8>?*
---
## <20><> <20><><EFBFBD><EFBFBD><EFBFBD>獢?
- [鈭穃<E988AD><E7A983><EFBFBD>蝵脫沲<E884AB><E6B2B2><EFBFBD><EFBFBD>㻩(../09-<2D><EFBFBD>摰墧鴌/03-鈭穃<E988AD><E7A983><EFBFBD>蝵脫沲<E884AB><E6B2B2><EFBFBD><EFBFBD>?md) - <20><>鉄摰峕㟲隞<E39FB2><E99A9E>蝷箔<E89DB7>
- [<EFBFBD><EFBFBD>蝡舀芋<EFBFBD><EFBFBD><EFBFBD><EFBFBD>霈曇恣-V2](../00-蝟餌<E89D9F><E9A48C><EFBFBD>霈曇恣/<2F><EFBFBD>蝡舀芋<E88880><EFBFBD><E5A092><EFBFBD>霈曇恣-V2.md) - <20><EFBFBD><E59786>餌熔
- [<5B>唳旿摨栞挽霈∟<E99C88><E2889F><EFBFBD>(./01-<2D>唳旿摨栞挽霈∟<E99C88><E2889F>?md)
- [API霈曇恣閫<EFBFBD><EFBFBD>](./02-API霈曇恣閫<E681A3><E996AB>.md)
- [<EFBFBD><EFBFBD><EFBFBD><EFBFBD>](./05-隞<><E99A9E><EFBFBD><E996AB>.md)
- [Git<EFBFBD>𣂷漱閫<EFBFBD><EFBFBD>](./06-Git<69>𣂷漱閫<E6BCB1><E996AB>.md)
---
## <20><> <20>湔鰵<E6B994><EFBFBD>
| <20><EFBFBD> | <20><>𧋦 | <20>䀹凒<E480B9><E58792>捆 | 蝏湔擪<E6B994>?|
|------|------|---------|--------|
| 2025-11-16 | V1.0 | <20>𥕦遣<F0A595A6><E981A3>﹝嚗<EFB99D><E59A97>銋劐<E98A8B><E58A90><EFBFBD><EFBFBD><E69298>𤏸<EFBFBD><F0A48FB8>?| <20><EFBFBD><E59786><EFBFBD> |
---
**<EFBFBD><EFBFBD>﹝蝏湔擪<EFBFBD><EFBFBD><EFBFBD>** <20><EFBFBD><E59786><EFBFBD>
**<EFBFBD><EFBFBD><EFBFBD>擧凒<EFBFBD><EFBFBD>** 2025-11-16
**<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>** <20>?撌脣<E6928C><E884A3>?
**撘箏<E69298><E7AE8F><EFBFBD>嚗?* <20>?<3F><><EFBFBD>劐誨<E58A90><E8AAA8><EFBFBD>鈭文<E988AD><EFBFBD>◆璉<E29786><E79289>?