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 的学习之旅!从基础语法到高级主题,你已经掌握了:
- 基础语法:字段、参数、别名、片段、变量
- 查询操作:分页、过滤、排序、批量查询
- 变更操作:创建、更新、删除、事务处理
- Schema 设计:类型定义、模块化组织
- 类型系统:标量、对象、枚举、接口、联合类型
- 解析器:数据获取、错误处理、性能优化
- 高级主题:认证授权、安全防护、联邦架构
现在你已经具备了使用 GraphQL 构建现代 API 的能力。继续实践,探索更多可能性!
最后更新:2026-03-29