Skip to content

客户端代码生成

自动生成类型安全的 API 客户端 SDK

概述

通过 OpenAPI Generator,我们可以为各种编程语言和框架自动生成客户端 SDK。这些 SDK 提供:

  • 🔒 类型安全: 自动生成的类型定义
  • 🚀 开箱即用: 包含认证、错误处理等功能
  • 📝 文档完整: 自动生成的代码注释
  • 🔄 保持同步: API 变更时重新生成即可

支持的客户端类型

Web 前端

生成器适用场景特性
typescript-axiosReact/Vue/AngularPromise-based, 拦截器支持
typescript-fetch现代浏览器原生 Fetch API
javascript传统项目ES5 兼容
typescript-angularAngular 项目服务注入,RxJS
typescript-rxjs响应式编程Observable 支持

移动端

生成器平台特性
swift5iOSCodable, async/await
kotlinAndroidCoroutines, Retrofit
dartFlutterNull safety
objciOS (Legacy)ARC 支持

后端/脚本

生成器语言特性
pythonPythonrequests, async 支持
goGo标准库 HTTP
rubyRuby多种 HTTP 库
phpPHPPSR-7, Guzzle
csharpC#HttpClient, async

TypeScript 客户端生成

基础配置

bash
# 生成 TypeScript Axios 客户端
openapi-generator-cli generate \
  -i api/openapi.yaml \
  -g typescript-axios \
  -o generated/typescript-client \
  --additional-properties=npmName=@company/api-client,npmVersion=1.0.0,supportsES6=true

高级配置文件

yaml
# typescript-client-config.yaml
generatorName: typescript-axios
outputDir: ./src/api/client
inputSpec: ./api/openapi.yaml

globalProperties:
  apis: true
  models: true
  supportingFiles: true
  modelDocs: false
  apiDocs: false

additionalProperties:
  npmName: "@company/api-client"
  npmVersion: "1.0.0"
  supportsES6: true
  withInterfaces: true
  withSeparateModelsAndApi: true
  modelPropertyNaming: camelCase
  enumPropertyNaming: UPPERCASE
  useSingleRequestParameter: true

typeMappings:
  DateTime: Date
  Date: string
  Long: number

importMappings:
  Date: Date

生成的代码示例

typescript
// generated/api/user-api.ts
import { Configuration } from './configuration';
import { User, CreateUserRequest, UpdateUserRequest } from './models';
import axios, { AxiosInstance } from 'axios';

export class UserApi {
  private axios: AxiosInstance;
  
  constructor(configuration?: Configuration) {
    this.axios = axios.create({
      baseURL: configuration?.basePath || 'https://api.example.com/v1',
      headers: {
        'Authorization': `Bearer ${configuration?.accessToken}`,
      }
    });
  }
  
  /**
   * 获取用户列表
   * @param page 页码
   * @param size 每页大小
   */
  async listUsers(page?: number, size?: number): Promise<User[]> {
    const response = await this.axios.get<User[]>('/users', {
      params: { page, size }
    });
    return response.data;
  }
  
  /**
   * 创建用户
   * @param createUserRequest 用户创建请求
   */
  async createUser(createUserRequest: CreateUserRequest): Promise<User> {
    const response = await this.axios.post<User>('/users', createUserRequest);
    return response.data;
  }
}

使用生成的客户端

typescript
// app.ts
import { UserApi, Configuration } from '@company/api-client';

// 配置客户端
const config = new Configuration({
  basePath: 'https://api.example.com/v1',
  accessToken: 'your-jwt-token'
});

const userApi = new UserApi(config);

// 使用 API
async function fetchUsers() {
  try {
    const users = await userApi.listUsers(1, 20);
    console.log('Users:', users);
  } catch (error) {
    console.error('Error fetching users:', error);
  }
}

// 创建用户
async function createNewUser() {
  const newUser = await userApi.createUser({
    name: 'John Doe',
    email: 'john@example.com',
    password: 'secure-password'
  });
  console.log('Created user:', newUser);
}

React 集成示例

自定义 Hook

typescript
// hooks/useApi.ts
import { useState, useEffect } from 'react';
import { Configuration, UserApi } from '@company/api-client';

const config = new Configuration({
  basePath: process.env.REACT_APP_API_URL,
  accessToken: localStorage.getItem('token') || undefined
});

export const useUserApi = () => {
  return new UserApi(config);
};

// hooks/useUsers.ts
export const useUsers = (page: number = 1, size: number = 20) => {
  const [users, setUsers] = useState<User[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);
  
  const userApi = useUserApi();
  
  useEffect(() => {
    const fetchUsers = async () => {
      try {
        setLoading(true);
        const data = await userApi.listUsers(page, size);
        setUsers(data);
      } catch (err) {
        setError(err as Error);
      } finally {
        setLoading(false);
      }
    };
    
    fetchUsers();
  }, [page, size]);
  
  return { users, loading, error };
};

React 组件

tsx
// components/UserList.tsx
import React from 'react';
import { useUsers } from '../hooks/useUsers';

export const UserList: React.FC = () => {
  const { users, loading, error } = useUsers();
  
  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>
          {user.name} - {user.email}
        </li>
      ))}
    </ul>
  );
};

