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

API 设计规范

文档版本:v1.0
最后更新:2025-01-27


目录

  1. API 设计原则
  2. 路由结构
  3. 认证授权
  4. 请求响应格式
  5. 错误处理
  6. API 版本管理

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 UIhttp://localhost:8000/api/docs
  • ReDochttp://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