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
25 KiB
关键配置补充说明 - 部署文档勘误与增强
文档版本: 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功能不可用,系统基本瘫痪!
解决方案
方案A:NAT网关(推荐,生产环境)
# 步骤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/月
方案B:SAE绑定公网IP(部分地域支持)
SAE控制台 > 应用配置 > 网络配置
└─ 查看是否有"公网访问"或"绑定EIP"选项
⚠️ 注意:
- 并非所有地域都支持
- 优先使用方案A(更稳定)
验证NAT网关是否生效
# 方法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:首次部署后端(临时配置)
# 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
# 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:更新后端配置
# SAE控制台 > 应用详情 > 环境变量
# 找到 DIFY_API_KEY,修改为真实值
DIFY_API_KEY=app-xxxxxxxxxxxxxxxxxxxxx
# 保存 > 重启应用
# SAE会执行滚动重启(零停机)
阶段4:验证PKB功能
# 测试知识库创建
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配置
// 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配置
// 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的Redis(6379)和Weaviate(8080)如果对公网开放:
❌ Redis无密码,可被攻击者直接访问
❌ Weaviate包含敏感的向量数据
❌ 可能被用于DDoS攻击的跳板
正确配置
docker-compose.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安全组配置
# 安全组规则(ECS控制台 > 安全组 > 配置规则)
入方向规则:
├─ 允许 80/TCP 来源:VPC网段(172.16.0.0/12) # Nginx
├─ 允许 22/TCP 来源:您的办公室IP # SSH管理
└─ 拒绝 所有 来源:0.0.0.0/0 # 默认拒绝
出方向规则:
└─ 允许 所有 目标:0.0.0.0/0 # 允许访问公网
验证安全配置
# 从公网测试(应该失败)
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配置
# frontend-v2/nginx.conf.template
http {
# ⚠️ 新增:支持大文件上传
client_max_body_size 50M;
# ⚠️ 新增:开启gzip(React大体积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过多,会导致OOM(Out of Memory)
解决方案
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个worker(2GB内存限制)
# timeout=120: 单个请求最多120秒(OCR可能很慢)
# max-requests=100: 100个请求后重启worker(防止内存泄漏)
SAE配置
# SAE控制台 > 应用配置 > 实例规格
CPU: 1核
内存: 2GB # ⚠️ 不要低于2GB
# 实例数量
最小实例数: 1
最大实例数: 3(根据流量自动扩容)
Workers数量计算公式
# 经验公式
workers = (CPU核数 × 2) + 1
# 但对于内存密集型应用(如PDF解析)
workers = min((内存GB / 单worker内存GB), (CPU核数 × 2) + 1)
# 示例:SAE 1核2GB
单worker内存 ≈ 800MB(PyMuPDF + Nougat)
workers = min(2GB / 0.8GB, 1×2+1) = min(2.5, 3) = 2
# 结论:workers=2 是安全值
监控OOM
# 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访问权限
# 本地生成SSH密钥(如果还没有)
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
# 将公钥添加到ECS
# ECS控制台 > 实例 > 远程连接 > 重置密钥对
# 或者手动添加到 ~/.ssh/authorized_keys
步骤2:建立SSH隧道
# 格式
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端口
步骤3:Navicat连接
连接类型:PostgreSQL
主机:localhost
端口:5433
用户名:aiclinical_rw
密码:(RDS密码)
数据库:ai_clinical_research
测试连接 → 成功!
步骤4:后台运行隧道(可选)
# 方法1:nohup后台运行
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 时区
# 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
# ... 其他配置
-- RDS PostgreSQL 时区配置
-- RDS控制台 > 参数设置 > timezone
timezone = Asia/Shanghai
验证时区:
# 查看容器时区
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:需要修改参数
修复步骤
# 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:每次部署使用新版本号(强烈推荐)
# 使用语义化版本号
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镜像拉取策略(测试环境)
# SAE控制台 > 应用配置 > 镜像设置
镜像拉取策略:Always
# ⚠️ 注意:
# - 每次重启都会拉取镜像(启动稍慢)
# - 适合测试环境,不推荐生产环境
最佳实践
| 环境 | 推荐方案 | 理由 |
|---|---|---|
| 生产环境 | 方案A(版本号管理) | 版本可追溯,稳定 |
| 测试环境 | 方案B(Always拉取) | 始终最新,方便 |
| 开发环境 | 方案A | 避免混乱 |
❌ 不要:
# 始终使用 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 | 包含OCR(Nougat) |
| 增强版 | 2核 | 8GB | 4 | 大量OCR + 高并发 |
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"] # ⚠️ 随机抖动,避免同时重启
监控与告警:
# SAE控制台 > 监控告警 > 创建告警规则
指标:内存使用率
阈值:> 80%
动作:发送通知 + 自动扩容(可选)
如果遇到OOM
方案1:升级内存(推荐)
# SAE控制台 > 应用配置 > 规格调整
1核2GB → 2核4GB(增加¥100/月)
方案2:限制并发(临时)
# 修改Dockerfile
CMD ["gunicorn", "main:app", \
"--workers", "1", \ # 降低并发
"--threads", "2"] # 使用线程而非进程
11. OSS签名URL - 安全的文件访问 ⭐⭐⭐⭐
用途:安全最佳实践
问题
如果OSS Bucket设置为Public:
❌ 任何人都可以访问所有文件
❌ 无法追踪谁访问了哪些文件
❌ 无法控制访问时效
解决方案
OSS Bucket配置
# OSS控制台 > Bucket列表 > aiclinical-data-prod
# 读写权限:私有(Private)
后端生成签名URL
// 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!
})
}
/**
* 生成签名URL(1小时有效期)
*/
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
// 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. 生成签名URL(1小时有效)
const ossAdapter = new OSSAdapter()
const signedUrl = await ossAdapter.getSignedUrl(document.ossKey, 3600)
// 4. 返回签名URL
res.json({
url: signedUrl,
expiresIn: 3600,
filename: document.filename
})
})
前端使用签名URL
// 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 // 签名URL,1小时有效
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.yaml(ECS端口安全,5分钟)⭐⭐⭐⭐⭐
☐ 3. 修改Dockerfile(Python 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
核心理念:安全第一、稳定第二、便利第三 ⭐⭐⭐