Python 客户端生成

生成配置

bash
openapi-generator-cli generate \
  -i api/openapi.yaml \
  -g python \
  -o generated/python-client \
  --additional-properties=packageName=company_api_client,projectName=company-api-client

配置文件

yaml
# python-client-config.yaml
generatorName: python
outputDir: ./generated/python-client
inputSpec: ./api/openapi.yaml

additionalProperties:
  packageName: company_api_client
  projectName: company-api-client
  packageVersion: 1.0.0
  library: urllib3  # 或 asyncio, tornado, requests
  generateSourceCodeOnly: false

使用示例

python
# main.py
from company_api_client import Configuration, ApiClient, UserApi
from company_api_client.models import CreateUserRequest

# 配置
configuration = Configuration(
    host="https://api.example.com/v1",
    access_token="your-jwt-token"
)

# 创建 API 客户端
with ApiClient(configuration) as api_client:
    user_api = UserApi(api_client)
    
    # 获取用户列表
    users = user_api.list_users(page=1, size=20)
    for user in users:
        print(f"User: {user.name} ({user.email})")
    
    # 创建用户
    new_user_request = CreateUserRequest(
        name="Jane Doe",
        email="jane@example.com",
        password="secure-password"
    )
    new_user = user_api.create_user(new_user_request)
    print(f"Created user: {new_user.id}")

异步客户端

python
# async_example.py
import asyncio
from company_api_client.async_api import AsyncUserApi
from company_api_client import Configuration

async def main():
    configuration = Configuration(
        host="https://api.example.com/v1",
        access_token="your-jwt-token"
    )
    
    async with AsyncUserApi(configuration) as api:
        # 并发获取多个用户
        tasks = [
            api.get_user(user_id=f"user-{i}")
            for i in range(1, 11)
        ]
        users = await asyncio.gather(*tasks)
        
        for user in users:
            print(f"Fetched: {user.name}")

if __name__ == "__main__":
    asyncio.run(main())

移动端客户端

Swift (iOS)

bash
# 生成 Swift 客户端
openapi-generator-cli generate \
  -i api/openapi.yaml \
  -g swift5 \
  -o generated/ios-client \
  --additional-properties=projectName=CompanyAPIClient,responseAs=AsyncAwait

使用示例:

swift
// UserService.swift
import CompanyAPIClient

class UserService {
    let api = UserAPI()
    
    func fetchUsers() async throws -> [User] {
        let users = try await api.listUsers(page: 1, size: 20)
        return users
    }
    
    func createUser(name: String, email: String) async throws -> User {
        let request = CreateUserRequest(
            name: name,
            email: email,
            password: "secure-password"
        )
        let user = try await api.createUser(createUserRequest: request)
        return user
    }
}

Kotlin (Android)

bash
# 生成 Kotlin 客户端
openapi-generator-cli generate \
  -i api/openapi.yaml \
  -g kotlin \
  -o generated/android-client \
  --additional-properties=packageName=com.company.api,library=retrofit2,dateLibrary=java8

使用示例:

kotlin
// UserRepository.kt
import com.company.api.apis.UserApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

class UserRepository {
    private val api = UserApi()
    
    suspend fun getUsers(page: Int = 1, size: Int = 20): List<User> {
        return withContext(Dispatchers.IO) {
            api.listUsers(page, size)
        }
    }
    
    suspend fun createUser(name: String, email: String): User {
        return withContext(Dispatchers.IO) {
            val request = CreateUserRequest(
                name = name,
                email = email,
                password = "secure-password"
            )
            api.createUser(request)
        }
    }
}

客户端定制

自定义模板

创建自定义 Mustache 模板:

