GraphQL 解析器 #

解析器概述 #

解析器(Resolver)是 GraphQL 服务器处理每个字段的核心逻辑,负责获取数据并返回给客户端。

text
┌─────────────────────────────────────────────────────────────┐
│                    解析器工作流程                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  查询请求:                                                  │
│  query {                                                    │
│    user(id: "1") {                                          │
│      name                                                   │
│      posts {                                                │
│        title                                                │
│      }                                                      │
│    }                                                        │
│  }                                                          │
│                                                             │
│  解析过程:                                                  │
│  ┌─────────────┐                                            │
│  │ Query.user  │ ← 解析 user 字段                           │
│  └──────┬──────┘                                            │
│         │                                                   │
│         ▼                                                   │
│  ┌─────────────┐                                            │
│  │ User.name   │ ← 解析 name 字段                           │
│  └─────────────┘                                            │
│         │                                                   │
│         ▼                                                   │
│  ┌─────────────┐                                            │
│  │ User.posts  │ ← 解析 posts 字段                          │
│  └──────┬──────┘                                            │
│         │                                                   │
│         ▼                                                   │
│  ┌─────────────┐                                            │
│  │ Post.title  │ ← 解析 title 字段                          │
│  └─────────────┘                                            │
│                                                             │
└─────────────────────────────────────────────────────────────┘

解析器函数 #

基本结构 #

解析器函数接收四个参数:

javascript
const resolvers = {
  Query: {
    user: (parent, args, context, info) => {
      return {
        id: args.id,
        name: 'Alice',
        email: 'alice@example.com'
      };
    }
  }
};

参数说明 #

text
┌─────────────────────────────────────────────────────────────┐
│                    解析器参数                                │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  fieldName(parent, args, context, info)                     │
│                                                             │
│  parent:父字段的返回值                                      │
│  - 根查询时为 undefined                                      │
│  - 嵌套字段时为父对象                                        │
│                                                             │
│  args:字段参数                                              │
│  - 包含客户端传递的参数                                      │
│  - { id: "1", limit: 10 }                                   │
│                                                             │
│  context:共享上下文                                         │
│  - 所有解析器共享的对象                                      │
│  - 包含认证信息、数据源等                                    │
│                                                             │
│  info:查询信息                                              │
│  - 包含查询的 AST                                            │
│  - 高级用途时使用                                            │
│                                                             │
└─────────────────────────────────────────────────────────────┘

参数示例 #

javascript
const resolvers = {
  Query: {
    user: (parent, args, context, info) => {
      console.log('parent:', parent);
      console.log('args:', args);
      console.log('context:', context);
      console.log('info:', info);
      
      return getUserById(args.id);
    }
  },
  
  User: {
    posts: (parent, args, context, info) => {
      console.log('parent:', parent);
      return getPostsByUserId(parent.id, args.limit);
    }
  }
};

Query 解析器 #

基本查询 #

javascript
const resolvers = {
  Query: {
    user: (_, { id }) => {
      return getUserById(id);
    },
    
    users: (_, { limit, offset }) => {
      return getUsers(limit, offset);
    },
    
    search: (_, { query, type, limit }) => {
      return searchAll(query, type, limit);
    }
  }
};

带过滤的查询 #

javascript
const resolvers = {
  Query: {
    users: async (_, { filter, orderBy, first, after }, { dataSources }) => {
      const { filter: dbFilter, sort, skip, limit } = buildQueryOptions({
        filter,
        orderBy,
        first,
        after
      });
      
      const users = await dataSources.userAPI.findMany({
        filter: dbFilter,
        sort,
        skip,
        limit: limit + 1
      });
      
      return buildConnection(users, first);
    }
  }
};

分页查询 #

javascript
const resolvers = {
  Query: {
    users: async (_, { first, after, filter, orderBy }, { dataSources }) => {
      const cursor = after ? decodeCursor(after) : null;
      
      const users = await dataSources.userAPI.findMany({
        filter,
        orderBy,
        cursor,
        limit: first + 1
      });
      
      const hasNextPage = users.length > first;
      const edges = users.slice(0, first).map(user => ({
        node: user,
        cursor: encodeCursor(user.id)
      }));
      
      return {
        edges,
        pageInfo: {
          hasNextPage,
          hasPreviousPage: !!cursor,
          startCursor: edges[0]?.cursor,
          endCursor: edges[edges.length - 1]?.cursor
        },
        totalCount: dataSources.userAPI.count(filter)
      };
    }
  }
};

