diff --git a/backend/.env.example b/backend/.env.example index ea649db..cfdd96b 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,6 +1,6 @@ APP_NAME=财务大模型智能问答客服系统 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_EXPIRE_MINUTES=480 SMS_PROVIDER=mock diff --git a/backend/README.md b/backend/README.md index 88cb05d..c7560c7 100644 --- a/backend/README.md +++ b/backend/README.md @@ -12,7 +12,7 @@ python -m uvicorn app.main:app --reload --port 8000 默认会使用 `sqlite:///./finance_ai.db`。生产环境请复制 `.env.example` 为 `.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 ``` diff --git a/backend/pyproject.toml b/backend/pyproject.toml index e1f31a7..dce98d1 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -7,7 +7,7 @@ dependencies = [ "fastapi>=0.110", "uvicorn[standard]>=0.27", "sqlalchemy>=2.0", - "psycopg2-binary>=2.9", + "psycopg[binary]>=3.1.0", "pydantic-settings>=2.0", "python-jose[cryptography]>=3.3", "passlib[bcrypt]>=1.7", diff --git a/deploy/.env.example b/deploy/.env.example index f319592..b8a328a 100644 --- a/deploy/.env.example +++ b/deploy/.env.example @@ -1,6 +1,6 @@ POSTGRES_DB=finance_ai POSTGRES_USER=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 ALLOWED_ORIGINS=http://localhost:5173,http://127.0.0.1:5173,http://localhost diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index 9e799e1..f307a7c 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -24,7 +24,7 @@ services: build: context: ../backend 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} ALLOWED_ORIGINS: ${ALLOWED_ORIGINS:-http://localhost:5173,http://127.0.0.1:5173,http://localhost} SMS_PROVIDER: mock diff --git a/deploy/setup-backend.sh b/deploy/setup-backend.sh new file mode 100644 index 0000000..5063e1e --- /dev/null +++ b/deploy/setup-backend.sh @@ -0,0 +1,246 @@ +#!/usr/bin/env bash +# 财务大模型智能问答客服系统 — 后端一键落地脚本 +# +# 前置: +# - Ubuntu 24.04,Python 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 < "${ENV_FILE}" </dev/null </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 <