178 lines
4.4 KiB
Markdown
178 lines
4.4 KiB
Markdown
# OpenAPI TypeScript 设置指南
|
||
|
||
## 🚀 快速开始
|
||
|
||
### 1. 已完成的步骤
|
||
|
||
✅ 创建了示例 OpenAPI 规范文件 (`./api/v1.yaml`)
|
||
✅ 生成了 TypeScript 类型定义 (`./src/lib/api/v1.d.ts`)
|
||
✅ 创建了 API 客户端 (`./src/lib/api/client.ts`)
|
||
✅ 创建了使用示例组件 (`./src/components/examples/ApiExample.tsx`)
|
||
|
||
### 2. 如何使用
|
||
|
||
#### 基本用法
|
||
|
||
```typescript
|
||
import { api } from '@/lib/api/client';
|
||
|
||
// 获取用户列表
|
||
const users = await api.users.getList({ page: 1, limit: 20 });
|
||
|
||
// 获取用户详情
|
||
const user = await api.users.getDetail(1);
|
||
|
||
// 创建农机
|
||
const newMachine = await api.machinery.create({
|
||
name: '新拖拉机',
|
||
type: 'tractor',
|
||
model: 'John Deere 6M',
|
||
});
|
||
```
|
||
|
||
#### 类型安全
|
||
|
||
```typescript
|
||
// ✅ 类型安全的 API 调用
|
||
const params = {
|
||
page: 1,
|
||
limit: 20,
|
||
status: 'running' as const, // TypeScript 会检查枚举值
|
||
};
|
||
|
||
// ❌ 错误会被 TypeScript 捕获
|
||
const wrongParams = {
|
||
status: 'invalid-status', // TypeScript 错误: 类型不匹配
|
||
};
|
||
```
|
||
|
||
## 🔄 更新 API 规范
|
||
|
||
### 当后端 API 发生变化时:
|
||
|
||
1. **更新 OpenAPI 规范文件**
|
||
```bash
|
||
# 方式一:手动编辑 ./api/v1.yaml
|
||
# 方式二:从后端自动生成(如果后端支持)
|
||
curl https://gitea-admin-test-app-app.dev.maimaiag.com/v3/api-docs > ./api/v1.yaml
|
||
```
|
||
|
||
2. **重新生成 TypeScript 类型**
|
||
```bash
|
||
npx openapi-typescript ./api/v1.yaml -o ./src/lib/api/v1.d.ts
|
||
```
|
||
|
||
3. **更新客户端代码**(如果接口有重大变化)
|
||
|
||
## 🛠️ 进阶配置
|
||
|
||
### 添加认证
|
||
|
||
```typescript
|
||
import { authClient } from '@/lib/api/client';
|
||
|
||
// 使用认证客户端
|
||
const token = localStorage.getItem('jwt-token');
|
||
const authenticatedApi = authClient.withAuth(token);
|
||
|
||
const user = await authenticatedApi.users.getDetail(1);
|
||
```
|
||
|
||
### 错误处理
|
||
|
||
```typescript
|
||
try {
|
||
const result = await api.users.getList();
|
||
// 处理成功响应
|
||
} catch (error) {
|
||
if (error.message.includes('404')) {
|
||
// 处理 404 错误
|
||
} else if (error.message.includes('401')) {
|
||
// 处理认证错误
|
||
}
|
||
}
|
||
```
|
||
|
||
### 自定义配置
|
||
|
||
```typescript
|
||
const customClient = createClient<paths>({
|
||
baseUrl: 'https://gitea-admin-test-app-app.dev.maimaiag.com/api/v1',
|
||
headers: {
|
||
'User-Agent': 'Smart-Crop-UI/1.0',
|
||
'X-API-Key': process.env.API_KEY,
|
||
},
|
||
// 添加请求拦截器
|
||
onRequest: async ({ request }) => {
|
||
// 可以在这里添加日志、重试逻辑等
|
||
console.log('发送请求:', request.url);
|
||
return request;
|
||
},
|
||
// 添加响应拦截器
|
||
onResponse: async ({ response }) => {
|
||
if (response.status === 401) {
|
||
// 处理 token 过期
|
||
window.location.href = '/login';
|
||
}
|
||
return response;
|
||
},
|
||
});
|
||
```
|
||
|
||
## 📁 文件结构
|
||
|
||
```
|
||
crop-x/
|
||
├── api/
|
||
│ └── v1.yaml # OpenAPI 规范文件
|
||
├── src/
|
||
│ ├── lib/
|
||
│ │ └── api/
|
||
│ │ ├── v1.d.ts # 生成的类型定义
|
||
│ │ └── client.ts # API 客户端封装
|
||
│ └── components/
|
||
│ └── examples/
|
||
│ └── ApiExample.tsx # 使用示例
|
||
```
|
||
|
||
## 🎯 最佳实践
|
||
|
||
### 1. 版本控制
|
||
- 将 `v1.yaml` 纳入版本控制
|
||
- 将生成的 `v1.d.ts` 也纳入版本控制,方便代码审查
|
||
- 在 CI/CD 中自动重新生成类型文件
|
||
|
||
### 2. 类型安全
|
||
- 优先使用 `api.machinery.create()` 这样的封装方法
|
||
- 避免直接使用 `client.POST()`
|
||
- 充分利用 TypeScript 的类型检查
|
||
|
||
### 3. 错误处理
|
||
- 在客户端封装中统一处理 API 错误
|
||
- 提供有意义的错误信息给用户
|
||
- 实现重试机制和降级策略
|
||
|
||
### 4. 性能优化
|
||
- 实现请求缓存
|
||
- 使用 React Query 或 SWR 进行数据获取
|
||
- 考虑添加请求去重
|
||
|
||
## 🔗 相关资源
|
||
|
||
- [openapi-typescript 官方文档](https://github.com/drwp/openapi-typescript)
|
||
- [OpenAPI 规范](https://swagger.io/specification/)
|
||
- [shadcn/ui](https://ui.shadcn.com/)
|
||
|
||
## ❓ 常见问题
|
||
|
||
**Q: 如何处理分页?**
|
||
A: API 已经支持分页参数 `page` 和 `limit`,响应中包含 `total` 字段。
|
||
|
||
**Q: 如何处理文件上传?**
|
||
A: 需要在 OpenAPI 规范中定义 `multipart/form-data` 格式的接口。
|
||
|
||
**Q: 如何实现 WebSocket 连接?**
|
||
A: 当前只支持 HTTP REST API,WebSocket 需要单独实现。
|
||
|
||
**Q: 如何添加 Mock 数据?**
|
||
A: 可以创建另一个客户端实例,返回模拟数据而不是真实请求。 |