API集成 #

一、REST API 集成 #

1.1 基本 REST API 调用 #

astro
---
// src/pages/users.astro
interface User {
  id: number;
  name: string;
  email: string;
  username: string;
}

const response = await fetch('https://jsonplaceholder.typicode.com/users');
const users: User[] = await response.json();
---

<div class="users">
  {users.map(user => (
    <div key={user.id} class="user-card">
      <h3>{user.name}</h3>
      <p>@{user.username}</p>
      <p>{user.email}</p>
    </div>
  ))}
</div>

1.2 封装 API 客户端 #

typescript
// src/lib/api-client.ts
interface RequestOptions {
  method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
  headers?: Record<string, string>;
  body?: any;
  timeout?: number;
}

class ApiClient {
  private baseUrl: string;
  private defaultHeaders: Record<string, string>;

  constructor(baseUrl: string, defaultHeaders: Record<string, string> = {}) {
    this.baseUrl = baseUrl;
    this.defaultHeaders = {
      'Content-Type': 'application/json',
      ...defaultHeaders,
    };
  }

  async request<T>(endpoint: string, options: RequestOptions = {}): Promise<T> {
    const { method = 'GET', headers = {}, body, timeout = 10000 } = options;

    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), timeout);

    try {
      const response = await fetch(`${this.baseUrl}${endpoint}`, {
        method,
        headers: { ...this.defaultHeaders, ...headers },
        body: body ? JSON.stringify(body) : undefined,
        signal: controller.signal,
      });

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }

      return await response.json();
    } finally {
      clearTimeout(timeoutId);
    }
  }

  get<T>(endpoint: string, options?: RequestOptions): Promise<T> {
    return this.request<T>(endpoint, { ...options, method: 'GET' });
  }

  post<T>(endpoint: string, body: any, options?: RequestOptions): Promise<T> {
    return this.request<T>(endpoint, { ...options, method: 'POST', body });
  }

  put<T>(endpoint: string, body: any, options?: RequestOptions): Promise<T> {
    return this.request<T>(endpoint, { ...options, method: 'PUT', body });
  }

  delete<T>(endpoint: string, options?: RequestOptions): Promise<T> {
    return this.request<T>(endpoint, { ...options, method: 'DELETE' });
  }
}

export const api = new ApiClient(
  import.meta.env.API_URL || 'https://api.example.com',
  {
    'Authorization': `Bearer ${import.meta.env.API_KEY}`,
  }
);

1.3 使用 API 客户端 #

astro
---
// src/pages/posts.astro
import { api } from '../lib/api-client';

interface Post {
  id: number;
  title: string;
  body: string;
}

const posts = await api.get<Post[]>('/posts');
---

<div>
  {posts.map(post => (
    <article key={post.id}>
      <h2>{post.title}</h2>
      <p>{post.body}</p>
    </article>
  ))}
</div>

二、GraphQL 集成 #

2.1 GraphQL 客户端 #

typescript
// src/lib/graphql-client.ts
interface GraphQLResponse<T> {
  data: T;
  errors?: Array<{ message: string }>;
}

class GraphQLClient {
  private endpoint: string;
  private headers: Record<string, string>;

  constructor(endpoint: string, headers: Record<string, string> = {}) {
    this.endpoint = endpoint;
    this.headers = headers;
  }

  async query<T>(query: string, variables?: Record<string, any>): Promise<T> {
    const response = await fetch(this.endpoint, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        ...this.headers,
      },
      body: JSON.stringify({ query, variables }),
    });

    const result: GraphQLResponse<T> = await response.json();

    if (result.errors && result.errors.length > 0) {
      throw new Error(result.errors[0].message);
    }

    return result.data;
  }
}

export const graphql = new GraphQLClient(
  import.meta.env.GRAPHQL_URL || 'https://api.example.com/graphql',
  {
    'Authorization': `Bearer ${import.meta.env.API_KEY}`,
  }
);

2.2 使用 GraphQL #

astro
---
// src/pages/blog.astro
import { graphql } from '../lib/graphql-client';

interface BlogData {
  posts: Array<{
    id: string;
    title: string;
    excerpt: string;
    author: {
      name: string;
    };
  }>;
}

const data = await graphql.query<BlogData>(`
  query GetPosts {
    posts {
      id
      title
      excerpt
      author {
        name
      }
    }
  }
`);
---

<div>
  {data.posts.map(post => (
    <article key={post.id}>
      <h2>{post.title}</h2>
      <p class="author">作者: {post.author.name}</p>
      <p>{post.excerpt}</p>
    </article>
  ))}
</div>

2.3 带变量的查询 #

astro
---
// src/pages/blog/[slug].astro
import { graphql } from '../../lib/graphql-client';

interface PostData {
  post: {
    id: string;
    title: string;
    content: string;
    author: { name: string };
  };
}

const { slug } = Astro.params;

const data = await graphql.query<PostData>(`
  query GetPost($slug: String!) {
    post(slug: $slug) {
      id
      title
      content
      author {
        name
      }
    }
  }
`, { slug });

