Files
AIclinicalresearch/docs/05-部署文档/_archive-2025首次部署/07-关键配置补充说明.md
HaHafeng 6124c7abc6 docs(platform): Add database documentation system and restructure deployment docs
Completed:
- Add 6 core database documents (docs/01-平台基础层/07-数据库/)
  Architecture overview, migration history, environment comparison,
  tech debt tracking, seed data management, PostgreSQL extensions
- Restructure deployment docs: archive 20 legacy files to _archive-2025/
- Create unified daily operations manual (01-日常更新操作手册.md)
- Add pending deployment change tracker (03-待部署变更清单.md)
- Update database development standard to v3.0 (three iron rules)
- Fix Prisma schema type drift: align @db.* annotations with actual DB
  IIT: UUID/Timestamptz(6), SSA: Timestamp(6)/VarChar(20/50/100)
- Add migration: 20260227_align_schema_with_db_types (idempotent ALTER)
- Add Cursor Rule for auto-reminding deployment change documentation
- Update system status guide v6.4 with deployment and DB doc references
- Add architecture consultation docs (Prisma guide, SAE deployment guide)

Technical details:
- Manual migration due to shadow DB limitation (TD-001 in tech debt)
- Deployment docs reduced from 20+ scattered files to 3 core documents
- Cursor Rule triggers on schema.prisma, package.json, Dockerfile changes

Made-with: Cursor
2026-02-27 14:35:25 +08:00