Mutation 解析器 #

创建操作 #

javascript
const resolvers = {
  Mutation: {
    createUser: async (_, { input }, { dataSources }) => {
      const existingUser = await dataSources.userAPI.findByEmail(input.email);
      
      if (existingUser) {
        return {
          user: null,
          success: false,
          message: 'Email already exists',
          errors: [{
            field: 'email',
            message: 'This email is already registered',
            code: 'DUPLICATE_EMAIL'
          }]
        };
      }
      
      const hashedPassword = await hashPassword(input.password);
      
      const user = await dataSources.userAPI.create({
        ...input,
        password: hashedPassword
      });
      
      return {
        user,
        success: true,
        message: 'User created successfully'
      };
    }
  }
};

更新操作 #

javascript
const resolvers = {
  Mutation: {
    updateUser: async (_, { id, input }, { dataSources, user }) => {
      if (!user) {
        throw new AuthenticationError('Not authenticated');
      }
      
      if (user.id !== id && user.role !== 'ADMIN') {
        throw new ForbiddenError('Not authorized');
      }
      
      const existingUser = await dataSources.userAPI.findById(id);
      
      if (!existingUser) {
        return {
          user: null,
          success: false,
          message: 'User not found'
        };
      }
      
      const updatedUser = await dataSources.userAPI.update(id, input);
      
      return {
        user: updatedUser,
        success: true,
        message: 'User updated successfully'
      };
    }
  }
};

删除操作 #

javascript
const resolvers = {
  Mutation: {
    deleteUser: async (_, { id }, { dataSources, user }) => {
      if (!user) {
        throw new AuthenticationError('Not authenticated');
      }
      
      if (user.id !== id && user.role !== 'ADMIN') {
        throw new ForbiddenError('Not authorized');
      }
      
      const deleted = await dataSources.userAPI.delete(id);
      
      return {
        success: deleted,
        message: deleted ? 'User deleted successfully' : 'User not found',
        deletedId: deleted ? id : null
      };
    }
  }
};

字段解析器 #

关联字段 #

javascript
const resolvers = {
  User: {
    posts: async (parent, { limit }, { dataSources }) => {
      return dataSources.postAPI.findByAuthorId(parent.id, limit);
    },
    
    comments: async (parent, { first, after }, { dataSources }) => {
      return dataSources.commentAPI.findByUserId(parent.id, first, after);
    },
    
    followers: async (parent, _, { dataSources }) => {
      return dataSources.userAPI.getFollowers(parent.id);
    }
  },
  
  Post: {
    author: async (parent, _, { dataSources }) => {
      return dataSources.userAPI.findById(parent.authorId);
    },
    
    comments: async (parent, { first, after }, { dataSources }) => {
      return dataSources.commentAPI.findByPostId(parent.id, first, after);
    },
    
    tags: (parent) => {
      return parent.tags || [];
    }
  }
};

计算字段 #

javascript
const resolvers = {
  User: {
    fullName: (parent) => {
      return `${parent.firstName} ${parent.lastName}`;
    },
    
    age: (parent) => {
      if (!parent.birthDate) return null;
      const today = new Date();
      const birthDate = new Date(parent.birthDate);
      let age = today.getFullYear() - birthDate.getFullYear();
      const monthDiff = today.getMonth() - birthDate.getMonth();
      if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
        age--;
      }
      return age;
    },
    
    isOnline: (parent) => {
      if (!parent.lastSeenAt) return false;
      const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
      return new Date(parent.lastSeenAt) > fiveMinutesAgo;
    }
  },
  
  Post: {
    excerpt: (parent, { length = 150 }) => {
      if (!parent.content) return '';
      return parent.content.length > length 
        ? parent.content.substring(0, length) + '...'
        : parent.content;
    },
    
    readingTime: (parent) => {
      const wordsPerMinute = 200;
      const wordCount = parent.content?.split(/\s+/).length || 0;
      return Math.ceil(wordCount / wordsPerMinute);
    }
  }
};

字段转换 #