const { post } = data;
---

<article>
  <h1>{post.title}</h1>
  <p>作者: {post.author.name}</p>
  <div set:html={post.content} />
</article>

三、CMS 集成 #

3.1 Contentful 集成 #

typescript
// src/lib/contentful.ts
interface ContentfulConfig {
  space: string;
  accessToken: string;
}

class ContentfulClient {
  private config: ContentfulConfig;

  constructor(config: ContentfulConfig) {
    this.config = config;
  }

  async getEntries<T>(contentType: string, options: Record<string, any> = {}) {
    const params = new URLSearchParams({
      access_token: this.config.accessToken,
      content_type: contentType,
      ...options,
    });

    const response = await fetch(
      `https://cdn.contentful.com/spaces/${this.config.space}/entries?${params}`
    );

    const data = await response.json();
    return data.items as Array<{ fields: T; sys: { id: string } }>;
  }

  async getEntry<T>(id: string) {
    const response = await fetch(
      `https://cdn.contentful.com/spaces/${this.config.space}/entries/${id}?access_token=${this.config.accessToken}`
    );

    const data = await response.json();
    return data.fields as T;
  }
}

export const contentful = new ContentfulClient({
  space: import.meta.env.CONTENTFUL_SPACE,
  accessToken: import.meta.env.CONTENTFUL_TOKEN,
});

3.2 Strapi 集成 #

typescript
// src/lib/strapi.ts
class StrapiClient {
  private baseUrl: string;
  private token: string;

  constructor(baseUrl: string, token: string) {
    this.baseUrl = baseUrl;
    this.token = token;
  }

  async get<T>(endpoint: string, params: Record<string, any> = {}) {
    const url = new URL(`${this.baseUrl}/api${endpoint}`);
    
    Object.entries(params).forEach(([key, value]) => {
      url.searchParams.append(key, String(value));
    });

    const response = await fetch(url.toString(), {
      headers: {
        'Authorization': `Bearer ${this.token}`,
      },
    });

    const data = await response.json();
    return data.data as T;
  }
}

export const strapi = new StrapiClient(
  import.meta.env.STRAPI_URL || 'https://api.strapi.com',
  import.meta.env.STRAPI_TOKEN
);

3.3 使用 CMS 数据 #

astro
---
// src/pages/blog/index.astro
import { contentful } from '../../lib/contentful';

interface BlogPost {
  title: string;
  slug: string;
  excerpt: string;
  publishedAt: string;
  featuredImage: {
    fields: {
      file: {
        url: string;
      };
    };
  };
}

const posts = await contentful.getEntries<BlogPost>('blogPost', {
  order: '-fields.publishedAt',
  limit: 10,
});
---

<div class="blog-posts">
  {posts.map(({ fields, sys }) => (
    <article key={sys.id} class="post">
      <img 
        src={`https:${fields.featuredImage.fields.file.url}`} 
        alt={fields.title}
      />
      <h2>{fields.title}</h2>
      <p>{fields.excerpt}</p>
      <a href={`/blog/${fields.slug}`}>阅读更多</a>
    </article>
  ))}
</div>

四、第三方服务集成 #

4.1 GitHub API #

astro
---
// src/pages/projects.astro
interface GitHubRepo {
  id: number;
  name: string;
  description: string;
  html_url: string;
  stargazers_count: number;
  forks_count: number;
  language: string;
}

const response = await fetch('https://api.github.com/users/username/repos', {
  headers: {
    'Authorization': `token ${import.meta.env.GITHUB_TOKEN}`,
    'Accept': 'application/vnd.github.v3+json',
  },
});

const repos: GitHubRepo[] = await response.json();
---

<div class="projects">
  {repos.map(repo => (
    <div key={repo.id} class="project-card">
      <h3>
        <a href={repo.html_url} target="_blank" rel="noopener">
          {repo.name}
        </a>
      </h3>
      <p>{repo.description}</p>
      <div class="stats">
        <span>⭐ {repo.stargazers_count}</span>
        <span>🍴 {repo.forks_count}</span>
        {repo.language && <span>{repo.language}</span>}
      </div>
    </div>
  ))}
</div>

4.2 Twitter API #

typescript
// src/lib/twitter.ts
interface Tweet {
  id: string;
  text: string;
  created_at: string;
  public_metrics: {
    like_count: number;
    retweet_count: number;
  };
}

async function getTweets(userId: string, maxResults = 10): Promise<Tweet[]> {
  const response = await fetch(
    `https://api.twitter.com/2/users/${userId}/tweets?max_results=${maxResults}&tweet.fields=created_at,public_metrics`,
    {
      headers: {
        'Authorization': `Bearer ${import.meta.env.TWITTER_BEARER_TOKEN}`,
      },
    }
  );

  const data = await response.json();
  return data.data;
}

export { getTweets };

4.3 天气 API #

