Jelajahi Sumber

部署脚本和配置信息上传

liuchengsen 1 bulan lalu
induk
melakukan
3155acd485
5 mengubah file dengan 973 tambahan dan 0 penghapusan
  1. 51 0
      deploy/nginx-pharmacopoeia.conf
  2. 17 0
      deploy/supervisord-java.conf
  3. 580 0
      docs/SERVER_DEPLOY.md
  4. 210 0
      docs/server-check.sh
  5. 115 0
      tools/start-java.sh

+ 51 - 0
deploy/nginx-pharmacopoeia.conf

@@ -0,0 +1,51 @@
+upstream backend {
+    server 127.0.0.1:9000;
+    keepalive 64;
+}
+
+limit_req_zone $binary_remote_addr zone=api_limit:10m rate=60r/m;
+
+server {
+    listen 80;
+    server_name _;
+
+    client_max_body_size 10m;
+
+    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;
+
+    # API 代理到 Java 后端 (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 "";
+
+        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;
+    }
+
+    # 前端静态页面
+    location / {
+        root /opt/pharmacopoeia-ai/static;
+        index index.html;
+        try_files $uri $uri/ /index.html;
+    }
+}

+ 17 - 0
deploy/supervisord-java.conf

@@ -0,0 +1,17 @@
+[program:pharmacopoeia-backend]
+command=java -Xms512m -Xmx2g -XX:+UseG1GC -Dfile.encoding=UTF-8 -jar /opt/pharmacopoeia-ai/backend-java/target/pharmacopoeia-ai-1.0.0.jar
+directory=/opt/pharmacopoeia-ai/backend-java
+user=root
+autostart=true
+autorestart=true
+startsecs=10
+stopwaitsecs=30
+stdout_logfile=/var/log/pharmacopoeia/backend.log
+stdout_logfile_maxbytes=50MB
+stdout_logfile_backups=10
+stderr_logfile=/var/log/pharmacopoeia/backend-error.log
+stderr_logfile_maxbytes=50MB
+stderr_logfile_backups=10
+environment=
+    POSTGRES_PASSWORD="postgres",
+    QWEN_API_KEY="sk-your-key"

+ 580 - 0
docs/SERVER_DEPLOY.md

