GraphQL 高级主题 #

认证与授权 #

认证方式 #

text
┌─────────────────────────────────────────────────────────────┐
│                    GraphQL 认证方式                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. HTTP Header                                             │
│     Authorization: Bearer <token>                           │
│                                                             │
│  2. Cookie                                                  │
│     Set-Cookie: token=<value>                               │
│                                                             │
│  3. WebSocket 子协议                                        │
│     Sec-WebSocket-Protocol: graphql-ws, token               │
│                                                             │
│  4. 查询参数(不推荐)                                       │
│     ?token=<value>                                          │
│                                                             │
└─────────────────────────────────────────────────────────────┘

JWT 认证实现 #

javascript
const jwt = require('jsonwebtoken');

const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: ({ req }) => {
    const token = req.headers.authorization?.replace('Bearer ', '') || '';
    
    let user = null;
    try {
      user = jwt.verify(token, process.env.JWT_SECRET);
    } catch (e) {
      // Token 无效或过期
    }
    
    return { user };
  }
});

权限控制 #

指令方式 #

graphql
directive @auth(requires: Role = USER) on FIELD_DEFINITION

enum Role {
  ADMIN
  MODERATOR
  USER
  GUEST
}

type Query {
  users: [User!]! @auth(requires: ADMIN)
  me: User @auth
}

type Mutation {
  deleteUser(id: ID!): DeleteResult! @auth(requires: ADMIN)
}

指令实现:

javascript
const { SchemaDirectiveVisitor } = require('apollo-server');
const { defaultFieldResolver } = require('graphql');

class AuthDirective extends SchemaDirectiveVisitor {
  visitFieldDefinition(field) {
    const requiredRole = this.args.requires;
    const originalResolve = field.resolve || defaultFieldResolver;
    
    field.resolve = async function (...args) {
      const context = args[2];
      
      if (!context.user) {
        throw new AuthenticationError('Not authenticated');
      }
      
      const roleHierarchy = { ADMIN: 4, MODERATOR: 3, USER: 2, GUEST: 1 };
      const userRole = context.user.role || 'GUEST';
      
      if (roleHierarchy[userRole] < roleHierarchy[requiredRole]) {
        throw new ForbiddenError('Not authorized');
      }
      
      return originalResolve.apply(this, args);
    };
  }
}

const server = new ApolloServer({
  typeDefs,
  resolvers,
  schemaDirectives: {
    auth: AuthDirective
  }
});

中间件方式 #

javascript
const resolvers = {
  Query: {
    users: async (_, args, context) => {
      requireRole(context, 'ADMIN');
      return getUsers();
    }
  }
};

function requireRole(context, role) {
  if (!context.user) {
    throw new AuthenticationError('Not authenticated');
  }
  
  const roleHierarchy = { ADMIN: 4, MODERATOR: 3, USER: 2, GUEST: 1 };
  const userRole = context.user.role || 'GUEST';
  
  if (roleHierarchy[userRole] < roleHierarchy[role]) {
    throw new ForbiddenError('Not authorized');
  }
}

字段级权限 #

javascript
const resolvers = {
  User: {
    email: (user, _, context) => {
      if (!context.user || (context.user.id !== user.id && context.user.role !== 'ADMIN')) {
        return null;
      }
      return user.email;
    },
    
    phone: (user, _, context) => {
      if (!context.user || context.user.id !== user.id) {
        return null;
      }
      return user.phone;
    }
  }
};

性能优化 #

查询复杂度分析 #

javascript
const { createComplexityLimitRule, simpleEstimator } = require('graphql-validation-complexity');

const complexityLimit = createComplexityLimitRule(1000, {
  estimators: [
    simpleEstimator({ defaultComplexity: 1 })
  ],
  onCost: (cost) => console.log(`Query cost: ${cost}`),
  formatErrorMessage: (cost) => 
    `Query with cost ${cost} exceeds maximum complexity`
});

const server = new ApolloServer({
  typeDefs,
  resolvers,
  validationRules: [complexityLimit]
});

