feat(admin): Add user management and upgrade to module permission system
Features - User Management (Phase 4.1): - Database: Add user_modules table for fine-grained module permissions - Database: Add 4 user permissions (view/create/edit/delete) to role_permissions - Backend: UserService (780 lines) - CRUD with tenant isolation - Backend: UserController + UserRoutes (648 lines) - 13 API endpoints - Backend: Batch import users from Excel - Frontend: UserListPage (412 lines) - list/filter/search/pagination - Frontend: UserFormPage (341 lines) - create/edit with module config - Frontend: UserDetailPage (393 lines) - details/tenant/module management - Frontend: 3 modal components (592 lines) - import/assign/configure - API: GET/POST/PUT/DELETE /api/admin/users/* endpoints Architecture Upgrade - Module Permission System: - Backend: Add getUserModules() method in auth.service - Backend: Login API returns modules array in user object - Frontend: AuthContext adds hasModule() method - Frontend: Navigation filters modules based on user.modules - Frontend: RouteGuard checks requiredModule instead of requiredVersion - Frontend: Remove deprecated version-based permission system - UX: Only show accessible modules in navigation (clean UI) - UX: Smart redirect after login (avoid 403 for regular users) Fixes: - Fix UTF-8 encoding corruption in ~100 docs files - Fix pageSize type conversion in userService (String to Number) - Fix authUser undefined error in TopNavigation - Fix login redirect logic with role-based access check - Update Git commit guidelines v1.2 with UTF-8 safety rules Database Changes: - CREATE TABLE user_modules (user_id, tenant_id, module_code, is_enabled) - ADD UNIQUE CONSTRAINT (user_id, tenant_id, module_code) - INSERT 4 permissions + role assignments - UPDATE PUBLIC tenant with 8 module subscriptions Technical: - Backend: 5 new files (~2400 lines) - Frontend: 10 new files (~2500 lines) - Docs: 1 development record + 2 status updates + 1 guideline update - Total: ~4900 lines of code Status: User management 100% complete, module permission system operational
This commit is contained in:
@@ -1,56 +1,67 @@
|
||||
# 鈭穃<EFBFBD><EFBFBD>罸<EFBFBD>蝵脫沲<EFBFBD><EFBFBD><EFBFBD><EFBFBD>?
|
||||
> **<2A><>﹝<EFBFBD><EFB99D>𧋦嚗?* V1.0
|
||||
> **<EFBFBD>𥕦遣<EFBFBD>交<EFBFBD>嚗?* 2025-11-16
|
||||
> **<EFBFBD><EFBFBD>鍂撖寡情嚗?* <20>𡒊垢撘<E59EA2><E69298>㻫<EFBFBD><E3BBAB>沲<EFBFBD><E6B2B2><EFBFBD><EFBFBD><EFBFBD><EFBFBD>蝏?
|
||||
# 云原生部署架构指南
|
||||
|
||||
> **文档版本:** V1.0
|
||||
> **创建日期:** 2025-11-16
|
||||
> **适用对象:** 后端开发、架构师、运维
|
||||
> **维护者:** 架构团队
|
||||
> **<EFBFBD>嗆<EFBFBD><EFBFBD><EFBFBD>** <20>?撌脣<E6928C><E884A3>?
|
||||
> **状态:** ✅ 已完成
|
||||
|
||||
---
|
||||
|
||||
## 📋 文档说明
|
||||
|
||||
<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>嚗?
|
||||
本文档提供 **AI临床研究平台** 部署到阿里云 Serverless 架构的完整指南。
|
||||
|
||||
> **⭐ 重要更新(2025-11-16)**:
|
||||
> 平台基础设施的详细实施计划和代码实现已迁移到:
|
||||
> **[平台基础设施规划](./04-平台基础设施规划.md)**
|
||||
>
|
||||
> <EFBFBD>祆<EFBFBD>獢<EFBFBD><EFBFBD><EFBFBD>虫<EFBFBD>嚗?> - 鈭穃<E988AD><E7A983><EFBFBD>沲<EFBFBD><E6B2B2><EFBFBD>颱<EFBFBD>霈曇恣
|
||||
> - <EFBFBD>輸<EFBFBD>鈭烐<EFBFBD><EFBFBD>⊿<EFBFBD>匧<EFBFBD><EFBFBD>屸<EFBFBD>蝵?> - Docker摰孵膥<E5ADB5>硋<EFBFBD><E7A18B>函蔡瘚<E894A1><E7989A>
|
||||
> - <EFBFBD>鞉𧋦隡啁<EFBFBD><EFBFBD>𣬚<EFBFBD><EFBFBD>批<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>
|
||||
**摰墧鴌<EFBFBD>園𡢿**嚗𡁜<E59A97>閫?[撟喳蝱<EFBFBD>箇<EFBFBD>霈暹鴌閫<EFBFBD><EFBFBD>](./04-撟喳蝱<E596B3>箇<EFBFBD>霈暹鴌閫<E9B48C><E996AB>.md) <20>?.5憭拙<EFBFBD><EFBFBD>質恣<EFBFBD>?
|
||||
> 本文档聚焦于:
|
||||
> - 云原生架构总体设计
|
||||
> - 阿里云服务选型和配置
|
||||
> - Docker容器化和部署流程
|
||||
> - 成本估算和监控告警
|
||||
|
||||
**文档定位**:
|
||||
- 本文档(03):**云原生部署架构总览** - 侧重云服务和部署流程
|
||||
- 04文档:**平台基础设施规划** - 侧重代码实现和开发指南
|
||||
|
||||
**阅读时间**:20 分钟
|
||||
**实施时间**:参见 [平台基础设施规划](./04-平台基础设施规划.md) 的2.5天实施计划
|
||||
|
||||
---
|
||||
|
||||
## <EFBFBD><EFBFBD>儭?<3F>嗆<EFBFBD>霂西圾
|
||||
## 🏗️ 架构详解
|
||||
|
||||
### 1. Serverless 应用引擎 (SAE)
|
||||
|
||||
#### **鈭批<EFBFBD><EFBFBD>寞<EFBFBD>?*
|
||||
#### **产品特性**
|
||||
|
||||
| <EFBFBD>寞<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/霂瑟<EFBFBD>甈?+ 摰硺<E691B0>韐?| <20>脲<EFBFBD><E884B2><EFBFBD>晶蝥?瞼200-500 |
|
||||
| **摰孵膥<EFBFBD>㚚<EFBFBD>蝵?* | <20>舀<EFBFBD> Docker <20>𨅯<EFBFBD> | <20>臬<EFBFBD>銝<EFBFBD><E98A9D>湔<EFBFBD>改<EFBFBD>敹恍<E695B9>笔<EFBFBD>皛?|
|
||||
| **自动扩缩容** | 根据流量自动调整实例数(0-100) | 高峰期不宕机,低谷期省成本 |
|
||||
| **按需付费** | ¥0.000110592/请求次 + 实例费 | 初期月费约 ¥200-500 |
|
||||
| **容器化部署** | 支持 Docker 镜像 | 环境一致性,快速回滚 |
|
||||
| **内置负载均衡** | 自动分配流量 | 无需单独购买 SLB |
|
||||
| **<EFBFBD>亙熒璉<EFBFBD><EFBFBD>?* | <20>芸𢆡<E88AB8>滚鍳撘<E98DB3>虜摰硺<E691B0> | <20>鞾<EFBFBD><E99EBE>舐鍂<E88890>?|
|
||||
| **健康检查** | 自动重启异常实例 | 提高可用性 |
|
||||
|
||||
#### **实例规格选择**
|
||||
|
||||
| 阶段 | 规格 | vCPU | 内存 | 适用场景 |
|
||||
|------|------|------|------|---------|
|
||||
| **撘<EFBFBD><EFBFBD>?瘚贝<E7989A>** | 0.5C1G | 0.5<EFBFBD>?| 1GB | <EFBFBD>亥窈瘙?< 1000 |
|
||||
| **<EFBFBD>脲<EFBFBD>** | 1C2G | 1<EFBFBD>?| 2GB | <EFBFBD>亥窈瘙?1000-5000 |
|
||||
| **<EFBFBD>鞾鵭<EFBFBD>?* | 2C4G | 2<EFBFBD>?| 4GB | <EFBFBD>亥窈瘙?5000-20000 |
|
||||
| **<EFBFBD>鞟<EFBFBD><EFBFBD>?* | 4C8G | 4<EFBFBD>?| 8GB | <EFBFBD>亥窈瘙?> 20000 |
|
||||
| **开发/测试** | 0.5C1G | 0.5核 | 1GB | 日请求 < 1000 |
|
||||
| **初期** | 1C2G | 1核 | 2GB | 日请求 1000-5000 |
|
||||
| **成长期** | 2C4G | 2核 | 4GB | 日请求 5000-20000 |
|
||||
| **成熟期** | 4C8G | 4核 | 8GB | 日请求 > 20000 |
|
||||
|
||||
**撱箄悅<EFBFBD>滨蔭**嚗?```yaml
|
||||
**建议配置**:
|
||||
```yaml
|
||||
# SAE 应用配置
|
||||
实例规格: 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%
|
||||
最小实例数: 1 # 避免冷启动
|
||||
最大实例数: 10
|
||||
CPU 触发扩容阈值: 70%
|
||||
内存触发扩容阈值: 80%
|
||||
```
|
||||
|
||||
---
|
||||
@@ -61,10 +72,10 @@ CPU 触发扩容阈
|
||||
|
||||
| 阶段 | 规格 | vCPU | 内存 | 最大连接数 | 月费 |
|
||||
|------|------|------|------|-----------|------|
|
||||
| **撘<EFBFBD><EFBFBD>?瘚贝<E7989A>** | <EFBFBD>箇<EFBFBD><EFBFBD>?1C1G | 1<EFBFBD>?| 1GB | 100 | 瞼120 |
|
||||
| **<EFBFBD>脲<EFBFBD>** | <EFBFBD>𡁶鍂<EFBFBD>?2C4G | 2<EFBFBD>?| 4GB | 400 | 瞼300 |
|
||||
| **<EFBFBD>鞾鵭<EFBFBD>?* | <20>𡁶鍂<F0A181B6>?4C8G | 4<EFBFBD>?| 8GB | 800 | 瞼600 |
|
||||
| **<EFBFBD>鞟<EFBFBD><EFBFBD>?* | <20>砌澈<E7A08C>?8C16G | 8<EFBFBD>?| 16GB | 1600 | 瞼1200 |
|
||||
| **开发/测试** | 基础版 1C1G | 1核 | 1GB | 100 | ¥120 |
|
||||
| **初期** | 通用版 2C4G | 2核 | 4GB | 400 | ¥300 |
|
||||
| **成长期** | 通用版 4C8G | 4核 | 8GB | 800 | ¥600 |
|
||||
| **成熟期** | 独享版 8C16G | 8核 | 16GB | 1600 | ¥1200 |
|
||||
|
||||
#### **关键配置**
|
||||
|
||||
@@ -81,12 +92,14 @@ FROM pg_stat_activity
|
||||
GROUP BY datname;
|
||||
```
|
||||
|
||||
**餈墧𦻖瘙㰘恣蝞堒<EFBFBD>撘?*嚗?```
|
||||
**连接池计算公式**:
|
||||
```
|
||||
每实例连接数 = RDS最大连接数 / SAE最大实例数 × 0.8(安全系数)
|
||||
|
||||
蝷箔<EFBFBD>嚗?RDS: 400餈墧𦻖
|
||||
SAE: <20><>憭?0摰硺<E691B0>
|
||||
瘥誩<EFBFBD>靘? 400 / 10 <20> 0.8 = 32餈墧𦻖
|
||||
示例:
|
||||
RDS: 400连接
|
||||
SAE: 最多10实例
|
||||
每实例: 400 / 10 × 0.8 = 32连接
|
||||
```
|
||||
|
||||
---
|
||||
@@ -99,7 +112,9 @@ SAE: 最
|
||||
Bucket名称: aiclinical-prod
|
||||
区域: 华东1(杭州)oss-cn-hangzhou
|
||||
存储类型: 标准存储
|
||||
霈輸䔮<EFBFBD><EFBFBD><EFBFBD>: 蝘<><E89D98>嚗㇊rivate嚗?<3F><>𧋦<EFBFBD>批<EFBFBD>: 撘<><E69298>?頝典<E9A09D>霈曄蔭: <20><>捂<EFBFBD>滨垢<E6BBA8>笔<EFBFBD>
|
||||
访问权限: 私有(Private)
|
||||
版本控制: 开启
|
||||
跨域设置: 允许前端域名
|
||||
```
|
||||
|
||||
#### **目录结构规划**
|
||||
@@ -107,11 +122,13 @@ Bucket名称: aiclinical-prod
|
||||
```
|
||||
aiclinical-prod/
|
||||
├── asl/
|
||||
<EFBFBD>? <20>鎿<EFBFBD><E98EBF><EFBFBD> pdfs/ # PDF<EFBFBD><EFBFBD>辣
|
||||
<EFBFBD>? <20>鎿<EFBFBD><E98EBF><EFBFBD> excel/ # Excel<EFBFBD><EFBFBD>辣
|
||||
<EFBFBD>? <20>婙<EFBFBD><E5A999><EFBFBD> exports/ # 撖澆枂<EFBFBD><EFBFBD>辣
|
||||
│ ├── pdfs/ # PDF文件
|
||||
│ ├── excel/ # Excel文件
|
||||
│ └── exports/ # 导出文件
|
||||
├── avatars/ # 用户头像
|
||||
<EFBFBD>鎿<EFBFBD><EFBFBD><EFBFBD> documents/ # <EFBFBD>亥<EFBFBD>摨𤘪<EFBFBD>獢?<3F>婙<EFBFBD><E5A999><EFBFBD> temp/ # 銝湔𧒄<E6B994><F0A79284>辣嚗?憭拙<E686AD><E68B99>芸𢆡<E88AB8>𣳇膄嚗?```
|
||||
├── documents/ # 知识库文档
|
||||
└── temp/ # 临时文件(1天后自动删除)
|
||||
```
|
||||
|
||||
#### **生命周期管理**
|
||||
|
||||
@@ -143,17 +160,20 @@ aiclinical-prod/
|
||||
|
||||
---
|
||||
|
||||
## <EFBFBD>凃 摮睃<E691AE><E79D83>質情撅<E68385>挽霈∴<E99C88><E288B4>詨<EFBFBD>嚗?
|
||||
## 💻 存储抽象层设计(核心)
|
||||
|
||||
### 接口定义
|
||||
|
||||
**文件**:`backend/src/common/storage/StorageAdapter.ts`
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* 摮睃<EFBFBD><EFBFBD>質情撅<EFBFBD>𦻖<EFBFBD>? *
|
||||
* 存储抽象层接口
|
||||
*
|
||||
* @description
|
||||
* - <EFBFBD>舀<EFBFBD><EFBFBD>砍𧑐<EFBFBD><EFBFBD>辣蝟餌<EFBFBD> + <20>輸<EFBFBD>鈭?OSS <20>删<EFBFBD><E588A0><EFBFBD>揢
|
||||
* - <EFBFBD>朞<EFBFBD><EFBFBD>臬<EFBFBD><EFBFBD>㗛<EFBFBD><EFBFBD>批<EFBFBD>摰䂿緵蝐? *
|
||||
* - 支持本地文件系统 + 阿里云 OSS 无缝切换
|
||||
* - 通过环境变量控制实现类
|
||||
*
|
||||
* @example
|
||||
* const storage = StorageFactory.create()
|
||||
* const url = await storage.upload('files/doc.pdf', buffer)
|
||||
@@ -161,7 +181,7 @@ aiclinical-prod/
|
||||
export interface StorageAdapter {
|
||||
/**
|
||||
* 上传文件
|
||||
* @param key 摮睃<EFBFBD><EFBFBD>殷<EFBFBD>頝臬<EFBFBD>嚗㚁<EFBFBD>憒?'asl/pdfs/xxx.pdf'
|
||||
* @param key 存储键(路径),如 'asl/pdfs/xxx.pdf'
|
||||
* @param buffer 文件内容
|
||||
* @returns 访问URL
|
||||
*/
|
||||
@@ -169,18 +189,21 @@ export interface StorageAdapter {
|
||||
|
||||
/**
|
||||
* 下载文件
|
||||
* @param key 摮睃<EFBFBD><EFBFBD>? * @returns <20><>辣<EFBFBD><E8BEA3>捆
|
||||
* @param key 存储键
|
||||
* @returns 文件内容
|
||||
*/
|
||||
download(key: string): Promise<Buffer>
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
* @param key 摮睃<EFBFBD><EFBFBD>? */
|
||||
* @param key 存储键
|
||||
*/
|
||||
delete(key: string): Promise<void>
|
||||
|
||||
/**
|
||||
* 获取访问URL
|
||||
* @param key 摮睃<EFBFBD><EFBFBD>? * @returns 摰峕㟲霈輸䔮URL
|
||||
* @param key 存储键
|
||||
* @returns 完整访问URL
|
||||
*/
|
||||
getUrl(key: string): string
|
||||
|
||||
@@ -205,9 +228,11 @@ import path from 'path'
|
||||
import { StorageAdapter } from './StorageAdapter.js'
|
||||
|
||||
/**
|
||||
* <EFBFBD>砍𧑐<EFBFBD><EFBFBD>辣蝟餌<EFBFBD>摮睃<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>? *
|
||||
* 本地文件系统存储适配器
|
||||
*
|
||||
* @description
|
||||
* - <EFBFBD>其<EFBFBD><EFBFBD>砍𧑐撘<EFBFBD><EFBFBD>𤑳㴓憓? * - <20><>辣摮睃<E691AE><E79D83>?./uploads <20>桀<EFBFBD>
|
||||
* - 用于本地开发环境
|
||||
* - 文件存储在 ./uploads 目录
|
||||
* - 通过 HTTP 访问:http://localhost:3001/uploads/xxx
|
||||
*/
|
||||
export class LocalAdapter implements StorageAdapter {
|
||||
@@ -233,7 +258,8 @@ export class LocalAdapter implements StorageAdapter {
|
||||
async upload(key: string, buffer: Buffer): Promise<string> {
|
||||
const filePath = path.join(this.uploadDir, key)
|
||||
|
||||
// 蝖桐<EFBFBD><EFBFBD>嗥𤌍敶訫<EFBFBD><EFBFBD>? await fs.mkdir(path.dirname(filePath), { recursive: true })
|
||||
// 确保父目录存在
|
||||
await fs.mkdir(path.dirname(filePath), { recursive: true })
|
||||
|
||||
// 写入文件
|
||||
await fs.writeFile(filePath, buffer)
|
||||
@@ -283,7 +309,8 @@ import OSS from 'ali-oss'
|
||||
import { StorageAdapter } from './StorageAdapter.js'
|
||||
|
||||
/**
|
||||
* <EFBFBD>輸<EFBFBD>鈭?OSS 摮睃<E691AE><E79D83><EFBFBD><EFBFBD><EFBFBD>? *
|
||||
* 阿里云 OSS 存储适配器
|
||||
*
|
||||
* @description
|
||||
* - 用于生产环境
|
||||
* - 文件存储在阿里云 OSS
|
||||
@@ -299,7 +326,7 @@ export class OSSAdapter implements StorageAdapter {
|
||||
this.bucket = process.env.OSS_BUCKET!
|
||||
|
||||
if (!this.region || !this.bucket) {
|
||||
throw new Error('OSS<EFBFBD>滨蔭蝻箏仃嚗鐾SS_REGION <EFBFBD>?OSS_BUCKET <EFBFBD>芾挽蝵?)
|
||||
throw new Error('OSS配置缺失:OSS_REGION 或 OSS_BUCKET 未设置')
|
||||
}
|
||||
|
||||
this.client = new OSS({
|
||||
@@ -307,7 +334,8 @@ export class OSSAdapter implements StorageAdapter {
|
||||
accessKeyId: process.env.OSS_ACCESS_KEY_ID!,
|
||||
accessKeySecret: process.env.OSS_ACCESS_KEY_SECRET!,
|
||||
bucket: this.bucket,
|
||||
// 雿輻鍂<EFBFBD><EFBFBD><EFBFBD>endpoint嚗𠄎AE霈輸䔮OSS<EFBFBD>齿<EFBFBD><EFBFBD>讛晶嚗? internal: process.env.NODE_ENV === 'production',
|
||||
// 使用内网endpoint(SAE访问OSS免流量费)
|
||||
internal: process.env.NODE_ENV === 'production',
|
||||
})
|
||||
}
|
||||
|
||||
@@ -336,7 +364,8 @@ export class OSSAdapter implements StorageAdapter {
|
||||
await this.client.delete(key)
|
||||
} catch (error) {
|
||||
console.error('OSS删除失败:', error)
|
||||
// <EFBFBD><EFBFBD>辣銝滚<EFBFBD><EFBFBD>冽𧒄銝齿<EFBFBD><EFBFBD>粹<EFBFBD>霂? }
|
||||
// 文件不存在时不抛出错误
|
||||
}
|
||||
}
|
||||
|
||||
getUrl(key: string): string {
|
||||
@@ -345,7 +374,7 @@ export class OSSAdapter implements StorageAdapter {
|
||||
}
|
||||
|
||||
async uploadMany(files: Array<{ key: string; buffer: Buffer }>): Promise<string[]> {
|
||||
// 撟嗉<EFBFBD>銝𠹺<EFBFBD>嚗<EFBFBD><EFBFBD>憭?0銝芸僎<E88AB8>𡢅<EFBFBD>
|
||||
// 并行上传(最多10个并发)
|
||||
const chunks = []
|
||||
for (let i = 0; i < files.length; i += 10) {
|
||||
chunks.push(files.slice(i, i + 10))
|
||||
@@ -364,7 +393,8 @@ export class OSSAdapter implements StorageAdapter {
|
||||
|
||||
/**
|
||||
* 生成签名URL(临时访问)
|
||||
* @param key 摮睃<EFBFBD><EFBFBD>? * @param expires 餈<><E9A488><EFBFBD>園𡢿嚗<F0A1A2BF><E59A97>嚗㚁<E59A97>暺䁅恕1撠𤩺𧒄
|
||||
* @param key 存储键
|
||||
* @param expires 过期时间(秒),默认1小时
|
||||
*/
|
||||
async getSignedUrl(key: string, expires: number = 3600): Promise<string> {
|
||||
return this.client.signatureUrl(key, { expires })
|
||||
@@ -374,7 +404,8 @@ export class OSSAdapter implements StorageAdapter {
|
||||
|
||||
---
|
||||
|
||||
### StorageFactory 撌亙<EFBFBD>蝐?
|
||||
### StorageFactory 工厂类
|
||||
|
||||
**文件**:`backend/src/common/storage/StorageFactory.ts`
|
||||
|
||||
```typescript
|
||||
@@ -383,11 +414,12 @@ import { LocalAdapter } from './LocalAdapter.js'
|
||||
import { OSSAdapter } from './OSSAdapter.js'
|
||||
|
||||
/**
|
||||
* 摮睃<E691AE>撌亙<E6928C>蝐? *
|
||||
* 存储工厂类
|
||||
*
|
||||
* @description
|
||||
* - 根据环境变量自动选择存储实现
|
||||
* - STORAGE_TYPE=local <EFBFBD>?LocalAdapter
|
||||
* - STORAGE_TYPE=oss <EFBFBD>?OSSAdapter
|
||||
* - STORAGE_TYPE=local → LocalAdapter
|
||||
* - STORAGE_TYPE=oss → OSSAdapter
|
||||
*/
|
||||
export class StorageFactory {
|
||||
private static instance: StorageAdapter | null = null
|
||||
@@ -404,7 +436,7 @@ export class StorageFactory {
|
||||
|
||||
switch (storageType) {
|
||||
case 'oss':
|
||||
console.log('<27>𣑐 雿輻鍂<E8BCBB>輸<EFBFBD>鈭?OSS 摮睃<E691AE>')
|
||||
console.log('📦 使用阿里云 OSS 存储')
|
||||
this.instance = new OSSAdapter()
|
||||
break
|
||||
|
||||
@@ -414,7 +446,7 @@ export class StorageFactory {
|
||||
break
|
||||
|
||||
default:
|
||||
throw new Error(`<EFBFBD>芰䰻<EFBFBD><EFBFBD><EFBFBD><EFBFBD>函掩<EFBFBD>? ${storageType}`)
|
||||
throw new Error(`未知的存储类型: ${storageType}`)
|
||||
}
|
||||
|
||||
return this.instance
|
||||
@@ -457,9 +489,11 @@ export async function uploadPdf(req, res) {
|
||||
// 读取文件内容
|
||||
const buffer = await file.toBuffer()
|
||||
|
||||
// <EFBFBD><EFBFBD><EFBFBD>摮睃<EFBFBD><EFBFBD>? const key = `asl/pdfs/${Date.now()}-${file.filename}`
|
||||
// 生成存储键
|
||||
const key = `asl/pdfs/${Date.now()}-${file.filename}`
|
||||
|
||||
// <EFBFBD>?銝𠹺<E98A9D><F0A0B9BA>啣<EFBFBD><E595A3>剁<EFBFBD><E58981>芸𢆡<E88AB8>寞旿<E5AF9E>臬<EFBFBD><E887AC>㗇𥋘Local<61>𤈛SS嚗? const url = await storage.upload(key, buffer)
|
||||
// ✅ 上传到存储(自动根据环境选择Local或OSS)
|
||||
const url = await storage.upload(key, buffer)
|
||||
|
||||
// 保存到数据库
|
||||
await prisma.aslLiterature.update({
|
||||
@@ -483,12 +517,14 @@ export async function uploadPdf(req, res) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Excel銝𠹺<EFBFBD>嚗<EFBFBD><EFBFBD><EFBFBD><EFBFBD>閬<EFBFBD><EFBFBD><EFBFBD>剁<EFBFBD><EFBFBD>湔𦻖閫<EFBFBD><EFBFBD>嚗? */
|
||||
* Excel上传(不需要存储,直接解析)
|
||||
*/
|
||||
export async function importExcel(req, res) {
|
||||
const file = await req.file()
|
||||
const buffer = await file.toBuffer()
|
||||
|
||||
// <EFBFBD>?<3F>湔𦻖隞𤾸<E99A9E>摮䁅圾<E48185>琜<EFBFBD>銝滩氜<E6BBA9>? const workbook = xlsx.read(buffer, { type: 'buffer' })
|
||||
// ✅ 直接从内存解析,不落盘
|
||||
const workbook = xlsx.read(buffer, { type: 'buffer' })
|
||||
const data = xlsx.utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]])
|
||||
|
||||
// 批量入库
|
||||
@@ -512,16 +548,19 @@ import { PrismaClient } from '@prisma/client'
|
||||
// 环境判断
|
||||
const isProduction = process.env.NODE_ENV === 'production'
|
||||
|
||||
// 霈∠<EFBFBD>餈墧𦻖瘙惩之撠?// <20>煺漣<E785BA>臬<EFBFBD>嚗鑹DS 400餈墧𦻖 / SAE 10摰硺<E691B0> <20> 0.8 = 32餈墧𦻖/摰硺<E691B0>
|
||||
// 撘<EFBFBD><EFBFBD>𤑳㴓憓<EFBFBD><EFBFBD><EFBFBD>砍𧑐 PostgreSQL嚗?餈墧𦻖頞喳<E9A09E>
|
||||
// 计算连接池大小
|
||||
// 生产环境:RDS 400连接 / SAE 10实例 × 0.8 = 32连接/实例
|
||||
// 开发环境:本地 PostgreSQL,5连接足够
|
||||
const connectionLimit = isProduction ? 32 : 5
|
||||
|
||||
/**
|
||||
* Prisma 摰X<EFBFBD>蝡舫<EFBFBD>蝵? */
|
||||
* Prisma 客户端配置
|
||||
*/
|
||||
export const prisma = new PrismaClient({
|
||||
log: isProduction
|
||||
? ['error', 'warn'] // 生产环境只记录错误和警告
|
||||
: ['query', 'error', 'warn'], // 撘<EFBFBD><EFBFBD>𤑳㴓憓<EFBFBD>扇敶閙<EFBFBD><EFBFBD>㗇䰻霂?
|
||||
: ['query', 'error', 'warn'], // 开发环境记录所有查询
|
||||
|
||||
datasources: {
|
||||
db: {
|
||||
url: process.env.DATABASE_URL
|
||||
@@ -530,12 +569,16 @@ export const prisma = new PrismaClient({
|
||||
|
||||
// 关键配置:连接池
|
||||
...(isProduction && {
|
||||
// 隞<EFBFBD>銁<EFBFBD>煺漣<EFBFBD>臬<EFBFBD><EFBFBD>滨蔭餈墧𦻖瘙𣳇<EFBFBD><EFBFBD>? datasources: {
|
||||
// 仅在生产环境配置连接池限制
|
||||
datasources: {
|
||||
db: {
|
||||
url: process.env.DATABASE_URL,
|
||||
// 餈墧𦻖瘙𣳇<EFBFBD>蝵? pool: {
|
||||
timeout: 5, // <20>瑕<EFBFBD>餈墧𦻖頞<F0A6BB96>𧒄嚗<F0A79284><E59A97>嚗? maxsize: connectionLimit, // <20><>憭扯<E686AD><E689AF>交㺭
|
||||
min: 2, // <EFBFBD><EFBFBD>撠譍<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>? }
|
||||
// 连接池配置
|
||||
pool: {
|
||||
timeout: 5, // 获取连接超时(秒)
|
||||
maxsize: connectionLimit, // 最大连接数
|
||||
min: 2, // 最小保持连接
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -543,7 +586,7 @@ export const prisma = new PrismaClient({
|
||||
|
||||
// 优雅关闭
|
||||
process.on('SIGINT', async () => {
|
||||
console.log('<EFBFBD>𣑐 甇<>銁<EFBFBD>喲𡡒<E596B2>唳旿摨栞<E691A8><E6A09E>?..')
|
||||
console.log('📦 正在关闭数据库连接...')
|
||||
await prisma.$disconnect()
|
||||
process.exit(0)
|
||||
})
|
||||
@@ -573,16 +616,18 @@ if (isProduction) {
|
||||
console.error(`⚠️ 数据库连接数过高: ${connectionCount}/${maxConnections}`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('餈墧𦻖<EFBFBD>啁<EFBFBD><EFBFBD>批仃韐?', error)
|
||||
console.error('连接数监控失败:', error)
|
||||
}
|
||||
}, 60000) // 瘥?0蝘埝<E89D98><E59F9D>乩<EFBFBD>甈?}
|
||||
}, 60000) // 每60秒检查一次
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🌍 环境变量管理
|
||||
|
||||
### <EFBFBD>砍𧑐撘<EFBFBD><EFBFBD>𤑳㴓憓?
|
||||
### 本地开发环境
|
||||
|
||||
**文件**:`backend/.env.development`
|
||||
|
||||
```bash
|
||||
@@ -593,13 +638,15 @@ NODE_ENV=development
|
||||
STORAGE_TYPE=local
|
||||
BASE_URL=http://localhost:3001
|
||||
|
||||
# <EFBFBD>唳旿摨?DATABASE_URL=postgresql://postgres:postgres@localhost:5432/aiclinical_dev
|
||||
# 数据库
|
||||
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/aiclinical_dev
|
||||
|
||||
# LLM配置
|
||||
LLM_API_KEY=sk-xxx
|
||||
LLM_BASE_URL=https://api.deepseek.com
|
||||
|
||||
# <EFBFBD>嗡<EFBFBD><EFBFBD>滚𦛚嚗<EFBFBD>𧋦<EFBFBD>唳<EFBFBD><EFBFBD><EFBFBD><EFBFBD>滨蔭嚗?OSS_REGION=
|
||||
# 其他服务(本地无需配置)
|
||||
OSS_REGION=
|
||||
OSS_ACCESS_KEY_ID=
|
||||
OSS_ACCESS_KEY_SECRET=
|
||||
OSS_BUCKET=
|
||||
@@ -609,7 +656,8 @@ OSS_BUCKET=
|
||||
|
||||
### 生产环境配置
|
||||
|
||||
**<EFBFBD>沒AE<EFBFBD>批<EFBFBD><EFBFBD>圈<EFBFBD>蝵殷<EFBFBD>銝滩<EFBFBD><EFBFBD>坔<EFBFBD><EFBFBD><EFBFBD>辣**嚗?
|
||||
**在SAE控制台配置,不要写入文件**:
|
||||
|
||||
```bash
|
||||
# 环境
|
||||
NODE_ENV=production
|
||||
@@ -617,10 +665,12 @@ NODE_ENV=production
|
||||
# 存储配置
|
||||
STORAGE_TYPE=oss
|
||||
OSS_REGION=oss-cn-hangzhou
|
||||
OSS_ACCESS_KEY_ID=LTAI5t***嚗<EFBFBD><EFBFBD>RAM<EFBFBD>冽<EFBFBD><EFBFBD>瑕<EFBFBD>嚗?OSS_ACCESS_KEY_SECRET=***
|
||||
OSS_ACCESS_KEY_ID=LTAI5t***(从RAM用户获取)
|
||||
OSS_ACCESS_KEY_SECRET=***
|
||||
OSS_BUCKET=aiclinical-prod
|
||||
|
||||
# <EFBFBD>唳旿摨?DATABASE_URL=postgresql://aiclinical:***@rm-xxx.mysql.rds.aliyuncs.com:5432/aiclinical_prod
|
||||
# 数据库
|
||||
DATABASE_URL=postgresql://aiclinical:***@rm-xxx.mysql.rds.aliyuncs.com:5432/aiclinical_prod
|
||||
|
||||
# LLM配置
|
||||
LLM_API_KEY=sk-***
|
||||
@@ -651,7 +701,8 @@ COPY prisma ./prisma/
|
||||
# 安装依赖(包括dev依赖,用于构建)
|
||||
RUN npm ci
|
||||
|
||||
# 憭滚<EFBFBD>皞𣂷誨<EFBFBD>?COPY . .
|
||||
# 复制源代码
|
||||
COPY . .
|
||||
|
||||
# 生成 Prisma Client
|
||||
RUN npx prisma generate
|
||||
@@ -668,14 +719,17 @@ WORKDIR /app
|
||||
COPY package*.json ./
|
||||
COPY prisma ./prisma/
|
||||
|
||||
# 隞<EFBFBD><EFBFBD>鋆<EFBFBD><EFBFBD>鈭找<EFBFBD>韏?RUN npm ci --only=production
|
||||
# 仅安装生产依赖
|
||||
RUN npm ci --only=production
|
||||
|
||||
# 生成 Prisma Client
|
||||
RUN npx prisma generate
|
||||
|
||||
# 隞擧<EFBFBD>撱粹𧫴畾萄<EFBFBD><EFBFBD>嗥<EFBFBD>霂穃<EFBFBD><EFBFBD><EFBFBD>誨<EFBFBD>?COPY --from=builder /app/dist ./dist
|
||||
# 从构建阶段复制编译后的代码
|
||||
COPY --from=builder /app/dist ./dist
|
||||
|
||||
# 憭滚<EFBFBD><EFBFBD>蹱<EFBFBD><EFBFBD><EFBFBD>隞塚<EFBFBD>憒<EFBFBD>rompts嚗?COPY prompts ./prompts
|
||||
# 复制静态文件(如prompts)
|
||||
COPY prompts ./prompts
|
||||
COPY config ./config
|
||||
|
||||
# 创建非root用户
|
||||
@@ -688,7 +742,8 @@ USER nodejs
|
||||
# 暴露端口
|
||||
EXPOSE 3001
|
||||
|
||||
# <EFBFBD>亙熒璉<EFBFBD><EFBFBD>?HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
|
||||
# 健康检查
|
||||
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)})"
|
||||
|
||||
# 启动应用
|
||||
@@ -705,7 +760,8 @@ CMD ["node", "dist/index.js"]
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
# PostgreSQL<EFBFBD>唳旿摨? postgres:
|
||||
# PostgreSQL数据库
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
container_name: aiclinical-postgres
|
||||
environment:
|
||||
@@ -754,8 +810,10 @@ volumes:
|
||||
redis-data:
|
||||
```
|
||||
|
||||
**雿輻鍂<EFBFBD>孵<EFBFBD>**嚗?```bash
|
||||
# <20>臬𢆡<E887AC><F0A286A1><EFBFBD>㗇<EFBFBD><E39787>?docker-compose up -d
|
||||
**使用方式**:
|
||||
```bash
|
||||
# 启动所有服务
|
||||
docker-compose up -d
|
||||
|
||||
# 查看日志
|
||||
docker-compose logs -f backend
|
||||
@@ -768,55 +826,69 @@ docker-compose down
|
||||
|
||||
## 🚀 部署流程
|
||||
|
||||
### Step 1: <EFBFBD><EFBFBD><EFBFBD><EFBFBD>輸<EFBFBD>鈭烐<EFBFBD><EFBFBD>?
|
||||
#### 1.1 撘<><E69298>帋<EFBFBD><E5B88B>唳旿摨?RDS
|
||||
### Step 1: 准备阿里云服务
|
||||
|
||||
#### 1.1 开通云数据库 RDS
|
||||
|
||||
```bash
|
||||
# 1. 登录阿里云控制台
|
||||
# 2. 搜索"云数据库 RDS"
|
||||
# 3. 创建实例
|
||||
# - 数据库类型:PostgreSQL 15
|
||||
# - 閫<EFBFBD>聢嚗?<3F>?GB嚗<42><E59A97>𡁶鍂<F0A181B6>页<EFBFBD>
|
||||
# - 摮睃<EFBFBD>蝛粹𡢿嚗?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
|
||||
# - 规格:2核4GB(通用型)
|
||||
# - 存储空间:20GB(SSD)
|
||||
# - 网络:VPC(与SAE同区域)
|
||||
# 4. 设置白名单(添加 SAE 应用的 VPC网段)
|
||||
# 5. 创建数据库用户
|
||||
# 6. 创建数据库:aiclinical_prod
|
||||
```
|
||||
|
||||
#### 1.2 撘<EFBFBD><EFBFBD>𡁜笆鞊∪<EFBFBD><EFBFBD>?OSS
|
||||
#### 1.2 开通对象存储 OSS
|
||||
|
||||
```bash
|
||||
# 1. 搜索"对象存储 OSS"
|
||||
# 2. 创建 Bucket
|
||||
# - Bucket名称:aiclinical-prod
|
||||
# - <EFBFBD>箏<EFBFBD>嚗𡁜<EFBFBD>銝?嚗<>㜺撌痹<E6928C>
|
||||
# - 摮睃<E691AE>蝐餃<E89D90>嚗𡁏<E59A97><F0A1818F><EFBFBD><EFBFBD><EFBFBD>?# - 霈輸䔮<E8BCB8><E494AE><EFBFBD>嚗𡁶<E59A97><F0A181B6>?# 3. <20>𥕦遣 RAM <20>冽<EFBFBD>嚗<EFBFBD>鍂鈭垾PI霈輸䔮嚗?# - <20><><EFBFBD>嚗鋫liyunOSSFullAccess
|
||||
# - <EFBFBD>瑕<EFBFBD> AccessKeyId <20>?AccessKeySecret
|
||||
# - 区域:华东1(杭州)
|
||||
# - 存储类型:标准存储
|
||||
# - 访问权限:私有
|
||||
# 3. 创建 RAM 用户(用于API访问)
|
||||
# - 权限:AliyunOSSFullAccess
|
||||
# - 获取 AccessKeyId 和 AccessKeySecret
|
||||
```
|
||||
|
||||
#### 1.3 撘<EFBFBD><EFBFBD>𡁜捆<EFBFBD>券<EFBFBD><EFBFBD>𤩺<EFBFBD><EFBFBD>?
|
||||
#### 1.3 开通容器镜像服务
|
||||
|
||||
```bash
|
||||
# 1. 搜索"容器镜像服务 ACR"
|
||||
# 2. 创建命名空间:aiclinical
|
||||
# 3. 创建镜像仓库:backend
|
||||
# - 蝐餃<EFBFBD>嚗𡁶<EFBFBD><EFBFBD>?# - <20>啣<EFBFBD>嚗𡁜<E59A97>銝?嚗<>㜺撌痹<E6928C>
|
||||
# - 类型:私有
|
||||
# - 地域:华东1(杭州)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 2: <EFBFBD><EFBFBD>遣<EFBFBD>峕綫<EFBFBD><EFBFBD><EFBFBD><EFBFBD>?
|
||||
### Step 2: 构建和推送镜像
|
||||
|
||||
```bash
|
||||
# 1. <EFBFBD>餃<EFBFBD><EFBFBD>輸<EFBFBD>鈭穃捆<EFBFBD>券<EFBFBD><EFBFBD>𤩺<EFBFBD><EFBFBD>?docker login --username=<雿删<E99BBF><E588A0>輸<EFBFBD>鈭𤏸揭<F0A48FB8>? registry.cn-hangzhou.aliyuncs.com
|
||||
# 1. 登录阿里云容器镜像服务
|
||||
docker login --username=<你的阿里云账号> registry.cn-hangzhou.aliyuncs.com
|
||||
|
||||
# 2. 构建镜像
|
||||
cd backend
|
||||
docker build -t aiclinical-backend:v1.0.0 .
|
||||
|
||||
# 3. <EFBFBD>𤘪<EFBFBD>蝑?docker tag aiclinical-backend:v1.0.0 \
|
||||
# 3. 打标签
|
||||
docker tag aiclinical-backend:v1.0.0 \
|
||||
registry.cn-hangzhou.aliyuncs.com/aiclinical/backend:v1.0.0
|
||||
|
||||
# 4. <EFBFBD>券<EFBFBD><EFBFBD><EFBFBD><EFBFBD>輸<EFBFBD>鈭?docker push registry.cn-hangzhou.aliyuncs.com/aiclinical/backend:v1.0.0
|
||||
# 4. 推送到阿里云
|
||||
docker push registry.cn-hangzhou.aliyuncs.com/aiclinical/backend:v1.0.0
|
||||
```
|
||||
|
||||
**<EFBFBD>𡁏𧋦<EFBFBD>芸𢆡<EFBFBD>?*嚗?
|
||||
**脚本自动化**:
|
||||
|
||||
**文件**:`backend/scripts/build-and-push.sh`
|
||||
|
||||
```bash
|
||||
@@ -833,14 +905,14 @@ fi
|
||||
echo "🔨 构建镜像: $VERSION"
|
||||
docker build -t aiclinical-backend:$VERSION .
|
||||
|
||||
echo "<EFBFBD>噡儭?<3F>𤘪<EFBFBD>蝑?
|
||||
echo "🏷️ 打标签"
|
||||
docker tag aiclinical-backend:$VERSION \
|
||||
registry.cn-hangzhou.aliyuncs.com/aiclinical/backend:$VERSION
|
||||
|
||||
echo "<EFBFBD>㨩 <20>券<EFBFBD><E588B8><EFBFBD><EFBFBD>輸<EFBFBD>鈭?
|
||||
echo "📤 推送到阿里云"
|
||||
docker push registry.cn-hangzhou.aliyuncs.com/aiclinical/backend:$VERSION
|
||||
|
||||
echo "<EFBFBD>?摰峕<E691B0>嚗?
|
||||
echo "✅ 完成!"
|
||||
echo "镜像地址: registry.cn-hangzhou.aliyuncs.com/aiclinical/backend:$VERSION"
|
||||
```
|
||||
|
||||
@@ -855,7 +927,7 @@ echo "镜像地址: registry.cn-hangzhou.aliyuncs.com/aiclinical/backend:$VERSIO
|
||||
应用类型: 容器镜像
|
||||
镜像地址: registry.cn-hangzhou.aliyuncs.com/aiclinical/backend:v1.0.0
|
||||
端口: 3001
|
||||
<EFBFBD>亙熒璉<EFBFBD><EFBFBD>亥楝敺? /health
|
||||
健康检查路径: /health
|
||||
```
|
||||
|
||||
#### 3.2 实例配置
|
||||
@@ -870,7 +942,7 @@ CPU触发扩容: 70%
|
||||
|
||||
#### 3.3 环境变量配置
|
||||
|
||||
<EFBFBD>?SAE <EFBFBD>批<EFBFBD><EFBFBD>圈<EFBFBD>蝵桃㴓憓<EFBFBD><EFBFBD><EFBFBD>𧶏<EFBFBD>**<2A>滩<EFBFBD>嚗?*嚗㚁<E59A97>
|
||||
在 SAE 控制台配置环境变量(**重要!**):
|
||||
|
||||
```bash
|
||||
NODE_ENV=production
|
||||
@@ -888,31 +960,38 @@ LOG_LEVEL=info
|
||||
|
||||
```yaml
|
||||
VPC: 选择与RDS相同的VPC
|
||||
摰匧<EFBFBD>蝏? <20><>捂3001蝡臬藁<E887AC>亦<EFBFBD>
|
||||
<EFBFBD>祉<EFBFBD>霈輸䔮: 撘<EFBFBD><EFBFBD>荔<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>祉<EFBFBD>SLB嚗?```
|
||||
安全组: 允许3001端口入站
|
||||
公网访问: 开启(分配公网SLB)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 4: 部署应用
|
||||
|
||||
```bash
|
||||
# 1. <EFBFBD>?SAE <EFBFBD>批<EFBFBD><EFBFBD>啁<EFBFBD><EFBFBD>?<3F>函蔡摨𠉛鍂"
|
||||
# 1. 在 SAE 控制台点击"部署应用"
|
||||
# 2. 选择镜像版本
|
||||
# 3. 确认配置无误
|
||||
# 4. <EFBFBD>孵稬"蝖桀<E89D96>"撘<>憪钅<E686AA>蝵?
|
||||
# 蝑匧<E89D91>3-5<><35><EFBFBD>嚗峕䰻<E5B395>钅<EFBFBD>蝵脫𠯫敹?```
|
||||
# 4. 点击"确定"开始部署
|
||||
|
||||
**<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
|
||||
# 等待3-5分钟,查看部署日志
|
||||
```
|
||||
|
||||
**部署成功标志**:
|
||||
- ✅ 实例状态:运行中
|
||||
- ✅ 健康检查:通过
|
||||
- ✅ 访问 `http://<SAE公网地址>/health` 返回 200
|
||||
|
||||
---
|
||||
|
||||
### Step 5: 验证部署
|
||||
|
||||
```bash
|
||||
# 1. <EFBFBD>亙熒璉<EFBFBD><EFBFBD>?curl http://<SAE<41>祉<EFBFBD><E7A589>啣<EFBFBD>>/health
|
||||
# 1. 健康检查
|
||||
curl http://<SAE公网地址>/health
|
||||
|
||||
# 憸<><E686B8><EFBFBD>滚<EFBFBD>嚗?{
|
||||
# 预期响应:
|
||||
{
|
||||
"status": "ok",
|
||||
"database": "connected",
|
||||
"timestamp": "2025-11-16T10:30:00.000Z"
|
||||
@@ -922,70 +1001,84 @@ VPC: 选择与RDS相同的VPC
|
||||
curl http://<SAE公网地址>/api/v1/health
|
||||
|
||||
# 3. 查看日志
|
||||
# <20>?SAE <20>批<EFBFBD><E689B9>?<3F>?摨𠉛鍂霂行<E99C82> <20>?摰墧𧒄<E5A2A7>亙<EFBFBD>
|
||||
# 在 SAE 控制台 → 应用详情 → 实时日志
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 成本估算
|
||||
|
||||
### <EFBFBD>脲<EFBFBD><EFBFBD>嗆挾嚗?00<30>冽<EFBFBD>嚗峕𠯫瘣?0嚗?
|
||||
### 初期阶段(100用户,日活20)
|
||||
|
||||
| 服务 | 规格 | 月费 |
|
||||
|------|------|------|
|
||||
| SAE | 1C2G × 1实例 | ¥200 |
|
||||
| RDS | 2C4G <EFBFBD>𡁶鍂<EFBFBD>?| 瞼300 |
|
||||
| RDS | 2C4G 通用版 | ¥300 |
|
||||
| OSS | 100GB标准存储 + 10GB流量 | ¥15 |
|
||||
| ACR | 私有仓库 | ¥0(免费额度) |
|
||||
| **<EFBFBD><EFBFBD>恣** | | **瞼515/<EFBFBD>?* |
|
||||
| **合计** | | **¥515/月** |
|
||||
|
||||
### 成长期(1000用户,日活200)
|
||||
|
||||
### <20>鞾鵭<E99EBE><E9B5AD><EFBFBD>1000<30>冽<EFBFBD>嚗峕𠯫瘣?00嚗?
|
||||
| 服务 | 规格 | 月费 |
|
||||
|------|------|------|
|
||||
| SAE | 2C4G × 平均3实例 | ¥600 |
|
||||
| RDS | 4C8G <EFBFBD>𡁶鍂<EFBFBD>?| 瞼600 |
|
||||
| RDS | 4C8G 通用版 | ¥600 |
|
||||
| OSS | 500GB标准存储 + 50GB流量 | ¥70 |
|
||||
| CDN | 100GB流量(可选) | ¥20 |
|
||||
| **<EFBFBD><EFBFBD>恣** | | **瞼1290/<EFBFBD>?* |
|
||||
| **合计** | | **¥1290/月** |
|
||||
|
||||
### 成熟期(10000用户,日活2000)
|
||||
|
||||
### <20>鞟<EFBFBD><E99E9F><EFBFBD><EFBFBD>10000<30>冽<EFBFBD>嚗峕𠯫瘣?000嚗?
|
||||
| 服务 | 规格 | 月费 |
|
||||
|------|------|------|
|
||||
| SAE | 4C8G × 平均10实例 | ¥2000 |
|
||||
| RDS | 8C16G <EFBFBD>砌澈<EFBFBD>?+ 霂餃<E99C82><E9A483><EFBFBD>氖 | 瞼2000 |
|
||||
| RDS | 8C16G 独享版 + 读写分离 | ¥2000 |
|
||||
| OSS | 2TB标准存储 + 500GB流量 | ¥300 |
|
||||
| CDN | 1TB流量 | ¥200 |
|
||||
| Redis | 2GB<EFBFBD><EFBFBD><EFBFBD><EFBFBD>?| 瞼200 |
|
||||
| **<EFBFBD><EFBFBD>恣** | | **瞼4700/<EFBFBD>?* |
|
||||
| Redis | 2GB标准版 | ¥200 |
|
||||
| **合计** | | **¥4700/月** |
|
||||
|
||||
---
|
||||
|
||||
## <EFBFBD><EFBFBD> <20>烐綉銝𤾸<E98A9D>霅?
|
||||
### 摨𠉛鍂<F0A0899B>扯<EFBFBD><E689AF>烐綉嚗㇁RMS嚗?
|
||||
## 🔍 监控与告警
|
||||
|
||||
### 应用性能监控(ARMS)
|
||||
|
||||
```yaml
|
||||
# <EFBFBD>?SAE <EFBFBD>批<EFBFBD><EFBFBD>啣<EFBFBD><EFBFBD>?ARMS
|
||||
# 在 SAE 控制台开通 ARMS
|
||||
监控指标:
|
||||
- RT(响应时间)
|
||||
- QPS嚗<EFBFBD><EFBFBD>蝘坿窈瘙<EFBFBD>㺭嚗? - <20>躰秤<E8BAB0>? - JVM<56><4D><EFBFBD>
|
||||
- QPS(每秒请求数)
|
||||
- 错误率
|
||||
- JVM内存
|
||||
|
||||
告警规则:
|
||||
- RT > 3蝘? - <20>躰秤<E8BAB0>?> 5%
|
||||
- <EFBFBD>舐鍂<EFBFBD>?< 99%
|
||||
- RT > 3秒
|
||||
- 错误率 > 5%
|
||||
- 可用率 < 99%
|
||||
```
|
||||
|
||||
### <EFBFBD>唳旿摨梶<EFBFBD><EFBFBD>?
|
||||
### 数据库监控
|
||||
|
||||
```yaml
|
||||
# RDS <EFBFBD>批<EFBFBD><EFBFBD>?<3F>?<3F>烐綉銝擧𥁒霅?<3F>烐綉<E78390><E7B689><EFBFBD>:
|
||||
- CPU<EFBFBD>拍鍂<EFBFBD>? - <20><><EFBFBD><EFBFBD>拍鍂<E68B8D>? - 餈墧𦻖<E5A2A7>? - IOPS
|
||||
# RDS 控制台 → 监控与报警
|
||||
监控指标:
|
||||
- CPU利用率
|
||||
- 内存利用率
|
||||
- 连接数
|
||||
- IOPS
|
||||
|
||||
告警规则:
|
||||
- CPU > 80%
|
||||
- 餈墧𦻖<EFBFBD>?> 320嚗?0%嚗? - 蝤<><E89DA4>雿輻鍂<E8BCBB>?> 80%
|
||||
- 连接数 > 320(80%)
|
||||
- 磁盘使用率 > 80%
|
||||
```
|
||||
|
||||
### 成本告警
|
||||
|
||||
```bash
|
||||
# 韐寧鍂銝剖<EFBFBD> <20>?韐寧鍂憸<E98D82>郎
|
||||
# 费用中心 → 费用预警
|
||||
设置预算:
|
||||
- 每月预算: ¥1000
|
||||
- 80%预警: ¥800
|
||||
@@ -1002,27 +1095,34 @@ curl http://<SAE公网地址>/api/v1/health
|
||||
|
||||
**症状**:应用启动失败,日志显示 `Connection refused`
|
||||
|
||||
**閫<EFBFBD><EFBFBD><EFBFBD>寞<EFBFBD>**嚗?```bash
|
||||
# 1. 璉<><E79289>?RDS <20>賢<EFBFBD><E8B3A2>?# 蝖桐<E89D96>瘛餃<E7989B>鈭?SAE 摨𠉛鍂<F0A0899B>?VPC蝵烐挾
|
||||
**解决方案**:
|
||||
```bash
|
||||
# 1. 检查 RDS 白名单
|
||||
# 确保添加了 SAE 应用的 VPC网段
|
||||
|
||||
# 2. 璉<EFBFBD><EFBFBD>?DATABASE_URL <EFBFBD>澆<EFBFBD>
|
||||
# 2. 检查 DATABASE_URL 格式
|
||||
# 正确格式: postgresql://user:pass@host:port/db
|
||||
|
||||
# 3. 璉<EFBFBD><EFBFBD>?RDS 摰硺<E691B0><E7A1BA>嗆<EFBFBD>?# 蝖桐<E89D96>摰硺<E691B0>餈鞱<E9A488>銝?```
|
||||
# 3. 检查 RDS 实例状态
|
||||
# 确保实例运行中
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 问题2:OSS 上传失败
|
||||
|
||||
**<EFBFBD><EFBFBD>𠶖**嚗𡁏<E59A97>隞嗡<E99A9E>隡㰘<E99AA1><E3B098>?403 Forbidden
|
||||
**症状**:文件上传返回 403 Forbidden
|
||||
|
||||
**閫<EFBFBD><EFBFBD><EFBFBD>寞<EFBFBD>**嚗?```bash
|
||||
# 1. 璉<><E79289>?RAM <20>冽<EFBFBD><E586BD><EFBFBD><EFBFBD>
|
||||
# 蝖桐<E89D96><E6A190>?AliyunOSSFullAccess
|
||||
**解决方案**:
|
||||
```bash
|
||||
# 1. 检查 RAM 用户权限
|
||||
# 确保有 AliyunOSSFullAccess
|
||||
|
||||
# 2. 璉<EFBFBD><EFBFBD>?Bucket <EFBFBD><EFBFBD><EFBFBD>
|
||||
# 蝖桐<EFBFBD>摨𠉛鍂<EFBFBD>匧<EFBFBD><EFBFBD>交<EFBFBD><EFBFBD>?
|
||||
# 3. 璉<><E79289>亦㴓憓<E3B493><E68693><EFBFBD>?# OSS_ACCESS_KEY_ID <20>?OSS_ACCESS_KEY_SECRET <20>臬炏甇<E7828F>&
|
||||
# 2. 检查 Bucket 权限
|
||||
# 确保应用有写入权限
|
||||
|
||||
# 3. 检查环境变量
|
||||
# OSS_ACCESS_KEY_ID 和 OSS_ACCESS_KEY_SECRET 是否正确
|
||||
```
|
||||
|
||||
---
|
||||
@@ -1031,13 +1131,15 @@ curl http://<SAE公网地址>/api/v1/health
|
||||
|
||||
**症状**:`Connection pool exhausted`
|
||||
|
||||
**閫<EFBFBD><EFBFBD><EFBFBD>寞<EFBFBD>**嚗?```typescript
|
||||
**解决方案**:
|
||||
```typescript
|
||||
// 1. 检查当前连接数
|
||||
const result = await prisma.$queryRaw`
|
||||
SELECT count(*) FROM pg_stat_activity
|
||||
`
|
||||
|
||||
// 2. 靚<EFBFBD>㟲餈墧𦻖瘙𣳇<EFBFBD>蝵?// <20>誩<EFBFBD>瘥誩<E798A5>靘贝<E99D98><E8B49D>交㺭嚗峕<E59A97>憓𧼮<E68693> RDS 閫<>聢
|
||||
// 2. 调整连接池配置
|
||||
// 减少每实例连接数,或增加 RDS 规格
|
||||
|
||||
// 3. 检查是否有连接泄漏
|
||||
// 确保所有查询都正确关闭
|
||||
@@ -1047,7 +1149,7 @@ const result = await prisma.$queryRaw`
|
||||
|
||||
## 📝 更新日志
|
||||
|
||||
| <EFBFBD>交<EFBFBD> | <20><>𧋦 | <20>䀹凒<E480B9><E58792>捆 | 蝏湔擪<E6B994>?|
|
||||
| 日期 | 版本 | 变更内容 | 维护者 |
|
||||
|------|------|---------|--------|
|
||||
| 2025-11-16 | V1.0 | 创建文档,定义云原生部署架构 | 架构团队 |
|
||||
|
||||
@@ -1056,11 +1158,13 @@ const result = await prisma.$queryRaw`
|
||||
## 📚 相关文档
|
||||
|
||||
- [前后端模块化架构设计-V2](../00-系统总体设计/前后端模块化架构设计-V2.md) - 架构总纲
|
||||
- [鈭穃<EFBFBD><EFBFBD>笔<EFBFBD><EFBFBD>𤏸<EFBFBD><EFBFBD><EFBFBD>(../04-撘<><E69298>𤏸<EFBFBD><F0A48FB8>?08-鈭穃<E988AD><E7A983>笔<EFBFBD><E7AC94>𤏸<EFBFBD><F0A48FB8>?md) - DO/DON'T 璉<EFBFBD><EFBFBD>交<EFBFBD><EFBFBD>?- [Schema<6D>𠉛氖<F0A0899B>嗆<EFBFBD>霈曇恣](./01-Schema<6D>𠉛氖<F0A0899B>嗆<EFBFBD>霈曇恣嚗?0銝迎<E98A9D>.md)
|
||||
- [<EFBFBD>唳旿摨栞<EFBFBD><EFBFBD>仿<EFBFBD>蝵孫(./02-<2D>唳旿摨栞<E691A8><E6A09E>仿<EFBFBD>蝵?md)
|
||||
- [云原生开发规范](../04-开发规范/08-云原生开发规范.md) - DO/DON'T 检查清单
|
||||
- [Schema隔离架构设计](./01-Schema隔离架构设计(10个).md)
|
||||
- [数据库连接配置](./02-数据库连接配置.md)
|
||||
|
||||
---
|
||||
|
||||
**文档维护者:** 架构团队
|
||||
**最后更新:** 2025-11-16
|
||||
**<EFBFBD><EFBFBD>﹝<EFBFBD>嗆<EFBFBD><EFBFBD><EFBFBD>** <20>?撌脣<E6928C><E884A3>?
|
||||
**文档状态:** ✅ 已完成
|
||||
|
||||
|
||||
Reference in New Issue
Block a user