javascript
const resolvers = {
  User: {
    avatar: (parent, { size = 'medium' }) => {
      if (!parent.avatarUrl) return null;
      const sizes = {
        small: 50,
        medium: 100,
        large: 200
      };
      return `${parent.avatarUrl}?size=${sizes[size]}`;
    },
    
    createdAt: (parent) => {
      return parent.createdAt?.toISOString();
    }
  }
};

上下文(Context) #

创建上下文 #

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

const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: ({ req }) => {
    const token = req.headers.authorization || '';
    const user = getUserFromToken(token);
    
    return {
      user,
      dataSources: {
        userAPI: new UserAPI(),
        postAPI: new PostAPI(),
        commentAPI: new CommentAPI()
      }
    };
  }
});

使用上下文 #

javascript
const resolvers = {
  Query: {
    me: (_, __, { user }) => {
      return user;
    },
    
    users: async (_, { filter }, { dataSources, user }) => {
      if (!user) {
        throw new AuthenticationError('Not authenticated');
      }
      
      return dataSources.userAPI.findMany(filter);
    }
  },
  
  Mutation: {
    createPost: async (_, { input }, { dataSources, user }) => {
      if (!user) {
        throw new AuthenticationError('Not authenticated');
      }
      
      return dataSources.postAPI.create({
        ...input,
        authorId: user.id
      });
    }
  }
};

数据源 #

javascript
const { RESTDataSource } = require('apollo-datasource-rest');

class UserAPI extends RESTDataSource {
  constructor() {
    super();
    this.baseURL = 'https://api.example.com/users/';
  }
  
  async findById(id) {
    return this.get(`/${id}`);
  }
  
  async findMany(filter) {
    return this.get('/', filter);
  }
  
  async create(user) {
    return this.post('/', user);
  }
  
  async update(id, user) {
    return this.put(`/${id}`, user);
  }
  
  async delete(id) {
    return this.delete(`/${id}`);
  }
}

错误处理 #

抛出错误 #

javascript
const { 
  AuthenticationError, 
  ForbiddenError, 
  UserInputError,
  ApolloError 
} = require('apollo-server');

const resolvers = {
  Query: {
    user: async (_, { id }, { user, dataSources }) => {
      if (!user) {
        throw new AuthenticationError('You must be logged in');
      }
      
      const targetUser = await dataSources.userAPI.findById(id);
      
      if (!targetUser) {
        throw new UserInputError('User not found', {
          invalidArgs: ['id']
        });
      }
      
      if (user.role !== 'ADMIN' && user.id !== id) {
        throw new ForbiddenError('Not authorized to view this user');
      }
      
      return targetUser;
    }
  }
};

自定义错误 #

javascript
class ValidationError extends ApolloError {
  constructor(message, field, code = 'VALIDATION_ERROR') {
    super(message, code, { field });
  }
}

class NotFoundError extends ApolloError {
  constructor(message, resource) {
    super(message, 'NOT_FOUND', { resource });
  }
}

const resolvers = {
  Mutation: {
    createUser: async (_, { input }, { dataSources }) => {
      if (!isValidEmail(input.email)) {
        throw new ValidationError('Invalid email format', 'email');
      }
      
      const existingUser = await dataSources.userAPI.findByEmail(input.email);
      
      if (existingUser) {
        throw new ValidationError('Email already exists', 'email', 'DUPLICATE_EMAIL');
      }
      
      return dataSources.userAPI.create(input);
    }
  }
};

错误格式化 #

javascript
const server = new ApolloServer({
  typeDefs,
  resolvers,
  formatError: (err) => {
    console.error(err);
    
    if (err.extensions?.code === 'INTERNAL_SERVER_ERROR') {
      return new ApolloError(
        'Internal server error',
        'INTERNAL_SERVER_ERROR'
      );
    }
    
    return err;
  }
});

性能优化 #

DataLoader #

解决 N+1 查询问题:

javascript
const DataLoader = require('dataloader');

const resolvers = {
  User: {
    posts: async (user, _, { loaders }) => {
      return loaders.postsByUserId.load(user.id);
    }
  }
};

const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: () => {
    return {
      loaders: {
        postsByUserId: new DataLoader(async (userIds) => {
          const posts = await getPostsByUserIds(userIds);
          return userIds.map(id => 
            posts.filter(post => post.authorId === id)
          );
        })
      }
    };
  }
});