astro
---
// src/pages/weather.astro
interface WeatherData {
  name: string;
  main: {
    temp: number;
    humidity: number;
  };
  weather: Array<{
    description: string;
    icon: string;
  }>;
}

const city = 'Beijing';
const apiKey = import.meta.env.OPENWEATHER_API_KEY;

const response = await fetch(
  `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${apiKey}&units=metric&lang=zh_cn`
);

const weather: WeatherData = await response.json();
---

<div class="weather">
  <h2>{weather.name}</h2>
  <img 
    src={`https://openweathermap.org/img/wn/${weather.weather[0].icon}@2x.png`}
    alt={weather.weather[0].description}
  />
  <p class="temp">{Math.round(weather.main.temp)}°C</p>
  <p>{weather.weather[0].description}</p>
  <p>湿度: {weather.main.humidity}%</p>
</div>

五、认证 API #

5.1 OAuth 集成 #

astro
---
// src/pages/auth/callback.astro
const code = Astro.url.searchParams.get('code');

if (!code) {
  return Astro.redirect('/login');
}

const tokenResponse = await fetch('https://oauth-provider.com/token', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
  },
  body: new URLSearchParams({
    grant_type: 'authorization_code',
    code,
    client_id: import.meta.env.OAUTH_CLIENT_ID,
    client_secret: import.meta.env.OAUTH_CLIENT_SECRET,
    redirect_uri: `${Astro.site}auth/callback`,
  }),
});

const { access_token } = await tokenResponse.json();

const userResponse = await fetch('https://oauth-provider.com/user', {
  headers: {
    'Authorization': `Bearer ${access_token}`,
  },
});

const user = await userResponse.json();
---

<h1>欢迎, {user.name}!</h1>

5.2 JWT 认证 #

typescript
// src/lib/auth.ts
interface User {
  id: string;
  email: string;
  name: string;
}

async function verifyToken(token: string): Promise<User | null> {
  try {
    const response = await fetch(`${import.meta.env.API_URL}/auth/verify`, {
      headers: {
        'Authorization': `Bearer ${token}`,
      },
    });

    if (!response.ok) return null;
    return await response.json();
  } catch {
    return null;
  }
}

export { verifyToken };

六、Webhook 集成 #

6.1 接收 Webhook #

astro
---
// src/pages/api/webhook.astro
if (Astro.request.method !== 'POST') {
  return new Response('Method not allowed', { status: 405 });
}

const body = await Astro.request.json();
const signature = Astro.request.headers.get('x-webhook-signature');

const expectedSignature = createHmac('sha256', import.meta.env.WEBHOOK_SECRET)
  .update(JSON.stringify(body))
  .digest('hex');

if (signature !== expectedSignature) {
  return new Response('Invalid signature', { status: 401 });
}

console.log('Webhook received:', body);

return new Response(JSON.stringify({ success: true }), {
  status: 200,
  headers: {
    'Content-Type': 'application/json',
  },
});
---

七、错误处理与重试 #

7.1 重试机制 #

typescript
// src/lib/retry.ts
interface RetryOptions {
  maxRetries?: number;
  delay?: number;
  backoff?: boolean;
}

async function fetchWithRetry<T>(
  url: string,
  options: RequestInit = {},
  retryOptions: RetryOptions = {}
): Promise<T> {
  const { maxRetries = 3, delay = 1000, backoff = true } = retryOptions;

  let lastError: Error | null = null;

  for (let i = 0; i < maxRetries; i++) {
    try {
      const response = await fetch(url, options);
      
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }
      
      return await response.json();
    } catch (error) {
      lastError = error as Error;
      
      if (i < maxRetries - 1) {
        const waitTime = backoff ? delay * Math.pow(2, i) : delay;
        await new Promise(resolve => setTimeout(resolve, waitTime));
      }
    }
  }

  throw lastError;
}

export { fetchWithRetry };

7.2 使用重试 #

astro
---
import { fetchWithRetry } from '../lib/retry';

const posts = await fetchWithRetry<Post[]>(
  'https://api.example.com/posts',
  {},
  { maxRetries: 3, delay: 1000 }
);
---

<div>
  {posts.map(post => (
    <article key={post.id}>{post.title}</article>
  ))}
</div>

八、总结 #

API 集成核心要点:

text
┌─────────────────────────────────────────────────────┐
│                 API 集成要点                         │
├─────────────────────────────────────────────────────┤
│                                                     │
│  🌐 REST API   封装客户端,统一管理请求             │
│                                                     │
│  📊 GraphQL    类型安全的查询语言                   │
│                                                     │
│  📝 CMS        Contentful、Strapi 等内容管理        │
│                                                     │
│  🔌 第三方     GitHub、Twitter 等服务集成           │
│                                                     │
│  🔐 认证       OAuth、JWT 认证方案                  │
│                                                     │
│  🔄 重试       错误处理与自动重试                   │
│                                                     │
└─────────────────────────────────────────────────────┘

下一步,让我们学习 静态数据,了解本地数据的管理方法!

最后更新:2026-03-28