# 中华药典 AI Agent — 全新服务器部署手册

> 适用：Ubuntu 24.04 LTS 全新安装  
> 版本：v2.0  
> 最后更新：2026-07-14

---

## 一、服务器配置要求

| 项目 | 最低 | 推荐 |
|------|------|------|
| CPU | 4 核 | 8 核 |
| 内存 | 8 GB | 16 GB |
| 系统盘 | 50 GB SSD | 100 GB SSD |
| 操作系统 | Ubuntu 22.04+ | Ubuntu 24.04 LTS |
| 网络 | 公网 IP | 公网 IP + 域名 |

不需要 GPU（LLM 走阿里云 Qwen API）。

---

## 二、部署总览（10 步）

```
① 基础环境     → apt update, 装 git/curl 等
② Docker       → 容器运行时
③ 拉取代码     → git clone 到 /opt/pharmacopoeia-ai
④ 环境变量     → .env 配置
⑤ PostgreSQL   → Docker 启动 pgvector
⑥ 数据库建表   → 导入 schema.sql
⑦ Python 后端  → venv + FastAPI + Supervisor 守护
⑧ Nginx       → 反向代理 + SSE 免缓冲
⑨ 防火墙      → ufw 只开放 80/443
⑩ 备份 + 日志  → cron 每日备份
```

---

## 三、详细步骤

### ① 基础环境

```bash
# 更新系统
sudo apt update && sudo apt upgrade -y

# 安装基础工具
sudo apt install -y curl wget git vim htop ca-certificates gnupg lsb-release

# 设置时区
sudo timedatectl set-timezone Asia/Shanghai
```

---

### ② 安装 Docker

```bash
# 官方安装脚本
curl -fsSL https://get.docker.com | sudo bash

# 把当前用户加入 docker 组（不用每次 sudo）
sudo usermod -aG docker $USER
newgrp docker

# 验证
docker --version
docker compose version
```

> Docker 镜像拉取慢的话，配置国内镜像加速：
> ```bash
> sudo tee /etc/docker/daemon.json << 'EOF'
> {
>   "registry-mirrors": ["https://docker.m.daocloud.io"]
> }
> EOF
> sudo systemctl restart docker
> ```

---

### ③ 拉取项目代码

```bash
# 创建应用目录
sudo mkdir -p /opt/pharmacopoeia-ai
sudo chown $USER:$USER /opt/pharmacopoeia-ai

# 拉取代码（替换为你的仓库地址）
cd /opt/pharmacopoeia-ai
git clone https://github.com/your-org/pharmacopoeia-ai.git .

# 确认目录结构
ls -la
# 应看到: backend-python/  deploy/  database/  docs/  .env.example  README.md
```

---

### ④ 配置环境变量

```bash
cd /opt/pharmacopoeia-ai

# 从模板创建
cp .env.example .env

# 生成随机密钥
echo "SECRET_KEY=$(openssl rand -hex 32)"
echo "DB_PASSWORD=$(openssl rand -base64 16)"
```

编辑 `.env`，修改以下值：

```bash
vim .env
```

```ini
# ===== 必改 =====
APP_ENV=production
APP_DEBUG=false
SECRET_KEY=<上面生成的随机密钥>

POSTGRES_PASSWORD=<上面生成的数据库密码>

QWEN_API_KEY=sk-xxxxxxxxxxxxxxxx    # 阿里云百炼 API Key

# ===== 有微信小程序才填 =====
WECHAT_APPID=wx1234567890
WECHAT_SECRET=xxxxxxxx

# ===== 生产建议 =====
QWEN_MAX_TOKENS=2048
LOG_LEVEL=WARNING
```

