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

18 KiB
Raw Blame History

鈭穃<EFBFBD><EFBFBD><EFBFBD><EFBFBD>𤏸<EFBFBD><EFBFBD>?

*<EFBFBD><EFBFBD><EFBFBD><EFBFBD>𧋦嚗? V1.1
*<EFBFBD>𥕦遣<EFBFBD><EFBFBD>嚗? 2025-11-16
<EFBFBD><EFBFBD><EFBFBD>擧凒<EFBFBD><EFBFBD> 2025-12-13 <20><> Postgres-Only <20><EFBFBD><EFBFBD><E996AB><EFBFBD><EFBFBD>
*<EFBFBD><EFBFBD>鍂撖寡情嚗? <20><><EFBFBD><EFBFBD><E58CA7>睲犖<E79DB2>? 撘箏<EFBFBD><EFBFBD><EFBFBD> <20>?敹<><EFBFBD><EFBFBD>
蝏湔擪<EFBFBD><EFBFBD><EFBFBD> <20><EFBFBD><E59786><EFBFBD>


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


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

撟喳蝱撌脫<EFBFBD>靘𤤿<EFBFBD><EFBFBD>滚𦛚

銝𡁜𦛚璅<EFBFBD>嚗㇁SL/AIA/PKB/DC蝑㚁<E89D91>摨磰砲憭滨鍂隞乩<E99A9E>撟喳蝱<E596B3><EFBFBD>嚗𣬚<E59A97><E79487>憭滚<E686AD><E6BB9A><EFBFBD>

<EFBFBD>滚𦛚 撖澆<EFBFBD><EFBFBD><EFBFBD> <EFBFBD><EFBFBD>? <EFBFBD><EFBFBD>
摮睃<EFBFBD><EFBFBD>滚𦛚 import { storage } from '@/common/storage' <EFBFBD><EFBFBD>辣銝𠹺<EFBFBD>銝贝蝸 <EFBFBD>?撟喳蝱蝥?
<EFBFBD><EFBFBD>蝟餌<EFBFBD> import { logger } from '@/common/logging' <EFBFBD><EFBFBD><EFBFBD><EFBFBD>𡝗𠯫敹? <EFBFBD>?撟喳蝱蝥?
<EFBFBD>郊隞餃𦛚 import { jobQueue } from '@/common/jobs' <EFBFBD>踵𧒄<EFBFBD>港遙<EFBFBD>? <EFBFBD>?撟喳蝱蝥?
蝻枏<EFBFBD><EFBFBD>滚𦛚 import { cache } from '@/common/cache' <EFBFBD><EFBFBD><EFBFBD>撘讐<EFBFBD>摮? <EFBFBD>?撟喳蝱蝥?
<EFBFBD><EFBFBD> <20><EFBFBD>蝏凋<E89D8F> import { CheckpointService } from '@/common/jobs' 隞餃𦛚<EFBFBD><EFBFBD>蝞∠<EFBFBD> <EFBFBD>?撟喳蝱蝥改<E89DA5><E694B9><EFBFBD>
*<EFBFBD>唳旿摨? import { prisma } from '@/config/database' <EFBFBD>唳旿摨𤘪<EFBFBD>雿? <EFBFBD>?撟喳蝱蝥?
LLM<EFBFBD><EFBFBD> import { LLMFactory } from '@/common/llm' LLM靚<EFBFBD> <EFBFBD>?撟喳蝱蝥?

蝷箔<EFBFBD>嚗𡁏迤蝖桐蝙<EFBFBD>典像<EFBFBD><EFBFBD><EFBFBD>?

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

<EFBFBD>?<3F>躰秤嚗𡁻<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>
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>摰䂿緵

**<2A><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>?

<EFBFBD>?<3F><EFBFBD><E588BB>𡁏<EFBFBD>嚗㇄O嚗?

1. <20><>辣摮睃<E691AE> <20>?

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

  • <EFBFBD>砍𧑐撘<EFBFBD><EFBFBD><EFBFBD><EFBFBD>煺漣<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>?- <20>芸𢆡<E88AB8>寞旿<E5AF9E><EFBFBD><E887AC><EFBFBD><E3979B><EFBFBD>揢摮睃<E691AE><E79D83><EFBFBD>

2. <20>唳旿摨栞<E691A8><E6A09E>?<3F>?

// <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>憭滨鍂餈墧𦻖

  • <EFBFBD><EFBFBD>餈墧𦻖<EFBFBD><EFBFBD>堒偷
  • 鈭见𦛚靽肽<EFBFBD><EFBFBD>唳旿銝<EFBFBD><EFBFBD><EFBFBD>?