mustache
{{! custom-api.mustache }}
{{#operations}}
export class {{classname}}Custom extends {{classname}} {
  constructor(configuration?: Configuration) {
    super(configuration);
  }
  
  {{#operation}}
  /**
   * {{summary}}
   * 自定义方法包装
   */
  async {{nickname}}WithRetry({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}{{#hasMore}}, {{/hasMore}}{{/allParams}}): Promise<{{{returnType}}}> {
    let attempts = 0;
    const maxAttempts = 3;
    
    while (attempts < maxAttempts) {
      try {
        return await this.{{nickname}}({{#allParams}}{{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}});
      } catch (error) {
        attempts++;
        if (attempts >= maxAttempts) throw error;
        await new Promise(resolve => setTimeout(resolve, 1000 * attempts));
      }
    }
    throw new Error('Max retry attempts reached');
  }
  {{/operation}}
}
{{/operations}}

使用自定义模板:

bash
openapi-generator-cli generate \
  -i api/openapi.yaml \
  -g typescript-axios \
  -o generated/client \
  -t ./templates \
  --additional-properties=customApi=true

拦截器和中间件

typescript
// interceptors.ts
import axios from 'axios';

// 请求拦截器
axios.interceptors.request.use(
  config => {
    // 添加时间戳
    config.params = {
      ...config.params,
      _t: Date.now()
    };
    
    // 添加追踪 ID
    config.headers['X-Request-ID'] = generateUUID();
    
    return config;
  },
  error => Promise.reject(error)
);

// 响应拦截器
axios.interceptors.response.use(
  response => {
    // 记录响应时间
    const duration = Date.now() - response.config.metadata.startTime;
    console.log(`API call took ${duration}ms`);
    return response;
  },
  error => {
    if (error.response?.status === 401) {
      // 刷新令牌
      return refreshToken().then(() => {
        return axios.request(error.config);
      });
    }
    return Promise.reject(error);
  }
);

测试生成的客户端

单元测试

typescript
// __tests__/userApi.test.ts
import { UserApi } from '../generated/api';
import MockAdapter from 'axios-mock-adapter';
import axios from 'axios';

describe('UserApi', () => {
  let api: UserApi;
  let mock: MockAdapter;
  
  beforeEach(() => {
    mock = new MockAdapter(axios);
    api = new UserApi();
  });
  
  afterEach(() => {
    mock.restore();
  });
  
  test('should fetch users', async () => {
    const mockUsers = [
      { id: '1', name: 'John', email: 'john@example.com' }
    ];
    
    mock.onGet('/users').reply(200, mockUsers);
    
    const users = await api.listUsers();
    expect(users).toEqual(mockUsers);
  });
  
  test('should handle errors', async () => {
    mock.onGet('/users').reply(500);
    
    await expect(api.listUsers()).rejects.toThrow();
  });
});

集成测试

python
# test_integration.py
import pytest
from company_api_client import UserApi, Configuration

@pytest.fixture
def api_client():
    config = Configuration(
        host="http://localhost:3000/api/v1"
    )
    return UserApi(configuration=config)

def test_user_lifecycle(api_client):
    # 创建用户
    user = api_client.create_user(
        name="Test User",
        email="test@example.com"
    )
    assert user.id is not None
    
    # 获取用户
    fetched_user = api_client.get_user(user.id)
    assert fetched_user.name == "Test User"
    
    # 更新用户
    updated_user = api_client.update_user(
        user.id,
        name="Updated User"
    )
    assert updated_user.name == "Updated User"
    
    # 删除用户
    api_client.delete_user(user.id)
    
    # 验证删除
    with pytest.raises(ApiException) as exc:
        api_client.get_user(user.id)
    assert exc.value.status == 404

发布和分发

NPM 发布

json
// package.json
{
  "name": "@company/api-client",
  "version": "1.0.0",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "files": [
    "dist"
  ],
  "scripts": {
    "build": "tsc",
    "prepare": "npm run build",
    "prepublishOnly": "npm test && npm run build"
  },
  "publishConfig": {
    "registry": "https://npm.company.com"
  }
}

发布流程:

bash
# 构建
npm run build

# 测试
npm test

# 发布
npm publish --access public

PyPI 发布

python
# setup.py
from setuptools import setup, find_packages

setup(
    name="company-api-client",
    version="1.0.0",
    packages=find_packages(),
    install_requires=[
        "urllib3>=1.25.3",
        "python-dateutil>=2.8.0",
        "pydantic>=1.8.0"
    ],
    python_requires=">=3.7",
    author="API Team",
    author_email="api@company.com",
    description="Company API Client",
    long_description=open("README.md").read(),
    long_description_content_type="text/markdown",
    url="https://github.com/company/api-client-python",
)

最佳实践

1. 版本管理

typescript
// version-check.ts
const API_VERSION = '1.0.0';
const MIN_SUPPORTED_VERSION = '0.9.0';

export function checkApiVersion(serverVersion: string): boolean {
  return compareVersions(serverVersion, MIN_SUPPORTED_VERSION) >= 0;
}

2. 错误处理

typescript
// error-handler.ts
export class APIErrorHandler {
  static handle(error: any): never {
    if (error.response) {
      // 服务器响应错误
      const apiError = {
        status: error.response.status,
        message: error.response.data.message || 'Unknown error',
        code: error.response.data.code,
        timestamp: new Date().toISOString()
      };
      throw new APIError(apiError);
    } else if (error.request) {
      // 网络错误
      throw new NetworkError('Network error occurred');
    } else {
      // 其他错误
      throw new UnknownError(error.message);
    }
  }
}

3. 性能优化

typescript
// cache.ts
export class APICache {
  private cache = new Map<string, CacheEntry>();
  
  async get<T>(
    key: string,
    fetcher: () => Promise<T>,
    ttl: number = 60000
  ): Promise<T> {
    const cached = this.cache.get(key);
    
    if (cached && cached.expires > Date.now()) {
      return cached.data as T;
    }
    
    const data = await fetcher();
    this.cache.set(key, {
      data,
      expires: Date.now() + ttl
    });
    
    return data;
  }
}

总结

通过 OpenAPI Generator 生成客户端 SDK:

  • 🚀 提高效率: 自动生成减少手工编码
  • 🔒 类型安全: 编译时发现错误
  • 📚 文档完整: 自动生成的注释和文档
  • 🔄 保持同步: API 变更时快速更新

正确使用客户端生成器,可以大大提高前后端协作效率,减少集成错误。

SOLO Development Guide