批量加载 #

javascript
class PostLoader {
  constructor() {
    this.loader = new DataLoader(async (userIds) => {
      const posts = await Post.find({ authorId: { $in: userIds } });
      const postsByUserId = {};
      
      posts.forEach(post => {
        if (!postsByUserId[post.authorId]) {
          postsByUserId[post.authorId] = [];
        }
        postsByUserId[post.authorId].push(post);
      });
      
      return userIds.map(id => postsByUserId[id] || []);
    });
  }
  
  load(userId) {
    return this.loader.load(userId);
  }
  
  loadMany(userIds) {
    return this.loader.loadMany(userIds);
  }
}

缓存 #

javascript
const resolvers = {
  Query: {
    user: async (_, { id }, { dataSources, cache }) => {
      const cacheKey = `user:${id}`;
      const cached = await cache.get(cacheKey);
      
      if (cached) {
        return cached;
      }
      
      const user = await dataSources.userAPI.findById(id);
      await cache.set(cacheKey, user, 300);
      
      return user;
    }
  }
};

查询复杂度 #

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

const server = new ApolloServer({
  typeDefs,
  resolvers,
  validationRules: [
    createComplexityLimitRule(1000, {
      onCost: (cost) => console.log('Query cost:', cost),
      formatErrorMessage: (cost) => 
        `Query with cost ${cost} exceeds complexity limit`
    })
  ]
});

订阅解析器 #

基本订阅 #

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

const pubsub = new PubSub();

const resolvers = {
  Subscription: {
    onUserCreated: {
      subscribe: () => pubsub.asyncIterator(['USER_CREATED'])
    },
    
    onPostCreated: {
      subscribe: () => pubsub.asyncIterator(['POST_CREATED'])
    }
  },
  
  Mutation: {
    createUser: async (_, { input }, { dataSources }) => {
      const user = await dataSources.userAPI.create(input);
      pubsub.publish('USER_CREATED', { onUserCreated: user });
      return user;
    }
  }
};

带过滤的订阅 #

javascript
const resolvers = {
  Subscription: {
    onCommentAdded: {
      subscribe: (_, { postId }, { pubsub }) => {
        return pubsub.asyncIterator([`COMMENT_ADDED_${postId}`]);
      }
    }
  },
  
  Mutation: {
    createComment: async (_, { input }, { dataSources, pubsub }) => {
      const comment = await dataSources.commentAPI.create(input);
      pubsub.publish(`COMMENT_ADDED_${input.postId}`, {
        onCommentAdded: comment
      });
      return comment;
    }
  }
};

带认证的订阅 #

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

解析器最佳实践 #

1. 使用数据源 #

javascript
const resolvers = {
  Query: {
    users: async (_, { filter }, { dataSources }) => {
      return dataSources.userAPI.findMany(filter);
    }
  }
};

2. 使用 DataLoader #

javascript
const resolvers = {
  User: {
    posts: (user, _, { loaders }) => {
      return loaders.postsByUserId.load(user.id);
    }
  }
};

3. 错误处理 #

javascript
const resolvers = {
  Query: {
    user: async (_, { id }, { dataSources }) => {
      const user = await dataSources.userAPI.findById(id);
      
      if (!user) {
        throw new UserInputError('User not found');
      }
      
      return user;
    }
  }
};

4. 权限控制 #

javascript
const resolvers = {
  Mutation: {
    deleteUser: async (_, { id }, { user, dataSources }) => {
      if (!user) {
        throw new AuthenticationError('Not authenticated');
      }
      
      if (user.role !== 'ADMIN') {
        throw new ForbiddenError('Not authorized');
      }
      
      return dataSources.userAPI.delete(id);
    }
  }
};

5. 输入验证 #

javascript
const resolvers = {
  Mutation: {
    createUser: async (_, { input }, { dataSources }) => {
      if (!isValidEmail(input.email)) {
        throw new UserInputError('Invalid email', {
          invalidArgs: ['email']
        });
      }
      
      if (input.password.length < 8) {
        throw new UserInputError('Password too short', {
          invalidArgs: ['password']
        });
      }
      
      return dataSources.userAPI.create(input);
    }
  }
};

完整示例 #

