lingmin_package@163.com 1 тиждень тому
батько
коміт
cf9b1bafe1

+ 911 - 0
文档/标注平台提供API接口设计文档.md

@@ -0,0 +1,911 @@
+# 标注平台对外 API 接口设计文档
+
+> 基于《标注平台提供API接口需求文档》编写
+> 版本:v1.0
+> 日期:2026-05-17
+
+---
+
+## 目录
+
+- [1. 概述](#1-概述)
+- [2. 架构设计](#2-架构设计)
+- [3. 认证与授权](#3-认证与授权)
+- [4. 接口列表](#4-接口列表)
+- [5. 接口详细设计](#5-接口详细设计)
+- [6. 数据模型](#6-数据模型)
+- [7. 错误码定义](#7-错误码定义)
+- [8. 文件组织结构](#8-文件组织结构)
+- [9. 安全设计](#9-安全设计)
+- [10. 调用时序图](#10-调用时序图)
+
+---
+
+## 1. 概述
+
+### 1.1 背景
+
+标注平台提供标准化的 RESTful API 接口,供**模型训练平台**(外部系统)调用,实现以下能力:
+
+1. 查询标注项目列表
+2. 按项目 ID 和数据集格式下载标注数据集
+3. 通过 API Key + Secret 签名机制完成认证与授权
+
+### 1.2 设计原则
+
+- **与现有代码风格一致**:复用项目已有的 FastAPI + Pydantic + PyMySQL 技术栈
+- **独立路由前缀**:所有对外 API 使用 `/api/v1/open` 前缀,与内部 API(`/api/projects` 等)区分
+- **独立认证机制**:不依赖现有 SSO/JWT 体系,采用 API Key + Secret 签名
+- **数据格式统一**:遵循项目已有的响应包装模式
+
+### 1.3 业务流程
+
+```
+模型训练平台                      标注平台
+    |                               |
+    |  1. 获取 Access Token          |
+    |------------------------------>|
+    |                               |
+    |  2. 查询项目列表               |
+    |------------------------------>|
+    |                               |
+    |  3. 选择项目,下载数据集        |
+    |------------------------------>|
+    |                               |
+```
+
+---
+
+## 2. 架构设计
+
+### 2.1 技术栈
+
+| 组件 | 技术 | 说明 |
+|------|------|------|
+| Web 框架 | FastAPI 0.109.0 | 与项目保持一致 |
+| 数据校验 | Pydantic 2.5.3 | 请求/响应模型 |
+| 数据库 | MySQL (PyMySQL 1.1.0) | 项目已有数据库 |
+| 签名算法 | HMAC-SHA256 | API Key + Secret 签名 |
+| Token 格式 | JWT (PyJWT 2.8.0) | Access Token |
+
+### 2.2 认证流程设计
+
+```
+┌─────────────────────────────────────────────────────────┐
+│                    认证与授权流程                          │
+│                                                         │
+│  模型训练平台                              标注平台        │
+│       │                                     │            │
+│       │  POST /api/v1/open/auth/token       │            │
+│       │  Header: X-Api-Key: {app_id}        │            │
+│       │  Header: X-Signature: {hmac_sig}    │            │
+│       │  Header: X-Timestamp: {timestamp}   │            │
+│       │  Header: X-Nonce: {nonce}           │            │
+│       │------------------------------------>│            │
+│       │                                     │            │
+│       │                    验证 app_id       │            │
+│       │                    验证 timestamp     │            │
+│       │                    验证 signature     │            │
+│       │                    生成 JWT Token     │            │
+│       │                                     │            │
+│       │  200 { access_token, expires_in }   │            │
+│       │<------------------------------------│            │
+│       │                                     │            │
+│       │  后续请求携带:                       │            │
+│       │  Authorization: Bearer {token}      │            │
+│       │------------------------------------>│            │
+└─────────────────────────────────────────────────────────┘
+```
+
+### 2.3 签名算法
+
+```
+signature = HMAC-SHA256(
+    key = app_secret,
+    message = app_id + timestamp + nonce
+)
+```
+
+- `timestamp`:当前 Unix 时间戳(秒),有效期 ±5 分钟
+- `nonce`:随机字符串(16 位),防止重放攻击
+- `signature`:十六进制编码的 HMAC-SHA256 签名
+
+---
+
+## 3. 认证与授权
+
+### 3.1 凭证管理
+
+#### 3.1.1 凭证分配
+
+每个接入系统在标注平台注册后,分配一对凭证:
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `app_id` | string | 应用唯一标识,注册时生成 |
+| `app_secret` | string | 应用密钥,仅初始化时展示一次,需妥善保存 |
+
+#### 3.1.2 数据库设计
+
+新增 `api_applications` 表:
+
+```sql
+CREATE TABLE api_applications (
+    id              VARCHAR(36) PRIMARY KEY,
+    app_id          VARCHAR(64)  NOT NULL UNIQUE,
+    app_name        VARCHAR(128) NOT NULL COMMENT '应用名称',
+    app_secret      VARCHAR(128) NOT NULL COMMENT '应用密钥(bcrypt 哈希存储)',
+    secret_plain    VARCHAR(128) DEFAULT NULL COMMENT '明文密钥(仅在创建时返回,后续不存储)',
+    status          VARCHAR(20)  NOT NULL DEFAULT 'active' COMMENT '状态: active/disabled',
+    description     TEXT         DEFAULT NULL COMMENT '应用描述',
+    created_by      VARCHAR(36)  DEFAULT NULL COMMENT '创建人用户ID',
+    created_at      TIMESTAMP    DEFAULT CURRENT_TIMESTAMP,
+    updated_at      TIMESTAMP    DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    last_used_at    TIMESTAMP    NULL COMMENT '最后一次使用时间',
+    INDEX idx_app_id (app_id),
+    INDEX idx_status (status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='API 接入应用管理';
+```
+
+#### 3.1.3 Token 设计
+
+| 字段 | 说明 |
+|------|------|
+| `access_token` | JWT 格式,有效期 2 小时 |
+| `token_type` | 固定值 `Bearer` |
+| `expires_in` | 过期时间(秒),默认 7200 |
+
+JWT Payload:
+
+```json
+{
+  "sub": "app_id_xxxx",
+  "app_id": "app_id_xxxx",
+  "app_name": "模型训练平台",
+  "iat": 1716000000,
+  "exp": 1716007200,
+  "type": "open_api_access"
+}
+```
+
+---
+
+## 4. 接口列表
+
+所有对外 API 统一前缀:`/api/v1/open`
+
+| 序号 | 方法 | 路径 | 说明 | 认证 |
+|------|------|------|------|------|
+| 1 | POST | `/api/v1/open/auth/token` | 获取 Access Token | API Key 签名 |
+| 2 | POST | `/api/v1/open/auth/refresh` | 刷新 Access Token | Bearer Token |
+| 3 | GET | `/api/v1/open/projects` | 查询项目列表 | Bearer Token |
+| 4 | GET | `/api/v1/open/projects/{project_id}` | 查询项目详情 | Bearer Token |
+| 5 | POST | `/api/v1/open/projects/{project_id}/datasets/download` | 数据集下载 | Bearer Token |
+
+---
+
+## 5. 接口详细设计
+
+### 5.1 获取 Access Token
+
+**接口路由**:`POST /api/v1/open/auth/token`
+
+**功能说明**:通过 API Key + Secret 签名获取访问令牌
+
+**请求头**:
+
+| Header | 类型 | 必录 | 说明 |
+|--------|------|------|------|
+| `X-Api-Key` | string | 是 | 应用 app_id |
+| `X-Signature` | string | 是 | HMAC-SHA256 签名值(十六进制) |
+| `X-Timestamp` | string | 是 | Unix 时间戳(秒) |
+| `X-Nonce` | string | 是 | 16 位随机字符串 |
+
+**请求体**:无
+
+**响应(200)**:
+
+```json
+{
+  "code": 0,
+  "message": "success",
+  "data": {
+    "access_token": "eyJhbGciOiJIUzI1NiIs...",
+    "token_type": "Bearer",
+    "expires_in": 7200
+  }
+}
+```
+
+**错误响应**:
+
+| HTTP 状态码 | error_code | 说明 |
+|-------------|-----------|------|
+| 401 | INVALID_API_KEY | app_id 不存在 |
+| 401 | APP_DISABLED | 应用已被禁用 |
+| 401 | INVALID_SIGNATURE | 签名验证失败 |
+| 401 | TIMESTAMP_EXPIRED | 时间戳过期(超过 ±5 分钟) |
+| 401 | NONCE_USED | Nonce 已被使用(重放攻击) |
+
+---
+
+### 5.2 刷新 Access Token
+
+**接口路由**:`POST /api/v1/open/auth/refresh`
+
+**功能说明**:使用有效的 Access Token 换取新的 Token,延长有效期
+
+**请求头**:
+
+| Header | 类型 | 必录 | 说明 |
+|--------|------|------|------|
+| `Authorization` | string | 是 | `Bearer {access_token}` |
+
+**请求体**:无
+
+**响应(200)**:
+
+```json
+{
+  "code": 0,
+  "message": "success",
+  "data": {
+    "access_token": "eyJhbGciOiJIUzI1NiIs...",
+    "token_type": "Bearer",
+    "expires_in": 7200
+  }
+}
+```
+
+---
+
+### 5.3 查询项目列表
+
+**接口路由**:`GET /api/v1/open/projects`
+
+**功能说明**:查询标注平台的项目列表,支持按项目名称筛选
+
+**请求头**:
+
+| Header | 类型 | 必录 | 说明 |
+|--------|------|------|------|
+| `Authorization` | string | 是 | `Bearer {access_token}` |
+
+**查询参数**:
+
+| 参数 | 类型 | 必录 | 说明 |
+|------|------|------|------|
+| `name` | string | 否 | 项目名称(模糊匹配) |
+| `type` | string | 否 | 项目类型:`image` / `text` |
+| `status` | string | 否 | 项目状态筛选 |
+| `page` | int | 否 | 页码,默认 1 |
+| `page_size` | int | 否 | 每页数量,默认 20,最大 100 |
+
+**响应(200)**:
+
+```json
+{
+  "code": 0,
+  "message": "success",
+  "data": {
+    "items": [
+      {
+        "project_id": "proj_abc123def456",
+        "project_name": "商品图片分类标注",
+        "description": "对电商平台商品图片进行分类标注",
+        "project_type": "image",
+        "task_type": "image_classification",
+        "status": "in_progress",
+        "created_by": "admin",
+        "created_at": "2026-05-10T10:30:00",
+        "updated_at": "2026-05-15T14:20:00",
+        "task_count": 500,
+        "completed_task_count": 350
+      }
+    ],
+    "total": 1,
+    "page": 1,
+    "page_size": 20,
+    "total_pages": 1,
+    "has_next": false,
+    "has_prev": false
+  }
+}
+```
+
+**字段说明**:
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `project_id` | string | 项目唯一标识 |
+| `project_name` | string | 项目名称 |
+| `description` | string | 项目描述 |
+| `project_type` | string | 项目类型:`image`(图片)/ `text`(文本) |
+| `task_type` | string | 具体任务类型,见下方映射表 |
+| `status` | string | 项目状态:`draft` / `configuring` / `ready` / `in_progress` / `completed` |
+| `created_by` | string | 项目创建人用户名 |
+| `created_at` | string | 创建时间(ISO 8601) |
+| `updated_at` | string | 最后更新时间(ISO 8601) |
+| `task_count` | int | 总任务数 |
+| `completed_task_count` | int | 已完成任务数 |
+
+**项目类型映射**:
+
+| `task_type`(数据库) | `project_type`(API 返回) | 说明 |
+|----------------------|--------------------------|------|
+| `text_classification` | `text` | 文本分类 |
+| `ner` | `text` | 命名实体识别 |
+| `image_classification` | `image` | 图片分类 |
+| `object_detection` | `image` | 目标检测 |
+| `polygon` | `image` | 多边形标注 |
+
+---
+
+### 5.4 查询项目详情
+
+**接口路由**:`GET /api/v1/open/projects/{project_id}`
+
+**功能说明**:根据项目 ID 查询项目详细信息
+
+**路径参数**:
+
+| 参数 | 类型 | 必录 | 说明 |
+|------|------|------|------|
+| `project_id` | string | 是 | 项目 ID |
+
+**请求头**:
+
+| Header | 类型 | 必录 | 说明 |
+|--------|------|------|------|
+| `Authorization` | string | 是 | `Bearer {access_token}` |
+
+**响应(200)**:
+
+```json
+{
+  "code": 0,
+  "message": "success",
+  "data": {
+    "project_id": "proj_abc123def456",
+    "project_name": "商品图片分类标注",
+    "description": "对电商平台商品图片进行分类标注",
+    "project_type": "image",
+    "task_type": "image_classification",
+    "status": "in_progress",
+    "created_by": "admin",
+    "created_at": "2026-05-10T10:30:00",
+    "updated_at": "2026-05-15T14:20:00",
+    "task_count": 500,
+    "completed_task_count": 350,
+    "assigned_task_count": 500,
+    "completion_percentage": 70.0
+  }
+}
+```
+
+**错误响应**:
+
+| HTTP 状态码 | error_code | 说明 |
+|-------------|-----------|------|
+| 404 | PROJECT_NOT_FOUND | 项目不存在 |
+
+---
+
+### 5.5 数据集下载
+
+**接口路由**:`POST /api/v1/open/projects/{project_id}/datasets/download`
+
+**功能说明**:根据项目 ID 和指定的数据集格式,导出并下载标注数据集
+
+**路径参数**:
+
+| 参数 | 类型 | 必录 | 说明 |
+|------|------|------|------|
+| `project_id` | string | 是 | 项目 ID |
+
+**请求头**:
+
+| Header | 类型 | 必录 | 说明 |
+|--------|------|------|------|
+| `Authorization` | string | 是 | `Bearer {access_token}` |
+
+**请求体**:
+
+```json
+{
+  "format": "coco",
+  "completed_only": true
+}
+```
+
+| 参数 | 类型 | 必录 | 说明 |
+|------|------|------|------|
+| `format` | string | 是 | 数据集格式,见格式说明表 |
+| `completed_only` | boolean | 否 | 是否只导出已完成的任务,默认 `true` |
+
+**支持的格式**:
+
+| `format` 值 | 数据类型 | 格式说明 | 对应后端方法 |
+|-------------|---------|---------|-------------|
+| `alpaca` | 文本 | Alpaca 指令微调格式 | `export_to_alpaca` |
+| `sharegpt` | 文本 | ShareGPT 对话格式 | `export_to_sharegpt` |
+| `json` | 图片 | 平台原生 JSON 格式 | `export_to_json` |
+| `csv` | 图片 | CSV 表格格式 | `export_to_csv` |
+| `coco` | 图片 | COCO 目标检测格式 | `export_to_coco` |
+| `yolo` | 图片 | YOLO 目标检测格式 | `export_to_yolo` |
+| `pascal_voc` | 图片 | PascalVOC XML 格式 | `export_to_pascal_voc` |
+
+**响应(200)**:
+
+```json
+{
+  "code": 0,
+  "message": "success",
+  "data": {
+    "project_id": "proj_abc123def456",
+    "format": "coco",
+    "total_exported": 350,
+    "file_url": "/api/v1/open/datasets/downloads/dl_abc123",
+    "file_name": "proj_abc123def456_coco_20260517_143000.json",
+    "file_size": 2048576,
+    "expires_at": "2026-05-17T16:30:00",
+    "status": "completed"
+  }
+}
+```
+
+**字段说明**:
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `project_id` | string | 项目 ID |
+| `format` | string | 导出格式 |
+| `total_exported` | int | 导出的任务数量 |
+| `file_url` | string | 文件下载链接(相对路径) |
+| `file_name` | string | 文件名 |
+| `file_size` | int | 文件大小(字节) |
+| `expires_at` | string | 下载链接过期时间(ISO 8601),2 小时后过期 |
+| `status` | string | 导出状态:`completed` / `failed` |
+
+**下载文件**:
+
+模型训练平台拿到 `file_url` 后,通过 GET 请求下载文件:
+
+```
+GET /api/v1/open/datasets/downloads/{download_token}
+Authorization: Bearer {access_token}
+```
+
+**错误响应**:
+
+| HTTP 状态码 | error_code | 说明 |
+|-------------|-----------|------|
+| 400 | INVALID_FORMAT | 不支持的导出格式 |
+| 400 | FORMAT_NOT_COMPATIBLE | 格式与项目类型不兼容(如文本项目请求 COCO 格式) |
+| 404 | PROJECT_NOT_FOUND | 项目不存在 |
+| 404 | NO_DATA_AVAILABLE | 项目中没有可导出的数据 |
+| 500 | EXPORT_FAILED | 导出失败 |
+
+---
+
+### 5.6 下载文件获取
+
+**接口路由**:`GET /api/v1/open/datasets/downloads/{download_token}`
+
+**功能说明**:根据下载令牌获取实际的数据集文件
+
+**路径参数**:
+
+| 参数 | 类型 | 必录 | 说明 |
+|------|------|------|------|
+| `download_token` | string | 是 | 下载令牌(5.5 接口返回的 file_url 中的 token) |
+
+**请求头**:
+
+| Header | 类型 | 必录 | 说明 |
+|--------|------|------|------|
+| `Authorization` | string | 是 | `Bearer {access_token}` |
+
+**响应**:
+
+- `Content-Type: application/octet-stream` 或对应格式的 MIME type
+- `Content-Disposition: attachment; filename="..."`
+- 响应体为实际文件内容
+
+---
+
+## 6. 数据模型
+
+### 6.1 统一响应格式
+
+```json
+{
+  "code": 0,
+  "message": "success",
+  "data": { ... }
+}
+```
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `code` | int | 业务状态码,0 表示成功,非 0 表示失败 |
+| `message` | string | 提示信息 |
+| `data` | object | 业务数据 |
+
+### 6.2 统一错误响应格式
+
+```json
+{
+  "code": 1001,
+  "message": "签名验证失败",
+  "data": null,
+  "error_code": "INVALID_SIGNATURE",
+  "error_details": {
+    "field": "X-Signature",
+    "reason": "HMAC signature mismatch"
+  }
+}
+```
+
+### 6.3 分页响应格式
+
+```json
+{
+  "code": 0,
+  "message": "success",
+  "data": {
+    "items": [...],
+    "total": 100,
+    "page": 1,
+    "page_size": 20,
+    "total_pages": 5,
+    "has_next": true,
+    "has_prev": false
+  }
+}
+```
+
+### 6.4 Pydantic Schema 定义
+
+#### 6.4.1 认证相关 Schema
+
+```python
+# schemas/open_auth.py
+
+class TokenRequest(BaseModel):
+    """Token 请求(签名信息通过 Header 传递)"""
+    pass  # 无请求体,签名信息在 Header 中
+
+class TokenResponseData(BaseModel):
+    access_token: str = Field(..., description="JWT 访问令牌")
+    token_type: str = Field(default="Bearer", description="令牌类型")
+    expires_in: int = Field(default=7200, description="过期时间(秒)")
+
+class TokenResponse(BaseModel):
+    code: int = Field(default=0, description="状态码")
+    message: str = Field(default="success", description="提示信息")
+    data: TokenResponseData
+
+class RefreshTokenResponse(BaseModel):
+    code: int = Field(default=0)
+    message: str = Field(default="success")
+    data: TokenResponseData
+```
+
+#### 6.4.2 项目相关 Schema
+
+```python
+# schemas/open_project.py
+
+class ProjectType(str, Enum):
+    IMAGE = "image"
+    TEXT = "text"
+
+class OpenProjectItem(BaseModel):
+    project_id: str = Field(..., description="项目ID")
+    project_name: str = Field(..., description="项目名称")
+    description: str = Field(default="", description="项目描述")
+    project_type: ProjectType = Field(..., description="项目类型")
+    task_type: str = Field(..., description="任务类型")
+    status: str = Field(..., description="项目状态")
+    created_by: str = Field(..., description="创建人")
+    created_at: datetime = Field(..., description="创建时间")
+    updated_at: Optional[datetime] = Field(None, description="更新时间")
+    task_count: int = Field(default=0, description="总任务数")
+    completed_task_count: int = Field(default=0, description="已完成任务数")
+
+class OpenProjectListData(BaseModel):
+    items: List[OpenProjectItem]
+    total: int
+    page: int
+    page_size: int
+    total_pages: int
+    has_next: bool
+    has_prev: bool
+
+class OpenProjectListResponse(BaseModel):
+    code: int = Field(default=0)
+    message: str = Field(default="success")
+    data: OpenProjectListData
+
+class OpenProjectDetailResponse(BaseModel):
+    code: int = Field(default=0)
+    message: str = Field(default="success")
+    data: OpenProjectItem  # 扩展字段在 service 层组装
+```
+
+#### 6.4.3 数据集下载 Schema
+
+```python
+# schemas/open_dataset.py
+
+class DatasetFormat(str, Enum):
+    # 文本格式
+    ALPACA = "alpaca"
+    SHAREGPT = "sharegpt"
+    # 图片格式
+    JSON = "json"
+    CSV = "csv"
+    COCO = "coco"
+    YOLO = "yolo"
+    PASCAL_VOC = "pascal_voc"
+
+TEXT_FORMATS = {DatasetFormat.ALPACA, DatasetFormat.SHAREGPT}
+IMAGE_FORMATS = {DatasetFormat.JSON, DatasetFormat.CSV, DatasetFormat.COCO,
+                 DatasetFormat.YOLO, DatasetFormat.PASCAL_VOC}
+
+class DatasetDownloadRequest(BaseModel):
+    format: DatasetFormat = Field(..., description="数据集格式")
+    completed_only: bool = Field(default=True, description="是否只导出已完成的任务")
+
+class DatasetDownloadResponseData(BaseModel):
+    project_id: str = Field(..., description="项目ID")
+    format: str = Field(..., description="导出格式")
+    total_exported: int = Field(..., description="导出任务数")
+    file_url: str = Field(..., description="下载链接")
+    file_name: str = Field(..., description="文件名")
+    file_size: Optional[int] = Field(None, description="文件大小(字节)")
+    expires_at: Optional[datetime] = Field(None, description="链接过期时间")
+    status: str = Field(default="completed", description="导出状态")
+
+class DatasetDownloadResponse(BaseModel):
+    code: int = Field(default=0)
+    message: str = Field(default="success")
+    data: DatasetDownloadResponseData
+```
+
+---
+
+## 7. 错误码定义
+
+### 7.1 通用错误码
+
+| code | error_code | HTTP 状态码 | 说明 |
+|------|-----------|------------|------|
+| 0 | SUCCESS | 200 | 请求成功 |
+| 1000 | INTERNAL_ERROR | 500 | 服务器内部错误 |
+| 1001 | INVALID_REQUEST | 400 | 请求参数无效 |
+| 1002 | UNAUTHORIZED | 401 | 未认证或认证过期 |
+
+### 7.2 认证错误码
+
+| code | error_code | HTTP 状态码 | 说明 |
+|------|-----------|------------|------|
+| 2001 | INVALID_API_KEY | 401 | app_id 不存在 |
+| 2002 | APP_DISABLED | 401 | 应用已被禁用 |
+| 2003 | INVALID_SIGNATURE | 401 | 签名验证失败 |
+| 2004 | TIMESTAMP_EXPIRED | 401 | 时间戳过期(超过 ±5 分钟) |
+| 2005 | NONCE_USED | 401 | Nonce 已被使用(重放攻击) |
+| 2006 | TOKEN_EXPIRED | 401 | Access Token 已过期 |
+| 2007 | TOKEN_INVALID | 401 | Access Token 无效 |
+
+### 7.3 业务错误码
+
+| code | error_code | HTTP 状态码 | 说明 |
+|------|-----------|------------|------|
+| 3001 | PROJECT_NOT_FOUND | 404 | 项目不存在 |
+| 3002 | INVALID_FORMAT | 400 | 不支持的导出格式 |
+| 3003 | FORMAT_NOT_COMPATIBLE | 400 | 格式与项目类型不兼容 |
+| 3004 | NO_DATA_AVAILABLE | 404 | 项目中没有可导出的数据 |
+| 3005 | EXPORT_FAILED | 500 | 导出失败 |
+| 3006 | DOWNLOAD_EXPIRED | 410 | 下载链接已过期 |
+
+---
+
+## 8. 文件组织结构
+
+基于现有项目结构,新增以下文件:
+
+```
+backend/
+├── routers/
+│   ├── api/
+│   │   └── v1/
+│   │       ├── __init__.py
+│   │       ├── open_auth_view.py      # 认证相关 API 路由
+│   │       └── open_project_view.py   # 项目和数据集 API 路由
+│
+├── services/
+│   ├── api/
+│   │   ├── __init__.py
+│   │   ├── open_auth_service.py       # 认证业务逻辑(Token 生成、签名验证)
+│   │   └── open_project_service.py    # 项目查询和数据集导出业务逻辑
+│   │
+│   └── open_middleware.py             # 开放 API 认证中间件
+│
+├── schemas/
+│   ├── open_auth.py                   # 认证相关 Pydantic 模型
+│   ├── open_project.py                # 项目相关 Pydantic 模型
+│   └── open_dataset.py                # 数据集下载 Pydantic 模型
+│
+└── database.py                        # 需增加 api_applications 表创建逻辑
+```
+
+### 8.1 main.py 修改
+
+在 `main.py` 中注册新的路由:
+
+```python
+from routers.api.v1 import open_auth_view, open_project_view
+
+# Include open API routers
+app.include_router(open_auth_view.router)
+app.include_router(open_project_view.router)
+```
+
+---
+
+## 9. 安全设计
+
+### 9.1 签名验证
+
+1. **时间戳校验**:请求时间与服务端时间差超过 5 分钟则拒绝
+2. **Nonce 去重**:使用过的 nonce 在有效期内不允许重复使用(存储于 Redis 或内存缓存)
+3. **HMAC-SHA256**:使用 app_secret 对 `app_id + timestamp + nonce` 进行签名
+
+### 9.2 Token 安全
+
+- Access Token 有效期 2 小时,过期后需重新通过签名获取
+- 提供刷新接口,持有有效 Token 时可延长有效期
+- Token 绑定到具体 app_id,不可跨应用使用
+
+### 9.3 限流策略
+
+- 获取 Token 接口:10 次/分钟(按 app_id 限流)
+- 数据集下载接口:5 次/分钟(按 app_id 限流)
+- 项目列表查询:30 次/分钟(按 app_id 限流)
+
+### 9.4 数据访问控制
+
+- 开放 API 只能访问 `status` 为 `ready` / `in_progress` / `completed` 的项目
+- 无法访问 `draft` / `configuring` 状态的项目
+- 不返回 XML 配置等内部敏感字段
+
+---
+
+## 10. 调用时序图
+
+### 10.1 完整业务流程
+
+```
+模型训练平台                  标注平台
+    |                          |
+    |  ① 获取 Token             |
+    |  POST /api/v1/open/auth/token
+    |  Headers:                 |
+    |    X-Api-Key: app_id_xxx  |
+    |    X-Signature: hmac...   |
+    |    X-Timestamp: 1716xxx   |
+    |    X-Nonce: rand16chars   |
+    |------------------------->|
+    |                          |  验证签名
+    |                          |  生成 JWT
+    |                          |
+    |  ① Response 200           |
+    |  { access_token, ... }    |
+    |<-------------------------|
+    |                          |
+    |                          |
+    |  ② 查询项目列表            |
+    |  GET /api/v1/open/projects?name=xxx
+    |  Authorization: Bearer xxx
+    |------------------------->|
+    |                          |  验证 Token
+    |                          |  查询数据库
+    |                          |
+    |  ② Response 200           |
+    |  { items: [...], ... }    |
+    |<-------------------------|
+    |                          |
+    |                          |
+    |  ③ 下载数据集              |
+    |  POST /api/v1/open/projects/{id}/datasets/download
+    |  Authorization: Bearer xxx
+    |  Body: { format: "coco" } |
+    |------------------------->|
+    |                          |  验证 Token
+    |                          |  执行导出
+    |                          |  生成下载链接
+    |                          |
+    |  ③ Response 200           |
+    |  { file_url: "/..." }     |
+    |<-------------------------|
+    |                          |
+    |                          |
+    |  ④ 下载文件               |
+    |  GET /api/v1/open/datasets/downloads/{token}
+    |  Authorization: Bearer xxx
+    |------------------------->|
+    |                          |  验证 Token
+    |                          |  返回文件流
+    |                          |
+    |  ④ Response 200           |
+    |  Content-Type: application/json
+    |  [文件内容]                |
+    |<-------------------------|
+```
+
+---
+
+## 附录
+
+### A. 签名生成示例(Python)
+
+```python
+import time
+import secrets
+import hmac
+import hashlib
+
+def generate_signature(app_id: str, app_secret: str) -> dict:
+    """生成 API 请求签名"""
+    timestamp = str(int(time.time()))
+    nonce = secrets.token_hex(8)  # 16 位随机字符串
+
+    message = f"{app_id}{timestamp}{nonce}"
+    signature = hmac.new(
+        app_secret.encode('utf-8'),
+        message.encode('utf-8'),
+        hashlib.sha256
+    ).hexdigest()
+
+    return {
+        "X-Api-Key": app_id,
+        "X-Signature": signature,
+        "X-Timestamp": timestamp,
+        "X-Nonce": nonce,
+    }
+```
+
+### B. 调用示例(curl)
+
+```bash
+# 1. 获取 Token
+curl -X POST http://localhost:8003/api/v1/open/auth/token \
+  -H "X-Api-Key: app_id_xxx" \
+  -H "X-Signature: abc123..." \
+  -H "X-Timestamp: 1716000000" \
+  -H "X-Nonce: a1b2c3d4e5f6g7h8"
+
+# 2. 查询项目列表
+curl -X GET "http://localhost:8003/api/v1/open/projects?page=1&page_size=20" \
+  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
+
+# 3. 下载数据集
+curl -X POST http://localhost:8003/api/v1/open/projects/proj_xxx/datasets/download \
+  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
+  -H "Content-Type: application/json" \
+  -d '{"format": "coco", "completed_only": true}'
+```
+
+### C. 与现有 External API 的关系
+
+| 维度 | External API (`/api/external`) | Open API (`/api/v1/open`) |
+|------|-------------------------------|--------------------------|
+| 用途 | 样本中心主动推送数据到标注平台 | 模型训练平台拉取标注结果 |
+| 认证 | 现有 JWT / Admin Token | API Key + Secret 签名 |
+| 方向 | 推模式(Push) | 拉模式(Pull) |
+| 操作 | 创建项目、查询进度、回调导出 | 查询列表、下载数据集 |
+| 权限 | 需要管理员权限 | 应用级权限,无角色概念 |
+
+两者认证机制独立,互不影响。Open API 是全新的独立接口体系。

+ 62 - 0
文档/标注平台提供API接口需求文档.md

@@ -0,0 +1,62 @@
+
+
+
+### 标注平台提供API接口需求文档
+   - 需求:标注平台提供API接口,供模型训练平台调用
+   - 项目列表查询接口(数据标注任务按项目粒度)
+     - 需求:提供标注平台标注数据集列表查询API,数据格式统一
+     - 参数:
+       - 项目名称,非必录参数
+     - 返回:
+       - 项目ID
+       - 项目名称
+       - 项目描述
+       - 项目类型(图片,文本)
+       - 项目状态
+       - 项目创建时间
+       - 项目更新时间
+       - 项目创建人
+   - 数据集下载接口	提供标注平台标注数据集下载API
+     - 需求:提供标注平台标注数据集下载API,数据格式统一
+     - 参数:
+       - 项目ID,必录参数
+       - 数据类型,必录参数,如下
+         - 文本
+           - AIpaca 数据集格式
+           - ShareGPT 数据集格式
+         - 图片
+           - JSON 数据集格式
+           - CSV 数据集格式
+           - COCO 数据集格式
+           - YOLO 数据集格式
+           - VOC 数据集格式
+     - 返回:
+       - 数据集文件
+   
+   - 提供认证与授权接口
+     - 采用 **API Key + Secret 签名机制**,外部系统通过以下方式获取访问 Token
+     - 凭证分配
+        - 每个接入系统在样本中心注册后,分配一对凭证:
+        - `app_id`:应用唯一标识
+        - `app_secret`:应用密钥(仅初始化时展示一次,需妥善保存)
+
+
+
+
+
+### API接口文档设计
+ - 业务逻辑定义
+   - 模型训练平台 通过项目列表查询接口,获取项目列表
+   - 根据项目ID,选择数据集类型,调用数据集下载接口,下载数据集
+
+
+ - API 接口定义
+   - API 接入的token接口定义代码逻辑 
+     - 接口路由 backend/routers/api/v1/token_api_view.py
+     - 业务逻辑 backend/routers/service/api/token_api_service.py
+   - API 接口定义代码逻辑 
+     - 接口路由 backend/routers/api/v1/project_api_view.py
+     - 业务逻辑 src/app/service/api/v1/project_api_service.py
+
+
+