深度限制 #

javascript
const depthLimit = require('graphql-depth-limit');

const server = new ApolloServer({
  typeDefs,
  resolvers,
  validationRules: [depthLimit(5)]
});

DataLoader 批量加载 #

javascript
const DataLoader = require('dataloader');

async function batchUsers(ids) {
  const users = await User.find({ _id: { $in: ids } });
  const userMap = new Map(users.map(u => [u.id.toString(), u]));
  return ids.map(id => userMap.get(id.toString()) || null);
}

const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: () => ({
    loaders: {
      user: new DataLoader(batchUsers),
      postsByUser: new DataLoader(batchPostsByUser)
    }
  })
});

缓存策略 #

响应缓存 #

javascript
const responseCachePlugin = require('apollo-server-plugin-response-cache');

const server = new ApolloServer({
  typeDefs,
  resolvers,
  plugins: [responseCachePlugin()],
  cache: new RedisCache({
    host: 'localhost',
    port: 6379
  })
});

字段级缓存 #

graphql
type User @cacheControl(maxAge: 300) {
  id: ID!
  name: String! @cacheControl(maxAge: 600)
  email: String!
  posts: [Post!]! @cacheControl(maxAge: 60)
}
javascript
const server = new ApolloServer({
  typeDefs,
  resolvers,
  plugins: [responseCachePlugin()]
});

分页优化 #

javascript
const resolvers = {
  Query: {
    users: async (_, { first, after }, { dataSources }) => {
      const cursor = after ? Buffer.from(after, 'base64').toString() : null;
      
      const query = cursor 
        ? { _id: { $gt: cursor } }
        : {};
      
      const users = await User.find(query)
        .limit(first + 1)
        .sort({ _id: 1 });
      
      const hasNextPage = users.length > first;
      const edges = users.slice(0, first).map(user => ({
        node: user,
        cursor: Buffer.from(user.id).toString('base64')
      }));
      
      return {
        edges,
        pageInfo: {
          hasNextPage,
          hasPreviousPage: !!cursor,
          startCursor: edges[0]?.cursor,
          endCursor: edges[edges.length - 1]?.cursor
        }
      };
    }
  }
};

安全防护 #

查询深度限制 #

javascript
const depthLimit = require('graphql-depth-limit');

const server = new ApolloServer({
  typeDefs,
  resolvers,
  validationRules: [depthLimit(5, { ignore: ['_internal'] })]
});

查询复杂度限制 #

javascript
const { createComplexityLimitRule } = require('graphql-validation-complexity');

const server = new ApolloServer({
  typeDefs,
  resolvers,
  validationRules: [
    createComplexityLimitRule(1000, {
      scalarCost: 1,
      objectCost: 10,
      listFactor: 100,
      onCost: (cost) => console.log(`Query cost: ${cost}`)
    })
  ]
});

查询超时 #

javascript
const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: ({ res }) => {
    res.setTimeout(30000);
    return {};
  }
});

请求大小限制 #

javascript
const express = require('express');
const { ApolloServer } = require('apollo-server-express');

const app = express();
app.use(express.json({ limit: '100kb' }));

const server = new ApolloServer({ typeDefs, resolvers });
server.applyMiddleware({ app });

输入验证 #

graphql
input CreateUserInput {
  name: String! @constraint(minLength: 2, maxLength: 50)
  email: String! @constraint(format: "email")
  password: String! @constraint(minLength: 8, pattern: "^(?=.*[A-Za-z])(?=.*\\d)")
  age: Int @constraint(min: 0, max: 150)
}
javascript
const { constraintDirective } = require('graphql-constraint-directive');

const server = new ApolloServer({
  typeDefs,
  resolvers,
  schemaDirectives: {
    constraint: constraintDirective
  }
});

敏感数据保护 #

javascript
const resolvers = {
  User: {
    email: (user, _, context) => {
      if (!canViewEmail(context.user, user)) {
        return null;
      }
      return user.email;
    },
    
    password: () => {
      throw new ForbiddenError('Cannot query password field');
    }
  }
};

