feat:psycopg2-binary>=2.9 → psycopg[binary]>=3.1.0

This commit is contained in:
shijing 2026-07-10 10:22:11 +08:00
parent 75694b3182
commit 389fe50cb9
6 changed files with 251 additions and 5 deletions

View File

@ -1,6 +1,6 @@
APP_NAME=财务大模型智能问答客服系统 APP_NAME=财务大模型智能问答客服系统
ENVIRONMENT=development ENVIRONMENT=development
DATABASE_URL=postgresql+psycopg2://finance_ai:finance_ai@127.0.0.1:5432/finance_ai DATABASE_URL=postgresql+psycopg://finance_ai:finance_ai@127.0.0.1:5432/finance_ai
JWT_SECRET_KEY=change-me-in-production JWT_SECRET_KEY=change-me-in-production
JWT_EXPIRE_MINUTES=480 JWT_EXPIRE_MINUTES=480
SMS_PROVIDER=mock SMS_PROVIDER=mock

View File

@ -12,7 +12,7 @@ python -m uvicorn app.main:app --reload --port 8000
默认会使用 `sqlite:///./finance_ai.db`。生产环境请复制 `.env.example``.env`,并配置: 默认会使用 `sqlite:///./finance_ai.db`。生产环境请复制 `.env.example``.env`,并配置:
```env ```env
DATABASE_URL=postgresql+psycopg2://finance_ai:finance_ai@127.0.0.1:5432/finance_ai DATABASE_URL=postgresql+psycopg://finance_ai:finance_ai@127.0.0.1:5432/finance_ai
JWT_SECRET_KEY=replace-with-secret JWT_SECRET_KEY=replace-with-secret
``` ```

View File