3. <20><EFBFBD><E887AC><EFBFBD><E3979B>滨蔭 <20>?

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

  • 蝐餃<EFBFBD>摰匧<EFBFBD>
  • 靘蹂<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>

4. <20>踵𧒄<E8B8B5>港遙<E6B8AF><EFBFBD><E288AA>?<3F>?

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

  • <EFBFBD><EFBFBD>雿㯄<EFBFBD><EFBFBD>游末
  • <EFBFBD><EFBFBD><EFBFBD><EFBFBD>隞餃𦛚

**<2A>?摰峕㟲摰噼殿<E599BC><E6AEBF><EFBFBD>?*嚗?霂西<E99C82> Postgres-Only撘<79>郊隞餃𦛚憭<F0A69B9A><E686AD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>抅鈭𥟠C Tool C摰峕㟲摰噼殿嚗?

5. <20><EFBFBD>颲枏枂 <20>?

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

  • 蝏𤘪<EFBFBD><EFBFBD>碶噶鈭擧䰻霂<EFBFBD><EFBFBD>?- <20><><EFBFBD><EFBFBD>嚗䔶<E59A97>隡帋腺憭?

6. <20>躰秤憭<E7A7A4><E686AD> <20>?

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

  • <EFBFBD><EFBFBD>霈啣<EFBFBD>霂衣<EFBFBD><EFBFBD>躰秤
  • 銝齿𠂔<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>?

7. 銝湔𧒄<E6B994><F0A79284>辣憭<E8BEA3><E686AD> <20>?

// <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>䭾說


<EFBFBD><EFBFBD> Postgres-Only <20><EFBFBD><EFBFBD><E996AB>嚗?025-12-13 <20><EFBFBD>嚗?

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

隞餃𦛚蝞∠<EFBFBD><EFBFBD><EFBFBD>迤蝖桀<EFBFBD>瘜?

<EFBFBD>?DO: 雿輻鍂 job.data 摮睃<E691AE>隞餃𦛚蝞∠<E89D9E>靽⊥<E99DBD>

// <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;
}

<EFBFBD>?DON'T: <20><EFBFBD><E585B6>∟”銝剖<E98A9D><E58996>其遙<E585B6>∠恣<E288A0><E681A3><EFBFBD>?

// <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>
    });
  }
}

銝箔<EFBFBD><EFBFBD><EFBFBD>撖對<EFBFBD>

  • <EFBFBD>?瘥譍葵璅<E79285><E288AA><EFBFBD>瘛餃<E7989B><E9A483><EFBFBD><E8A9A8><EFBFBD><EFBFBD>畾蛛<E795BE><EFBFBD><E99A9E><EFBFBD><EFBFBD>嚗?- <20>?餈嘥<E9A488> DRY <20><EFBFBD>
  • <EFBFBD>?餈嘥<E9A488> 3 撅<><EFBFBD><E6B2B2><EFBFBD><EFBFBD>?- <20>?蝏湔擪<E6B994>圈𠗕嚗<F0A09795><EFBFBD><EFBFBD><EFBFBD><E9A489><EFBFBD><EFBFBD>㺿憭𡁜<E686AD>嚗?

<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>澆ế<EFBFBD><EFBFBD><EFBFBD><EFBFBD>

<EFBFBD>?DO: 摰䂿緵<E482BF><EFBFBD><E7AE84>峕芋撘誩<E69298><E8AAA9>?

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>摨?  }
}

*銝箔<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>嚗?

  • <EFBFBD>?撠譍遙<E8AD8D><E288AA><EFBFBD>摨䈑<E691A8><E48891>𣳇<EFBFBD><F0A3B387>堒辣餈<E8BEA3><E9A488>
  • <EFBFBD>?憭找遙<E689BE><EFBFBD><E28ABF><EFBFBD><EFBFBD>𣈲<EFBFBD><F0A388B2><EFBFBD>寧賒隡𩤃<E99AA1>
  • <EFBFBD>?<3F><EFBFBD>銝𤾸虾<F0A4BEB8><EFBFBD>批像銵?

<EFBFBD>?DON'T: <20><><EFBFBD>劐遙<E58A90><EFBFBD>韏圈<E99F8F><E59C88>?

// <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>匧辣餈?}

銝箔<EFBFBD><EFBFBD><EFBFBD>撖對<EFBFBD>

  • <EFBFBD>?撠譍遙<E8AD8D><EFBFBD>摨娍<E691A8><EFBFBD><E59A97><EFBFBD><EFBFBD>頧株砭<E6A0AA><EFBFBD>嚗?- <20>?瘚芾晶<E88ABE><EFBFBD><EFBFBD><E99F8F>
  • <EFBFBD>?<3F><EFBFBD>雿㯄<E99BBF>撌?