1029 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 关键配置补充说明 - 部署文档勘误与增强
> **文档版本:** v1.0
> **创建日期:** 2025-12-14
> **文档性质:** 对5个独立部署文档的关键补充
> **优先级:** ⭐⭐⭐⭐⭐ 必读包含3个P0/P1致命问题
---
## 📋 文档说明
本文档基于对5个独立部署文档的深度审查补充了**3个致命问题**和**若干最佳实践**。这些内容在原文档中遗漏或未充分强调,但对生产环境部署至关重要。
**请在部署前务必阅读本文档!**
---
## 🚨 致命问题修正P0/P1
### 1. SAE孤岛效应 - NAT网关配置 ⭐⭐⭐⭐⭐
**问题严重度P0致命**
#### 问题描述
```
SAE部署在VPC内默认没有公网出口
影响场景:
❌ 后端调用 DeepSeek/OpenAI API → 超时
❌ Python下载公网PDF → 超时
❌ npm install公网依赖构建时→ 失败
结果所有AI功能不可用系统基本瘫痪
```
#### 解决方案
**方案ANAT网关推荐生产环境**
```bash
# 步骤1创建NAT网关
阿里云控制台 > VPC > NAT网关 > 创建NAT网关
├─ VPC选择SAE所在的VPC
├─ 交换机选择SAE所在的交换机
├─ 规格:小型(够用)
└─ 计费方式:按使用量计费
# 步骤2创建并绑定EIP
NAT网关详情 > 弹性公网IP > 绑定弹性公网IP
├─ 创建新EIP或选择已有EIP
├─ 带宽:按使用流量(成本低)
└─ 确认绑定
# 步骤3配置SNAT条目
NAT网关详情 > SNAT管理 > 创建SNAT条目
├─ 选择交换机SAE所在的交换机如 vsw-xxxxx
├─ 选择公网IP刚才绑定的EIP
└─ 确认创建
成本NAT网关¥60/月 + EIP流量费¥30-50/月 = ¥90-110/月
```
**方案BSAE绑定公网IP部分地域支持**
```bash
SAE控制台 > 应用配置 > 网络配置
└─ 查看是否有"公网访问""绑定EIP"选项
⚠️ 注意:
- 并非所有地域都支持
- 优先使用方案A更稳定
```
#### 验证NAT网关是否生效
```bash
# 方法1在SAE应用日志中查看
# 应用启动后查看是否有DeepSeek API调用成功的日志
# 方法2通过云助手执行命令SAE控制台 > 实例列表 > 登录实例)
curl -I https://api.deepseek.com
# 应该返回 200 OK而不是超时
# 方法3测试Python下载公网PDF
curl -I https://arxiv.org/pdf/2301.00001.pdf
# 应该返回 200 OK
```
#### 更新的文档
-`00-部署架构总览.md`物理架构图已增加NAT网关
-`00-部署架构总览.md`成本估算已更新¥1,200-1,250/月)
- ⚠️ `05-Node.js后端-SAE容器部署指南.md`:需要在"SAE应用配置"章节增加网络配置说明
- ⚠️ `04-Python微服务-SAE容器部署指南.md`:同上
---
### 2. 部署依赖死锁 - Dify API Key鸡生蛋问题 ⭐⭐⭐⭐⭐
**问题严重度P1严重**
#### 问题描述
```
死锁链:
1. 后端启动需要 DIFY_API_KEY
2. DIFY_API_KEY 需要 Dify 启动并人工登录后才能生成
3. 后端如果健康检查失败,会无限重启
结果后端无法启动或启动后PKB模块不可用
```
#### 解决方案(分阶段部署)
**阶段1首次部署后端临时配置**
```bash
# SAE环境变量配置
DIFY_API_KEY=temp_placeholder_will_update_later
# ⚠️ 重要:后端代码需要容错处理
# backend/src/common/rag/DifyClient.ts
constructor() {
const apiKey = process.env.DIFY_API_KEY
if (!apiKey || apiKey === 'temp' || apiKey.startsWith('temp_')) {
console.warn('⚠️ Dify API Key未配置PKB模块将不可用')
this.enabled = false
return
}
this.client = new DifySDK(apiKey)
this.enabled = true
}
// 所有Dify调用前检查
async createDataset(name: string) {
if (!this.enabled) {
throw new Error('Dify服务未配置请先配置DIFY_API_KEY环境变量')
}
// ... 正常逻辑
}
```
**阶段2部署Dify并获取真实Key**
```bash
# 1. 部署Dify到ECS参考 03-Dify-ECS部署完全指南.md
cd /opt/dify
docker-compose up -d
# 2. 等待服务启动约2-3分钟
docker-compose logs -f api
# 3. 浏览器访问 http://ECS公网IP
# 4. 注册管理员账号(首次访问会提示)
# 5. 创建API Key
# 设置 > API密钥 > 创建密钥 > 复制
# 格式app-xxxxxxxxxxxxxxxxxxxxx
# 6. 记录API Key妥善保存
DIFY_API_KEY=app-xxxxxxxxxxxxxxxxxxxxx
```
**阶段3更新后端配置**
```bash
# SAE控制台 > 应用详情 > 环境变量
# 找到 DIFY_API_KEY修改为真实值
DIFY_API_KEY=app-xxxxxxxxxxxxxxxxxxxxx
# 保存 > 重启应用
# SAE会执行滚动重启零停机
```
**阶段4验证PKB功能**
```bash
# 测试知识库创建
curl -X POST https://your-api.com/api/v1/pkb/knowledge-bases \
-H "Authorization: Bearer YOUR_USER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"测试知识库","description":"测试"}'
# 应该返回 200 OK而不是 "Dify服务未配置" 错误
```
#### 更新的文档
-`00-部署架构总览.md`:部署顺序已更新,明确分阶段部署
- ⚠️ `05-Node.js后端-SAE容器部署指南.md`:需要在"环境变量配置"章节增加临时配置说明
- ⚠️ `03-Dify-ECS部署完全指南.md`:需要在"首次访问"章节增加API Key生成步骤
---
### 3. HTTP Client超时配置 - 防止连接泄漏 ⭐⭐⭐⭐
**问题严重度P1严重**
#### 问题描述
```
Python服务处理PDF/OCR可能需要60-120秒
如果后端HTTP Client没有设置超时会导致
❌ 连接数堆积
❌ 后端实例内存耗尽
❌ 数据库连接池耗尽
```
#### 解决方案
**后端HTTP Client配置**
```typescript
// backend/src/common/http/httpClient.ts
import axios from 'axios'
export const pythonServiceClient = axios.create({
baseURL: process.env.EXTRACTION_SERVICE_URL || 'http://localhost:8000',
timeout: 120000, // ⚠️ 120秒2分钟
timeoutErrorMessage: 'Python微服务响应超时>2分钟',
headers: {
'Content-Type': 'application/json'
}
})
// 请求拦截器(可选,用于日志)
pythonServiceClient.interceptors.request.use(
(config) => {
console.log(`[HTTP] 调用Python服务: ${config.method?.toUpperCase()} ${config.url}`)
return config
},
(error) => Promise.reject(error)
)
// 响应拦截器(错误处理)
pythonServiceClient.interceptors.response.use(
(response) => response,
(error) => {
if (error.code === 'ECONNABORTED') {
console.error('[HTTP] Python服务超时:', error.message)
}
return Promise.reject(error)
}
)
```
**Dify Client配置**
```typescript
// backend/src/common/rag/DifyClient.ts
import axios from 'axios'
const difyHttpClient = axios.create({
baseURL: process.env.DIFY_API_URL || 'http://localhost/v1',
timeout: 60000, // ⚠️ 60秒Dify响应较快
headers: {
'Authorization': `Bearer ${process.env.DIFY_API_KEY}`,
'Content-Type': 'application/json'
}
})
```
#### 超时时间建议
| 服务 | 超时时间 | 理由 |
|------|---------|------|
| **Python微服务** | 120秒 | PDF解析Nougat OCR可能需要60-120秒 |
| **Dify API** | 60秒 | RAG检索通常<10秒60秒足够 |
| **外部LLM API** | 60秒 | DeepSeek/OpenAI流式响应60秒足够 |
| **数据库查询** | 30秒 | Prisma默认复杂查询可能需要10-20秒 |
#### 更新的文档
- ⚠️ `05-Node.js后端-SAE容器部署指南.md`:需要在"代码准备"章节增加HTTP Client配置
---
## ⚠️ 重要安全配置
### 4. ECS端口安全 - Redis/Weaviate不对外开放 ⭐⭐⭐⭐⭐
**问题严重度P0致命安全风险**
#### 问题描述
```
Dify的Redis6379和Weaviate8080如果对公网开放
❌ Redis无密码可被攻击者直接访问
❌ Weaviate包含敏感的向量数据
❌ 可能被用于DDoS攻击的跳板
```
#### 正确配置
**docker-compose.yaml 端口配置**
```yaml
services:
# ❌ 错误示例(危险)
redis:
ports:
- "6379:6379" # 对所有网卡开放,包括公网!
# ✅ 正确配置
redis:
image: redis:6-alpine
ports:
- "127.0.0.1:6379:6379" # 只监听 localhost
restart: always
volumes:
- ./volumes/redis/data:/data
command: redis-server --save 60 1 --loglevel warning
# ✅ 正确配置
weaviate:
image: semitechnologies/weaviate:1.19.0
ports:
- "127.0.0.1:8080:8080" # 只监听 localhost
restart: always
environment:
- QUERY_DEFAULTS_LIMIT=25
- AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED=true
- PERSISTENCE_DATA_PATH=/var/lib/weaviate
# ✅ 只有Nginx需要对外VPC内网
nginx:
image: nginx:latest
ports:
- "80:80" # 对VPC内网开放不是公网
restart: always
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
depends_on:
- api
- web
```
#### ECS安全组配置
```bash
# 安全组规则ECS控制台 > 安全组 > 配置规则)
入方向规则:
├─ 允许 80/TCP 来源VPC网段172.16.0.0/12 # Nginx
├─ 允许 22/TCP 来源您的办公室IP # SSH管理
└─ 拒绝 所有 来源0.0.0.0/0 # 默认拒绝
出方向规则:
└─ 允许 所有 目标0.0.0.0/0 # 允许访问公网
```
#### 验证安全配置
```bash
# 从公网测试(应该失败)
telnet ECS公网IP 6379
# 应该超时或拒绝连接
telnet ECS公网IP 8080
# 应该超时或拒绝连接
# 从VPC内测试应该成功
# 在SAE应用中执行
curl http://172.16.x.x # Dify内网地址
# 应该返回 Dify 的响应
```
#### 更新的文档
- ⚠️ `03-Dify-ECS部署完全指南.md`:需要在"docker-compose.yaml配置"章节强调端口安全
---
### 5. Nginx client_max_body_size - 支持大文件上传 ⭐⭐⭐⭐
**问题严重度P2一般**
#### 问题描述
```
医疗PDF可能很大10-50MB
Nginx默认限制1MB
结果:用户上传大文件时返回 413 Request Entity Too Large
```
#### 解决方案
**前端Nginx配置**
```nginx
# frontend-v2/nginx.conf.template
http {
# ⚠️ 新增:支持大文件上传
client_max_body_size 50M;
# ⚠️ 新增开启gzipReact大体积JS
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
gzip_min_length 1000;
gzip_comp_level 6;
server {
listen 8080;
server_name _;
# 根目录
root /usr/share/nginx/html;
index index.html;
# API反向代理
location /api/ {
proxy_pass http://${BACKEND_SERVICE_HOST}:${BACKEND_SERVICE_PORT}/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# ⚠️ 新增:代理超时配置
proxy_connect_timeout 120s;
proxy_send_timeout 120s;
proxy_read_timeout 120s;
}
# SPA路由
location / {
try_files $uri $uri/ /index.html;
}
# 健康检查
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
}
}
```
#### 更新的文档
- ⚠️ `06-前端Nginx-SAE容器部署指南.md`:需要在"nginx.conf.template"章节增加配置
---
### 6. Python Workers限制 - 防止OOM ⭐⭐⭐⭐⭐
**问题严重度P1严重**
#### 问题描述
```
PyMuPDF/Nougat OCR非常吃内存单个请求可能占用500MB-1GB
SAE配置2GB内存
如果Gunicorn workers过多会导致OOMOut of Memory
```
#### 解决方案
**Dockerfile配置**
```dockerfile
# extraction_service/Dockerfile
# 运行阶段
FROM python:3.11-slim
# ... 其他配置 ...
# ⚠️ 关键限制workers防止OOM
CMD ["gunicorn", "app.main:app", \
"--workers", "2", \
"--worker-class", "uvicorn.workers.UvicornWorker", \
"--bind", "0.0.0.0:8000", \
"--timeout", "120", \
"--max-requests", "100", \
"--max-requests-jitter", "10", \
"--access-logfile", "-", \
"--error-logfile", "-"]
# workers=2: 最多2个worker2GB内存限制
# timeout=120: 单个请求最多120秒OCR可能很慢
# max-requests=100: 100个请求后重启worker防止内存泄漏
```
**SAE配置**
```bash
# SAE控制台 > 应用配置 > 实例规格
CPU: 1核
内存: 2GB # ⚠️ 不要低于2GB
# 实例数量
最小实例数: 1
最大实例数: 3根据流量自动扩容
```
#### Workers数量计算公式
```python
# 经验公式
workers = (CPU核数 × 2) + 1
# 但对于内存密集型应用如PDF解析
workers = min((内存GB / 单worker内存GB), (CPU核数 × 2) + 1)
# 示例SAE 1核2GB
单worker内存 800MBPyMuPDF + Nougat
workers = min(2GB / 0.8GB, 1×2+1) = min(2.5, 3) = 2
# 结论workers=2 是安全值
```
#### 监控OOM
```bash
# SAE控制台 > 监控 > 内存使用率
# 如果经常达到90%+,说明需要:
# 1. 减少workers从2降到1
# 2. 增加内存从2GB升到4GB
# 3. 优化代码(减少内存占用)
```
#### 更新的文档
- ⚠️ `04-Python微服务-SAE容器部署指南.md`:需要在"Dockerfile"章节强调workers限制
---
## 📖 开发调试最佳实践
### 7. SSH隧道 - 本地直连RDS数据库 ⭐⭐⭐⭐
**用途:开发便利性(非必需,但强烈推荐)**
#### 场景
```
开发人员需要用Navicat/DBeaver查看RDS数据
但RDS只允许VPC内网访问
解决通过ECS作为跳板机建立SSH隧道
```
#### 操作步骤
**步骤1确保ECS有SSH访问权限**
```bash
# 本地生成SSH密钥如果还没有
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
# 将公钥添加到ECS
# ECS控制台 > 实例 > 远程连接 > 重置密钥对
# 或者手动添加到 ~/.ssh/authorized_keys
```
**步骤2建立SSH隧道**
```bash
# 格式
ssh -N -L 本地端口:RDS内网地址:RDS端口 root@ECS公网IP -i 密钥文件
# 示例
ssh -N -L 5433:rm-bp1xxxxx.pg.rds.aliyuncs.com:5432 \
root@120.55.xx.xx \
-i ~/.ssh/dify-ecs.pem
# 参数说明:
# -N: 不执行远程命令,只建立隧道
# -L: 本地端口转发
# 5433: 本地监听端口避免与本地PostgreSQL 5432冲突
# rm-bp1xxxxx...: RDS内网地址
# 5432: RDS端口
```
**步骤3Navicat连接**
```
连接类型PostgreSQL
主机localhost
端口5433
用户名aiclinical_rw
密码RDS密码
数据库ai_clinical_research
测试连接 → 成功!
```
**步骤4后台运行隧道可选**
```bash
# 方法1nohup后台运行
nohup ssh -N -L 5433:rm-xxxxx.pg.rds.aliyuncs.com:5432 \
root@ECS-IP -i key.pem > /dev/null 2>&1 &
# 方法2创建systemd服务Linux
# /etc/systemd/system/rds-tunnel.service
[Unit]
Description=SSH Tunnel to RDS
After=network.target
[Service]
Type=simple
User=your-user
ExecStart=/usr/bin/ssh -N -L 5433:rm-xxxxx.pg.rds.aliyuncs.com:5432 root@ECS-IP -i /home/your-user/.ssh/key.pem
Restart=always
[Install]
WantedBy=multi-user.target
# 启动服务
sudo systemctl start rds-tunnel
sudo systemctl enable rds-tunnel
```
#### 安全注意事项
```
⚠️ 不要将SSH密钥提交到Git
⚠️ 定期轮换ECS的SSH密钥
⚠️ 只在开发环境使用生产环境通过VPN访问
```
---
### 8. 时区统一配置 - 防止日志时间混乱 ⭐⭐⭐⭐⭐
**问题严重度P2重要**
#### 问题描述
```
不同服务的时区不一致会导致:
❌ 日志时间对不上前端14:00后端06:00
❌ pg-boss定时任务在错误时间触发
❌ 用户看到的时间戳错误
❌ 排查故障极为痛苦
```
#### 解决方案
**所有服务统一使用 `Asia/Shanghai` 时区**
```dockerfile
# backend/Dockerfile - Node.js后端
FROM node:22-alpine
RUN apk add --no-cache tzdata
ENV TZ=Asia/Shanghai
# ... 其他配置
# extraction_service/Dockerfile - Python微服务
FROM python:3.11-slim
RUN apt-get update && apt-get install -y tzdata
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# ... 其他配置
# frontend-v2/Dockerfile - 前端(已配置)
FROM nginx:1.25-alpine
RUN apk add --no-cache tzdata
ENV TZ=Asia/Shanghai
# ... 其他配置
```
```sql
-- RDS PostgreSQL 时区配置
-- RDS控制台 > 参数设置 > timezone
timezone = Asia/Shanghai
```
**验证时区:**
```bash
# 查看容器时区
docker exec <container-id> date
# 应该显示Sat Dec 14 14:30:00 CST 2024
# 查看RDS时区
psql -c "SHOW timezone;"
# 应该显示Asia/Shanghai
```
#### 影响范围
- ✅ Node.js后端需要更新Dockerfile
- ✅ Python微服务需要更新Dockerfile
- ✅ 前端Nginx已正确配置
- ✅ RDS PostgreSQL需要修改参数
#### 修复步骤
```bash
# 1. 修改Node.js后端Dockerfile
cd backend
# 在Dockerfile中添加时区配置见上方示例
# 2. 修改Python微服务Dockerfile
cd extraction_service
# 在Dockerfile中添加时区配置见上方示例
# 3. 修改RDS时区
# RDS控制台 > 参数设置 > timezone > Asia/Shanghai
# 需要重启RDS实例
# 4. 验证
docker exec backend-container date
docker exec python-container date
psql -h rds-host -c "SHOW timezone;"
```
---
### 9. 镜像拉取策略 - 防止代码不更新 ⭐⭐⭐⭐⭐
**问题严重度P2重要**
#### 问题描述
```
场景:
开发者修改代码 → 构建镜像 → 推送到ACR覆盖v1.0.0
→ SAE部署 → 发现代码没更新???
原因:
SAE默认镜像拉取策略可能是 IfNotPresent
如果本地已有 v1.0.0,不会重新拉取
```
#### 解决方案
**方案A每次部署使用新版本号强烈推荐**
```bash
# 使用语义化版本号
v1.0.0 → v1.0.1 → v1.0.2 ...
# 或使用时间戳
v20251214-1430 → v20251214-1530 ...
# 或使用Git SHA
v-a1b2c3d → v-b2c3d4e ...
# 构建示例
docker build -t backend:v1.0.1 .
docker tag backend:v1.0.1 registry.cn-hangzhou.aliyuncs.com/aiclinical/backend:v1.0.1
docker push registry.cn-hangzhou.aliyuncs.com/aiclinical/backend:v1.0.1
# SAE部署时选择新版本号
```
**方案B配置SAE镜像拉取策略测试环境**
```bash
# SAE控制台 > 应用配置 > 镜像设置
镜像拉取策略Always
# ⚠️ 注意:
# - 每次重启都会拉取镜像(启动稍慢)
# - 适合测试环境,不推荐生产环境
```
#### 最佳实践
| 环境 | 推荐方案 | 理由 |
|------|---------|------|
| **生产环境** | 方案A版本号管理 | 版本可追溯,稳定 |
| **测试环境** | 方案BAlways拉取 | 始终最新,方便 |
| **开发环境** | 方案A | 避免混乱 |
**❌ 不要:**
```bash
# 始终使用 latest 标签(无法追溯版本)
docker tag backend:latest ...
```
---
### 10. Python服务内存管理 - 防止OOM ⭐⭐⭐⭐
**问题严重度P2重要**
#### 问题描述
```
Python服务PyMuPDF/Nougat内存密集容易OOM
❌ 单个PDF OCR可能占用500MB-1GB内存
❌ 多个并发请求会导致内存溢出
❌ SAE默认2GB内存可能不够
```
#### 解决方案
**规格建议:**
| 场景 | CPU | 内存 | Workers | 适用情况 |
|------|-----|------|---------|---------|
| **基础版** | 1核 | 2GB | 2 | 简单PDF解析 |
| **标准版** | 2核 | 4GB | 3 | 包含OCRNougat |
| **增强版** | 2核 | 8GB | 4 | 大量OCR + 高并发 |
**Dockerfile优化已应用**
```dockerfile
# extraction_service/Dockerfile
CMD ["gunicorn", "main:app", \
"--bind", "0.0.0.0:8000", \
"--workers", "2", \ # ⚠️ 限制并发
"--timeout", "120", \ # ⚠️ 2分钟超时
"--max-requests", "100", \ # ⚠️ 处理100个请求后重启worker
"--max-requests-jitter", "10"] # ⚠️ 随机抖动,避免同时重启
```
**监控与告警:**
```bash
# SAE控制台 > 监控告警 > 创建告警规则
指标:内存使用率
阈值:> 80%
动作:发送通知 + 自动扩容(可选)
```
#### 如果遇到OOM
**方案1升级内存推荐**
```bash
# SAE控制台 > 应用配置 > 规格调整
1核2GB → 2核4GB增加¥100/月)
```
**方案2限制并发临时**
```dockerfile
# 修改Dockerfile
CMD ["gunicorn", "main:app", \
"--workers", "1", \ # 降低并发
"--threads", "2"] # 使用线程而非进程
```
---
### 11. OSS签名URL - 安全的文件访问 ⭐⭐⭐⭐
**用途:安全最佳实践**
#### 问题
```
如果OSS Bucket设置为Public
❌ 任何人都可以访问所有文件
❌ 无法追踪谁访问了哪些文件
❌ 无法控制访问时效
```
#### 解决方案
**OSS Bucket配置**
```bash
# OSS控制台 > Bucket列表 > aiclinical-data-prod
# 读写权限私有Private
```
**后端生成签名URL**
```typescript
// backend/src/common/storage/OSSAdapter.ts
import OSS from 'ali-oss'
export class OSSAdapter {
private client: OSS
constructor() {
this.client = new OSS({
region: process.env.OSS_REGION!,
accessKeyId: process.env.OSS_ACCESS_KEY_ID!,
accessKeySecret: process.env.OSS_ACCESS_KEY_SECRET!,
bucket: process.env.OSS_BUCKET!
})
}
/**
* 生成签名URL1小时有效期
*/
async getSignedUrl(objectKey: string, expiresIn: number = 3600): Promise<string> {
try {
const url = this.client.signatureUrl(objectKey, {
expires: expiresIn, // 秒默认1小时
method: 'GET'
})
return url
} catch (error) {
console.error('[OSS] 生成签名URL失败:', error)
throw error
}
}
/**
* 上传文件
*/
async uploadFile(objectKey: string, filePath: string): Promise<string> {
try {
const result = await this.client.put(objectKey, filePath)
console.log(`[OSS] 文件上传成功: ${objectKey}`)
// 返回签名URL不是公开URL
return this.getSignedUrl(objectKey)
} catch (error) {
console.error('[OSS] 文件上传失败:', error)
throw error
}
}
}
```
**API返回签名URL**
```typescript
// backend/src/modules/pkb/documentController.ts
router.get('/documents/:id/download', async (req, res) => {
const { id } = req.params
// 1. 查询文档元数据
const document = await prisma.document.findUnique({
where: { id }
})
if (!document) {
return res.status(404).json({ error: '文档不存在' })
}
// 2. 权限校验(确保用户有权访问)
if (document.userId !== req.user.id) {
return res.status(403).json({ error: '无权访问此文档' })
}
// 3. 生成签名URL1小时有效
const ossAdapter = new OSSAdapter()
const signedUrl = await ossAdapter.getSignedUrl(document.ossKey, 3600)
// 4. 返回签名URL
res.json({
url: signedUrl,
expiresIn: 3600,
filename: document.filename
})
})
```
**前端使用签名URL**
```typescript
// frontend-v2/src/api/documents.ts
export async function downloadDocument(documentId: string) {
// 1. 调用后端API获取签名URL
const response = await fetch(`/api/v1/pkb/documents/${documentId}/download`, {
headers: {
'Authorization': `Bearer ${getToken()}`
}
})
const { url, filename } = await response.json()
// 2. 使用签名URL下载文件
const link = document.createElement('a')
link.href = url // 签名URL1小时有效
link.download = filename
link.click()
}
```
#### 优势
```
✅ 安全只有授权用户才能获取签名URL
✅ 时效URL自动过期1小时后失效
✅ 审计:可以记录谁访问了哪些文件
✅ 灵活:可以动态调整过期时间
```
---
## 📝 总结
### 必须立即修复的问题
| # | 问题 | 严重度 | 影响 | 修复时间 |
|---|------|--------|------|---------|
| 1 | **NAT网关缺失** | P0 | 所有AI功能不可用 | 15分钟 |
| 2 | **Dify API Key死锁** | P1 | PKB模块不可用 | 10分钟分阶段部署 |
| 3 | **HTTP超时未配置** | P1 | 连接泄漏,系统崩溃 | 5分钟代码修改 |
| 4 | **ECS端口对外开放** | P0 | 安全风险,可被攻击 | 5分钟docker-compose修改 |
| 5 | **Python Workers过多** | P1 | OOM服务崩溃 | 2分钟Dockerfile修改 |
| 6 | **Nginx文件大小限制** | P2 | 大文件上传失败 | 2分钟nginx.conf修改 |
### 推荐但非必需的优化
| # | 优化 | 价值 | 实施时间 |
|---|------|------|---------|
| 7 | **SSH隧道** | 开发便利性 | 10分钟 |
| 8 | **OSS签名URL** | 安全最佳实践 | 30分钟代码修改 |
### 下一步行动
```
☐ 1. 创建NAT网关必需15分钟⭐⭐⭐⭐⭐
☐ 2. 修改docker-compose.yamlECS端口安全5分钟⭐⭐⭐⭐⭐
☐ 3. 修改DockerfilePython workers限制2分钟⭐⭐⭐⭐
☐ 4. 修改nginx.conf文件大小限制2分钟⭐⭐⭐⭐
☐ 5. 修改后端代码HTTP超时5分钟⭐⭐⭐⭐
☐ 6. 修改后端代码Dify容错5分钟⭐⭐⭐⭐
☐ 7. 更新部署流程(分阶段部署,文档更新)⭐⭐⭐⭐
☐ 8. 统一时区配置必需15分钟⭐⭐⭐⭐⭐
☐ 9. 配置镜像拉取策略必需5分钟⭐⭐⭐⭐⭐
☐ 10. Python内存管理必需10分钟⭐⭐⭐⭐
☐ 11. 可选配置SSH隧道开发便利10分钟
☐ 12. 可选实现OSS签名URL安全30分钟
总计必需修复约70分钟可选优化约40分钟
```
---
**文档创建人:** AI助手
**最后更新:** 2025-12-14
**版本:** v1.0
**核心理念:安全第一、稳定第二、便利第三** ⭐⭐⭐