You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
12 KiB
12 KiB
API 设计规范
文档版本:v1.0
最后更新:2025-01-27
目录
API 设计原则
1. RESTful 风格
遵循 REST 架构风格,使用标准 HTTP 方法:
| 方法 | 用途 | 示例 |
|---|---|---|
| GET | 获取资源 | GET /api/v1/projects |
| POST | 创建资源 | POST /api/v1/projects |
| PUT | 完整更新资源 | PUT /api/v1/projects/{id} |
| PATCH | 部分更新资源 | PATCH /api/v1/projects/{id} |
| DELETE | 删除资源 | DELETE /api/v1/projects/{id} |
2. 资源命名
- 使用复数名词:
/projects而非/project - 使用小写字母和连字符:
/project-members而非/projectMembers - 避免动词:
POST /projects而非/createProject
3. 嵌套资源
GET /api/v1/projects/{project_id}/storyboards
POST /api/v1/projects/{project_id}/storyboards
GET /api/v1/projects/{project_id}/storyboards/{storyboard_id}
PUT /api/v1/projects/{project_id}/storyboards/{storyboard_id}
DELETE /api/v1/projects/{project_id}/storyboards/{storyboard_id}
路由结构
完整 API 路由表
/api/v1
├── /auth
│ ├── POST /register # 用户注册
│ ├── POST /login # 用户登录
│ ├── POST /logout # 用户登出
│ └── POST /refresh # 刷新 Token
│
├── /folders
│ ├── GET / # 获取文件夹列表
│ ├── POST / # 创建文件夹
│ ├── GET /{id} # 获取文件夹详情
│ ├── PUT /{id} # 更新文件夹
│ ├── DELETE /{id} # 删除文件夹
│ ├── POST /{id}/move # 移动文件夹
│ └── GET /tree # 获取文件夹树
│
├── /projects
│ ├── GET / # 获取项目列表
│ ├── POST / # 创建项目
│ ├── GET /{id} # 获取项目详情
│ ├── PUT /{id} # 更新项目
│ ├── DELETE /{id} # 删除项目
│ ├── POST /{id}/move # 移动项目到文件夹
│ └── GET /{id}/members # 获取项目成员
│
├── /scripts
│ ├── GET / # 获取剧本列表
│ ├── POST / # 创建剧本
│ ├── GET /{id} # 获取剧本详情
│ ├── PUT /{id} # 更新剧本
│ ├── DELETE /{id} # 删除剧本
│ ├── POST /{id}/approve # 审批剧本
│ ├── GET /{id}/versions # 获取版本历史
│ ├── POST /{id}/characters # 添加角色
│ └── POST /{id}/scenes # 添加场景
│
├── /attachments
│ ├── POST / # 上传附件
│ ├── GET /{id} # 获取附件详情
│ ├── GET /{id}/download # 获取下载链接
│ └── DELETE /{id} # 删除附件
│
├── /storyboards
│ ├── GET / # 获取分镜列表
│ ├── POST / # 创建分镜
│ ├── GET /{id} # 获取分镜详情
│ ├── PUT /{id} # 更新分镜
│ └── DELETE /{id} # 删除分镜
│
├── /resources
│ ├── GET / # 获取资源列表
│ ├── POST /upload # 上传资源
│ ├── GET /{id} # 获取资源详情
│ └── DELETE /{id} # 删除资源
│
├── /videos
│ ├── GET / # 获取视频列表
│ ├── POST / # 创建视频
│ ├── GET /{id} # 获取视频详情
│ └── DELETE /{id} # 删除视频
│
├── /ai
│ ├── POST /generate-image # 生成图片
│ ├── POST /generate-video # 生成视频
│ ├── POST /generate-sound # 生成音效
│ ├── POST /generate-voice # 生成配音
│ └── GET /jobs/{id} # 查询任务状态
│
└── /export
├── POST / # 导出项目
└── GET /jobs/{id} # 查询导出状态
认证授权
JWT Token 认证
流程:
1. 用户登录 → 服务器返回 access_token 和 refresh_token
2. 客户端在请求头中携带 access_token
3. 服务器验证 token,返回数据
4. token 过期后,使用 refresh_token 刷新
实现代码:
# app/core/security.py
from datetime import datetime, timedelta
from typing import Optional
from jose import JWTError, jwt
from passlib.context import CryptContext
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login")
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
"""创建访问令牌"""
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
async def get_current_user(token: str = Depends(oauth2_scheme)):
"""获取当前用户"""
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
user_id: str = payload.get("sub")
if user_id is None:
raise credentials_exception
except JWTError:
raise credentials_exception
# 从数据库获取用户
user = get_user(user_id)
if user is None:
raise credentials_exception
return user
权限控制
基于角色的访问控制(RBAC):
# app/core/security.py
from enum import Enum
class Role(str, Enum):
OWNER = "owner"
EDITOR = "editor"
VIEWER = "viewer"
def require_role(required_role: Role):
"""权限检查装饰器"""
async def role_checker(
current_user = Depends(get_current_user),
project_id: str = None
):
# 检查用户在项目中的角色
user_role = get_user_role(current_user.id, project_id)
role_hierarchy = {
Role.OWNER: 3,
Role.EDITOR: 2,
Role.VIEWER: 1
}
if role_hierarchy[user_role] < role_hierarchy[required_role]:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Insufficient permissions"
)
return current_user
return role_checker
使用示例:
@router.delete("/{project_id}")
async def delete_project(
project_id: str,
current_user = Depends(require_role(Role.OWNER))
):
"""删除项目(仅 owner 可操作)"""
pass
请求响应格式
请求格式
查询参数(GET 请求):
GET /api/v1/projects?type=mine&page=1&page_size=20
请求体(POST/PUT 请求):
{
"name": "我的项目",
"description": "项目描述",
"type": "mine",
"folder_id": "folder-123"
}
响应格式
成功响应:
{
"id": "project-123",
"name": "我的项目",
"description": "项目描述",
"type": "mine",
"owner_id": "user-456",
"created_at": "2025-01-27T10:00:00Z",
"updated_at": "2025-01-27T10:00:00Z"
}
列表响应(带分页):
{
"items": [
{
"id": "project-123",
"name": "项目1"
},
{
"id": "project-456",
"name": "项目2"
}
],
"total": 100,
"page": 1,
"page_size": 20,
"total_pages": 5
}
错误处理
标准错误响应
{
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "项目不存在",
"details": {
"project_id": "project-123"
}
}
}
HTTP 状态码
| 状态码 | 说明 | 使用场景 |
|---|---|---|
| 200 | OK | 请求成功 |
| 201 | Created | 资源创建成功 |
| 204 | No Content | 删除成功(无返回内容) |
| 400 | Bad Request | 请求参数错误 |
| 401 | Unauthorized | 未认证 |
| 403 | Forbidden | 无权限 |
| 404 | Not Found | 资源不存在 |
| 409 | Conflict | 资源冲突 |
| 422 | Unprocessable Entity | 数据验证失败 |
| 500 | Internal Server Error | 服务器错误 |
错误码定义
# app/core/error_codes.py
class ErrorCode:
# 通用错误
INTERNAL_ERROR = "INTERNAL_ERROR"
INVALID_REQUEST = "INVALID_REQUEST"
# 认证错误
UNAUTHORIZED = "UNAUTHORIZED"
INVALID_TOKEN = "INVALID_TOKEN"
TOKEN_EXPIRED = "TOKEN_EXPIRED"
# 权限错误
FORBIDDEN = "FORBIDDEN"
INSUFFICIENT_PERMISSIONS = "INSUFFICIENT_PERMISSIONS"
# 资源错误
RESOURCE_NOT_FOUND = "RESOURCE_NOT_FOUND"
RESOURCE_ALREADY_EXISTS = "RESOURCE_ALREADY_EXISTS"
# 业务错误
INVALID_PROJECT_TYPE = "INVALID_PROJECT_TYPE"
FOLDER_CYCLE_DETECTED = "FOLDER_CYCLE_DETECTED"
API 版本管理
URL 版本控制
/api/v1/projects # 版本 1
/api/v2/projects # 版本 2
版本兼容性
- 向后兼容:新版本应尽量保持向后兼容
- 废弃通知:在响应头中添加废弃警告
@router.get("/projects")
async def get_projects(response: Response):
response.headers["X-API-Deprecated"] = "This endpoint will be removed in v2"
return projects
版本迁移
在文档中明确说明版本差异:
## v1 → v2 迁移指南
### 变更内容
- `type` 字段重命名为 `project_type`
- 新增 `folder_id` 字段
- 移除 `thumbnail_url` 字段(改用 `/attachments` 接口)
### 迁移步骤
1. 更新客户端代码,使用新字段名
2. 调用 `/api/v2/projects/migrate` 迁移数据
3. 切换到 v2 API
API 文档
自动生成文档
FastAPI 自动生成 OpenAPI 文档:
- Swagger UI:
http://localhost:8000/api/docs - ReDoc:
http://localhost:8000/api/redoc
文档配置
# app/main.py
from fastapi import FastAPI
app = FastAPI(
title="Jointo API",
description="AI 驱动的视频创作平台",
version="1.0.0",
docs_url="/api/docs",
redoc_url="/api/redoc",
openapi_url="/api/openapi.json"
)
相关文档
文档版本:v1.0
最后更新:2025-01-27