<EFBFBD><EFBFBD><EFBFBD>潭綫<EFBFBD>𣂼<EFBFBD>?

隞餃𦛚蝐餃<EFBFBD> <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>? <EFBFBD><EFBFBD>
<EFBFBD><EFBFBD>讃蝑偦<EFBFBD>? 50蝭? <EFBFBD><EFBFBD>~7蝘𡜐<E89D98>50蝭㻬5<E3BBAC><35><EFBFBD>
<EFBFBD>唳旿<EFBFBD>𣂼<EFBFBD> 50<EFBFBD>? <EFBFBD>閙辺~5-10蝘𡜐<E89D98>50<35>﹚5<EFB99A><35><EFBFBD>
蝏蠘恣璅<EFBFBD> 30銝? <EFBFBD>蓥葵~10蝘𡜐<E89D98>30銝泠5<E6B3A0><35><EFBFBD>
暺䁅恕 50<EFBFBD>? <EFBFBD>𡁶鍂<EFBFBD><EFBFBD><EFBFBD>?

<EFBFBD>?蝳<><EFBFBD>𡁏<EFBFBD>嚗㇄ON'T嚗?

1. <20>砍𧑐<E7A08D><F0A79190>辣摮睃<E691AE> <20>?

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

**<2A><EFBFBD>**嚗?- 摰孵膥<E5ADB5>滚鍳<E6BB9A>𡝗<EFBFBD>摰孵<E691B0><E5ADB5><EFBFBD>辣銝

  • 憭𡁜<EFBFBD>靘钅𡢿<EFBFBD><EFBFBD><EFBFBD>曹澈<EFBFBD><EFBFBD>
  • <EFBFBD><EFBFBD>蝛粹𡢿<EFBFBD><EFBFBD>

**甇<><EFBFBD>𡁏<EFBFBD>**嚗帋蝙<E5B88B>?storage.upload() 銝𠹺<E98A9D><F0A0B9BA>?OSS


2. <20><><EFBFBD>蝻枏<E89DBB> <20>?

// <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>峕郊

**<2A><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>?

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

**<2A><EFBFBD>**嚗?- <20><EFBFBD><E4ADBE><EFBFBD><EFBFBD><EFBFBD>

  • 摰匧<EFBFBD>憌𡡞埯嚗<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
  • <EFBFBD>函蔡<EFBFBD>圈𠗕

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

  • <EFBFBD><EFBFBD><EFBFBD>曄內餈𥕦漲

**甇<><EFBFBD>𡁏<EFBFBD>**嚗𡁜<E59A97>甇乩遙<E4B9A9>?+ 餈𥕦漲頧株砭嚗<E7A0AD><E59A97> DO 蝚?<3F><EFBFBD>


5. <20>砍𧑐<E7A08D><EFBFBD><E4BA99><EFBFBD><20>?

// <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>嚗屸𠗕隞交䰻霂?```

**<EFBFBD><EFBFBD>**?- 摰孵膥<EFBFBD>滚鍳<EFBFBD><EFBFBD>
- 憭𡁜<EFBFBD>靘𧢲𠯫敹堒<EFBFBD><EFBFBD>?- <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>

**<EFBFBD><EFBFBD>𡁏<EFBFBD>**?```typescript
// <20>?颲枏枂<E69E8F>?stdout嚗䔶蝙<E494B6>?logger
logger.info({ userId, action: 'login' }, 'User logged in')

6. <20>啣遣<E595A3>唳旿摨栞<E691A8><E6A09E>?<3F>?

// <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>餈質葵

  • <EFBFBD><EFBFBD>雿㯄<EFBFBD>撌?- <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>?
<0A>?*<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>?


<EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>獢?


<EFBFBD><EFBFBD> <20>湔鰵<E6B994><EFBFBD>

<EFBFBD><EFBFBD> <EFBFBD><EFBFBD>𧋦 <EFBFBD>䀹凒<EFBFBD><EFBFBD> 蝏湔擪<EFBFBD>?
2025-11-16 V1.0 <EFBFBD>𥕦遣<EFBFBD><EFBFBD>﹝嚗<EFBFBD><EFBFBD>銋劐<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>𤏸<EFBFBD><EFBFBD>? <EFBFBD><EFBFBD><EFBFBD><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>? *撘箏<EFBFBD><EFBFBD><EFBFBD>嚗? <20>?<3F><><EFBFBD>劐誨<E58A90><E8AAA8><EFBFBD>鈭文<E988AD><EFBFBD>◆璉<E29786><E79289>?