@ -7,7 +7,7 @@ dependencies = [
"fastapi>=0.110", "fastapi>=0.110",
"uvicorn[standard]>=0.27", "uvicorn[standard]>=0.27",
"sqlalchemy>=2.0", "sqlalchemy>=2.0",
"psycopg2-binary>=2.9", "psycopg[binary]>=3.1.0",
"pydantic-settings>=2.0", "pydantic-settings>=2.0",
"python-jose[cryptography]>=3.3", "python-jose[cryptography]>=3.3",
"passlib[bcrypt]>=1.7", "passlib[bcrypt]>=1.7",

View File

@ -1,6 +1,6 @@
POSTGRES_DB=finance_ai POSTGRES_DB=finance_ai
POSTGRES_USER=finance_ai POSTGRES_USER=finance_ai
POSTGRES_PASSWORD=finance_ai POSTGRES_PASSWORD=finance_ai
DATABASE_URL=postgresql+psycopg2://finance_ai:finance_ai@postgres:5432/finance_ai DATABASE_URL=postgresql+psycopg://finance_ai:finance_ai@postgres:5432/finance_ai
JWT_SECRET_KEY=change-me-in-production JWT_SECRET_KEY=change-me-in-production
ALLOWED_ORIGINS=http://localhost:5173,http://127.0.0.1:5173,http://localhost ALLOWED_ORIGINS=http://localhost:5173,http://127.0.0.1:5173,http://localhost

View File

@ -24,7 +24,7 @@ services:
build: build:
context: ../backend context: ../backend
environment: environment:
DATABASE_URL: ${DATABASE_URL:-postgresql+psycopg2://finance_ai:finance_ai@postgres:5432/finance_ai} DATABASE_URL: ${DATABASE_URL:-postgresql+psycopg://finance_ai:finance_ai@postgres:5432/finance_ai}
JWT_SECRET_KEY: ${JWT_SECRET_KEY:-change-me-in-production} JWT_SECRET_KEY: ${JWT_SECRET_KEY:-change-me-in-production}
ALLOWED_ORIGINS: ${ALLOWED_ORIGINS:-http://localhost:5173,http://127.0.0.1:5173,http://localhost} ALLOWED_ORIGINS: ${ALLOWED_ORIGINS:-http://localhost:5173,http://127.0.0.1:5173,http://localhost}
SMS_PROVIDER: mock SMS_PROVIDER: mock

246
deploy/setup-backend.sh Normal file
View File

@ -0,0 +1,246 @@
#!/usr/bin/env bash
# 财务大模型智能问答客服系统 — 后端一键落地脚本
#
# 前置:
# - Ubuntu 24.04Python 3.12、postgresql、redis-server、nginx 已通过 apt 安装
# - 代码已放在 /home/ubuntu/finai/{backend,frontend,miniapp}
# - 用 ubuntu 账号执行本脚本;脚本内需要 root 权限的步骤会自己 sudo
#
# 用法:
# bash /home/ubuntu/finai/deploy/setup-backend.sh
# 可选环境变量:
# DOMAIN=finai.ctc-zc.com 站点域名(写进 ALLOWED_ORIGINS
# DB_NAME=finai PG 库名 = 用户名
# DB_PASS=<留空自动生成> PG 用户密码
# FORCE_ENV=1 即使 .env 已存在也覆盖重写
#
# 幂等性:
# venv、依赖、PG 用户/库、.env、systemd unit 均检查后再动作,重复执行安全。
set -Eeuo pipefail
# ─── 可调参数 ──────────────────────────────────────────────
FINAI_HOME="/home/ubuntu/finai"
BACKEND_DIR="${FINAI_HOME}/backend"
STORAGE_DIR="${BACKEND_DIR}/storage"
VENV_DIR="${BACKEND_DIR}/.venv"
ENV_FILE="${BACKEND_DIR}/.env"
DOMAIN="${DOMAIN:-finai.ctc-zc.com}"
DB_NAME="${DB_NAME:-finai}"
DB_USER="${DB_NAME}"
DB_PASS="${DB_PASS:-}"
FORCE_ENV="${FORCE_ENV:-0}"
SERVICE_NAME="finance-ai-backend"
SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service"
# ─── 工具函数 ──────────────────────────────────────────────
c_green() { printf '\033[32m%s\033[0m\n' "$*"; }
c_yellow() { printf '\033[33m%s\033[0m\n' "$*"; }
c_red() { printf '\033[31m%s\033[0m\n' "$*" >&2; }
step() { printf '\n\033[36m▶ %s\033[0m\n' "$*"; }
require_cmd() {
command -v "$1" >/dev/null 2>&1 || {
c_red "缺少命令:$1,请先 apt 安装对应包"; exit 1;
}
}
# ─── 0. 前置检查 ───────────────────────────────────────────
step "0. 前置检查"
if [[ "${EUID}" -eq 0 ]]; then
c_red "请用 ubuntu 账号(非 root运行脚本内部会按需 sudo。"
exit 1
fi
if [[ "$(whoami)" != "ubuntu" ]]; then
c_yellow "当前用户是 $(whoami)(非 ubuntu若你确认要用当前用户可继续5 秒内 Ctrl+C 中止…"
sleep 5
fi
require_cmd python3.12
require_cmd psql
require_cmd systemctl
require_cmd openssl
require_cmd curl
if [[ ! -d "${BACKEND_DIR}" ]]; then
c_red "找不到 ${BACKEND_DIR},请先把代码上传到 ${FINAI_HOME}/backend"
exit 1
fi
if [[ ! -f "${BACKEND_DIR}/pyproject.toml" ]]; then
c_red "${BACKEND_DIR}/pyproject.toml 不存在,代码目录不完整"
exit 1
fi
sudo chown -R ubuntu:ubuntu "${FINAI_HOME}"
# ─── 1. Python 虚拟环境 + 依赖 ─────────────────────────────
step "1. 虚拟环境 & 依赖"
if [[ ! -x "${VENV_DIR}/bin/python" ]]; then
python3.12 -m venv "${VENV_DIR}"
c_green "venv 已创建:${VENV_DIR}"
else
c_yellow "venv 已存在,跳过创建"
fi
# shellcheck disable=SC1091
source "${VENV_DIR}/bin/activate"
pip install --upgrade pip >/dev/null
pip install . >/dev/null
c_green "依赖安装完成"
python - <<'PY'
import fastapi, sqlalchemy, pydantic_settings, httpx
from tencentcloud.lke.v20231130 import lke_client # noqa: F401
print(f" fastapi={fastapi.__version__} sqlalchemy={sqlalchemy.__version__}")
PY
mkdir -p "${STORAGE_DIR}"
# ─── 2. PostgreSQL 库 & 用户 ───────────────────────────────
step "2. PostgreSQL 建库建用户"
sudo systemctl is-active --quiet postgresql || {
c_red "postgresql 服务未运行,先 sudo systemctl start postgresql"
exit 1
}
user_exists=$(sudo -u postgres psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='${DB_USER}'" || true)
db_exists=$(sudo -u postgres psql -tAc "SELECT 1 FROM pg_database WHERE datname='${DB_NAME}'" || true)
if [[ "${user_exists}" != "1" ]]; then
if [[ -z "${DB_PASS}" ]]; then
DB_PASS="$(openssl rand -hex 16)"
c_yellow "自动生成 DB_PASS = ${DB_PASS} ← 保存好"
fi
sudo -u postgres psql -v ON_ERROR_STOP=1 <<SQL
CREATE USER ${DB_USER} WITH PASSWORD '${DB_PASS}';
SQL
c_green "PG 用户 ${DB_USER} 已创建"
else
c_yellow "PG 用户 ${DB_USER} 已存在"
if [[ -z "${DB_PASS}" ]]; then
if [[ -f "${ENV_FILE}" ]] && grep -q '^DATABASE_URL=' "${ENV_FILE}"; then
DB_PASS="$(sed -nE 's|^DATABASE_URL=postgresql\+psycopg://[^:]+:([^@]+)@.*|\1|p' "${ENV_FILE}")"
c_yellow "从旧 .env 读回 DB_PASS = ${DB_PASS}"
else
DB_PASS="$(openssl rand -hex 16)"
c_yellow "旧用户密码未知,改写为新 DB_PASS = ${DB_PASS}"
sudo -u postgres psql -v ON_ERROR_STOP=1 <<SQL
ALTER USER ${DB_USER} WITH PASSWORD '${DB_PASS}';
SQL
fi
fi
fi
if [[ "${db_exists}" != "1" ]]; then
sudo -u postgres psql -v ON_ERROR_STOP=1 <<SQL
CREATE DATABASE ${DB_NAME} OWNER ${DB_USER} ENCODING 'UTF8';
GRANT ALL PRIVILEGES ON DATABASE ${DB_NAME} TO ${DB_USER};
SQL
c_green "PG 数据库 ${DB_NAME} 已创建"
else
c_yellow "PG 数据库 ${DB_NAME} 已存在"
fi
PGPASSWORD="${DB_PASS}" psql -h 127.0.0.1 -U "${DB_USER}" -d "${DB_NAME}" -c '\conninfo' \
|| { c_red "PG 连接失败,请检查 pg_hba.conf 是否放行 127.0.0.1/32 scram-sha-256"; exit 1; }
# ─── 3. 生成 .env ──────────────────────────────────────────
step "3. 生成 .env"
if [[ -f "${ENV_FILE}" && "${FORCE_ENV}" != "1" ]]; then
c_yellow ".env 已存在,保留不改。要重写请用 FORCE_ENV=1 重跑"
else
JWT_KEY="$(openssl rand -hex 32)"
cat > "${ENV_FILE}" <<EOF
ENVIRONMENT=production
DATABASE_URL=postgresql+psycopg://${DB_USER}:${DB_PASS}@127.0.0.1:5432/${DB_NAME}
JWT_SECRET_KEY=${JWT_KEY}
JWT_EXPIRE_MINUTES=480
ALLOWED_ORIGINS=https://${DOMAIN},http://${DOMAIN}
SMS_PROVIDER=mock
ADP_PROVIDER=mock
STORAGE_PROVIDER=local
LOCAL_STORAGE_DIR=${STORAGE_DIR}
# 接真实腾讯云 ADP 时把 mock 换 tencent并填以下
# ADP_BOT_APP_KEY=xxx
# ADP_KNOWLEDGE_BIZ_ID=xxx
# TENCENT_SECRET_ID=xxx
# TENCENT_SECRET_KEY=xxx
# TENCENT_REGION=ap-guangzhou
EOF
chmod 600 "${ENV_FILE}"
c_green ".env 已写入 ${ENV_FILE}"
fi
# ─── 4. systemd 单元 ──────────────────────────────────────
step "4. 部署 systemd 单元 ${SERVICE_NAME}"
sudo tee "${SERVICE_FILE}" >/dev/null <<EOF
[Unit]
Description=Finance AI Backend (FastAPI)
After=network.target postgresql.service redis-server.service
Wants=postgresql.service redis-server.service
[Service]
Type=simple
User=ubuntu
Group=ubuntu
WorkingDirectory=${BACKEND_DIR}
EnvironmentFile=${ENV_FILE}
ExecStart=${VENV_DIR}/bin/uvicorn app.main:app --host 127.0.0.1 --port 8000 --workers 2 --proxy-headers
Restart=always
RestartSec=3
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=false
ReadWritePaths=${BACKEND_DIR}
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable "${SERVICE_NAME}" >/dev/null
sudo systemctl restart "${SERVICE_NAME}"
sleep 2
sudo systemctl --no-pager status "${SERVICE_NAME}" | head -n 12 || true
# ─── 5. 健康检查 ──────────────────────────────────────────
step "5. 健康检查"
for i in 1 2 3 4 5; do
if curl -fsS http://127.0.0.1:8000/health >/dev/null; then
c_green "GET /health OK"
break
fi
if [[ "$i" == "5" ]]; then
c_red "5 次探测均失败,跑 journalctl -u ${SERVICE_NAME} -n 100 --no-pager 排查"
exit 1
fi
sleep 2
done
# ─── 6. 总结 ──────────────────────────────────────────────
step "6. 完成 ✔"
cat <<EOF
后端已上线http://127.0.0.1:8000 (仅本机可达,供 Nginx 反代)
默认管理员: 手机号 13800000000 验证码 123456 mock 模式)
数据库: ${DB_NAME} / ${DB_USER} / ${DB_PASS}
JWT 密钥: 保存在 ${ENV_FILE}chmod 600
systemd sudo systemctl status ${SERVICE_NAME}
日志: journalctl -u ${SERVICE_NAME} -f
下一步:按 run.md §4 构建并发布前端到 NginxHTTPS 见 §4.4certbot -d ${DOMAIN})。
EOF