3.6.4 GraphQL 客户端开发 #
在构建了 GraphQL 服务器之后,本节将详细介绍如何开发 GraphQL 客户端应用。我们将学习如何使用 Go 语言构建 GraphQL 客户端,包括查询执行、变更操作、订阅处理、错误处理和性能优化等内容。
GraphQL 客户端库 #
Go 生态系统中有几个优秀的 GraphQL 客户端库:
- machinebox/graphql - 简单易用的 GraphQL 客户端
- shurcooL/graphql - 类型安全的 GraphQL 客户端
- 99designs/gqlgen - 支持客户端代码生成
- hasura/go-graphql-client - 功能丰富的客户端库
本节主要使用 machinebox/graphql
和 shurcooL/graphql
进行演示。
基础客户端实现 #
安装依赖 #
go get github.com/machinebox/graphql
go get github.com/shurcooL/graphql
go get golang.org/x/oauth2
简单客户端示例 #
// client/simple_client.go
package main
import (
"context"
"fmt"
"log"
"github.com/machinebox/graphql"
)
func main() {
// 创建 GraphQL 客户端
client := graphql.NewClient("http://localhost:8080/graphql")
// 设置请求头(如果需要认证)
client.Header.Set("Authorization", "Bearer your-jwt-token")
// 创建查询
req := graphql.NewRequest(`
query {
users {
id
name
email
role
createdAt
}
}
`)
// 定义响应结构
var resp struct {
Users []struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Role string `json:"role"`
CreatedAt string `json:"createdAt"`
} `json:"users"`
}
// 执行查询
ctx := context.Background()
if err := client.Run(ctx, req, &resp); err != nil {
log.Fatal(err)
}
// 处理响应
fmt.Printf("Found %d users:\n", len(resp.Users))
for _, user := range resp.Users {
fmt.Printf("- %s (%s): %s\n", user.Name, user.Role, user.Email)
}
}
类型安全的客户端 #
使用 shurcooL/graphql
库可以实现类型安全的 GraphQL 客户端:
// client/typed_client.go
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/shurcooL/graphql"
"golang.org/x/oauth2"
)
// 定义 GraphQL 类型
type User struct {
ID graphql.ID `graphql:"id"`
Name graphql.String `graphql:"name"`
Email graphql.String `graphql:"email"`
Role UserRole `graphql:"role"`
CreatedAt time.Time `graphql:"createdAt"`
Posts []Post `graphql:"posts"`
}
type Post struct {
ID graphql.ID `graphql:"id"`
Title graphql.String `graphql:"title"`
Content graphql.String `graphql:"content"`
Status PostStatus `graphql:"status"`
CreatedAt time.Time `graphql:"createdAt"`
Author User `graphql:"author"`
Tags []Tag `graphql:"tags"`
}
type Tag struct {
ID graphql.ID `graphql:"id"`
Name graphql.String `graphql:"name"`
}
type UserRole string
type PostStatus string
const (
UserRoleAdmin UserRole = "ADMIN"
UserRoleUser UserRole = "USER"
PostStatusDraft PostStatus = "DRAFT"
PostStatusPublished PostStatus = "PUBLISHED"
)
func main() {
// 创建带认证的客户端
src := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: "your-jwt-token"},
)
httpClient := oauth2.NewClient(context.Background(), src)
client := graphql.NewClient("http://localhost:8080/graphql", httpClient)
// 查询用户列表
queryUsers(client)
// 查询单个用户
queryUser(client, "1")
// 创建用户
createUser(client)
// 更新用户
updateUser(client, "1")
// 创建文章
createPost(client)
}
func queryUsers(client *graphql.Client) {
var query struct {
Users []User `graphql:"users(role: $role)"`
}
variables := map[string]interface{}{
"role": (*UserRole)(nil), // nil 表示不过滤
}
err := client.Query(context.Background(), &query, variables)
if err != nil {
log.Printf("Query users error: %v", err)
return
}
fmt.Printf("Found %d users:\n", len(query.Users))
for _, user := range query.Users {
fmt.Printf("- %s (%s): %s\n", user.Name, user.Role, user.Email)
}
}
func queryUser(client *graphql.Client, userID string) {
var query struct {
User *User `graphql:"user(id: $id)"`
}
variables := map[string]interface{}{
"id": graphql.ID(userID),
}
err := client.Query(context.Background(), &query, variables)
if err != nil {
log.Printf("Query user error: %v", err)
return
}
if query.User == nil {
fmt.Printf("User with ID %s not found\n", userID)
return
}
user := query.User
fmt.Printf("User: %s (%s)\n", user.Name, user.Email)
fmt.Printf("Posts: %d\n", len(user.Posts))
for _, post := range user.Posts {
fmt.Printf(" - %s (%s)\n", post.Title, post.Status)
}
}
func createUser(client *graphql.Client) {
var mutation struct {
CreateUser User `graphql:"createUser(input: $input)"`
}
input := map[string]interface{}{
"name": "Bob Wilson",
"email": "[email protected]",
"role": UserRoleUser,
"avatar": "https://example.com/bob.jpg",
}
variables := map[string]interface{}{
"input": input,
}
err := client.Mutate(context.Background(), &mutation, variables)
if err != nil {
log.Printf("Create user error: %v", err)
return
}
user := mutation.CreateUser
fmt.Printf("Created user: %s (ID: %s)\n", user.Name, user.ID)
}
func updateUser(client *graphql.Client, userID string) {
var mutation struct {
UpdateUser User `graphql:"updateUser(id: $id, input: $input)"`
}
input := map[string]interface{}{
"name": "Updated Name",
}
variables := map[string]interface{}{
"id": graphql.ID(userID),
"input": input,
}
err := client.Mutate(context.Background(), &mutation, variables)
if err != nil {
log.Printf("Update user error: %v", err)
return
}
user := mutation.UpdateUser
fmt.Printf("Updated user: %s (ID: %s)\n", user.Name, user.ID)
}
func createPost(client *graphql.Client) {
var mutation struct {
CreatePost Post `graphql:"createPost(input: $input)"`
}
input := map[string]interface{}{
"title": "My New Post",
"content": "This is the content of my new post.",
"status": PostStatusDraft,
"tags": []string{"go", "graphql"},
}
variables := map[string]interface{}{
"input": input,
}
err := client.Mutate(context.Background(), &mutation, variables)
if err != nil {
log.Printf("Create post error: %v", err)
return
}
post := mutation.CreatePost
fmt.Printf("Created post: %s (ID: %s)\n", post.Title, post.ID)
fmt.Printf("Author: %s\n", post.Author.Name)
fmt.Printf("Tags: ")
for i, tag := range post.Tags {
if i > 0 {
fmt.Print(", ")
}
fmt.Print(tag.Name)
}
fmt.Println()
}
高级客户端功能 #
1. 请求拦截器和中间件 #
// client/interceptor.go
package client
import (
"context"
"fmt"
"log"
"net/http"
"time"
"github.com/machinebox/graphql"
)
// RequestInterceptor 请求拦截器接口
type RequestInterceptor interface {
Intercept(req *http.Request) error
}
// ResponseInterceptor 响应拦截器接口
type ResponseInterceptor interface {
Intercept(resp *http.Response) error
}
// LoggingInterceptor 日志拦截器
type LoggingInterceptor struct{}
func (l *LoggingInterceptor) Intercept(req *http.Request) error {
log.Printf("GraphQL Request: %s %s", req.Method, req.URL.String())
return nil
}
// AuthInterceptor 认证拦截器
type AuthInterceptor struct {
Token string
}
func (a *AuthInterceptor) Intercept(req *http.Request) error {
if a.Token != "" {
req.Header.Set("Authorization", "Bearer "+a.Token)
}
return nil
}
// RetryInterceptor 重试拦截器
type RetryInterceptor struct {
MaxRetries int
Delay time.Duration
}
func (r *RetryInterceptor) Intercept(req *http.Request) error {
// 重试逻辑在客户端层面实现
return nil
}
// EnhancedClient 增强的 GraphQL 客户端
type EnhancedClient struct {
client *graphql.Client
requestInterceptors []RequestInterceptor
responseInterceptors []ResponseInterceptor
}
func NewEnhancedClient(endpoint string) *EnhancedClient {
return &EnhancedClient{
client: graphql.NewClient(endpoint),
requestInterceptors: make([]RequestInterceptor, 0),
responseInterceptors: make([]ResponseInterceptor, 0),
}
}
func (c *EnhancedClient) AddRequestInterceptor(interceptor RequestInterceptor) {
c.requestInterceptors = append(c.requestInterceptors, interceptor)
}
func (c *EnhancedClient) AddResponseInterceptor(interceptor ResponseInterceptor) {
c.responseInterceptors = append(c.responseInterceptors, interceptor)
}
func (c *EnhancedClient) Run(ctx context.Context, req *graphql.Request, resp interface{}) error {
// 应用请求拦截器
httpReq, err := req.HTTP(ctx)
if err != nil {
return err
}
for _, interceptor := range c.requestInterceptors {
if err := interceptor.Intercept(httpReq); err != nil {
return err
}
}
// 执行请求
return c.client.Run(ctx, req, resp)
}
// 使用示例
func ExampleEnhancedClient() {
client := NewEnhancedClient("http://localhost:8080/graphql")
// 添加拦截器
client.AddRequestInterceptor(&LoggingInterceptor{})
client.AddRequestInterceptor(&AuthInterceptor{Token: "your-jwt-token"})
// 创建查询
req := graphql.NewRequest(`
query {
users {
id
name
email
}
}
`)
var resp struct {
Users []struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
} `json:"users"`
}
// 执行查询
if err := client.Run(context.Background(), req, &resp); err != nil {
log.Fatal(err)
}
fmt.Printf("Found %d users\n", len(resp.Users))
}
2. 缓存机制 #
// client/cache.go
package client
import (
"context"
"crypto/md5"
"encoding/json"
"fmt"
"sync"
"time"
"github.com/machinebox/graphql"
)
// CacheEntry 缓存条目
type CacheEntry struct {
Data interface{}
ExpiresAt time.Time
}
// Cache 缓存接口
type Cache interface {
Get(key string) (interface{}, bool)
Set(key string, value interface{}, ttl time.Duration)
Delete(key string)
Clear()
}
// MemoryCache 内存缓存实现
type MemoryCache struct {
mu sync.RWMutex
items map[string]*CacheEntry
}
func NewMemoryCache() *MemoryCache {
cache := &MemoryCache{
items: make(map[string]*CacheEntry),
}
// 启动清理 goroutine
go cache.cleanup()
return cache
}
func (c *MemoryCache) Get(key string) (interface{}, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
entry, exists := c.items[key]
if !exists {
return nil, false
}
if time.Now().After(entry.ExpiresAt) {
delete(c.items, key)
return nil, false
}
return entry.Data, true
}
func (c *MemoryCache) Set(key string, value interface{}, ttl time.Duration) {
c.mu.Lock()
defer c.mu.Unlock()
c.items[key] = &CacheEntry{
Data: value,
ExpiresAt: time.Now().Add(ttl),
}
}
func (c *MemoryCache) Delete(key string) {
c.mu.Lock()
defer c.mu.Unlock()
delete(c.items, key)
}
func (c *MemoryCache) Clear() {
c.mu.Lock()
defer c.mu.Unlock()
c.items = make(map[string]*CacheEntry)
}
func (c *MemoryCache) cleanup() {
ticker := time.NewTicker(time.Minute)
defer ticker.Stop()
for range ticker.C {
c.mu.Lock()
now := time.Now()
for key, entry := range c.items {
if now.After(entry.ExpiresAt) {
delete(c.items, key)
}
}
c.mu.Unlock()
}
}
// CachedClient 带缓存的 GraphQL 客户端
type CachedClient struct {
client *graphql.Client
cache Cache
ttl time.Duration
}
func NewCachedClient(endpoint string, cache Cache, ttl time.Duration) *CachedClient {
return &CachedClient{
client: graphql.NewClient(endpoint),
cache: cache,
ttl: ttl,
}
}
func (c *CachedClient) Run(ctx context.Context, req *graphql.Request, resp interface{}) error {
// 生成缓存键
key := c.generateCacheKey(req)
// 尝试从缓存获取
if cached, found := c.cache.Get(key); found {
return c.copyResponse(cached, resp)
}
// 执行查询
if err := c.client.Run(ctx, req, resp); err != nil {
return err
}
// 缓存结果(只缓存查询,不缓存变更)
if !c.isMutation(req) {
c.cache.Set(key, resp, c.ttl)
}
return nil
}
func (c *CachedClient) generateCacheKey(req *graphql.Request) string {
data, _ := json.Marshal(map[string]interface{}{
"query": req.Query(),
"variables": req.Vars(),
})
return fmt.Sprintf("%x", md5.Sum(data))
}
func (c *CachedClient) isMutation(req *graphql.Request) bool {
query := req.Query()
return len(query) > 8 && query[:8] == "mutation"
}
func (c *CachedClient) copyResponse(src, dst interface{}) error {
data, err := json.Marshal(src)
if err != nil {
return err
}
return json.Unmarshal(data, dst)
}
// 使用示例
func ExampleCachedClient() {
cache := NewMemoryCache()
client := NewCachedClient("http://localhost:8080/graphql", cache, 5*time.Minute)
req := graphql.NewRequest(`
query {
users {
id
name
email
}
}
`)
var resp struct {
Users []struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
} `json:"users"`
}
// 第一次请求(从服务器获取)
start := time.Now()
if err := client.Run(context.Background(), req, &resp); err != nil {
log.Fatal(err)
}
fmt.Printf("First request took: %v\n", time.Since(start))
// 第二次请求(从缓存获取)
start = time.Now()
if err := client.Run(context.Background(), req, &resp); err != nil {
log.Fatal(err)
}
fmt.Printf("Second request took: %v\n", time.Since(start))
}
3. 批量请求 #
// client/batch.go
package client
import (
"context"
"fmt"
"sync"
"time"
"github.com/machinebox/graphql"
)
// BatchRequest 批量请求项
type BatchRequest struct {
Request *graphql.Request
Response interface{}
Error error
Done chan struct{}
}
// BatchClient 批量请求客户端
type BatchClient struct {
client *graphql.Client
batchSize int
batchDelay time.Duration
queue chan *BatchRequest
mu sync.Mutex
batch []*BatchRequest
}
func NewBatchClient(endpoint string, batchSize int, batchDelay time.Duration) *BatchClient {
client := &BatchClient{
client: graphql.NewClient(endpoint),
batchSize: batchSize,
batchDelay: batchDelay,
queue: make(chan *BatchRequest, 100),
batch: make([]*BatchRequest, 0, batchSize),
}
go client.processBatch()
return client
}
func (c *BatchClient) Run(ctx context.Context, req *graphql.Request, resp interface{}) error {
batchReq := &BatchRequest{
Request: req,
Response: resp,
Done: make(chan struct{}),
}
c.queue <- batchReq
select {
case <-batchReq.Done:
return batchReq.Error
case <-ctx.Done():
return ctx.Err()
}
}
func (c *BatchClient) processBatch() {
ticker := time.NewTicker(c.batchDelay)
defer ticker.Stop()
for {
select {
case req := <-c.queue:
c.mu.Lock()
c.batch = append(c.batch, req)
shouldFlush := len(c.batch) >= c.batchSize
c.mu.Unlock()
if shouldFlush {
c.flushBatch()
}
case <-ticker.C:
c.flushBatch()
}
}
}
func (c *BatchClient) flushBatch() {
c.mu.Lock()
if len(c.batch) == 0 {
c.mu.Unlock()
return
}
batch := c.batch
c.batch = make([]*BatchRequest, 0, c.batchSize)
c.mu.Unlock()
// 并发执行批量请求
var wg sync.WaitGroup
for _, req := range batch {
wg.Add(1)
go func(batchReq *BatchRequest) {
defer wg.Done()
defer close(batchReq.Done)
batchReq.Error = c.client.Run(context.Background(), batchReq.Request, batchReq.Response)
}(req)
}
wg.Wait()
}
4. 订阅支持 #
// client/subscription.go
package client
import (
"context"
"encoding/json"
"fmt"
"log"
"net/url"
"github.com/gorilla/websocket"
)
// SubscriptionClient WebSocket 订阅客户端
type SubscriptionClient struct {
url string
conn *websocket.Conn
subs map[string]chan interface{}
mu sync.RWMutex
}
func NewSubscriptionClient(endpoint string) *SubscriptionClient {
u, _ := url.Parse(endpoint)
if u.Scheme == "http" {
u.Scheme = "ws"
} else if u.Scheme == "https" {
u.Scheme = "wss"
}
u.Path = "/graphql"
return &SubscriptionClient{
url: u.String(),
subs: make(map[string]chan interface{}),
}
}
func (c *SubscriptionClient) Connect() error {
conn, _, err := websocket.DefaultDialer.Dial(c.url, nil)
if err != nil {
return err
}
c.conn = conn
// 发送连接初始化消息
initMsg := map[string]interface{}{
"type": "connection_init",
}
if err := c.conn.WriteJSON(initMsg); err != nil {
return err
}
// 启动消息处理 goroutine
go c.handleMessages()
return nil
}
func (c *SubscriptionClient) Subscribe(query string, variables map[string]interface{}) (<-chan interface{}, error) {
if c.conn == nil {
return nil, fmt.Errorf("not connected")
}
id := fmt.Sprintf("sub_%d", time.Now().UnixNano())
subMsg := map[string]interface{}{
"id": id,
"type": "start",
"payload": map[string]interface{}{
"query": query,
"variables": variables,
},
}
if err := c.conn.WriteJSON(subMsg); err != nil {
return nil, err
}
ch := make(chan interface{}, 10)
c.mu.Lock()
c.subs[id] = ch
c.mu.Unlock()
return ch, nil
}
func (c *SubscriptionClient) Unsubscribe(id string) error {
if c.conn == nil {
return fmt.Errorf("not connected")
}
stopMsg := map[string]interface{}{
"id": id,
"type": "stop",
}
if err := c.conn.WriteJSON(stopMsg); err != nil {
return err
}
c.mu.Lock()
if ch, exists := c.subs[id]; exists {
close(ch)
delete(c.subs, id)
}
c.mu.Unlock()
return nil
}
func (c *SubscriptionClient) handleMessages() {
for {
var msg map[string]interface{}
if err := c.conn.ReadJSON(&msg); err != nil {
log.Printf("WebSocket read error: %v", err)
break
}
msgType, ok := msg["type"].(string)
if !ok {
continue
}
switch msgType {
case "data":
c.handleDataMessage(msg)
case "error":
c.handleErrorMessage(msg)
case "complete":
c.handleCompleteMessage(msg)
}
}
}
func (c *SubscriptionClient) handleDataMessage(msg map[string]interface{}) {
id, ok := msg["id"].(string)
if !ok {
return
}
payload, ok := msg["payload"]
if !ok {
return
}
c.mu.RLock()
ch, exists := c.subs[id]
c.mu.RUnlock()
if exists {
select {
case ch <- payload:
default:
// Channel is full, skip this message
}
}
}
func (c *SubscriptionClient) handleErrorMessage(msg map[string]interface{}) {
log.Printf("Subscription error: %v", msg)
}
func (c *SubscriptionClient) handleCompleteMessage(msg map[string]interface{}) {
id, ok := msg["id"].(string)
if !ok {
return
}
c.mu.Lock()
if ch, exists := c.subs[id]; exists {
close(ch)
delete(c.subs, id)
}
c.mu.Unlock()
}
func (c *SubscriptionClient) Close() error {
if c.conn != nil {
return c.conn.Close()
}
return nil
}
// 使用示例
func ExampleSubscriptionClient() {
client := NewSubscriptionClient("ws://localhost:8080/graphql")
if err := client.Connect(); err != nil {
log.Fatal(err)
}
defer client.Close()
// 订阅用户创建事件
ch, err := client.Subscribe(`
subscription {
userCreated {
id
name
email
createdAt
}
}
`, nil)
if err != nil {
log.Fatal(err)
}
// 处理订阅消息
for data := range ch {
fmt.Printf("New user created: %v\n", data)
}
}
错误处理和重试机制 #
// client/error_handling.go
package client
import (
"context"
"fmt"
"log"
"math"
"time"
"github.com/machinebox/graphql"
)
// GraphQLError GraphQL 错误类型
type GraphQLError struct {
Message string `json:"message"`
Locations []GraphQLErrorLocation `json:"locations"`
Path []interface{} `json:"path"`
Extensions map[string]interface{} `json:"extensions"`
}
type GraphQLErrorLocation struct {
Line int `json:"line"`
Column int `json:"column"`
}
// GraphQLResponse GraphQL 响应类型
type GraphQLResponse struct {
Data interface{} `json:"data"`
Errors []GraphQLError `json:"errors"`
}
// RetryConfig 重试配置
type RetryConfig struct {
MaxRetries int
InitialDelay time.Duration
MaxDelay time.Duration
BackoffFactor float64
RetryableErrors []string
}
// DefaultRetryConfig 默认重试配置
func DefaultRetryConfig() *RetryConfig {
return &RetryConfig{
MaxRetries: 3,
InitialDelay: 100 * time.Millisecond,
MaxDelay: 5 * time.Second,
BackoffFactor: 2.0,
RetryableErrors: []string{
"NETWORK_ERROR",
"TIMEOUT_ERROR",
"INTERNAL_ERROR",
},
}
}
// ResilientClient 具有错误处理和重试机制的客户端
type ResilientClient struct {
client *graphql.Client
retryConfig *RetryConfig
}
func NewResilientClient(endpoint string, config *RetryConfig) *ResilientClient {
if config == nil {
config = DefaultRetryConfig()
}
return &ResilientClient{
client: graphql.NewClient(endpoint),
retryConfig: config,
}
}
func (c *ResilientClient) Run(ctx context.Context, req *graphql.Request, resp interface{}) error {
var lastErr error
for attempt := 0; attempt <= c.retryConfig.MaxRetries; attempt++ {
if attempt > 0 {
delay := c.calculateDelay(attempt)
log.Printf("Retrying GraphQL request (attempt %d/%d) after %v",
attempt, c.retryConfig.MaxRetries, delay)
select {
case <-time.After(delay):
case <-ctx.Done():
return ctx.Err()
}
}
err := c.client.Run(ctx, req, resp)
if err == nil {
return nil
}
lastErr = err
// 检查是否应该重试
if !c.shouldRetry(err) {
break
}
}
return fmt.Errorf("GraphQL request failed after %d attempts: %w",
c.retryConfig.MaxRetries+1, lastErr)
}
func (c *ResilientClient) calculateDelay(attempt int) time.Duration {
delay := float64(c.retryConfig.InitialDelay) * math.Pow(c.retryConfig.BackoffFactor, float64(attempt-1))
if delay > float64(c.retryConfig.MaxDelay) {
delay = float64(c.retryConfig.MaxDelay)
}
return time.Duration(delay)
}
func (c *ResilientClient) shouldRetry(err error) bool {
// 这里可以根据错误类型判断是否应该重试
// 例如网络错误、超时错误等可以重试
// 而语法错误、认证错误等不应该重试
// 简化实现:检查错误消息
errMsg := err.Error()
for _, retryableError := range c.retryConfig.RetryableErrors {
if contains(errMsg, retryableError) {
return true
}
}
return false
}
func contains(s, substr string) bool {
return len(s) >= len(substr) && s[:len(substr)] == substr
}
// 使用示例
func ExampleResilientClient() {
config := &RetryConfig{
MaxRetries: 5,
InitialDelay: 200 * time.Millisecond,
MaxDelay: 10 * time.Second,
BackoffFactor: 2.0,
RetryableErrors: []string{
"network",
"timeout",
"internal",
},
}
client := NewResilientClient("http://localhost:8080/graphql", config)
req := graphql.NewRequest(`
query {
users {
id
name
email
}
}
`)
var resp struct {
Users []struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
} `json:"users"`
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := client.Run(ctx, req, &resp); err != nil {
log.Printf("Request failed: %v", err)
return
}
fmt.Printf("Successfully retrieved %d users\n", len(resp.Users))
}
性能优化 #
1. 连接池 #
// client/pool.go
package client
import (
"context"
"sync"
"github.com/machinebox/graphql"
)
// ClientPool GraphQL 客户端池
type ClientPool struct {
clients chan *graphql.Client
factory func() *graphql.Client
mu sync.Mutex
}
func NewClientPool(size int, factory func() *graphql.Client) *ClientPool {
pool := &ClientPool{
clients: make(chan *graphql.Client, size),
factory: factory,
}
// 预填充池
for i := 0; i < size; i++ {
pool.clients <- factory()
}
return pool
}
func (p *ClientPool) Get() *graphql.Client {
select {
case client := <-p.clients:
return client
default:
return p.factory()
}
}
func (p *ClientPool) Put(client *graphql.Client) {
select {
case p.clients <- client:
default:
// Pool is full, discard the client
}
}
func (p *ClientPool) Run(ctx context.Context, req *graphql.Request, resp interface{}) error {
client := p.Get()
defer p.Put(client)
return client.Run(ctx, req, resp)
}
2. 查询优化 #
// client/optimization.go
package client
import (
"fmt"
"strings"
)
// QueryOptimizer 查询优化器
type QueryOptimizer struct{}
func NewQueryOptimizer() *QueryOptimizer {
return &QueryOptimizer{}
}
// OptimizeQuery 优化 GraphQL 查询
func (o *QueryOptimizer) OptimizeQuery(query string) string {
// 移除不必要的空白字符
query = strings.TrimSpace(query)
query = strings.ReplaceAll(query, "\n", " ")
query = strings.ReplaceAll(query, "\t", " ")
// 移除多余的空格
for strings.Contains(query, " ") {
query = strings.ReplaceAll(query, " ", " ")
}
return query
}
// GenerateFieldSelection 生成字段选择
func (o *QueryOptimizer) GenerateFieldSelection(fields []string) string {
return strings.Join(fields, "\n ")
}
// BuildQuery 构建优化的查询
func (o *QueryOptimizer) BuildQuery(operation, name string, args map[string]interface{}, fields []string) string {
var query strings.Builder
query.WriteString(operation)
query.WriteString(" {\n ")
query.WriteString(name)
if len(args) > 0 {
query.WriteString("(")
var argParts []string
for key, value := range args {
argParts = append(argParts, fmt.Sprintf("%s: %v", key, value))
}
query.WriteString(strings.Join(argParts, ", "))
query.WriteString(")")
}
query.WriteString(" {\n ")
query.WriteString(o.GenerateFieldSelection(fields))
query.WriteString("\n }\n}")
return o.OptimizeQuery(query.String())
}
// 使用示例
func ExampleQueryOptimizer() {
optimizer := NewQueryOptimizer()
// 构建优化的查询
query := optimizer.BuildQuery(
"query",
"users",
map[string]interface{}{
"first": 10,
"role": "USER",
},
[]string{
"id",
"name",
"email",
"posts { id title }",
},
)
fmt.Println("Optimized query:")
fmt.Println(query)
}
通过这些高级功能和优化技术,你可以构建出功能强大、性能优异的 GraphQL 客户端应用。这些技术包括请求拦截、缓存机制、批量处理、订阅支持、错误处理和性能优化等,能够满足各种复杂的业务需求。