订阅(Subscription) #

基本订阅 #

javascript
const { PubSub } = require('apollo-server');

const pubsub = new PubSub();

const typeDefs = `
  type Subscription {
    onMessageCreated: Message!
    onUserUpdated(id: ID!): User!
  }
  
  type Mutation {
    createMessage(content: String!): Message!
  }
`;

const resolvers = {
  Subscription: {
    onMessageCreated: {
      subscribe: () => pubsub.asyncIterator(['MESSAGE_CREATED'])
    },
    
    onUserUpdated: {
      subscribe: (_, { id }) => pubsub.asyncIterator([`USER_UPDATED_${id}`])
    }
  },
  
  Mutation: {
    createMessage: async (_, { content }, { dataSources }) => {
      const message = await dataSources.messageAPI.create({ content });
      pubsub.publish('MESSAGE_CREATED', { onMessageCreated: message });
      return message;
    }
  }
};

WebSocket 配置 #

javascript
const { ApolloServer } = require('apollo-server');
const { createServer } = require('http');
const { WebSocketServer } = require('ws');
const { useServer } = require('graphql-ws/lib/use/ws');

const httpServer = createServer();

const wsServer = new WebSocketServer({
  server: httpServer,
  path: '/graphql'
});

useServer({
  schema,
  context: async (ctx) => {
    const token = ctx.connectionParams?.authorization;
    const user = getUserFromToken(token);
    return { user };
  }
}, wsServer);

const server = new ApolloServer({
  schema,
  plugins: [{
    serverWillStart() {
      return {
        drainServer() {
          wsServer.close();
        }
      };
    }
  }]
});

订阅过滤 #

javascript
const resolvers = {
  Subscription: {
    onCommentAdded: {
      subscribe: (_, { postId }, { pubsub }) => {
        return pubsub.asyncIterator([`COMMENT_ADDED_${postId}`]);
      },
      resolve: (payload) => {
        return payload.onCommentAdded;
      }
    }
  }
};

订阅认证 #

javascript
const resolvers = {
  Subscription: {
    onNotification: {
      subscribe: (_, __, { user, pubsub }) => {
        if (!user) {
          throw new AuthenticationError('Not authenticated');
        }
        return pubsub.asyncIterator([`NOTIFICATION_${user.id}`]);
      }
    }
  }
};

联邦架构(Federation) #

概述 #

text
┌─────────────────────────────────────────────────────────────┐
│                    GraphQL 联邦架构                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   ┌─────────────────────────────────────────────────────┐   │
│   │                  Gateway 网关                        │   │
│   │                  /graphql                            │   │
│   └─────────────────────┬───────────────────────────────┘   │
│                         │                                   │
│          ┌──────────────┼──────────────┐                    │
│          │              │              │                    │
│          ▼              ▼              ▼                    │
│   ┌──────────┐   ┌──────────┐   ┌──────────┐              │
│   │ 用户服务  │   │ 订单服务  │   │ 商品服务  │              │
│   │ /graphql │   │ /graphql │   │ /graphql │              │
│   └──────────┘   └──────────┘   └──────────┘              │
│                                                             │
└─────────────────────────────────────────────────────────────┘

用户服务 #

graphql
extend schema
  @link(url: "https://specs.apollo.dev/federation/v2.0",
        import: ["@key", "@external"])

type User @key(fields: "id") {
  id: ID!
  name: String!
  email: String!
}

type Query {
  user(id: ID!): User
  users: [User!]!
}

订单服务 #

graphql
extend schema
  @link(url: "https://specs.apollo.dev/federation/v2.0",
        import: ["@key", "@external"])

type Order @key(fields: "id") {
  id: ID!
  userId: ID!
  user: User
  products: [Product!]!
  total: Float!
  status: OrderStatus!
}

extend type User @key(fields: "id") {
  id: ID! @external
  orders: [Order!]!
}

type Query {
  order(id: ID!): Order
  orders: [Order!]!
}