> **Qwen API Key 获取**：注册 [阿里云百炼](https://bailian.console.aliyun.com/) → 模型广场 → 选择 qwen-max → API-KEY 管理 → 创建 API Key。
>
> 新用户有免费额度，足够开发测试。

---

### ⑤ 启动 PostgreSQL + Redis

```bash
cd /opt/pharmacopoeia-ai/deploy

# 先确认 .env 中的密码已写入 docker-compose 能读取
# docker-compose.yml 会读取 POSTGRES_PASSWORD 环境变量
source /opt/pharmacopoeia-ai/.env

# 启动容器
docker compose up -d

# 等待健康检查通过
sleep 10
docker ps
# 应该看到 pharmacopoeia-pg (healthy) 和 pharmacopoeia-redis (healthy)

# 验证 PG
docker exec pharmacopoeia-pg psql -U postgres -d pharmacopoeia -c "SELECT 1"
# 返回 1 行 = 正常

# 验证 Redis
docker exec pharmacopoeia-redis redis-cli ping
# 返回 PONG = 正常

# 设置开机自启
docker update --restart=always pharmacopoeia-pg pharmacopoeia-redis
```

---

### ⑥ 初始化数据库表

```bash
# 导入表结构
docker exec -i pharmacopoeia-pg psql -U postgres -d pharmacopoeia \
  < /opt/pharmacopoeia-ai/database/schema.sql

# 确认表已创建（应看到 9 张表）
docker exec pharmacopoeia-pg psql -U postgres -d pharmacopoeia -c "\dt"
```

9 张表说明：

| 表 | 用途 |
|---|------|
| `drugs` | 药品主表 |
| `drug_chunks` | 药品切片 + 1024维向量（RAG 核心） |
| `knowledge_points` | 执业药师知识点 |
| `questions` | 题库 |
| `conversations` | 对话会话 |
| `messages` | 对话消息 |
| `users` | 用户 |
| `user_progress` | 学习进度 |
| `answer_records` | 答题记录 |

---

### ⑦ 编译并启动 Java 后端

#### 7.1 编译

```bash
cd /opt/pharmacopoeia-ai/backend-java

# 编译（跳过测试）
mvn clean package -DskipTests
```

#### 7.2 手动启动测试

```bash
# 设置数据库密码环境变量
export POSTGRES_PASSWORD=postgres

# 启动
java -jar target/pharmacopoeia-ai-1.0.0.jar

# 看到 Started PharmacopoeiaApplication 后
# 新开终端验证：
# curl http://localhost:9000/health
# 返回 {"status":"ok"} 则正常

# Ctrl+C 停掉
```

#### 7.3 使用启动脚本管理

```bash
# 赋予执行权限
chmod +x /opt/pharmacopoeia-ai/tools/start-java.sh

# 启动
bash /opt/pharmacopoeia-ai/tools/start-java.sh start

# 查看状态
bash /opt/pharmacopoeia-ai/tools/start-java.sh status

# 查看日志
bash /opt/pharmacopoeia-ai/tools/start-java.sh logs

# 重启
bash /opt/pharmacopoeia-ai/tools/start-java.sh restart

# 停止
bash /opt/pharmacopoeia-ai/tools/start-java.sh stop
```

#### 7.4 配置 Supervisor 守护进程（推荐）

```bash
sudo apt install -y supervisor

# 创建日志目录
sudo mkdir -p /var/log/pharmacopoeia

# 使用项目自带的 Supervisor 配置
sudo cp /opt/pharmacopoeia-ai/deploy/supervisord-java.conf /etc/supervisor/conf.d/pharmacopoeia.conf

# ⚠️ 编辑配置，填入真实的 QWEN_API_KEY
sudo vim /etc/supervisor/conf.d/pharmacopoeia.conf

# 启动
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start pharmacopoeia-backend

# 查看状态
sudo supervisorctl status
```

---

### ⑧ 配置 Nginx

```bash
# 安装 Nginx
sudo apt install -y nginx

# 创建站点配置
sudo tee /etc/nginx/sites-available/pharmacopoeia << 'NGINX'
# ---- 后端上游 ----
upstream backend {
    server 127.0.0.1:8000;
    keepalive 64;
}

# ---- 限流 ----
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=60r/m;

# ---- HTTP（有域名后可加 HTTPS） ----
server {
    listen 80;
    server_name _;

    client_max_body_size 10m;

    # Gzip
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml text/javascript;

    # 安全头
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;

    # API 反向代理（SSE 流式关键：禁用缓冲）
    location /api/ {
        proxy_pass http://backend;
        proxy_http_version 1.1;
        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_set_header Connection "";

        # SSE 支持
        proxy_buffering off;
        proxy_cache off;
        proxy_read_timeout 300s;

        # 限流
        limit_req zone=api_limit burst=20 nodelay;
    }

    # 健康检查（不记录日志）
    location /health {
        proxy_pass http://backend;
        access_log off;
    }

    # API 文档
    location /docs {
        proxy_pass http://backend;
    }

    # 静态文件
    location /static/ {
        alias /opt/pharmacopoeia-ai/static/;
        expires 7d;
        add_header Cache-Control "public, immutable";
    }

    # 根路径 → 前端页面
    location / {
        alias /opt/pharmacopoeia-ai/static/;
        try_files $uri /index.html;
    }
}
NGINX

# 启用站点
sudo ln -sf /etc/nginx/sites-available/pharmacopoeia /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default

# 测试配置
sudo nginx -t

# 重启 Nginx
sudo systemctl reload nginx
sudo systemctl enable nginx
```

**验证**：浏览器访问 `http://服务器IP/` 应该看到前端页面。

---

### ⑨ 配置防火墙

```bash
# 安装 ufw
sudo apt install -y ufw

# 只开放必要端口
sudo ufw allow 22/tcp      # SSH
sudo ufw allow 80/tcp      # HTTP
sudo ufw allow 443/tcp     # HTTPS

# 开启防火墙
sudo ufw --force enable
sudo ufw status verbose
```

>   PostgreSQL (5432)、Redis (6379)、FastAPI (8000) **不对外开放**，只通过本地回环访问。

---

### ⑩ 数据库备份

```bash
# 创建备份脚本
sudo tee /opt/pharmacopoeia-ai/tools/backup.sh << 'EOF'
#!/bin/bash
BACKUP_DIR="/opt/backups/pharmacopoeia"
mkdir -p "$BACKUP_DIR"
DATE=$(date +%Y%m%d_%H%M%S)
docker exec pharmacopoeia-pg pg_dump -U postgres pharmacopoeia \
  > "$BACKUP_DIR/pg_${DATE}.sql"
# 保留最近 7 天
find "$BACKUP_DIR" -name "pg_*.sql" -mtime +7 -delete
echo "$(date '+%F %T') backup done: pg_${DATE}.sql" >> /var/log/pharmacopoeia/backup.log
EOF
sudo chmod +x /opt/pharmacopoeia-ai/tools/backup.sh

# 添加 cron（每天凌晨 2:00）
(crontab -l 2>/dev/null; echo "0 2 * * * /opt/pharmacopoeia-ai/tools/backup.sh") | crontab -
```

---

## 四、配置域名和 SSL（有域名后执行）

```bash
# 安装 certbot
sudo apt install -y certbot python3-certbot-nginx

# 先修改 Nginx 配置中的 server_name
sudo vim /etc/nginx/sites-available/pharmacopoeia
# 把 server_name _; 改成 server_name yaodian.example.com;
sudo nginx -t && sudo systemctl reload nginx

# 自动签发 SSL 证书
sudo certbot --nginx -d yaodian.example.com

# 证书会自动续签，可手动测试续签流程：
sudo certbot renew --dry-run
```

---

## 五、导入数据（有药品数据后执行）

```bash
cd /opt/pharmacopoeia-ai/data-pipeline

# 安装数据管道依赖
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt

# 准备数据文件放到 data-pipeline/data/ 下
# 格式参考 docs/SCHEME.md

# 执行向量化入库
python ingest.py
```

---

## 六、最终验证清单

部署完成后逐项确认：

```bash
# 1. Docker 容器运行
docker ps | grep pharmacopoeia
# ✅ 两个容器都是 healthy

# 2. PostgreSQL 可访问
docker exec pharmacopoeia-pg psql -U postgres -d pharmacopoeia -c "SELECT count(*) FROM information_schema.tables WHERE table_schema='public'"
# ✅ 返回 9

# 3. Python 后端运行中
sudo supervisorctl status pharmacopoeia-backend
# ✅ RUNNING

# 4. API 响应正常
curl http://localhost:8000/health
# ✅ {"status":"ok","app":"PharmacopoeiaAI","env":"production"}

# 5. Nginx 代理正常
curl http://localhost/health
# ✅ 同上

# 6. 外网访问（用你电脑浏览器）
# http://服务器IP/
# ✅ 能看到前端页面

# 7. 防火墙生效
sudo ufw status | grep -E "80|443"
# ✅ ALLOW
```

---

## 七、日常运维命令

| 操作 | 命令 |
|------|------|
| 查看后端状态 | `sudo supervisorctl status` |
| 重启后端 | `sudo supervisorctl restart pharmacopoeia-backend` |
| 查看后端实时日志 | `sudo supervisorctl tail -f pharmacopoeia-backend` |
| 查看错误日志 | `tail -100 /var/log/pharmacopoeia/backend-error.log` |
| 重启 Nginx | `sudo systemctl reload nginx` |
| 重启 PG | `docker restart pharmacopoeia-pg` |
| 重启 Redis | `docker restart pharmacopoeia-redis` |
| 进入 PG 控制台 | `docker exec -it pharmacopoeia-pg psql -U postgres -d pharmacopoeia` |
| 手动备份 | `bash /opt/pharmacopoeia-ai/tools/backup.sh` |
| 更新代码并重启 | `cd /opt/pharmacopoeia-ai && git pull && sudo supervisorctl restart pharmacopoeia-backend` |
| 查看磁盘 | `df -h` |
| 查看内存 | `free -h` |

---

## 八、部署架构图

```
公网用户
    │
    ▼
┌──────────────────┐
│  UFW 防火墙       │  开放: 22, 80, 443
└────────┬─────────┘
         │
         ▼
┌──────────────────┐
│  Nginx :80/:443   │  SSL / 反向代理 / 限流 / 静态文件
└────────┬─────────┘
         │ :8000 (本地)
         ▼
┌──────────────────┐
│  Supervisor       │  进程守护, 挂了自动重启
│  ┌──────────────┐ │
│  │ Uvicorn x4   │ │
│  │ FastAPI :8000│ │
│  └──────┬───────┘ │
└─────────┼─────────┘
          │
   ┌──────┼──────┐
   ▼      ▼      ▼
┌──────┐ ┌────┐ ┌──────────┐
│ PG   │ │Redis│ │ Qwen API │
│ :5432│ │:6379│ │ (阿里云)  │
│Docker│ │Dockr│ │ 云端     │
└──────┘ └────┘ └──────────┘

快速管理:
  bash tools/start-java.sh {start|stop|restart|status|logs}
```

---

## 九、常见问题

### Q: pip install 报错，sentence-transformers 编译失败？
```bash
sudo apt install -y build-essential python3-dev
# 重试
pip install sentence-transformers
```

### Q: docker compose 提示 .env 中的变量未生效？
```bash
# 在 docker compose 目录下创建 .env 软链接
cd /opt/pharmacopoeia-ai/deploy
ln -sf ../.env .env
docker compose up -d
```

### Q: Qwen API 连不上？
```bash
# 测试网络可达性
curl -I https://dashscope.aliyuncs.com
# 如果超时，检查服务器能否访问外网，是否需要代理
```

### Q: 如何查看 AI 对话是否正常工作？
```bash
# 测试非流式接口
curl -X POST http://localhost:8000/api/v1/chat/ask \
  -H "Content-Type: application/json" \
  -d '{"query": "阿莫西林是什么药"}'
```
