## Video-Flow API 使用指南（方案一：保持现状，统一规范与示例）

> 目标：在不改动现有代码结构与签名的前提下，提供清晰的一致性用法、错误处理约定与多场景接入示例。

### 目录结构与职责

- 基础配置从 `@/lib/env` 导入 `baseUrl`
- `request.ts`：Axios 实例与拦截器、通用 `get/post/put/del`、`stream`（SSE风格下载进度）、`downloadStream`、`streamJsonPost`
- `errorHandle.ts`：错误码映射与统一提示、特殊码处理（如 401 跳转登录、402 不弹提示）
- `common.ts`：通用类型与与上传相关的工具（获取七牛 Token、上传）
- `resources.ts`：示例资源接口封装（基于 `post`）
- 其他业务模块（如 `video_flow.ts`、`export-adapter.ts` 等）各自封装具体接口/逻辑

### 统一调用规范

1. 使用 `request.ts` 提供的 `get/post/put/del` 包装函数发起请求，返回后端响应体（已通过响应拦截器做业务码检查）。
2. 业务成功码：`code === 0` 或 `code === 202`（长任务/排队等需要前端自行处理状态）。若非成功码，拦截器会调用 `errorHandle` 并 `Promise.reject`。
3. 认证：前端从 `localStorage.token` 注入 `Authorization: Bearer <token>`，请确保登录流程写入 `token`。
4. 基础地址：从 `@/lib/env` 的 `baseUrl` 获取，统一管理环境变量。

### 错误处理约定

- `errorHandle(code, message?)`：统一弹出错误（402 除外），并处理特殊码：
  - `401`：清除 `localStorage.token` 并跳转 `/login`
  - `402`：由 `request.ts` 响应拦截器在收到后端 `402` 且带 `detail` 时触发通知（积分不足），并拒绝请求
- 网络/解析类错误：`handleRequestError` 会降级为通用错误提示并抛出异常

### 常用请求示例

```ts
import { get, post, put, del } from '@/api/request';

type User = { id: number; name: string };
type ApiResponse<T> = { code: number; successful: boolean; message: string; data: T };

// GET
const user = await get<ApiResponse<User>>('/user/detail?id=1');

// POST
const created = await post<ApiResponse<User>>('/user/create', { name: 'Evan' });

// PUT
await put<ApiResponse<null>>('/user/update', { id: 1, name: 'Kent' });

// DELETE
await del<ApiResponse<null>>('/user/remove?id=1');
```

### 流式与下载（SSE/文件）

1) SSE/渐进消息（基于 Axios 下载进度事件解析 `data: {}` 行）

```ts
import { stream } from '@/api/request';

await stream<{ type: string; message?: string; progress?: number }>({
    url: '/api/video-flow/export/ai-clips',
    method: 'POST',
    data: { /* your body */ },
    onMessage: (evt) => {
        if (evt.type === 'progress') {
            // 更新进度条
        }
    },
    onError: (err) => { /* 兜底错误处理 */ },
    onComplete: () => { /* 完成回调 */ }
});
```

2) 文本行分隔的 JSON 流（`fetch` + `ReadableStream`，每行一个 JSON）

```ts
import { streamJsonPost } from '@/api/request';

await streamJsonPost('/sse/json', { /* body */ }, (json) => {
    // 每次解析到一行 JSON
});
```

3) 文件下载（Blob）

```ts
import { downloadStream } from '@/api/request';

await downloadStream('/download/file', 'result.mp4');
```

### 多场景接入指南

#### 浏览器前端（React/Next.js CSR）

- 直接使用 `get/post/put/del`；确保登录后将 `token` 写入 `localStorage`
- 环境变量：在 `.env.local` 配置 `NEXT_PUBLIC_BASE_URL`，通过 `@/lib/env` 统一管理
- 错误提示：由 `errorHandle` 统一处理；402 会展示积分不足通知

#### Next.js Route Handler（服务端 API）

- 如需在 Route Handler 内调用后端接口：建议使用服务端 `fetch` 并自行附加服务端凭证；避免直接依赖 `localStorage`
- 若需要转发到现有后端并复用通知/事件流格式，可参考 `api/export-adapter.ts` 的 SSE 写法

#### Next.js Server Components/SSR

- 服务端不具备 `localStorage`，如需鉴权请改为从 Cookie/Headers 传递 token，并在转发时设置 `Authorization`
- 服务器端可直接使用 `fetch(baseUrl + path, { headers })`

#### Node/Serverless（Vercel/Cloudflare）

- 同 SSR 原则：不使用 `localStorage`；从密钥存储中读取后以请求头传递
- SSE 转发时，按 `text/event-stream` 写入 `ReadableStream` 并传递 `data: <json>\n\n`

### 最佳实践

- 统一通过 `request.ts` 发起请求，不绕过拦截器；对长任务/排队返回 `202` 时在页面层处理轮询或 SSE
- 上传流程：使用 `common.ts/getUploadToken` 获取 token 与 domain，再使用 `uploadToQiniu` 上传，监听进度
- 将后端的业务错误码保持为服务端规范，在前端只做展示与关键码分支（401/402）

### 常见问题（FAQ）

- Q: 为什么拿不到 `localStorage` 中的 token？
  - A: 服务端渲染或 Route Handler 中无 `localStorage`，需通过 Cookie/Headers 传递。
- Q: 返回 `code !== 0/202` 时如何自定义处理？
  - A: 可在业务层 `try/catch` 捕获 `post/get` 的拒绝 Promise，并根据 `error.message` 或后端 `data.message` 做分支。
- Q: SSE 收不到事件？
  - A: 确认后端使用 `text/event-stream`，每条消息以 `data: <json>\n\n` 输出；前端在 `onDownloadProgress` 中按行解析。

---


