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%)
1067 lines
25 KiB
Markdown
1067 lines
25 KiB
Markdown
# 鈭穃<E988AD><E7A983>罸<EFBFBD>蝵脫沲<E884AB><E6B2B2><EFBFBD><EFBFBD>?
|
||
> **<2A><>﹝<EFBFBD><EFB99D>𧋦嚗?* V1.0
|
||
> **<2A>𥕦遣<F0A595A6>交<EFBFBD>嚗?* 2025-11-16
|
||
> **<2A><>鍂撖寡情嚗?* <20>𡒊垢撘<E59EA2><E69298>㻫<EFBFBD><E3BBAB>沲<EFBFBD><E6B2B2><EFBFBD><EFBFBD><EFBFBD><EFBFBD>蝏?
|
||
> **蝏湔擪<E6B994><E693AA><EFBFBD>** <20>嗆<EFBFBD><E59786>a<EFBFBD>
|
||
> **<2A>嗆<EFBFBD><E59786><EFBFBD>** <20>?撌脣<E6928C><E884A3>?
|
||
---
|
||
|
||
## <20><> <20><>﹝霂湔<E99C82>
|
||
|
||
<EFBFBD>祆<EFBFBD>獢<EFBFBD><EFBFBD>靘?**AI銝游<E98A9D><E6B8B8>𠉛弦撟喳蝱** <20>函蔡<E587BD>圈燵<E59C88>䔶<EFBFBD> Serverless <20>嗆<EFBFBD><E59786><EFBFBD><EFBFBD><EFBFBD>湔<EFBFBD><E6B994>𨰜<EFBFBD>?
|
||
> **潃?<3F>滩<EFBFBD><E6BBA9>湔鰵嚗?025-11-16嚗?*嚗?
|
||
> 撟喳蝱<EFBFBD>箇<EFBFBD>霈暹鴌<EFBFBD><EFBFBD>祕蝏<EFBFBD><EFBFBD><EFBFBD>質恣<EFBFBD>鍦<EFBFBD>隞<EFBFBD><EFBFBD>摰䂿緵撌脰<EFBFBD>蝘餃<EFBFBD>嚗?
|
||
> **[撟喳蝱<E596B3>箇<EFBFBD>霈暹鴌閫<E9B48C><E996AB>](./04-撟喳蝱<E596B3>箇<EFBFBD>霈暹鴌閫<E9B48C><E996AB>.md)**
|
||
>
|
||
> <EFBFBD>祆<EFBFBD>獢<EFBFBD><EFBFBD><EFBFBD>虫<EFBFBD>嚗?> - 鈭穃<E988AD><E7A983><EFBFBD>沲<EFBFBD><E6B2B2><EFBFBD>颱<EFBFBD>霈曇恣
|
||
> - <20>輸<EFBFBD>鈭烐<E988AD><E78390>⊿<EFBFBD>匧<EFBFBD><E58CA7>屸<EFBFBD>蝵?> - Docker摰孵膥<E5ADB5>硋<EFBFBD><E7A18B>函蔡瘚<E894A1><E7989A>
|
||
> - <20>鞉𧋦隡啁<E99AA1><E59581>𣬚<EFBFBD><F0A3AC9A>批<EFBFBD>霅?
|
||
**<2A><>﹝摰帋<E691B0>**嚗?- <20>祆<EFBFBD>獢<EFBFBD><E78DA2>03嚗㚁<E59A97>**鈭穃<E988AD><E7A983>罸<EFBFBD>蝵脫沲<E884AB><E6B2B2><EFBFBD>餉<EFBFBD>** - 靘折<E99D98>鈭烐<E988AD><E78390>∪<EFBFBD><E288AA>函蔡瘚<E894A1><E7989A>
|
||
- 04<30><34>﹝嚗?*撟喳蝱<E596B3>箇<EFBFBD>霈暹鴌閫<E9B48C><E996AB>** - 靘折<E99D98>隞<EFBFBD><E99A9E>摰䂿緵<E482BF><E7B7B5><EFBFBD><EFBFBD>烐<EFBFBD><E78390>?
|
||
**<2A><>粉<EFBFBD>園𡢿**嚗?0 <20><><EFBFBD>
|
||
**摰墧鴌<E5A2A7>園𡢿**嚗𡁜<E59A97>閫?[撟喳蝱<EFBFBD>箇<EFBFBD>霈暹鴌閫<EFBFBD><EFBFBD>](./04-撟喳蝱<E596B3>箇<EFBFBD>霈暹鴌閫<E9B48C><E996AB>.md) <20>?.5憭拙<EFBFBD><EFBFBD>質恣<EFBFBD>?
|
||
---
|
||
|
||
## <20><>儭?<3F>嗆<EFBFBD>霂西圾
|
||
|
||
### 1. Serverless 摨𠉛鍂撘閙<E69298> (SAE)
|
||
|
||
#### **鈭批<E988AD><E689B9>寞<EFBFBD>?*
|
||
|
||
| <20>寞<EFBFBD>?| 霂湔<E99C82> | 隡睃飵 |
|
||
|------|------|------|
|
||
| **<EFBFBD>芸𢆡<EFBFBD>拍憬摰?* | <20>寞旿瘚<E697BF><E7989A><EFBFBD>芸𢆡靚<F0A286A1>㟲摰硺<E691B0><E7A1BA>堆<EFBFBD>0-100嚗?| 擃睃陸<E79D83>煺<EFBFBD>摰閙㦤嚗䔶<E59A97>靚瑟<E99D9A><E7919F><EFBFBD><EFBFBD><EFBFBD>?|
|
||
| **<EFBFBD>厰<EFBFBD>隞䁅晶** | 瞼0.000110592/霂瑟<E99C82>甈?+ 摰硺<E691B0>韐?| <20>脲<EFBFBD><E884B2><EFBFBD>晶蝥?瞼200-500 |
|
||
| **摰孵膥<E5ADB5>㚚<EFBFBD>蝵?* | <20>舀<EFBFBD> Docker <20>𨅯<EFBFBD> | <20>臬<EFBFBD>銝<EFBFBD><E98A9D>湔<EFBFBD>改<EFBFBD>敹恍<E695B9>笔<EFBFBD>皛?|
|
||
| **<EFBFBD><EFBFBD>蔭韐蠘蝸<EFBFBD><EFBFBD>﹛** | <20>芸𢆡<E88AB8><F0A286A1><EFBFBD>瘚<EFBFBD><E7989A> | <20>𣳇<EFBFBD><F0A3B387>閧𡠺韐凋僭 SLB |
|
||
| **<EFBFBD>亙熒璉<EFBFBD><EFBFBD>?* | <20>芸𢆡<E88AB8>滚鍳撘<E98DB3>虜摰硺<E691B0> | <20>鞾<EFBFBD><E99EBE>舐鍂<E88890>?|
|
||
|
||
#### **摰硺<E691B0>閫<EFBFBD>聢<EFBFBD>㗇𥋘**
|
||
|
||
| <20>嗆挾 | 閫<>聢 | vCPU | <20><><EFBFBD> | <20><>鍂<EFBFBD>箸艶 |
|
||
|------|------|------|------|---------|
|
||
| **撘<><E69298>?瘚贝<E7989A>** | 0.5C1G | 0.5<EFBFBD>?| 1GB | <20>亥窈瘙?< 1000 |
|
||
| **<EFBFBD>脲<EFBFBD>** | 1C2G | 1<>?| 2GB | <20>亥窈瘙?1000-5000 |
|
||
| **<EFBFBD>鞾鵭<EFBFBD>?* | 2C4G | 2<>?| 4GB | <20>亥窈瘙?5000-20000 |
|
||
| **<EFBFBD>鞟<EFBFBD><EFBFBD>?* | 4C8G | 4<>?| 8GB | <20>亥窈瘙?> 20000 |
|
||
|
||
**撱箄悅<E7AE84>滨蔭**嚗?```yaml
|
||
# SAE 摨𠉛鍂<F0A0899B>滨蔭
|
||
摰硺<EFBFBD>閫<EFBFBD>聢: 1C2G
|
||
<EFBFBD><EFBFBD>撠誩<EFBFBD>靘𧢲㺭: 1 # <20>踹<EFBFBD><E8B8B9>瑕鍳<E79195>?<3F><>憭批<E686AD>靘𧢲㺭: 10
|
||
CPU 閫血<E996AB><E8A180>拙捆<E68B99><E68D86><EFBFBD>? 70%
|
||
<EFBFBD><EFBFBD><EFBFBD>閫血<EFBFBD><EFBFBD>拙捆<EFBFBD><EFBFBD><EFBFBD>? 80%
|
||
```
|
||
|
||
---
|
||
|
||
### 2. 鈭烐㺭<E78390>桀<EFBFBD> RDS (PostgreSQL 15)
|
||
|
||
#### **閫<>聢<EFBFBD>匧<EFBFBD>**
|
||
|
||
| <20>嗆挾 | 閫<>聢 | vCPU | <20><><EFBFBD> | <20><>憭扯<E686AD><E689AF>交㺭 | <20><>晶 |
|
||
|------|------|------|------|-----------|------|
|
||
| **撘<><E69298>?瘚贝<E7989A>** | <20>箇<EFBFBD><E7AE87>?1C1G | 1<>?| 1GB | 100 | 瞼120 |
|
||
| **<2A>脲<EFBFBD>** | <20>𡁶鍂<F0A181B6>?2C4G | 2<>?| 4GB | 400 | 瞼300 |
|
||
| **<2A>鞾鵭<E99EBE>?* | <20>𡁶鍂<F0A181B6>?4C8G | 4<>?| 8GB | 800 | 瞼600 |
|
||
| **<2A>鞟<EFBFBD><E99E9F>?* | <20>砌澈<E7A08C>?8C16G | 8<>?| 16GB | 1600 | 瞼1200 |
|
||
|
||
#### **<2A>喲睸<E596B2>滨蔭**
|
||
|
||
```sql
|
||
-- <20>亦<EFBFBD>敶枏<E695B6><E69E8F><EFBFBD>憭扯<E686AD><E689AF>交㺭
|
||
SHOW max_connections;
|
||
|
||
-- <20>亦<EFBFBD>敶枏<E695B6>瘣餉<E798A3>餈墧𦻖
|
||
SELECT count(*) FROM pg_stat_activity;
|
||
|
||
-- <20>㗇㺭<E39787>桀<EFBFBD><E6A180><EFBFBD><EFBFBD>蝏蠘恣餈墧𦻖
|
||
SELECT datname, count(*)
|
||
FROM pg_stat_activity
|
||
GROUP BY datname;
|
||
```
|
||
|
||
**餈墧𦻖瘙㰘恣蝞堒<E89D9E>撘?*嚗?```
|
||
瘥誩<EFBFBD>靘贝<EFBFBD><EFBFBD>交㺭 = RDS<44><53>憭扯<E686AD><E689AF>交㺭 / SAE<41><45>憭批<E686AD>靘𧢲㺭 <20> 0.8嚗<EFBFBD><EFBFBD><EFBFBD>函頂<EFBFBD>堆<EFBFBD>
|
||
|
||
蝷箔<EFBFBD>嚗?RDS: 400餈墧𦻖
|
||
SAE: <20><>憭?0摰硺<E691B0>
|
||
瘥誩<EFBFBD>靘? 400 / 10 <20> 0.8 = 32餈墧𦻖
|
||
```
|
||
|
||
---
|
||
|
||
### 3. 撖寡情摮睃<E691AE> OSS
|
||
|
||
#### **Bucket <20>滨蔭**
|
||
|
||
```yaml
|
||
Bucket<EFBFBD>滨妍: aiclinical-prod
|
||
<EFBFBD>箏<EFBFBD>: <20>𦒘<EFBFBD>1嚗<31>㜺撌痹<E6928C>oss-cn-hangzhou
|
||
摮睃<EFBFBD>蝐餃<EFBFBD>: <20><><EFBFBD>摮睃<E691AE>
|
||
霈輸䔮<EFBFBD><EFBFBD><EFBFBD>: 蝘<><E89D98>嚗㇊rivate嚗?<3F><>𧋦<EFBFBD>批<EFBFBD>: 撘<><E69298>?頝典<E9A09D>霈曄蔭: <20><>捂<EFBFBD>滨垢<E6BBA8>笔<EFBFBD>
|
||
```
|
||
|
||
#### **<2A>桀<EFBFBD>蝏𤘪<E89D8F>閫<EFBFBD><E996AB>**
|
||
|
||
```
|
||
aiclinical-prod/
|
||
<EFBFBD>鎿<EFBFBD><EFBFBD><EFBFBD> asl/
|
||
<EFBFBD>? <20>鎿<EFBFBD><E98EBF><EFBFBD> pdfs/ # PDF<44><46>辣
|
||
<EFBFBD>? <20>鎿<EFBFBD><E98EBF><EFBFBD> excel/ # Excel<65><6C>辣
|
||
<EFBFBD>? <20>婙<EFBFBD><E5A999><EFBFBD> exports/ # 撖澆枂<E6BE86><E69E82>辣
|
||
<EFBFBD>鎿<EFBFBD><EFBFBD><EFBFBD> avatars/ # <20>冽<EFBFBD>憭游<E686AD>
|
||
<EFBFBD>鎿<EFBFBD><EFBFBD><EFBFBD> documents/ # <20>亥<EFBFBD>摨𤘪<E691A8>獢?<3F>婙<EFBFBD><E5A999><EFBFBD> temp/ # 銝湔𧒄<E6B994><F0A79284>辣嚗?憭拙<E686AD><E68B99>芸𢆡<E88AB8>𣳇膄嚗?```
|
||
|
||
#### **<2A>笔𦶢<E7AC94>冽<EFBFBD>蝞∠<E89D9E>**
|
||
|
||
```json
|
||
{
|
||
"Rules": [
|
||
{
|
||
"ID": "delete-temp-files",
|
||
"Prefix": "temp/",
|
||
"Status": "Enabled",
|
||
"Expiration": {
|
||
"Days": 1
|
||
}
|
||
},
|
||
{
|
||
"ID": "archive-old-pdfs",
|
||
"Prefix": "asl/pdfs/",
|
||
"Status": "Enabled",
|
||
"Transitions": [
|
||
{
|
||
"Days": 90,
|
||
"StorageClass": "IA" // 頧砌蛹雿𡡞<E99BBF>霈輸䔮
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## <20>凃 摮睃<E691AE><E79D83>質情撅<E68385>挽霈∴<E99C88><E288B4>詨<EFBFBD>嚗?
|
||
### <20>亙藁摰帋<E691B0>
|
||
|
||
**<2A><>辣**嚗䫤backend/src/common/storage/StorageAdapter.ts`
|
||
|
||
```typescript
|
||
/**
|
||
* 摮睃<E691AE><E79D83>質情撅<E68385>𦻖<EFBFBD>? *
|
||
* @description
|
||
* - <20>舀<EFBFBD><E88880>砍𧑐<E7A08D><F0A79190>辣蝟餌<E89D9F> + <20>輸<EFBFBD>鈭?OSS <20>删<EFBFBD><E588A0><EFBFBD>揢
|
||
* - <20>朞<EFBFBD><E69C9E>臬<EFBFBD><E887AC>㗛<EFBFBD><E3979B>批<EFBFBD>摰䂿緵蝐? *
|
||
* @example
|
||
* const storage = StorageFactory.create()
|
||
* const url = await storage.upload('files/doc.pdf', buffer)
|
||
*/
|
||
export interface StorageAdapter {
|
||
/**
|
||
* 銝𠹺<E98A9D><F0A0B9BA><EFBFBD>辣
|
||
* @param key 摮睃<E691AE><E79D83>殷<EFBFBD>頝臬<E9A09D>嚗㚁<E59A97>憒?'asl/pdfs/xxx.pdf'
|
||
* @param buffer <20><>辣<EFBFBD><E8BEA3>捆
|
||
* @returns 霈輸䔮URL
|
||
*/
|
||
upload(key: string, buffer: Buffer): Promise<string>
|
||
|
||
/**
|
||
* 銝贝蝸<E8B49D><E89DB8>辣
|
||
* @param key 摮睃<E691AE><E79D83>? * @returns <20><>辣<EFBFBD><E8BEA3>捆
|
||
*/
|
||
download(key: string): Promise<Buffer>
|
||
|
||
/**
|
||
* <20>𣳇膄<F0A3B387><E88684>辣
|
||
* @param key 摮睃<E691AE><E79D83>? */
|
||
delete(key: string): Promise<void>
|
||
|
||
/**
|
||
* <20>瑕<EFBFBD>霈輸䔮URL
|
||
* @param key 摮睃<E691AE><E79D83>? * @returns 摰峕㟲霈輸䔮URL
|
||
*/
|
||
getUrl(key: string): string
|
||
|
||
/**
|
||
* <20>寥<EFBFBD>銝𠹺<E98A9D>
|
||
* @param files <20><>辣<EFBFBD>𡑒”
|
||
* @returns URL<52>𡑒”
|
||
*/
|
||
uploadMany(files: Array<{ key: string; buffer: Buffer }>): Promise<string[]>
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### LocalAdapter 摰䂿緵嚗<E7B7B5>𧋦<EFBFBD>啣<EFBFBD><E595A3>𡢅<EFBFBD>
|
||
|
||
**<EFBFBD><EFBFBD>辣**嚗䫤backend/src/common/storage/LocalAdapter.ts`
|
||
|
||
```typescript
|
||
import fs from 'fs/promises'
|
||
import path from 'path'
|
||
import { StorageAdapter } from './StorageAdapter.js'
|
||
|
||
/**
|
||
* <20>砍𧑐<E7A08D><F0A79190>辣蝟餌<E89D9F>摮睃<E691AE><E79D83><EFBFBD><EFBFBD><EFBFBD>? *
|
||
* @description
|
||
* - <20>其<EFBFBD><E585B6>砍𧑐撘<F0A79190><E69298>𤑳㴓憓? * - <20><>辣摮睃<E691AE><E79D83>?./uploads <20>桀<EFBFBD>
|
||
* - <20>朞<EFBFBD> HTTP 霈輸䔮嚗冴ttp://localhost:3001/uploads/xxx
|
||
*/
|
||
export class LocalAdapter implements StorageAdapter {
|
||
private uploadDir: string
|
||
private baseUrl: string
|
||
|
||
constructor() {
|
||
this.uploadDir = path.resolve(process.cwd(), 'uploads')
|
||
this.baseUrl = process.env.BASE_URL || 'http://localhost:3001'
|
||
|
||
// 蝖桐<E89D96><E6A190>桀<EFBFBD>摮睃銁
|
||
this.ensureUploadDir()
|
||
}
|
||
|
||
private async ensureUploadDir() {
|
||
try {
|
||
await fs.mkdir(this.uploadDir, { recursive: true })
|
||
} catch (error) {
|
||
console.error('<27>𥕦遣銝𠹺<E98A9D><F0A0B9BA>桀<EFBFBD>憭梯揖:', error)
|
||
}
|
||
}
|
||
|
||
async upload(key: string, buffer: Buffer): Promise<string> {
|
||
const filePath = path.join(this.uploadDir, key)
|
||
|
||
// 蝖桐<E89D96><E6A190>嗥𤌍敶訫<E695B6><E8A8AB>? await fs.mkdir(path.dirname(filePath), { recursive: true })
|
||
|
||
// <20>坔<EFBFBD><E59D94><EFBFBD>辣
|
||
await fs.writeFile(filePath, buffer)
|
||
|
||
// 餈𥪜<E9A488>霈輸䔮URL
|
||
return this.getUrl(key)
|
||
}
|
||
|
||
async download(key: string): Promise<Buffer> {
|
||
const filePath = path.join(this.uploadDir, key)
|
||
return await fs.readFile(filePath)
|
||
}
|
||
|
||
async delete(key: string): Promise<void> {
|
||
const filePath = path.join(this.uploadDir, key)
|
||
try {
|
||
await fs.unlink(filePath)
|
||
} catch (error) {
|
||
// <20><>辣銝滚<E98A9D><E6BB9A>冽𧒄敹賜裦<E8B39C>躰秤
|
||
if ((error as any).code !== 'ENOENT') {
|
||
throw error
|
||
}
|
||
}
|
||
}
|
||
|
||
getUrl(key: string): string {
|
||
return `${this.baseUrl}/uploads/${key}`
|
||
}
|
||
|
||
async uploadMany(files: Array<{ key: string; buffer: Buffer }>): Promise<string[]> {
|
||
const urls = await Promise.all(
|
||
files.map(file => this.upload(file.key, file.buffer))
|
||
)
|
||
return urls
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### OSSAdapter 摰䂿緵嚗<E7B7B5><E59A97>鈭抒㴓憓<E3B493><E68693>
|
||
|
||
**<2A><>辣**嚗䫤backend/src/common/storage/OSSAdapter.ts`
|
||
|
||
```typescript
|
||
import OSS from 'ali-oss'
|
||
import { StorageAdapter } from './StorageAdapter.js'
|
||
|
||
/**
|
||
* <20>輸<EFBFBD>鈭?OSS 摮睃<E691AE><E79D83><EFBFBD><EFBFBD><EFBFBD>? *
|
||
* @description
|
||
* - <20>其<EFBFBD><E585B6>煺漣<E785BA>臬<EFBFBD>
|
||
* - <20><>辣摮睃<E691AE><E79D83>券燵<E588B8>䔶<EFBFBD> OSS
|
||
* - <20>舀<EFBFBD><E88880><EFBFBD><EFBFBD>/憭𣇉<E686AD>霈輸䔮
|
||
*/
|
||
export class OSSAdapter implements StorageAdapter {
|
||
private client: OSS
|
||
private bucket: string
|
||
private region: string
|
||
|
||
constructor() {
|
||
this.region = process.env.OSS_REGION!
|
||
this.bucket = process.env.OSS_BUCKET!
|
||
|
||
if (!this.region || !this.bucket) {
|
||
throw new Error('OSS<53>滨蔭蝻箏仃嚗鐾SS_REGION <20>?OSS_BUCKET <20>芾挽蝵?)
|
||
}
|
||
|
||
this.client = new OSS({
|
||
region: this.region,
|
||
accessKeyId: process.env.OSS_ACCESS_KEY_ID!,
|
||
accessKeySecret: process.env.OSS_ACCESS_KEY_SECRET!,
|
||
bucket: this.bucket,
|
||
// 雿輻鍂<E8BCBB><E98D82><EFBFBD>endpoint嚗𠄎AE霈輸䔮OSS<53>齿<EFBFBD><E9BDBF>讛晶嚗? internal: process.env.NODE_ENV === 'production',
|
||
})
|
||
}
|
||
|
||
async upload(key: string, buffer: Buffer): Promise<string> {
|
||
try {
|
||
const result = await this.client.put(key, buffer)
|
||
return result.url
|
||
} catch (error) {
|
||
console.error('OSS銝𠹺<EFBFBD>憭梯揖:', error)
|
||
throw new Error(`OSS銝𠹺<E98A9D>憭梯揖: ${key}`)
|
||
}
|
||
}
|
||
|
||
async download(key: string): Promise<Buffer> {
|
||
try {
|
||
const result = await this.client.get(key)
|
||
return result.content as Buffer
|
||
} catch (error) {
|
||
console.error('OSS銝贝蝸憭梯揖:', error)
|
||
throw new Error(`OSS銝贝蝸憭梯揖: ${key}`)
|
||
}
|
||
}
|
||
|
||
async delete(key: string): Promise<void> {
|
||
try {
|
||
await this.client.delete(key)
|
||
} catch (error) {
|
||
console.error('OSS<EFBFBD>𣳇膄憭梯揖:', error)
|
||
// <20><>辣銝滚<E98A9D><E6BB9A>冽𧒄銝齿<E98A9D><E9BDBF>粹<EFBFBD>霂? }
|
||
}
|
||
|
||
getUrl(key: string): string {
|
||
// 餈𥪜<E9A488>憭𣇉<E686AD>霈輸䔮URL
|
||
return `https://${this.bucket}.${this.region}.aliyuncs.com/${key}`
|
||
}
|
||
|
||
async uploadMany(files: Array<{ key: string; buffer: Buffer }>): Promise<string[]> {
|
||
// 撟嗉<E6929F>銝𠹺<E98A9D>嚗<EFBFBD><E59A97>憭?0銝芸僎<E88AB8>𡢅<EFBFBD>
|
||
const chunks = []
|
||
for (let i = 0; i < files.length; i += 10) {
|
||
chunks.push(files.slice(i, i + 10))
|
||
}
|
||
|
||
const urls: string[] = []
|
||
for (const chunk of chunks) {
|
||
const chunkUrls = await Promise.all(
|
||
chunk.map(file => this.upload(file.key, file.buffer))
|
||
)
|
||
urls.push(...chunkUrls)
|
||
}
|
||
|
||
return urls
|
||
}
|
||
|
||
/**
|
||
* <20><><EFBFBD>蝑曉<E89D91>URL嚗<4C>葩<EFBFBD>嗉挪<E59789>殷<EFBFBD>
|
||
* @param key 摮睃<E691AE><E79D83>? * @param expires 餈<><E9A488><EFBFBD>園𡢿嚗<F0A1A2BF><E59A97>嚗㚁<E59A97>暺䁅恕1撠𤩺𧒄
|
||
*/
|
||
async getSignedUrl(key: string, expires: number = 3600): Promise<string> {
|
||
return this.client.signatureUrl(key, { expires })
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### StorageFactory 撌亙<E6928C>蝐?
|
||
**<2A><>辣**嚗䫤backend/src/common/storage/StorageFactory.ts`
|
||
|
||
```typescript
|
||
import { StorageAdapter } from './StorageAdapter.js'
|
||
import { LocalAdapter } from './LocalAdapter.js'
|
||
import { OSSAdapter } from './OSSAdapter.js'
|
||
|
||
/**
|
||
* 摮睃<E691AE>撌亙<E6928C>蝐? *
|
||
* @description
|
||
* - <20>寞旿<E5AF9E>臬<EFBFBD><E887AC>㗛<EFBFBD><E3979B>芸𢆡<E88AB8>㗇𥋘摮睃<E691AE>摰䂿緵
|
||
* - STORAGE_TYPE=local <20>?LocalAdapter
|
||
* - STORAGE_TYPE=oss <20>?OSSAdapter
|
||
*/
|
||
export class StorageFactory {
|
||
private static instance: StorageAdapter | null = null
|
||
|
||
/**
|
||
* <20>𥕦遣摮睃<E691AE>摰硺<E691B0>嚗<EFBFBD><E59A97>靘𧢲芋撘𧶏<E69298>
|
||
*/
|
||
static create(): StorageAdapter {
|
||
if (this.instance) {
|
||
return this.instance
|
||
}
|
||
|
||
const storageType = process.env.STORAGE_TYPE || 'local'
|
||
|
||
switch (storageType) {
|
||
case 'oss':
|
||
console.log('<27>𣑐 雿輻鍂<E8BCBB>輸<EFBFBD>鈭?OSS 摮睃<E691AE>')
|
||
this.instance = new OSSAdapter()
|
||
break
|
||
|
||
case 'local':
|
||
console.log('<27><> 雿輻鍂<E8BCBB>砍𧑐<E7A08D><F0A79190>辣摮睃<E691AE>')
|
||
this.instance = new LocalAdapter()
|
||
break
|
||
|
||
default:
|
||
throw new Error(`<EFBFBD>芰䰻<EFBFBD><EFBFBD><EFBFBD><EFBFBD>函掩<EFBFBD>? ${storageType}`)
|
||
}
|
||
|
||
return this.instance
|
||
}
|
||
|
||
/**
|
||
* <20>滨蔭摰硺<E691B0>嚗<EFBFBD>鍂鈭擧<E988AD>霂𤏪<E99C82>
|
||
*/
|
||
static reset() {
|
||
this.instance = null
|
||
}
|
||
}
|
||
|
||
// 撖澆枂<E6BE86>蓥<EFBFBD>
|
||
export const storage = StorageFactory.create()
|
||
```
|
||
|
||
---
|
||
|
||
### 雿輻鍂蝷箔<E89DB7>
|
||
|
||
```typescript
|
||
// backend/src/modules/asl/controllers/literatureController.ts
|
||
|
||
import { storage } from '../../../common/storage/StorageFactory.js'
|
||
import { prisma } from '../../../config/database.js'
|
||
|
||
/**
|
||
* 銝𠹺<E98A9D>PDF<44><46>辣
|
||
*/
|
||
export async function uploadPdf(req, res) {
|
||
try {
|
||
const { literatureId } = req.params
|
||
const file = await req.file()
|
||
|
||
if (!file) {
|
||
return res.status(400).send({ error: 'No file uploaded' })
|
||
}
|
||
|
||
// 霂餃<E99C82><E9A483><EFBFBD>辣<EFBFBD><E8BEA3>捆
|
||
const buffer = await file.toBuffer()
|
||
|
||
// <20><><EFBFBD>摮睃<E691AE><E79D83>? const key = `asl/pdfs/${Date.now()}-${file.filename}`
|
||
|
||
// <20>?銝𠹺<E98A9D><F0A0B9BA>啣<EFBFBD><E595A3>剁<EFBFBD><E58981>芸𢆡<E88AB8>寞旿<E5AF9E>臬<EFBFBD><E887AC>㗇𥋘Local<61>𤈛SS嚗? const url = await storage.upload(key, buffer)
|
||
|
||
// 靽嘥<E99DBD><E598A5>唳㺭<E594B3>桀<EFBFBD>
|
||
await prisma.aslLiterature.update({
|
||
where: { id: literatureId },
|
||
data: {
|
||
pdfUrl: url,
|
||
pdfOssKey: key,
|
||
pdfFileSize: buffer.length,
|
||
}
|
||
})
|
||
|
||
res.send({
|
||
success: true,
|
||
url,
|
||
size: buffer.length
|
||
})
|
||
} catch (error) {
|
||
console.error('PDF銝𠹺<E98A9D>憭梯揖:', error)
|
||
res.status(500).send({ error: 'Upload failed' })
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Excel銝𠹺<E98A9D>嚗<EFBFBD><E59A97><EFBFBD><EFBFBD>閬<EFBFBD><E996AC><EFBFBD>剁<EFBFBD><E58981>湔𦻖閫<F0A6BB96><E996AB>嚗? */
|
||
export async function importExcel(req, res) {
|
||
const file = await req.file()
|
||
const buffer = await file.toBuffer()
|
||
|
||
// <20>?<3F>湔𦻖隞𤾸<E99A9E>摮䁅圾<E48185>琜<EFBFBD>銝滩氜<E6BBA9>? const workbook = xlsx.read(buffer, { type: 'buffer' })
|
||
const data = xlsx.utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]])
|
||
|
||
// <20>寥<EFBFBD><E5AFA5>亙<EFBFBD>
|
||
await prisma.aslLiterature.createMany({ data })
|
||
|
||
res.send({ success: true, count: data.length })
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## <20>圲 <20>唳旿摨栞<E691A8><E6A09E>交<EFBFBD><E4BAA4>滨蔭
|
||
|
||
### Prisma<6D>滨蔭
|
||
|
||
**<2A><>辣**嚗䫤backend/src/config/database.ts`
|
||
|
||
```typescript
|
||
import { PrismaClient } from '@prisma/client'
|
||
|
||
// <20>臬<EFBFBD><E887AC>斗鱏
|
||
const isProduction = process.env.NODE_ENV === 'production'
|
||
|
||
// 霈∠<E99C88>餈墧𦻖瘙惩之撠?// <20>煺漣<E785BA>臬<EFBFBD>嚗鑹DS 400餈墧𦻖 / SAE 10摰硺<E691B0> <20> 0.8 = 32餈墧𦻖/摰硺<E691B0>
|
||
// 撘<><E69298>𤑳㴓憓<E3B493><E68693><EFBFBD>砍𧑐 PostgreSQL嚗?餈墧𦻖頞喳<E9A09E>
|
||
const connectionLimit = isProduction ? 32 : 5
|
||
|
||
/**
|
||
* Prisma 摰X<E691B0>蝡舫<E89DA1>蝵? */
|
||
export const prisma = new PrismaClient({
|
||
log: isProduction
|
||
? ['error', 'warn'] // <20>煺漣<E785BA>臬<EFBFBD><E887AC>芾扇敶閖<E695B6>霂臬<E99C82>霅血<E99C85>
|
||
: ['query', 'error', 'warn'], // 撘<><E69298>𤑳㴓憓<E3B493>扇敶閙<E695B6><E99699>㗇䰻霂?
|
||
datasources: {
|
||
db: {
|
||
url: process.env.DATABASE_URL
|
||
}
|
||
},
|
||
|
||
// <20>喲睸<E596B2>滨蔭嚗朞<E59A97><E69C9E>交<EFBFBD>
|
||
...(isProduction && {
|
||
// 隞<>銁<EFBFBD>煺漣<E785BA>臬<EFBFBD><E887AC>滨蔭餈墧𦻖瘙𣳇<E79899><F0A3B387>? datasources: {
|
||
db: {
|
||
url: process.env.DATABASE_URL,
|
||
// 餈墧𦻖瘙𣳇<E79899>蝵? pool: {
|
||
timeout: 5, // <20>瑕<EFBFBD>餈墧𦻖頞<F0A6BB96>𧒄嚗<F0A79284><E59A97>嚗? maxsize: connectionLimit, // <20><>憭扯<E686AD><E689AF>交㺭
|
||
min: 2, // <20><>撠譍<E692A0><E8AD8D><EFBFBD><EFBFBD><EFBFBD>? }
|
||
}
|
||
}
|
||
})
|
||
})
|
||
|
||
// 隡㗛<E99AA1><E3979B>喲𡡒
|
||
process.on('SIGINT', async () => {
|
||
console.log('<27>𣑐 甇<>銁<EFBFBD>喲𡡒<E596B2>唳旿摨栞<E691A8><E6A09E>?..')
|
||
await prisma.$disconnect()
|
||
process.exit(0)
|
||
})
|
||
|
||
process.on('SIGTERM', async () => {
|
||
console.log('<27>𣑐 <20>嗅<EFBFBD>SIGTERM嚗峕迤<E5B395>典<EFBFBD><E585B8>剜㺭<E5899C>桀<EFBFBD>餈墧𦻖...')
|
||
await prisma.$disconnect()
|
||
process.exit(0)
|
||
})
|
||
|
||
// 餈墧𦻖<E5A2A7>啁<EFBFBD><E59581>改<EFBFBD>隞<EFBFBD><E99A9E>鈭抒㴓憓<E3B493><E68693>
|
||
if (isProduction) {
|
||
setInterval(async () => {
|
||
try {
|
||
const result = await prisma.$queryRaw<Array<{ count: bigint }>>`
|
||
SELECT count(*) as count
|
||
FROM pg_stat_activity
|
||
WHERE datname = current_database()
|
||
`
|
||
|
||
const connectionCount = Number(result[0].count)
|
||
console.log(`<EFBFBD><EFBFBD> 敶枏<E695B6><E69E8F>唳旿摨栞<E691A8><E6A09E>交㺭: ${connectionCount}`)
|
||
|
||
// <20>𡃏郎<F0A1838F><E9838E><EFBFBD>潘<EFBFBD>80%
|
||
const maxConnections = 400 // <20>寞旿RDS閫<53>聢霈曄蔭
|
||
if (connectionCount > maxConnections * 0.8) {
|
||
console.error(`<EFBFBD>𩤃<EFBFBD> <20>唳旿摨栞<E691A8><E6A09E>交㺭餈<E3BAAD><E9A488>: ${connectionCount}/${maxConnections}`)
|
||
}
|
||
} catch (error) {
|
||
console.error('餈墧𦻖<E5A2A7>啁<EFBFBD><E59581>批仃韐?', error)
|
||
}
|
||
}, 60000) // 瘥?0蝘埝<E89D98><E59F9D>乩<EFBFBD>甈?}
|
||
```
|
||
|
||
---
|
||
|
||
## <20><> <20>臬<EFBFBD><E887AC>㗛<EFBFBD>蝞∠<E89D9E>
|
||
|
||
### <20>砍𧑐撘<F0A79190><E69298>𤑳㴓憓?
|
||
**<2A><>辣**嚗䫤backend/.env.development`
|
||
|
||
```bash
|
||
# <20>臬<EFBFBD>
|
||
NODE_ENV=development
|
||
|
||
# 摮睃<E691AE><E79D83>滨蔭
|
||
STORAGE_TYPE=local
|
||
BASE_URL=http://localhost:3001
|
||
|
||
# <20>唳旿摨?DATABASE_URL=postgresql://postgres:postgres@localhost:5432/aiclinical_dev
|
||
|
||
# LLM<4C>滨蔭
|
||
LLM_API_KEY=sk-xxx
|
||
LLM_BASE_URL=https://api.deepseek.com
|
||
|
||
# <20>嗡<EFBFBD><E597A1>滚𦛚嚗<F0A69B9A>𧋦<EFBFBD>唳<EFBFBD><E594B3><EFBFBD><EFBFBD>滨蔭嚗?OSS_REGION=
|
||
OSS_ACCESS_KEY_ID=
|
||
OSS_ACCESS_KEY_SECRET=
|
||
OSS_BUCKET=
|
||
```
|
||
|
||
---
|
||
|
||
### <20>煺漣<E785BA>臬<EFBFBD><E887AC>滨蔭
|
||
|
||
**<2A>沒AE<41>批<EFBFBD><E689B9>圈<EFBFBD>蝵殷<E89DB5>銝滩<E98A9D><E6BBA9>坔<EFBFBD><E59D94><EFBFBD>辣**嚗?
|
||
```bash
|
||
# <20>臬<EFBFBD>
|
||
NODE_ENV=production
|
||
|
||
# 摮睃<E691AE><E79D83>滨蔭
|
||
STORAGE_TYPE=oss
|
||
OSS_REGION=oss-cn-hangzhou
|
||
OSS_ACCESS_KEY_ID=LTAI5t***嚗<><E59A97>RAM<41>冽<EFBFBD><E586BD>瑕<EFBFBD>嚗?OSS_ACCESS_KEY_SECRET=***
|
||
OSS_BUCKET=aiclinical-prod
|
||
|
||
# <20>唳旿摨?DATABASE_URL=postgresql://aiclinical:***@rm-xxx.mysql.rds.aliyuncs.com:5432/aiclinical_prod
|
||
|
||
# LLM<4C>滨蔭
|
||
LLM_API_KEY=sk-***
|
||
LLM_BASE_URL=https://api.deepseek.com
|
||
|
||
# <20>亙<EFBFBD>蝥批<E89DA5>
|
||
LOG_LEVEL=info
|
||
```
|
||
|
||
---
|
||
|
||
## <20>閦 Docker <20>滨蔭
|
||
|
||
### Dockerfile
|
||
|
||
**<2A><>辣**嚗䫤backend/Dockerfile`
|
||
|
||
```dockerfile
|
||
# ==================== <20><>遣<EFBFBD>嗆挾 ====================
|
||
FROM node:20-alpine AS builder
|
||
|
||
WORKDIR /app
|
||
|
||
# 憭滚<E686AD>靘肽<E99D98><E882BD><EFBFBD>辣
|
||
COPY package*.json ./
|
||
COPY prisma ./prisma/
|
||
|
||
# 摰㕑<E691B0>靘肽<E99D98>嚗<EFBFBD><E59A97><EFBFBD>查ev靘肽<E99D98>嚗𣬚鍂鈭擧<E988AD>撱綽<E692B1>
|
||
RUN npm ci
|
||
|
||
# 憭滚<E686AD>皞𣂷誨<F0A382B7>?COPY . .
|
||
|
||
# <20><><EFBFBD> Prisma Client
|
||
RUN npx prisma generate
|
||
|
||
# <20><>遣 TypeScript
|
||
RUN npm run build
|
||
|
||
# ==================== <20>煺漣<E785BA>嗆挾 ====================
|
||
FROM node:20-alpine
|
||
|
||
WORKDIR /app
|
||
|
||
# 憭滚<E686AD>靘肽<E99D98><E882BD><EFBFBD>辣
|
||
COPY package*.json ./
|
||
COPY prisma ./prisma/
|
||
|
||
# 隞<><E99A9E>鋆<EFBFBD><E98B86>鈭找<E988AD>韏?RUN npm ci --only=production
|
||
|
||
# <20><><EFBFBD> Prisma Client
|
||
RUN npx prisma generate
|
||
|
||
# 隞擧<E99A9E>撱粹𧫴畾萄<E795BE><E89084>嗥<EFBFBD>霂穃<E99C82><E7A983><EFBFBD>誨<EFBFBD>?COPY --from=builder /app/dist ./dist
|
||
|
||
# 憭滚<E686AD><E6BB9A>蹱<EFBFBD><E8B9B1><EFBFBD>隞塚<E99A9E>憒<EFBFBD>rompts嚗?COPY prompts ./prompts
|
||
COPY config ./config
|
||
|
||
# <20>𥕦遣<F0A595A6>𡇙oot<6F>冽<EFBFBD>
|
||
RUN addgroup -g 1001 -S nodejs && \
|
||
adduser -S nodejs -u 1001
|
||
|
||
# <20><>揢<EFBFBD>圈<EFBFBD>root<6F>冽<EFBFBD>
|
||
USER nodejs
|
||
|
||
# <20>湧蠧蝡臬藁
|
||
EXPOSE 3001
|
||
|
||
# <20>亙熒璉<E78692><E79289>?HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
|
||
CMD node -e "require('http').get('http://localhost:3001/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
|
||
|
||
# <20>臬𢆡摨𠉛鍂
|
||
CMD ["node", "dist/index.js"]
|
||
```
|
||
|
||
---
|
||
|
||
### docker-compose.yml嚗<6C>𧋦<EFBFBD>唳<EFBFBD>霂𤏪<E99C82>
|
||
|
||
**<EFBFBD><EFBFBD>辣**嚗䫤docker-compose.yml`
|
||
|
||
```yaml
|
||
version: '3.8'
|
||
|
||
services:
|
||
# PostgreSQL<51>唳旿摨? postgres:
|
||
image: postgres:15-alpine
|
||
container_name: aiclinical-postgres
|
||
environment:
|
||
POSTGRES_USER: postgres
|
||
POSTGRES_PASSWORD: postgres
|
||
POSTGRES_DB: aiclinical_dev
|
||
ports:
|
||
- "5432:5432"
|
||
volumes:
|
||
- postgres-data:/var/lib/postgresql/data
|
||
healthcheck:
|
||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||
interval: 10s
|
||
timeout: 5s
|
||
retries: 5
|
||
|
||
# Redis嚗<73>虾<EFBFBD>㚁<EFBFBD>
|
||
redis:
|
||
image: redis:7-alpine
|
||
container_name: aiclinical-redis
|
||
ports:
|
||
- "6379:6379"
|
||
volumes:
|
||
- redis-data:/data
|
||
|
||
# <20>𡒊垢摨𠉛鍂
|
||
backend:
|
||
build:
|
||
context: ./backend
|
||
dockerfile: Dockerfile
|
||
container_name: aiclinical-backend
|
||
ports:
|
||
- "3001:3001"
|
||
environment:
|
||
NODE_ENV: development
|
||
STORAGE_TYPE: local
|
||
DATABASE_URL: postgresql://postgres:postgres@postgres:5432/aiclinical_dev
|
||
depends_on:
|
||
postgres:
|
||
condition: service_healthy
|
||
volumes:
|
||
- ./backend/uploads:/app/uploads # <20>砍𧑐摮睃<E691AE><E79D83>桀<EFBFBD>
|
||
|
||
volumes:
|
||
postgres-data:
|
||
redis-data:
|
||
```
|
||
|
||
**雿輻鍂<E8BCBB>孵<EFBFBD>**嚗?```bash
|
||
# <20>臬𢆡<E887AC><F0A286A1><EFBFBD>㗇<EFBFBD><E39787>?docker-compose up -d
|
||
|
||
# <20>亦<EFBFBD><E4BAA6>亙<EFBFBD>
|
||
docker-compose logs -f backend
|
||
|
||
# <20>𨀣迫<F0A880A3>滚𦛚
|
||
docker-compose down
|
||
```
|
||
|
||
---
|
||
|
||
## <20><> <20>函蔡瘚<E894A1><E7989A>
|
||
|
||
### Step 1: <20><><EFBFBD><EFBFBD>輸<EFBFBD>鈭烐<E988AD><E78390>?
|
||
#### 1.1 撘<><E69298>帋<EFBFBD><E5B88B>唳旿摨?RDS
|
||
|
||
```bash
|
||
# 1. <20>餃<EFBFBD><E9A483>輸<EFBFBD>鈭烐綉<E78390>嗅蝱
|
||
# 2. <20>𦦵揣"鈭烐㺭<E78390>桀<EFBFBD> RDS"
|
||
# 3. <20>𥕦遣摰硺<E691B0>
|
||
# - <20>唳旿摨梶掩<E6A2B6>页<EFBFBD>PostgreSQL 15
|
||
# - 閫<>聢嚗?<3F>?GB嚗<42><E59A97>𡁶鍂<F0A181B6>页<EFBFBD>
|
||
# - 摮睃<E691AE>蝛粹𡢿嚗?0GB嚗𠄎SD嚗?# - 蝵𤑳<E89DB5>嚗间PC嚗<43><E59A97>SAE<41><45>躹<EFBFBD><E8BAB9><EFBFBD>
|
||
# 4. 霈曄蔭<E69B84>賢<EFBFBD><E8B3A2>𤏪<EFBFBD>瘛餃<E7989B> SAE 摨𠉛鍂<F0A0899B>?VPC蝵烐挾嚗?# 5. <20>𥕦遣<F0A595A6>唳旿摨梶鍂<E6A2B6>?# 6. <20>𥕦遣<F0A595A6>唳旿摨橒<E691A8>aiclinical_prod
|
||
```
|
||
|
||
#### 1.2 撘<><E69298>𡁜笆鞊∪<E99E8A><E288AA>?OSS
|
||
|
||
```bash
|
||
# 1. <20>𦦵揣"撖寡情摮睃<E691AE> OSS"
|
||
# 2. <20>𥕦遣 Bucket
|
||
# - Bucket<65>滨妍嚗惨iclinical-prod
|
||
# - <20>箏<EFBFBD>嚗𡁜<E59A97>銝?嚗<>㜺撌痹<E6928C>
|
||
# - 摮睃<E691AE>蝐餃<E89D90>嚗𡁏<E59A97><F0A1818F><EFBFBD><EFBFBD><EFBFBD>?# - 霈輸䔮<E8BCB8><E494AE><EFBFBD>嚗𡁶<E59A97><F0A181B6>?# 3. <20>𥕦遣 RAM <20>冽<EFBFBD>嚗<EFBFBD>鍂鈭垾PI霈輸䔮嚗?# - <20><><EFBFBD>嚗鋫liyunOSSFullAccess
|
||
# - <20>瑕<EFBFBD> AccessKeyId <20>?AccessKeySecret
|
||
```
|
||
|
||
#### 1.3 撘<><E69298>𡁜捆<F0A1819C>券<EFBFBD><E588B8>𤩺<EFBFBD><F0A4A9BA>?
|
||
```bash
|
||
# 1. <20>𦦵揣"摰孵膥<E5ADB5>𨅯<EFBFBD><F0A885AF>滚𦛚 ACR"
|
||
# 2. <20>𥕦遣<F0A595A6>賢<EFBFBD>蝛粹𡢿嚗惨iclinical
|
||
# 3. <20>𥕦遣<F0A595A6>𨅯<EFBFBD>隞枏<E99A9E>嚗颹ackend
|
||
# - 蝐餃<E89D90>嚗𡁶<E59A97><F0A181B6>?# - <20>啣<EFBFBD>嚗𡁜<E59A97>銝?嚗<>㜺撌痹<E6928C>
|
||
```
|
||
|
||
---
|
||
|
||
### Step 2: <20><>遣<EFBFBD>峕綫<E5B395><E7B6AB><EFBFBD><EFBFBD>?
|
||
```bash
|
||
# 1. <20>餃<EFBFBD><E9A483>輸<EFBFBD>鈭穃捆<E7A983>券<EFBFBD><E588B8>𤩺<EFBFBD><F0A4A9BA>?docker login --username=<雿删<E99BBF><E588A0>輸<EFBFBD>鈭𤏸揭<F0A48FB8>? registry.cn-hangzhou.aliyuncs.com
|
||
|
||
# 2. <20><>遣<EFBFBD>𨅯<EFBFBD>
|
||
cd backend
|
||
docker build -t aiclinical-backend:v1.0.0 .
|
||
|
||
# 3. <20>𤘪<EFBFBD>蝑?docker tag aiclinical-backend:v1.0.0 \
|
||
registry.cn-hangzhou.aliyuncs.com/aiclinical/backend:v1.0.0
|
||
|
||
# 4. <20>券<EFBFBD><E588B8><EFBFBD><EFBFBD>輸<EFBFBD>鈭?docker push registry.cn-hangzhou.aliyuncs.com/aiclinical/backend:v1.0.0
|
||
```
|
||
|
||
**<2A>𡁏𧋦<F0A1818F>芸𢆡<E88AB8>?*嚗?
|
||
**<2A><>辣**嚗䫤backend/scripts/build-and-push.sh`
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
|
||
VERSION=$1
|
||
|
||
if [ -z "$VERSION" ]; then
|
||
echo "Usage: ./build-and-push.sh <version>"
|
||
echo "Example: ./build-and-push.sh v1.0.0"
|
||
exit 1
|
||
fi
|
||
|
||
echo "<EFBFBD>鍂 <20><>遣<EFBFBD>𨅯<EFBFBD>: $VERSION"
|
||
docker build -t aiclinical-backend:$VERSION .
|
||
|
||
echo "<EFBFBD>噡儭?<3F>𤘪<EFBFBD>蝑?
|
||
docker tag aiclinical-backend:$VERSION \
|
||
registry.cn-hangzhou.aliyuncs.com/aiclinical/backend:$VERSION
|
||
|
||
echo "<EFBFBD>㨩 <20>券<EFBFBD><E588B8><EFBFBD><EFBFBD>輸<EFBFBD>鈭?
|
||
docker push registry.cn-hangzhou.aliyuncs.com/aiclinical/backend:$VERSION
|
||
|
||
echo "<22>?摰峕<E691B0>嚗?
|
||
echo "<EFBFBD>𨅯<EFBFBD><EFBFBD>啣<EFBFBD>: registry.cn-hangzhou.aliyuncs.com/aiclinical/backend:$VERSION"
|
||
```
|
||
|
||
---
|
||
|
||
### Step 3: <20>𥕦遣 SAE 摨𠉛鍂
|
||
|
||
#### 3.1 <20>箸𧋦<E7AEB8>滨蔭
|
||
|
||
```yaml
|
||
摨𠉛鍂<EFBFBD>滨妍: aiclinical-backend
|
||
摨𠉛鍂蝐餃<EFBFBD>: 摰孵膥<EFBFBD>𨅯<EFBFBD>
|
||
<EFBFBD>𨅯<EFBFBD><EFBFBD>啣<EFBFBD>: registry.cn-hangzhou.aliyuncs.com/aiclinical/backend:v1.0.0
|
||
蝡臬藁: 3001
|
||
<EFBFBD>亙熒璉<EFBFBD><EFBFBD>亥楝敺? /health
|
||
```
|
||
|
||
#### 3.2 摰硺<E691B0><E7A1BA>滨蔭
|
||
|
||
```yaml
|
||
摰硺<EFBFBD>閫<EFBFBD>聢: 1C2G
|
||
<EFBFBD><EFBFBD>撠誩<EFBFBD>靘𧢲㺭: 1
|
||
<EFBFBD><EFBFBD>憭批<EFBFBD>靘𧢲㺭: 10
|
||
CPU閫血<EFBFBD><EFBFBD>拙捆: 70%
|
||
<EFBFBD><EFBFBD><EFBFBD>閫血<EFBFBD><EFBFBD>拙捆: 80%
|
||
```
|
||
|
||
#### 3.3 <20>臬<EFBFBD><E887AC>㗛<EFBFBD><E3979B>滨蔭
|
||
|
||
<EFBFBD>?SAE <20>批<EFBFBD><E689B9>圈<EFBFBD>蝵桃㴓憓<E3B493><E68693><EFBFBD>𧶏<EFBFBD>**<2A>滩<EFBFBD>嚗?*嚗㚁<E59A97>
|
||
|
||
```bash
|
||
NODE_ENV=production
|
||
STORAGE_TYPE=oss
|
||
DATABASE_URL=postgresql://aiclinical:***@rm-xxx.mysql.rds.aliyuncs.com:5432/aiclinical_prod
|
||
OSS_REGION=oss-cn-hangzhou
|
||
OSS_ACCESS_KEY_ID=LTAI5t***
|
||
OSS_ACCESS_KEY_SECRET=***
|
||
OSS_BUCKET=aiclinical-prod
|
||
LLM_API_KEY=sk-***
|
||
LOG_LEVEL=info
|
||
```
|
||
|
||
#### 3.4 VPC蝵𤑳<E89DB5><F0A491B3>滨蔭
|
||
|
||
```yaml
|
||
VPC: <EFBFBD>㗇𥋘銝竃DS<EFBFBD>詨<EFBFBD><EFBFBD><EFBFBD>PC
|
||
摰匧<EFBFBD>蝏? <20><>捂3001蝡臬藁<E887AC>亦<EFBFBD>
|
||
<EFBFBD>祉<EFBFBD>霈輸䔮: 撘<EFBFBD><EFBFBD>荔<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>祉<EFBFBD>SLB嚗?```
|
||
|
||
---
|
||
|
||
### Step 4: <20>函蔡摨𠉛鍂
|
||
|
||
```bash
|
||
# 1. <20>?SAE <20>批<EFBFBD><E689B9>啁<EFBFBD><E59581>?<3F>函蔡摨𠉛鍂"
|
||
# 2. <20>㗇𥋘<E39787>𨅯<EFBFBD><F0A885AF><EFBFBD>𧋦
|
||
# 3. 蝖株恕<E6A0AA>滨蔭<E6BBA8>㰘秤
|
||
# 4. <20>孵稬"蝖桀<E89D96>"撘<>憪钅<E686AA>蝵?
|
||
# 蝑匧<E89D91>3-5<><35><EFBFBD>嚗峕䰻<E5B395>钅<EFBFBD>蝵脫𠯫敹?```
|
||
|
||
**<2A>函蔡<E587BD>𣂼<EFBFBD><F0A382BC><EFBFBD><EFBFBD>**嚗?- <EFBFBD>?摰硺<E691B0><E7A1BA>嗆<EFBFBD><E59786><EFBFBD>餈鞱<E9A488>銝?- <20>?<3F>亙熒璉<E78692><E79289>伐<EFBFBD><E4BC90>朞<EFBFBD>
|
||
- <EFBFBD>?霈輸䔮 `http://<SAE<41>祉<EFBFBD><E7A589>啣<EFBFBD>>/health` 餈𥪜<E9A488> 200
|
||
|
||
---
|
||
|
||
### Step 5: 撉諹<E69289><E8ABB9>函蔡
|
||
|
||
```bash
|
||
# 1. <20>亙熒璉<E78692><E79289>?curl http://<SAE<41>祉<EFBFBD><E7A589>啣<EFBFBD>>/health
|
||
|
||
# 憸<><E686B8><EFBFBD>滚<EFBFBD>嚗?{
|
||
"status": "ok",
|
||
"database": "connected",
|
||
"timestamp": "2025-11-16T10:30:00.000Z"
|
||
}
|
||
|
||
# 2. API瘚贝<E7989A>
|
||
curl http://<SAE<41>祉<EFBFBD><E7A589>啣<EFBFBD>>/api/v1/health
|
||
|
||
# 3. <20>亦<EFBFBD><E4BAA6>亙<EFBFBD>
|
||
# <20>?SAE <20>批<EFBFBD><E689B9>?<3F>?摨𠉛鍂霂行<E99C82> <20>?摰墧𧒄<E5A2A7>亙<EFBFBD>
|
||
```
|
||
|
||
---
|
||
|
||
## <20><> <20>鞉𧋦隡啁<E99AA1>
|
||
|
||
### <20>脲<EFBFBD><E884B2>嗆挾嚗?00<30>冽<EFBFBD>嚗峕𠯫瘣?0嚗?
|
||
| <20>滚𦛚 | 閫<>聢 | <20><>晶 |
|
||
|------|------|------|
|
||
| SAE | 1C2G <20> 1摰硺<E691B0> | 瞼200 |
|
||
| RDS | 2C4G <20>𡁶鍂<F0A181B6>?| 瞼300 |
|
||
| OSS | 100GB<47><42><EFBFBD>摮睃<E691AE> + 10GB瘚<42><E7989A> | 瞼15 |
|
||
| ACR | 蝘<><E89D98>隞枏<E99A9E> | 瞼0嚗<30><E59A97>韐寥<E99F90>摨佗<E691A8> |
|
||
| **<EFBFBD><EFBFBD>恣** | | **瞼515/<2F>?* |
|
||
|
||
### <20>鞾鵭<E99EBE><E9B5AD><EFBFBD>1000<30>冽<EFBFBD>嚗峕𠯫瘣?00嚗?
|
||
| <20>滚𦛚 | 閫<>聢 | <20><>晶 |
|
||
|------|------|------|
|
||
| SAE | 2C4G <20> 撟喳<E6929F>3摰硺<E691B0> | 瞼600 |
|
||
| RDS | 4C8G <20>𡁶鍂<F0A181B6>?| 瞼600 |
|
||
| OSS | 500GB<47><42><EFBFBD>摮睃<E691AE> + 50GB瘚<42><E7989A> | 瞼70 |
|
||
| CDN | 100GB瘚<42><E7989A>嚗<EFBFBD>虾<EFBFBD>㚁<EFBFBD> | 瞼20 |
|
||
| **<EFBFBD><EFBFBD>恣** | | **瞼1290/<2F>?* |
|
||
|
||
### <20>鞟<EFBFBD><E99E9F><EFBFBD><EFBFBD>10000<30>冽<EFBFBD>嚗峕𠯫瘣?000嚗?
|
||
| <20>滚𦛚 | 閫<>聢 | <20><>晶 |
|
||
|------|------|------|
|
||
| SAE | 4C8G <20> 撟喳<E6929F>10摰硺<E691B0> | 瞼2000 |
|
||
| RDS | 8C16G <20>砌澈<E7A08C>?+ 霂餃<E99C82><E9A483><EFBFBD>氖 | 瞼2000 |
|
||
| OSS | 2TB<54><42><EFBFBD>摮睃<E691AE> + 500GB瘚<42><E7989A> | 瞼300 |
|
||
| CDN | 1TB瘚<42><E7989A> | 瞼200 |
|
||
| Redis | 2GB<47><42><EFBFBD><EFBFBD>?| 瞼200 |
|
||
| **<EFBFBD><EFBFBD>恣** | | **瞼4700/<2F>?* |
|
||
|
||
---
|
||
|
||
## <20><> <20>烐綉銝𤾸<E98A9D>霅?
|
||
### 摨𠉛鍂<F0A0899B>扯<EFBFBD><E689AF>烐綉嚗㇁RMS嚗?
|
||
```yaml
|
||
# <20>?SAE <20>批<EFBFBD><E689B9>啣<EFBFBD><E595A3>?ARMS
|
||
<EFBFBD>烐綉<EFBFBD><EFBFBD><EFBFBD>:
|
||
- RT嚗<EFBFBD><EFBFBD>摨娍𧒄<EFBFBD>湛<EFBFBD>
|
||
- QPS嚗<EFBFBD><EFBFBD>蝘坿窈瘙<EFBFBD>㺭嚗? - <20>躰秤<E8BAB0>? - JVM<56><4D><EFBFBD>
|
||
|
||
<EFBFBD>𡃏郎閫<EFBFBD><EFBFBD>:
|
||
- RT > 3蝘? - <20>躰秤<E8BAB0>?> 5%
|
||
- <EFBFBD>舐鍂<EFBFBD>?< 99%
|
||
```
|
||
|
||
### <20>唳旿摨梶<E691A8><E6A2B6>?
|
||
```yaml
|
||
# RDS <20>批<EFBFBD><E689B9>?<3F>?<3F>烐綉銝擧𥁒霅?<3F>烐綉<E78390><E7B689><EFBFBD>:
|
||
- CPU<EFBFBD>拍鍂<EFBFBD>? - <20><><EFBFBD><EFBFBD>拍鍂<E68B8D>? - 餈墧𦻖<E5A2A7>? - IOPS
|
||
|
||
<EFBFBD>𡃏郎閫<EFBFBD><EFBFBD>:
|
||
- CPU > 80%
|
||
- 餈墧𦻖<EFBFBD>?> 320嚗?0%嚗? - 蝤<><E89DA4>雿輻鍂<E8BCBB>?> 80%
|
||
```
|
||
|
||
### <20>鞉𧋦<E99E89>𡃏郎
|
||
|
||
```bash
|
||
# 韐寧鍂銝剖<E98A9D> <20>?韐寧鍂憸<E98D82>郎
|
||
霈曄蔭憸<EFBFBD><EFBFBD>:
|
||
- 瘥𤩺<E798A5>憸<EFBFBD><E686B8>: 瞼1000
|
||
- 80%憸<>郎: 瞼800
|
||
- <20>𡁶䰻<F0A181B6>孵<EFBFBD>: <20>桐辣 + <20>凋縑
|
||
```
|
||
|
||
---
|
||
|
||
## <20><> <20><><EFBFBD><EFBFBD>埝䰻
|
||
|
||
### 撣貉<E692A3><E8B289>桅<EFBFBD>
|
||
|
||
#### <20>桅<EFBFBD>1嚗𡁏㺭<F0A1818F>桀<EFBFBD>餈墧𦻖憭梯揖
|
||
|
||
**<EFBFBD><EFBFBD>𠶖**嚗𡁜<E59A97><F0A1819C>典鍳<E585B8>典仃韐伐<E99F90><E4BC90>亙<EFBFBD><E4BA99>曄內 `Connection refused`
|
||
|
||
**閫<><E996AB><EFBFBD>寞<EFBFBD>**嚗?```bash
|
||
# 1. 璉<><E79289>?RDS <20>賢<EFBFBD><E8B3A2>?# 蝖桐<E89D96>瘛餃<E7989B>鈭?SAE 摨𠉛鍂<F0A0899B>?VPC蝵烐挾
|
||
|
||
# 2. 璉<><E79289>?DATABASE_URL <20>澆<EFBFBD>
|
||
# 甇<>&<EFBFBD>澆<EFBFBD>: postgresql://user:pass@host:port/db
|
||
|
||
# 3. 璉<><E79289>?RDS 摰硺<E691B0><E7A1BA>嗆<EFBFBD>?# 蝖桐<E89D96>摰硺<E691B0>餈鞱<E9A488>銝?```
|
||
|
||
---
|
||
|
||
#### <20>桅<EFBFBD>2嚗鐾SS 銝𠹺<E98A9D>憭梯揖
|
||
|
||
**<2A><>𠶖**嚗𡁏<E59A97>隞嗡<E99A9E>隡㰘<E99AA1><E3B098>?403 Forbidden
|
||
|
||
**閫<><E996AB><EFBFBD>寞<EFBFBD>**嚗?```bash
|
||
# 1. 璉<><E79289>?RAM <20>冽<EFBFBD><E586BD><EFBFBD><EFBFBD>
|
||
# 蝖桐<E89D96><E6A190>?AliyunOSSFullAccess
|
||
|
||
# 2. 璉<><E79289>?Bucket <20><><EFBFBD>
|
||
# 蝖桐<E89D96>摨𠉛鍂<F0A0899B>匧<EFBFBD><E58CA7>交<EFBFBD><E4BAA4>?
|
||
# 3. 璉<><E79289>亦㴓憓<E3B493><E68693><EFBFBD>?# OSS_ACCESS_KEY_ID <20>?OSS_ACCESS_KEY_SECRET <20>臬炏甇<E7828F>&
|
||
```
|
||
|
||
---
|
||
|
||
#### <20>桅<EFBFBD>3嚗朞<E59A97><E69C9E>交㺭<E4BAA4>堒偷
|
||
|
||
**<2A><>𠶖**嚗䫤Connection pool exhausted`
|
||
|
||
**閫<><E996AB><EFBFBD>寞<EFBFBD>**嚗?```typescript
|
||
// 1. 璉<><E79289>亙<EFBFBD><E4BA99>滩<EFBFBD><E6BBA9>交㺭
|
||
const result = await prisma.$queryRaw`
|
||
SELECT count(*) FROM pg_stat_activity
|
||
`
|
||
|
||
// 2. 靚<>㟲餈墧𦻖瘙𣳇<E79899>蝵?// <20>誩<EFBFBD>瘥誩<E798A5>靘贝<E99D98><E8B49D>交㺭嚗峕<E59A97>憓𧼮<E68693> RDS 閫<>聢
|
||
|
||
// 3. 璉<><E79289>交糓<E4BAA4>行<EFBFBD>餈墧𦻖瘜<F0A6BB96><E7989C>
|
||
// 蝖桐<E89D96><E6A190><EFBFBD><EFBFBD>㗇䰻霂a<E99C82>甇<EFBFBD>&<EFBFBD>喲𡡒
|
||
```
|
||
|
||
---
|
||
|
||
## <20><> <20>湔鰵<E6B994>亙<EFBFBD>
|
||
|
||
| <20>交<EFBFBD> | <20><>𧋦 | <20>䀹凒<E480B9><E58792>捆 | 蝏湔擪<E6B994>?|
|
||
|------|------|---------|--------|
|
||
| 2025-11-16 | V1.0 | <20>𥕦遣<F0A595A6><E981A3>﹝嚗<EFB99D><E59A97>銋劐<E98A8B><E58A90>毺<EFBFBD><E6AFBA>函蔡<E587BD>嗆<EFBFBD> | <20>嗆<EFBFBD><E59786>a<EFBFBD> |
|
||
|
||
---
|
||
|
||
## <20><> <20>詨<EFBFBD><E8A9A8><EFBFBD>﹝
|
||
|
||
- [<EFBFBD>滚<EFBFBD>蝡舀芋<EFBFBD>堒<EFBFBD><EFBFBD>嗆<EFBFBD>霈曇恣-V2](../00-蝟餌<E89D9F><E9A48C>颱<EFBFBD>霈曇恣/<2F>滚<EFBFBD>蝡舀芋<E88880>堒<EFBFBD><E5A092>嗆<EFBFBD>霈曇恣-V2.md) - <20>嗆<EFBFBD><E59786>餌熔
|
||
- [鈭穃<EFBFBD><EFBFBD>笔<EFBFBD><EFBFBD>𤏸<EFBFBD><EFBFBD><EFBFBD>(../04-撘<><E69298>𤏸<EFBFBD><F0A48FB8>?08-鈭穃<E988AD><E7A983>笔<EFBFBD><E7AC94>𤏸<EFBFBD><F0A48FB8>?md) - DO/DON'T 璉<><E79289>交<EFBFBD><E4BAA4>?- [Schema<6D>𠉛氖<F0A0899B>嗆<EFBFBD>霈曇恣](./01-Schema<6D>𠉛氖<F0A0899B>嗆<EFBFBD>霈曇恣嚗?0銝迎<E98A9D>.md)
|
||
- [<5B>唳旿摨栞<E691A8><E6A09E>仿<EFBFBD>蝵孫(./02-<2D>唳旿摨栞<E691A8><E6A09E>仿<EFBFBD>蝵?md)
|
||
|
||
---
|
||
|
||
**<EFBFBD><EFBFBD>﹝蝏湔擪<EFBFBD><EFBFBD><EFBFBD>** <20>嗆<EFBFBD><E59786>a<EFBFBD>
|
||
**<EFBFBD><EFBFBD><EFBFBD>擧凒<EFBFBD>堆<EFBFBD>** 2025-11-16
|
||
**<EFBFBD><EFBFBD>﹝<EFBFBD>嗆<EFBFBD><EFBFBD><EFBFBD>** <20>?撌脣<E6928C><E884A3>?
|