补充 README:项目结构、完整接口清单与测试/部署说明
同步文档与当前实现,补上 profiles stats 等缺失接口,并细化认证约定、环境变量与模块化测试说明。
This commit is contained in:
@@ -9,6 +9,36 @@ OpenCloud 的 FastAPI 后端,替代原项目中的 Supabase Database、Auth
|
||||
- Argon2 密码哈希、JWT Access Token、数据库 Refresh Session
|
||||
- 本地持久化图片目录、Pillow 图片验证和缩略图生成
|
||||
- SMTP 邮箱确认与密码重置
|
||||
- 依赖管理:`uv`(`pyproject.toml` + `uv.lock`)
|
||||
|
||||
## 项目结构
|
||||
|
||||
```text
|
||||
app/
|
||||
main.py # FastAPI 入口、CORS、/media 静态挂载
|
||||
config.py # 环境变量与生产校验
|
||||
database.py # 异步引擎与会话
|
||||
models.py # SQLAlchemy 模型
|
||||
schemas.py # 请求/响应 Pydantic 模型
|
||||
security.py # 密码哈希、JWT、Token 工具
|
||||
deps.py # 依赖注入(当前用户、管理员)
|
||||
serializers.py # ORM → 响应 DTO
|
||||
seed.py # 云类型与初始管理员
|
||||
routers/
|
||||
auth.py # 认证
|
||||
clouds.py # 云图
|
||||
cloud_types.py # 云类型
|
||||
collections.py # 图鉴
|
||||
profiles.py # 用户资料
|
||||
admin.py # 管理后台
|
||||
health.py # 健康检查
|
||||
services/
|
||||
email.py # 邮件发送
|
||||
storage.py # 图片校验、存储与删除
|
||||
alembic/ # 数据库迁移
|
||||
tests/ # 按模块划分的 pytest 用例
|
||||
data/uploads/ # 运行时图片目录(gitignore)
|
||||
```
|
||||
|
||||
## 本地启动
|
||||
|
||||
@@ -48,17 +78,19 @@ uv run uvicorn app.main:app --reload
|
||||
|
||||
- API:`http://localhost:8000/api/v1`
|
||||
- Swagger:`http://localhost:8000/docs`
|
||||
- ReDoc:`http://localhost:8000/redoc`
|
||||
- 健康检查:`http://localhost:8000/api/v1/health`
|
||||
- 媒体文件:`http://localhost:8000/media/...`
|
||||
|
||||
## 认证约定
|
||||
|
||||
登录成功后响应包含有效期较短的 `access_token`。前端在请求头中发送:
|
||||
登录成功后响应包含有效期较短的 `access_token`(默认 15 分钟)。前端在请求头中发送:
|
||||
|
||||
```text
|
||||
Authorization: Bearer <access_token>
|
||||
```
|
||||
|
||||
Refresh Token 只保存在 HttpOnly Cookie 中。前端调用 `/api/v1/auth/refresh` 和 `/api/v1/auth/logout` 时必须允许 Cookie:
|
||||
Refresh Token 只保存在 HttpOnly Cookie 中(默认名 `opencloud_refresh`,有效期 30 天)。前端调用 `/api/v1/auth/refresh` 和 `/api/v1/auth/logout` 时必须允许 Cookie:
|
||||
|
||||
```ts
|
||||
fetch(url, { credentials: 'include' })
|
||||
@@ -66,46 +98,96 @@ fetch(url, { credentials: 'include' })
|
||||
|
||||
开发环境默认 `EMAIL_DELIVERY_MODE=console`,邮箱确认和密码重置链接会输出到后端日志。如需真实发信,请在 `.env` 中配置 SMTP 参数并将 `EMAIL_DELIVERY_MODE` 改为 `smtp`。
|
||||
|
||||
密码最短 8 位;注册后需完成邮箱确认才能登录。管理员接口需要 `profile.role = admin` 的 Access Token。
|
||||
|
||||
## 主要接口
|
||||
|
||||
精确的请求/响应字段以 Swagger/OpenAPI 为准。以下为当前路由清单与常用约定。
|
||||
|
||||
### 系统
|
||||
|
||||
- `GET /api/v1/health`:健康检查
|
||||
|
||||
### 认证
|
||||
|
||||
- `POST /api/v1/auth/register`
|
||||
- `POST /api/v1/auth/resend-confirmation`
|
||||
- `POST /api/v1/auth/confirm-email`
|
||||
- `POST /api/v1/auth/login`
|
||||
- `POST /api/v1/auth/refresh`
|
||||
- `POST /api/v1/auth/logout`
|
||||
- `GET /api/v1/auth/me`
|
||||
- `POST /api/v1/auth/forgot-password`
|
||||
- `POST /api/v1/auth/reset-password`
|
||||
- `PATCH /api/v1/auth/password`
|
||||
| 方法 | 路径 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `POST` | `/api/v1/auth/register` | 注册 |
|
||||
| `POST` | `/api/v1/auth/resend-confirmation` | 重发确认邮件 |
|
||||
| `POST` | `/api/v1/auth/confirm-email` | 确认邮箱 |
|
||||
| `POST` | `/api/v1/auth/login` | 登录,返回 access token 并设置 refresh cookie |
|
||||
| `POST` | `/api/v1/auth/refresh` | 用 refresh cookie 换取新 access token |
|
||||
| `POST` | `/api/v1/auth/logout` | 注销当前会话 |
|
||||
| `GET` | `/api/v1/auth/me` | 当前用户与资料 |
|
||||
| `POST` | `/api/v1/auth/forgot-password` | 发起密码重置 |
|
||||
| `POST` | `/api/v1/auth/reset-password` | 凭 token 重置密码 |
|
||||
| `PATCH` | `/api/v1/auth/password` | 已登录用户修改密码 |
|
||||
|
||||
### 云图和图鉴
|
||||
### 云图
|
||||
|
||||
- `GET/POST /api/v1/clouds`
|
||||
- `GET /api/v1/clouds/map`
|
||||
- `GET/PATCH/DELETE /api/v1/clouds/{id}`
|
||||
- `POST /api/v1/clouds/batch-delete`
|
||||
- `GET /api/v1/cloud-types`
|
||||
- `GET /api/v1/cloud-types/{id}/clouds`
|
||||
- `GET /api/v1/collections/me`
|
||||
| 方法 | 路径 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `GET` | `/api/v1/clouds` | 公开画廊分页;支持 `page`、`page_size`、`type_id`、`search` |
|
||||
| `GET` | `/api/v1/clouds/map` | 地图点位;必填 `start`/`end`,可选 `time_field`、`limit` |
|
||||
| `GET` | `/api/v1/clouds/{id}` | 云图详情(未审核/隐藏仅本人或管理员可见) |
|
||||
| `POST` | `/api/v1/clouds` | 上传云图(需登录) |
|
||||
| `PATCH` | `/api/v1/clouds/{id}` | 更新元数据(本人) |
|
||||
| `DELETE` | `/api/v1/clouds/{id}` | 删除(本人) |
|
||||
| `POST` | `/api/v1/clouds/batch-delete` | 批量删除(本人) |
|
||||
|
||||
上传使用 `multipart/form-data`,文件字段为 `image`;服务端自行验证、重编码原图并生成 JPEG 缩略图。新图片状态始终为 `pending`,`user_id` 从 Access Token 获取,经纬度在服务端保留两位小数。
|
||||
上传使用 `multipart/form-data`,文件字段为 `image`;其他表单字段包括:
|
||||
|
||||
### 用户和管理后台
|
||||
- `cloud_type_id` 与 `custom_cloud_type` 二选一
|
||||
- `latitude` / `longitude`(成对出现,服务端保留两位小数)
|
||||
- `location_name`、`description`、`captured_at`、`is_hidden`
|
||||
|
||||
- `GET /api/v1/profiles/{user_id}`
|
||||
- `PATCH /api/v1/profiles/me`
|
||||
- `GET /api/v1/profiles/{user_id}/clouds`
|
||||
- `GET /api/v1/admin/stats`
|
||||
- `GET/PATCH /api/v1/admin/users`
|
||||
- `GET /api/v1/admin/clouds`
|
||||
- `PATCH /api/v1/admin/clouds/status`
|
||||
- `PATCH /api/v1/admin/clouds/visibility`
|
||||
- `POST /api/v1/admin/clouds/batch-delete`
|
||||
服务端会验证格式(JPEG/PNG/WebP)、限制大小与像素、重编码原图并生成 JPEG 缩略图。新图片状态始终为 `pending`,`user_id` 从 Access Token 获取。审核通过后,若使用标准云类型会自动解锁图鉴。
|
||||
|
||||
精确的请求和响应模型以 Swagger/OpenAPI 为准。
|
||||
画廊 `search` 约定:
|
||||
|
||||
- 普通关键词:匹配中英文云类型名、自定义类型名
|
||||
- 以 `@` 开头:按用户名模糊搜索
|
||||
|
||||
### 云类型与图鉴
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `GET` | `/api/v1/cloud-types` | 全部云类型 |
|
||||
| `GET` | `/api/v1/cloud-types/{id}` | 单个云类型 |
|
||||
| `GET` | `/api/v1/cloud-types/{id}/clouds` | 该类型下的公开云图分页 |
|
||||
| `GET` | `/api/v1/collections/me` | 当前用户已解锁图鉴(需登录) |
|
||||
|
||||
seed 会写入 10 种标准云类型(积云、层云、卷云等),含中英文名、云族与稀有度。
|
||||
|
||||
### 用户资料
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `GET` | `/api/v1/profiles/{user_id}` | 公开资料 |
|
||||
| `PATCH` | `/api/v1/profiles/me` | 修改自己的昵称(需登录) |
|
||||
| `GET` | `/api/v1/profiles/{user_id}/clouds` | 用户云图分页;本人/管理员可见全部,他人仅公开已审核 |
|
||||
| `GET` | `/api/v1/profiles/{user_id}/stats` | 用户统计(**管理员**):全部/公开上传数、收藏数 |
|
||||
|
||||
`ProfileStatsOut` 字段:
|
||||
|
||||
- `user_id`、`username`、`email`、`created_at`
|
||||
- `cloud_count`:该用户全部云图数
|
||||
- `public_cloud_count`:已审核且未隐藏的公开云图数
|
||||
- `collection_count`:已解锁图鉴数
|
||||
|
||||
### 管理后台
|
||||
|
||||
均需管理员 Access Token。
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `GET` | `/api/v1/admin/stats` | 全站统计(用户数、今日上传、审核状态分布等;“今日”按 Asia/Shanghai) |
|
||||
| `GET` | `/api/v1/admin/users` | 用户列表分页 |
|
||||
| `PATCH` | `/api/v1/admin/users/{user_id}` | 修改角色或禁用状态(不能禁用/降权自己) |
|
||||
| `GET` | `/api/v1/admin/clouds` | 全部云图;可按 `status`、`is_hidden` 过滤 |
|
||||
| `PATCH` | `/api/v1/admin/clouds/status` | 批量修改审核状态 |
|
||||
| `PATCH` | `/api/v1/admin/clouds/visibility` | 批量修改隐藏状态 |
|
||||
| `POST` | `/api/v1/admin/clouds/batch-delete` | 批量删除云图与本地文件 |
|
||||
|
||||
## 环境变量
|
||||
|
||||
@@ -117,14 +199,19 @@ cp .env.example .env
|
||||
|
||||
其中关键配置包括:
|
||||
|
||||
- `DATABASE_URL`:异步 SQLAlchemy 连接串,PostgreSQL 使用 `postgresql+asyncpg://`
|
||||
- `SECRET_KEY`:JWT 签名密钥,生产环境至少 32 个字符
|
||||
- `FRONTEND_URL`:认证邮件中的前端地址
|
||||
- `PUBLIC_BASE_URL`:API 和图片公开地址
|
||||
- `CORS_ORIGINS`:逗号分隔的前端来源
|
||||
- `UPLOAD_DIR`:持久化图片目录
|
||||
- `EMAIL_DELIVERY_MODE`:`console` 或 `smtp`
|
||||
- `ADMIN_EMAIL`、`ADMIN_PASSWORD`:执行 seed 时可选创建管理员
|
||||
| 变量 | 说明 |
|
||||
| --- | --- |
|
||||
| `DATABASE_URL` | 异步 SQLAlchemy 连接串,PostgreSQL 使用 `postgresql+asyncpg://` |
|
||||
| `SECRET_KEY` | JWT 签名密钥;生产环境至少 32 个字符,勿使用示例值 |
|
||||
| `ENVIRONMENT` | `development` / `test` / `production` |
|
||||
| `FRONTEND_URL` | 认证邮件中的前端地址 |
|
||||
| `PUBLIC_BASE_URL` | API 和图片公开地址 |
|
||||
| `CORS_ORIGINS` | 逗号分隔的前端来源 |
|
||||
| `UPLOAD_DIR` | 持久化图片目录 |
|
||||
| `MAX_UPLOAD_BYTES` | 上传大小上限(默认 20MB) |
|
||||
| `EMAIL_DELIVERY_MODE` | `console` 或 `smtp` |
|
||||
| `ADMIN_EMAIL` / `ADMIN_PASSWORD` | 执行 seed 时可选创建管理员 |
|
||||
| `COOKIE_SECURE` / `COOKIE_SAMESITE` | Refresh Cookie 安全属性 |
|
||||
|
||||
生产环境还必须设置 `ENVIRONMENT=production`、`COOKIE_SECURE=true`,并通过 HTTPS 访问。
|
||||
|
||||
@@ -177,11 +264,22 @@ uv run python -m compileall -q app main.py
|
||||
uv run alembic upgrade head --sql
|
||||
```
|
||||
|
||||
测试覆盖注册、邮箱确认、登录、资料修改、上传、缩略图、图鉴解锁、管理员审核、画廊、地图和删除的完整流程。
|
||||
测试使用临时 SQLite 与上传目录,按模块覆盖:
|
||||
|
||||
- `tests/test_auth.py`:注册、邮箱确认、登录、刷新、登出、改密
|
||||
- `tests/test_clouds.py`:上传、缩略图、画廊、地图、更新与删除
|
||||
- `tests/test_collections.py`:图鉴解锁
|
||||
- `tests/test_cloud_types.py`:云类型查询
|
||||
- `tests/test_profiles.py`:资料、用户云图、管理员 stats
|
||||
- `tests/test_admin.py`:统计、用户管理、审核与批量操作
|
||||
|
||||
当前约 80 个用例。生产环境仍建议在真实 PostgreSQL 上再跑一遍迁移与关键路径。
|
||||
|
||||
## 部署注意事项
|
||||
|
||||
- `data/uploads` 必须挂载到持久化磁盘并纳入备份。
|
||||
- PostgreSQL 和上传目录需要分别制定备份与恢复方案。
|
||||
- 单机部署可由 FastAPI 提供 `/media`;高流量部署建议由 Nginx/Caddy 直接服务该目录。
|
||||
- 多实例部署时,上传目录需共享存储或改为对象存储;当前实现为本地文件系统。
|
||||
- `is_hidden` 与原 Supabase 公共 bucket 行为一致,只阻止页面发现,知道图片 URL 的人仍可直接访问。若需要真正私密图片,应改为鉴权下载或短期签名 URL。
|
||||
- 生产环境请使用独立的非超级用户数据库账号,并轮换 `SECRET_KEY` 与管理员密码。
|
||||
|
||||
Reference in New Issue
Block a user