# 阶段 2:SQLAlchemy + Alembic + 用户登录体系 ## 1. 数据库与迁移 - **连接**:backend 通过环境变量 `DATABASE_URL`(异步 `postgresql+asyncpg://...`)连接 PostgreSQL;Alembic 使用 `DATABASE_URL_SYNC`(同步 `postgresql://...`)。 - **表结构**: - **users**:id (uuid)、username、password_hash、role、is_active、created_at - **audit_logs**:id (uuid)、actor_user_id (FK users)、action、meta_json (JSONB)、created_at ### 迁移流程说明 Alembic 会按版本顺序执行 `alembic/versions/` 下的迁移脚本,在数据库中创建或修改表,并在库中记录当前版本(表 `alembic_version`)。 执行时: 1. 读取 `backend/alembic.ini` 和 `backend/alembic/env.py`。 2. `env.py` 从 `app.config.settings` 读取 `database_url_sync`(即环境变量 `DATABASE_URL_SYNC`),用该同步连接串连接 PostgreSQL。 3. 对比数据库中的 `alembic_version` 与本地迁移文件,将尚未执行的迁移按顺序执行(例如 `001_users_and_audit_logs.py` 会创建 `users`、`audit_logs` 表)。 --- ### 方式 A:Docker 启动时自动迁移 **步骤:** 1. 在项目根目录执行: ```bash docker compose up -d ``` 2. Compose 会先启动 `db`,等待健康检查通过后再启动 `backend`。 3. **backend 容器**的启动命令为: ```text sh -c "alembic upgrade head && uvicorn app.main:app --host 0.0.0.0 --port 8000" ``` 4. **迁移阶段**: - 容器内当前目录为 `/app`(即 backend 代码根目录),`PYTHONPATH=/app`。 - 执行 `alembic upgrade head` 时,Alembic 在 `/app` 下找到 `alembic.ini`、`alembic/env.py` 和 `alembic/versions/`。 - 环境变量由 `docker-compose` 注入(含 `DATABASE_URL_SYNC=postgresql://wecom:wecom_secret@db:5432/wecom_ai`,注意主机名为 `db`)。 - `env.py` 通过 `app.config.settings` 读到该连接串,用**同步**驱动连接 PostgreSQL,执行所有未执行的迁移(如创建 `users`、`audit_logs`)。 5. **应用启动**:迁移成功后执行 `uvicorn ...`,FastAPI 启动。 **注意**:若迁移失败(例如数据库未就绪、连接串错误),整条 CMD 会失败,容器退出,backend 不会起来。可查看日志:`docker compose logs backend`。 **一键迁移脚本(Docker)**:在项目根目录执行其一即可完成「起 db → 等就绪 → 执行 alembic upgrade head」: - **Windows PowerShell**:`.\deploy\scripts\migrate.ps1` - **Linux / macOS**:`bash deploy/scripts/migrate.sh` - **任意平台(Python)**:`python deploy/scripts/migrate.py`(默认 Docker);本机迁移用 `python deploy/scripts/migrate.py --local` --- ### 方式 B:本机执行迁移 适用于在本机用 Python 直接跑迁移、数据库可在本机访问(本地 PostgreSQL 或已映射端口的 Docker 数据库)的情况。 **前提**: - 本机已安装 Python 3.12、PostgreSQL 客户端库(`psycopg2-binary`)。 - 数据库已存在(例如 Docker 只起 db:`docker compose up -d db`),且已知连接信息。 **步骤:** 1. **进入 backend 目录**(迁移必须在 backend 根目录执行,以便找到 `alembic.ini` 和 `alembic/`): ```bash cd backend ``` 2. **准备环境变量**(二选一): - 在项目根目录已有 `.env` 时,可在 backend 下复制一份,让 `app.config` 自动读取: ```bash cp ../.env .env ``` - 或直接设置连接串(PowerShell 示例): ```powershell $env:DATABASE_URL_SYNC = "postgresql://wecom:wecom_secret@localhost:5432/wecom_ai" ``` Linux/macOS: ```bash export DATABASE_URL_SYNC=postgresql://wecom:wecom_secret@localhost:5432/wecom_ai ``` 若数据库在 Docker 且未改端口,主机填 `localhost`、端口 `5432` 即可。 3. **安装依赖**(若未装过): ```bash pip install -r requirements.txt ``` 4. **执行迁移**: ```bash alembic upgrade head ``` - Alembic 会读取当前目录下的 `alembic.ini` 和 `alembic/env.py`。 - `env.py` 里会 `from app.config import settings`、`from app.models import Base`,因此当前目录必须在 backend(或设置 `PYTHONPATH` 指向 backend),这样 `app` 才能正确解析。 - 执行后,数据库中会创建/更新表,并写入 `alembic_version`。 5. **验证**:连接数据库查看是否有 `users`、`audit_logs` 及 `alembic_version` 表。 **本机常见问题**: - 报错 `No module named 'app'`:未在 `backend` 目录执行,或未设置 `PYTHONPATH`。解决:`cd backend` 再执行,或 `PYTHONPATH=backend alembic -c backend/alembic.ini upgrade head`(在项目根目录时)。 - 连接被拒绝:检查 `DATABASE_URL_SYNC` 的主机、端口、用户名、密码、数据库名是否与真实 PostgreSQL 一致(Docker 时主机为 `localhost`,端口一般为 `5432`)。 ## 2. 如何创建管理员 使用 seed 脚本(从 **项目根目录** 执行,依赖已安装的 Python 和 .env): ```bash # 确保 .env 存在,且 DATABASE_URL_SYNC 指向数据库(Docker 时用 localhost:5432) pip install python-dotenv bcrypt psycopg2-binary sqlalchemy python deploy/scripts/seed.py ``` 可选环境变量(在 .env 或导出): - `ADMIN_USERNAME`:默认 `admin` - `ADMIN_PASSWORD`:默认 `admin` - `DATABASE_URL_SYNC`:同步连接串,Docker 时一般为 `postgresql://wecom:wecom_secret@localhost:5432/wecom_ai` 脚本会检查 `users` 表是否存在;若不存在会提示先执行迁移。若用户名已存在则跳过创建。 ## 3. Auth API - **POST /api/auth/login** - Body: `{"username":"admin","password":"admin"}` - 成功:200,`{"access_token":"...","token_type":"bearer"}` - 失败:401/403 - **GET /api/auth/me** - Header: `Authorization: Bearer ` - 成功:200,当前用户信息(id、username、role、is_active、created_at) - 失败:401/403 ## 4. 管理后台 - **Token 存储**:使用 **localStorage**,key 为 `"token"`。实现见 `admin/lib/api.ts` 注释。生产环境可改为 httpOnly Cookie 由后端 Set-Cookie。 - **登录页 /login**:表单提交后调用 `POST /api/auth/login`,成功则写入 localStorage 并跳转 `/dashboard`。 - **Dashboard /dashboard**:需登录;进入时请求 `GET /api/auth/me`,失败则清除 token 并跳转 `/login`;成功则展示当前用户信息与退出按钮。 - **根路径 /**:有 token 则跳转 `/dashboard`,否则跳转 `/login`。 ## 5. 安全 - **密码**:bcrypt(passlib + bcrypt 4.1.2)。 - **JWT**:有过期时间,由 `JWT_EXPIRE_MINUTES` 控制(默认 60 分钟);密钥 `JWT_SECRET` 需在生产环境修改。 ## 6. 如何登录验证 1. 启动:`docker compose up -d`(自动执行迁移)。 2. 创建管理员:`python deploy/scripts/seed.py`(见上文)。 3. 打开 http://localhost 或 http://localhost:3000,应跳转登录页。 4. 输入 admin / admin(或你在 .env 里设置的 `ADMIN_USERNAME` / `ADMIN_PASSWORD`),提交后应跳转 Dashboard 并显示当前用户信息。 5. 点击退出后应回到登录页;再次访问 /dashboard 应被重定向到 /login。 直连 API 验证: ```bash # 登录取 token TOKEN=$(curl -s -X POST http://localhost:8000/api/auth/login -H "Content-Type: application/json" -d '{"username":"admin","password":"admin"}' | jq -r .access_token) # 当前用户 curl -s -H "Authorization: Bearer $TOKEN" http://localhost:8000/api/auth/me ```