商品服务 #

graphql
extend schema
  @link(url: "https://specs.apollo.dev/federation/v2.0",
        import: ["@key"])

type Product @key(fields: "id") {
  id: ID!
  name: String!
  price: Float!
  description: String
}

type Query {
  product(id: ID!): Product
  products: [Product!]!
}

网关配置 #

javascript
const { ApolloGateway } = require('@apollo/gateway');
const { ApolloServer } = require('apollo-server');

const gateway = new ApolloGateway({
  serviceList: [
    { name: 'users', url: 'http://users-service:4001/graphql' },
    { name: 'orders', url: 'http://orders-service:4002/graphql' },
    { name: 'products', url: 'http://products-service:4003/graphql' }
  ]
});

const server = new ApolloServer({
  gateway,
  subscriptions: false
});

工具与生态 #

Apollo Studio #

javascript
const server = new ApolloServer({
  typeDefs,
  resolvers,
  plugins: [
    require('apollo-server-core').ApolloServerPluginLandingPageDisabled()
  ]
});

代码生成 #

graphql
query GetUser($id: ID!) {
  user(id: $id) {
    id
    name
    email
  }
}

生成 TypeScript 类型:

typescript
export interface GetUserQuery {
  user: {
    id: string;
    name: string;
    email: string;
  } | null;
}

export interface GetUserQueryVariables {
  id: string;
}

测试 #

javascript
const { createTestClient } = require('apollo-server-testing');

const server = new ApolloServer({ typeDefs, resolvers });
const { query, mutate } = createTestClient(server);

describe('User Queries', () => {
  test('get user by id', async () => {
    const result = await query({
      query: GET_USER,
      variables: { id: '1' }
    });
    
    expect(result.data.user).toBeTruthy();
    expect(result.data.user.name).toBe('Alice');
  });
  
  test('create user', async () => {
    const result = await mutate({
      mutation: CREATE_USER,
      variables: {
        input: {
          name: 'Bob',
          email: 'bob@example.com'
        }
      }
    });
    
    expect(result.data.createUser.success).toBe(true);
  });
});

最佳实践总结 #

text
┌─────────────────────────────────────────────────────────────┐
│                    GraphQL 最佳实践                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  认证授权:                                                  │
│  ✅ 使用 JWT 或 Session 认证                                │
│  ✅ 实现字段级权限控制                                       │
│  ✅ 使用指令或中间件统一处理                                 │
│                                                             │
│  性能优化:                                                  │
│  ✅ 使用 DataLoader 批量加载                                │
│  ✅ 实现查询复杂度限制                                       │
│  ✅ 合理使用缓存                                            │
│  ✅ 限制查询深度                                            │
│                                                             │
│  安全防护:                                                  │
│  ✅ 输入验证                                                │
│  ✅ 查询深度限制                                            │
│  ✅ 查询复杂度限制                                          │
│  ✅ 请求大小限制                                            │
│  ✅ 敏感数据保护                                            │
│                                                             │
│  架构设计:                                                  │
│  ✅ 模块化 Schema                                           │
│  ✅ 联邦架构拆分服务                                         │
│  ✅ 统一错误处理                                            │
│  ✅ 完善的日志记录                                          │
│                                                             │
└─────────────────────────────────────────────────────────────┘

学习资源 #

总结 #

恭喜你完成了 GraphQL 的学习之旅!从基础语法到高级主题,你已经掌握了:

  1. 基础语法:字段、参数、别名、片段、变量
  2. 查询操作:分页、过滤、排序、批量查询
  3. 变更操作:创建、更新、删除、事务处理
  4. Schema 设计:类型定义、模块化组织
  5. 类型系统:标量、对象、枚举、接口、联合类型
  6. 解析器:数据获取、错误处理、性能优化
  7. 高级主题:认证授权、安全防护、联邦架构

现在你已经具备了使用 GraphQL 构建现代 API 的能力。继续实践,探索更多可能性!

最后更新:2026-03-29