javascript
const { 
  ApolloServer, 
  AuthenticationError, 
  ForbiddenError,
  UserInputError,
  PubSub 
} = require('apollo-server');
const DataLoader = require('dataloader');

const pubsub = new PubSub();

const resolvers = {
  Query: {
    me: (_, __, { user }) => user,
    
    user: async (_, { id }, { dataSources, user }) => {
      if (!user) {
        throw new AuthenticationError('Not authenticated');
      }
      
      const targetUser = await dataSources.userAPI.findById(id);
      
      if (!targetUser) {
        throw new UserInputError('User not found');
      }
      
      return targetUser;
    },
    
    users: async (_, { filter, orderBy, first, after }, { dataSources, user }) => {
      if (!user || user.role !== 'ADMIN') {
        throw new ForbiddenError('Not authorized');
      }
      
      return dataSources.userAPI.findMany({ filter, orderBy, first, after });
    },
    
    search: async (_, { query, type, limit }, { dataSources }) => {
      return dataSources.searchAPI.search(query, type, limit);
    }
  },
  
  Mutation: {
    createUser: async (_, { input }, { dataSources }) => {
      const existingUser = await dataSources.userAPI.findByEmail(input.email);
      
      if (existingUser) {
        throw new UserInputError('Email already exists', {
          invalidArgs: ['email']
        });
      }
      
      const user = await dataSources.userAPI.create(input);
      pubsub.publish('USER_CREATED', { onUserCreated: user });
      
      return {
        user,
        success: true,
        message: 'User created successfully'
      };
    },
    
    updateUser: async (_, { id, input }, { dataSources, user }) => {
      if (!user) {
        throw new AuthenticationError('Not authenticated');
      }
      
      if (user.id !== id && user.role !== 'ADMIN') {
        throw new ForbiddenError('Not authorized');
      }
      
      const updatedUser = await dataSources.userAPI.update(id, input);
      
      return {
        user: updatedUser,
        success: true,
        message: 'User updated successfully'
      };
    },
    
    deleteUser: async (_, { id }, { dataSources, user }) => {
      if (!user) {
        throw new AuthenticationError('Not authenticated');
      }
      
      if (user.id !== id && user.role !== 'ADMIN') {
        throw new ForbiddenError('Not authorized');
      }
      
      const deleted = await dataSources.userAPI.delete(id);
      
      return {
        success: deleted,
        message: deleted ? 'User deleted' : 'User not found',
        deletedId: deleted ? id : null
      };
    }
  },
  
  Subscription: {
    onUserCreated: {
      subscribe: () => pubsub.asyncIterator(['USER_CREATED'])
    },
    
    onUserUpdated: {
      subscribe: (_, { id }) => pubsub.asyncIterator([`USER_UPDATED_${id}`])
    }
  },
  
  User: {
    posts: async (user, { limit }, { loaders }) => {
      return loaders.postsByUserId.load(user.id);
    },
    
    fullName: (user) => `${user.firstName} ${user.lastName}`,
    
    age: (user) => {
      if (!user.birthDate) return null;
      return calculateAge(user.birthDate);
    }
  },
  
  Post: {
    author: async (post, _, { loaders }) => {
      return loaders.userById.load(post.authorId);
    },
    
    excerpt: (post, { length = 150 }) => {
      return post.content?.substring(0, length) || '';
    }
  }
};

const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: ({ req }) => {
    const token = req.headers.authorization || '';
    const user = getUserFromToken(token);
    
    return {
      user,
      pubsub,
      dataSources: {
        userAPI: new UserAPI(),
        postAPI: new PostAPI(),
        searchAPI: new SearchAPI()
      },
      loaders: {
        userById: new DataLoader(async (ids) => {
          const users = await User.find({ _id: { $in: ids } });
          return ids.map(id => users.find(u => u.id === id));
        }),
        postsByUserId: new DataLoader(async (userIds) => {
          const posts = await Post.find({ authorId: { $in: userIds } });
          return userIds.map(id => posts.filter(p => p.authorId === id));
        })
      }
    };
  },
  
  formatError: (err) => {
    console.error(err);
    return err;
  }
});

下一步 #

现在你已经掌握了解析器的实现方法,接下来学习 高级主题,了解认证授权、性能优化、安全防护等进阶内容!

最后更新:2026-03-29