@@ -0,0 +1,580 @@
+# 中华药典 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": "阿莫西林是什么药"}'
+```

+ 210 - 0
docs/server-check.sh

@@ -0,0 +1,210 @@
+#!/bin/bash
+# ============================================
+# 中华药典 AI Agent — 服务器环境检查脚本
+# 用法: bash server-check.sh
+# ============================================
+
+set -e
+
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+NC='\033[0m'
+
+pass()  { echo -e "${GREEN}✅ $1${NC}"; }
+warn()  { echo -e "${YELLOW}⚠️  $1${NC}"; }
+fail()  { echo -e "${RED}❌ $1${NC}"; }
+
+echo "╔══════════════════════════════════════════╗"
+echo "║  中华药典 AI Agent  服务器环境检查        ║"
+echo "╚══════════════════════════════════════════╝"
+echo ""
+
+# ---- 系统信息 ----
+echo "━━━━━━━━━━ 系统信息 ━━━━━━━━━━━━"
+echo "主机名: $(hostname)"
+echo "内核:   $(uname -r)"
+if [ -f /etc/os-release ]; then
+    . /etc/os-release
+    echo "系统:   $NAME $VERSION"
+fi
+echo "架构:   $(uname -m)"
+echo ""
+
+# ---- CPU ----
+echo "━━━━━━━━━━ CPU ━━━━━━━━━━━━"
+CORES=$(nproc)
+CPU_MODEL=$(lscpu 2>/dev/null | grep "Model name" | cut -d: -f2 | xargs)
+echo "型号:   $CPU_MODEL"
+echo "核心数: $CORES"
+if [ "$CORES" -ge 4 ]; then
+    pass "核心数满足要求 (≥4)"
+else
+    fail "核心数不足,需要 ≥4,当前 $CORES"
+fi
+echo ""
+
+# ---- 内存 ----
+echo "━━━━━━━━━━ 内存 ━━━━━━━━━━━━"
+MEM_TOTAL_KB=$(grep MemTotal /proc/meminfo | awk '{print $2}')
+MEM_TOTAL_GB=$((MEM_TOTAL_KB / 1024 / 1024))
+echo "总内存: ${MEM_TOTAL_GB} GB"
+free -h | head -2
+if [ "$MEM_TOTAL_GB" -ge 8 ]; then
+    pass "内存满足要求 (≥8 GB)"
+elif [ "$MEM_TOTAL_GB" -ge 4 ]; then
+    warn "内存偏小,最低 8 GB,当前 ${MEM_TOTAL_GB} GB"
+else
+    fail "内存不足,需要 ≥8 GB,当前 ${MEM_TOTAL_GB} GB"
+fi
+echo ""
+
+# ---- 磁盘 ----
+echo "━━━━━━━━━━ 磁盘 ━━━━━━━━━━━━"
+df -h / | tail -1
+DISK_AVAIL_GB=$(df -BG / | tail -1 | awk '{print $4}' | sed 's/G//')
+echo "可用空间: ${DISK_AVAIL_GB} GB"
+if [ "$DISK_AVAIL_GB" -ge 20 ]; then
+    pass "磁盘空间满足要求 (≥20 GB)"
+elif [ "$DISK_AVAIL_GB" -ge 10 ]; then
+    warn "磁盘空间偏小,建议 ≥20 GB,当前 ${DISK_AVAIL_GB} GB"
+else
+    fail "磁盘空间不足,需要 ≥20 GB,当前 ${DISK_AVAIL_GB} GB"
+fi
+echo ""
+
+# ---- Docker ----
+echo "━━━━━━━━━━ Docker ━━━━━━━━━━━━"
+if command -v docker &>/dev/null; then
+    DOCKER_VER=$(docker --version 2>/dev/null | awk '{print $3}' | sed 's/,//')
+    echo "版本:   $DOCKER_VER"
+    if docker info &>/dev/null 2>&1; then
+        pass "Docker 已安装且运行中 ($DOCKER_VER)"
+    else
+        warn "Docker 已安装但守护进程未运行"
+    fi
+else
+    fail "Docker 未安装"
+fi
+
+if docker compose version &>/dev/null 2>&1; then
+    COMPOSE_VER=$(docker compose version 2>/dev/null | awk '{print $4}' | sed 's/v//')
+    pass "Docker Compose 已安装 ($COMPOSE_VER)"
+elif command -v docker-compose &>/dev/null; then
+    COMPOSE_VER=$(docker-compose --version 2>/dev/null | awk '{print $4}' | sed 's/,//')
+    pass "Docker Compose (旧版) 已安装 ($COMPOSE_VER)"
+else
+    fail "Docker Compose 未安装"
+fi
+echo ""
+
+# ---- Python ----
+echo "━━━━━━━━━━ Python ━━━━━━━━━━━━"
+if command -v python3 &>/dev/null; then
+    PY_VER=$(python3 --version 2>&1 | awk '{print $2}')
+    PY_MAJOR=$(echo "$PY_VER" | cut -d. -f1)
+    PY_MINOR=$(echo "$PY_VER" | cut -d. -f2)
+    echo "版本:   $PY_VER"
+    if [ "$PY_MAJOR" -ge 3 ] && [ "$PY_MINOR" -ge 11 ]; then
+        pass "Python 版本满足要求 (≥3.11)"
+    else
+        warn "Python 版本偏低,建议 ≥3.11,当前 $PY_VER"
+    fi
+else
+    fail "Python3 未安装"
+fi
+
+if command -v pip3 &>/dev/null; then
+    echo "pip:     $(pip3 --version 2>&1 | awk '{print $2}')"
+else
+    warn "pip3 未安装"
+fi
+echo ""
+
+# ---- Java / Maven (可选) ----
+echo "━━━━━━━━━━ Java (可选) ━━━━━━━━━━━━"
+if command -v java &>/dev/null; then
+    JAVA_VER=$(java -version 2>&1 | head -1 | awk -F'"' '{print $2}')
+    echo "Java:    $JAVA_VER"
+    JAVA_MAJOR=$(echo "$JAVA_VER" | cut -d. -f1)
+    if [ "$JAVA_MAJOR" -ge 21 ]; then
+        pass "Java 版本满足 Spring Boot 3.3 要求 (≥21)"
+    else
+        warn "Java 版本偏低,Spring Boot 需要 ≥21,但 Python 后端不受影响"
+    fi
+else
+    echo "Java 未安装(不影响,可用 Python 后端)"
+fi
+
+if command -v mvn &>/dev/null; then
+    echo "Maven:   $(mvn --version 2>&1 | head -1 | awk '{print $3}')"
+else
+    echo "Maven 未安装(不影响,可用 Python 后端)"
+fi
+echo ""
+
+# ---- Nginx ----
+echo "━━━━━━━━━━ Nginx ━━━━━━━━━━━━"
+if command -v nginx &>/dev/null; then
+    NGINX_VER=$(nginx -v 2>&1 | awk -F'/' '{print $2}')
+    pass "Nginx 已安装 ($NGINX_VER)"
+else
+    warn "Nginx 未安装(生产环境需要)"
+fi
+echo ""
+
+# ---- 关键端口 ----
+echo "━━━━━━━━━━ 端口占用情况 ━━━━━━━━━━━━"
+check_port() {
+    local port=$1
+    local name=$2
+    if ss -tlnp 2>/dev/null | grep -q ":${port} "; then
+        fail "端口 $port ($name) 已被占用"
+        ss -tlnp | grep ":${port} "
+    else
+        pass "端口 $port ($name) 空闲"
+    fi
+}
+check_port 80   "Nginx HTTP"
+check_port 443  "Nginx HTTPS"
+check_port 5432 "PostgreSQL"
+check_port 6379 "Redis"
+check_port 8000 "FastAPI"
+check_port 9000 "Spring Boot"
+echo ""
+
+# ---- 外网连通性 ----
+echo "━━━━━━━━━━ 外网连通 ━━━━━━━━━━━━"
+if curl -s --connect-timeout 5 https://dashscope.aliyuncs.com > /dev/null 2>&1; then
+    pass "可访问 Qwen API (dashscope.aliyuncs.com)"
+else
+    fail "无法访问 Qwen API,请检查网络/代理"
+fi
+
+if curl -s --connect-timeout 5 https://registry-1.docker.io > /dev/null 2>&1; then
+    pass "可访问 Docker Hub"
+else
+    warn "无法访问 Docker Hub(镜像拉取可能需换国内源)"
+fi
+echo ""
+
+# ---- 防火墙 ----
+echo "━━━━━━━━━━ 防火墙 ━━━━━━━━━━━━"
+if command -v ufw &>/dev/null; then
+    sudo ufw status 2>/dev/null | head -5 || warn "无法获取 ufw 状态"
+elif command -v firewall-cmd &>/dev/null; then
+    sudo firewall-cmd --list-all 2>/dev/null || warn "无法获取 firewalld 状态"
+else
+    warn "未检测到防火墙工具"
+fi
+echo ""
+
+# ---- 总结 ----
+echo "╔══════════════════════════════════════════╗"
+echo "║              检查完成                     ║"
+echo "╚══════════════════════════════════════════╝"
+echo ""
+echo "必备项: Docker | Docker Compose | Python 3.11+ | 端口 5432/6379/8000 空闲 | Qwen API 可达"
+echo "可选项: Nginx (生产推荐) | Java 21+Maven (只有 Spring Boot 后端需要)"
+echo ""
+echo "详情参考: docs/SERVER_DEPLOY.md"

+ 115 - 0
tools/start-java.sh

@@ -0,0 +1,115 @@
+#!/bin/bash
+# ============================================
+# 中华药典 AI Agent — Java 后端启动脚本
+# ============================================
+
+set -e
+
+APP_DIR="/opt/pharmacopoeia-ai/backend-java"
+JAR_FILE="$APP_DIR/target/pharmacopoeia-ai-1.0.0.jar"
+LOG_DIR="/var/log/pharmacopoeia"
+PID_FILE="$APP_DIR/app.pid"
+
+# 加载 .env 环境变量
+if [ -f /opt/pharmacopoeia-ai/.env ]; then
+    set -a
+    source /opt/pharmacopoeia-ai/.env
+    set +a
+fi
+
+# 创建日志目录
+mkdir -p "$LOG_DIR"
+
+# ---- 通用函数 ----
+running() {
+    [ -f "$PID_FILE" ] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null
+}
+
+start() {
+    if running; then
+        echo "后端已在运行中 (PID: $(cat "$PID_FILE"))"
+        return 1
+    fi
+
+    if [ ! -f "$JAR_FILE" ]; then
+        echo "JAR 不存在,正在编译..."
+        cd "$APP_DIR"
+        mvn clean package -DskipTests -q
+    fi
+
+    echo "启动后端..."
+    cd "$APP_DIR"
+
+    # JVM 参数
+    JVM_OPTS="-Xms512m -Xmx2g"
+    JVM_OPTS="$JVM_OPTS -XX:+UseG1GC"
+    JVM_OPTS="$JVM_OPTS -Dfile.encoding=UTF-8"
+    JVM_OPTS="$JVM_OPTS -Djava.security.egd=file:/dev/./urandom"
+
+    nohup java $JVM_OPTS -jar "$JAR_FILE" \
+        >> "$LOG_DIR/backend.log" 2>&1 &
+
+    echo $! > "$PID_FILE"
+    echo "已启动 (PID: $(cat "$PID_FILE"))"
+    echo "日志: $LOG_DIR/backend.log"
+    echo ""
+    tail -f "$LOG_DIR/backend.log"
+}
+
+stop() {
+    if ! running; then
+        echo "后端未运行"
+        rm -f "$PID_FILE"
+        return 0
+    fi
+
+    PID=$(cat "$PID_FILE")
+    echo "停止后端 (PID: $PID)..."
+    kill "$PID"
+
+    # 等待最多 30 秒
+    for i in $(seq 1 30); do
+        if ! kill -0 "$PID" 2>/dev/null; then
+            echo "已停止"
+            rm -f "$PID_FILE"
+            return 0
+        fi
+        sleep 1
+    done
+
+    # 强制终止
+    echo "超时,强制终止..."
+    kill -9 "$PID" 2>/dev/null
+    rm -f "$PID_FILE"
+    echo "已强制停止"
+}
+
+restart() {
+    stop
+    sleep 2
+    start
+}
+
+status() {
+    if running; then
+        echo "✅ 后端运行中 (PID: $(cat "$PID_FILE"))"
+    else
+        echo "❌ 后端未运行"
+    fi
+}
+
+logs() {
+    tail -100 "$LOG_DIR/backend.log"
+}
+
+case "${1:-start}" in
+    start)   start ;;
+    stop)    stop ;;
+    restart) restart ;;
+    status)  status ;;
+    logs)    logs ;;
+    *)
+        echo "用法: $0 {start|stop|restart|status|logs}"
+        exit 1
+        ;;
+esac