当前位置: 首页 > news >正文

网站容量空间一般要多大四川网站建设外包服务

网站容量空间一般要多大,四川网站建设外包服务,北京seo公司助力网络营销,工厂找订单哪个平台最好目录 什么是GraphQLGraphQL核心概念GraphQL Schema定义语言查询(Queries)变更(Mutations)订阅(Subscriptions)Schema设计最佳实践服务端实现客户端使用高级特性性能优化实战项目 什么是GraphQL GraphQL是由Facebook开发的一种API查询语言和运行时。它为API提供了完整且易于理…

目录

  1. 什么是GraphQL
  2. GraphQL核心概念
  3. GraphQL Schema定义语言
  4. 查询(Queries)
  5. 变更(Mutations)
  6. 订阅(Subscriptions)
  7. Schema设计最佳实践
  8. 服务端实现
  9. 客户端使用
  10. 高级特性
  11. 性能优化
  12. 实战项目

什么是GraphQL

GraphQL是由Facebook开发的一种API查询语言和运行时。它为API提供了完整且易于理解的数据描述,客户端能够准确获得所需的数据,没有多余信息。

GraphQL vs REST API

特性REST APIGraphQL
数据获取多个端点,可能过度获取单一端点,精确获取
版本控制需要版本管理无需版本控制
缓存HTTP缓存查询级缓存
实时更新轮询或WebSocket内置订阅

GraphQL的优势

  • 精确数据获取: 客户端指定需要的字段,避免过度获取
  • 强类型系统: 完整的类型定义和验证
  • 单一端点: 所有操作通过一个URL处理
  • 内省功能: API自我描述,便于开发工具
  • 实时订阅: 内置实时数据推送功能

GraphQL核心概念

1. Schema(模式)

Schema定义了API的结构,包括可用的操作和数据类型。

type Query {user(id: ID!): Userposts: [Post!]!
}type User {id: ID!name: String!email: String!posts: [Post!]!
}type Post {id: ID!title: String!content: String!author: User!
}

2. Types(类型)

标量类型(Scalar Types)
  • Int: 32位有符号整数
  • Float: 双精度浮点数
  • String: UTF-8字符串
  • Boolean: true或false
  • ID: 唯一标识符
对象类型(Object Types)
type User {id: ID!name: String!email: Stringage: IntisActive: Boolean!
}
枚举类型(Enum Types)
enum Status {ACTIVEINACTIVEPENDING
}type User {id: ID!name: String!status: Status!
}
接口类型(Interface Types)
interface Node {id: ID!
}type User implements Node {id: ID!name: String!email: String!
}type Post implements Node {id: ID!title: String!content: String!
}
联合类型(Union Types)
union SearchResult = User | Post | Commenttype Query {search(query: String!): [SearchResult!]!
}

3. 字段参数和修饰符

type Query {# 必需参数user(id: ID!): User# 可选参数users(limit: Int, offset: Int): [User!]!# 默认值posts(status: Status = ACTIVE): [Post!]!
}type User {# 必需字段id: ID!name: String!# 可选字段email: String# 数组字段posts: [Post!]! # 非空数组,包含非空Post对象tags: [String] # 可为空的数组,包含可为空的字符串
}

GraphQL Schema定义语言

完整Schema示例

# 标量类型定义
scalar Date
scalar Upload# 枚举定义
enum UserRole {USERADMINMODERATOR
}enum PostStatus {DRAFTPUBLISHEDARCHIVED
}# 输入类型
input CreateUserInput {name: String!email: String!password: String!role: UserRole = USER
}input UpdatePostInput {title: Stringcontent: Stringstatus: PostStatustags: [String!]
}# 接口定义
interface Node {id: ID!createdAt: Date!updatedAt: Date!
}# 对象类型
type User implements Node {id: ID!createdAt: Date!updatedAt: Date!name: String!email: String!role: UserRole!posts: [Post!]!profile: UserProfile
}type UserProfile {bio: Stringavatar: Stringwebsite: Stringlocation: String
}type Post implements Node {id: ID!createdAt: Date!updatedAt: Date!title: String!content: String!status: PostStatus!author: User!tags: [String!]!comments: [Comment!]!likesCount: Int!
}type Comment implements Node {id: ID!createdAt: Date!updatedAt: Date!content: String!author: User!post: Post!replies: [Comment!]!parentComment: Comment
}# 查询根类型
type Query {# 用户查询me: Useruser(id: ID!): Userusers(first: Int = 10after: Stringrole: UserRolesearch: String): UserConnection!# 文章查询post(id: ID!): Postposts(first: Int = 10after: Stringstatus: PostStatusauthorId: IDtags: [String!]): PostConnection!# 搜索search(query: String!, type: SearchType): SearchResult!
}# 变更根类型
type Mutation {# 用户操作createUser(input: CreateUserInput!): CreateUserPayload!updateUser(id: ID!, input: UpdateUserInput!): UpdateUserPayload!deleteUser(id: ID!): DeleteUserPayload!# 文章操作createPost(input: CreatePostInput!): CreatePostPayload!updatePost(id: ID!, input: UpdatePostInput!): UpdatePostPayload!deletePost(id: ID!): DeletePostPayload!publishPost(id: ID!): PublishPostPayload!# 评论操作createComment(input: CreateCommentInput!): CreateCommentPayload!updateComment(id: ID!, input: UpdateCommentInput!): UpdateCommentPayload!deleteComment(id: ID!): DeleteCommentPayload!
}# 订阅根类型
type Subscription {postAdded: Post!postUpdated(id: ID!): Post!commentAdded(postId: ID!): Comment!userOnlineStatus(userId: ID!): UserOnlineStatus!
}# 连接类型(分页)
type UserConnection {edges: [UserEdge!]!pageInfo: PageInfo!totalCount: Int!
}type UserEdge {node: User!cursor: String!
}type PostConnection {edges: [PostEdge!]!pageInfo: PageInfo!totalCount: Int!
}type PostEdge {node: Post!cursor: String!
}type PageInfo {hasNextPage: Boolean!hasPreviousPage: Boolean!startCursor: StringendCursor: String
}# 搜索结果
union SearchResult = User | Post | Commentenum SearchType {ALLUSERSPOSTSCOMMENTS
}# 变更结果类型
interface MutationPayload {success: Boolean!errors: [Error!]!
}type CreateUserPayload implements MutationPayload {success: Boolean!errors: [Error!]!user: User
}type Error {message: String!field: Stringcode: String
}

查询(Queries)

1. 基础查询

# 简单字段查询
query {me {idnameemail}
}# 带参数的查询
query {user(id: "123") {idnameemailposts {idtitlecreatedAt}}
}# 嵌套查询
query {posts {idtitleauthor {idnameprofile {bioavatar}}comments {idcontentauthor {name}}}
}

2. 查询变量

# 定义查询变量
query GetUser($userId: ID!, $includeInactive: Boolean = false) {user(id: $userId) {idnameemailposts(includeInactive: $includeInactive) {idtitlestatus}}
}# 变量值
{"userId": "123","includeInactive": true
}

3. 字段别名

query {user(id: "123") {idfullName: nameemailAddress: emailpublishedPosts: posts(status: PUBLISHED) {idtitle}draftPosts: posts(status: DRAFT) {idtitle}}
}

4. 片段(Fragments)

# 定义片段
fragment UserInfo on User {idnameemailprofile {bioavatar}
}fragment PostInfo on Post {idtitlecontentcreatedAtlikesCount
}# 使用片段
query {me {...UserInfoposts {...PostInfoauthor {...UserInfo}}}
}# 内联片段
query {search(query: "GraphQL") {... on User {idnameemail}... on Post {idtitlecontent}... on Comment {idcontentpost {title}}}
}

5. 指令(Directives)

query GetUser($userId: ID!, $includeProfile: Boolean!, $includePosts: Boolean!) {user(id: $userId) {idnameemailprofile @include(if: $includeProfile) {bioavatar}posts @skip(if: $includePosts) {idtitle}}
}

6. 分页查询

# 游标分页
query GetPosts($first: Int!, $after: String) {posts(first: $first, after: $after) {edges {node {idtitlecontentauthor {name}}cursor}pageInfo {hasNextPagehasPreviousPagestartCursorendCursor}totalCount}
}# 偏移分页
query GetUsers($limit: Int!, $offset: Int!) {users(limit: $limit, offset: $offset) {idnameemail}usersCount
}

变更(Mutations)

1. 基础变更

# 创建用户
mutation CreateUser($input: CreateUserInput!) {createUser(input: $input) {successerrors {messagefield}user {idnameemailcreatedAt}}
}# 变量
{"input": {"name": "张三","email": "zhangsan@example.com","password": "password123"}
}

2. 更新操作

mutation UpdatePost($id: ID!, $input: UpdatePostInput!) {updatePost(id: $id, input: $input) {successerrors {messagefield}post {idtitlecontentstatusupdatedAt}}
}# 变量
{"id": "post123","input": {"title": "新标题","content": "更新的内容","status": "PUBLISHED"}
}

3. 删除操作

mutation DeletePost($id: ID!) {deletePost(id: $id) {successerrors {message}deletedPostId: id}
}

4. 批量操作

mutation BatchUpdatePosts($updates: [PostUpdateInput!]!) {updatePosts(updates: $updates) {successerrors {messageindex}posts {idtitlestatus}}
}

5. 文件上传

mutation UploadAvatar($file: Upload!, $userId: ID!) {uploadAvatar(file: $file, userId: $userId) {successerrors {message}user {idprofile {avatar}}}
}

订阅(Subscriptions)

1. 基础订阅

subscription PostAdded {postAdded {idtitlecontentauthor {name}createdAt}
}subscription CommentAdded($postId: ID!) {commentAdded(postId: $postId) {idcontentauthor {nameavatar}createdAt}
}

2. 用户状态订阅

subscription UserOnlineStatus($userId: ID!) {userOnlineStatus(userId: $userId) {userIdisOnlinelastSeen}
}

3. 实时通知

subscription Notifications {notificationAdded {idtypemessagereadcreatedAtuser {id}}
}

Schema设计最佳实践

1. 命名规范

# 类型名使用PascalCase
type User {# 字段名使用camelCasefirstName: String!lastName: String!emailAddress: String!
}# 枚举值使用UPPER_SNAKE_CASE
enum UserStatus {ACTIVEINACTIVEPENDING_VERIFICATION
}

2. 输入输出分离

# 输入类型
input CreateUserInput {name: String!email: String!password: String!
}input UpdateUserInput {name: Stringemail: String
}# 输出类型
type User {id: ID!name: String!email: String!createdAt: Date!updatedAt: Date!
}

3. 错误处理设计

type MutationResult {success: Boolean!errors: [Error!]!
}type Error {message: String!code: String!field: String
}type CreateUserPayload {success: Boolean!errors: [Error!]!user: User
}

4. 分页设计

# Relay风格的游标分页
type UserConnection {edges: [UserEdge!]!pageInfo: PageInfo!totalCount: Int!
}type UserEdge {node: User!cursor: String!
}type PageInfo {hasNextPage: Boolean!hasPreviousPage: Boolean!startCursor: StringendCursor: String
}

服务端实现

1. Node.js + GraphQL (Apollo Server)

const { ApolloServer, gql } = require('apollo-server-express');
const express = require('express');// Schema定义
const typeDefs = gql`type User {id: ID!name: String!email: String!posts: [Post!]!}type Post {id: ID!title: String!content: String!author: User!}type Query {users: [User!]!user(id: ID!): Userposts: [Post!]!post(id: ID!): Post}type Mutation {createUser(name: String!, email: String!): User!createPost(title: String!, content: String!, authorId: ID!): Post!}type Subscription {postAdded: Post!}
`;// 模拟数据
const users = [{ id: '1', name: '张三', email: 'zhangsan@example.com' },{ id: '2', name: '李四', email: 'lisi@example.com' }
];const posts = [{ id: '1', title: 'GraphQL入门', content: '学习GraphQL...', authorId: '1' },{ id: '2', title: 'Apollo Server', content: '使用Apollo Server...', authorId: '2' }
];// 解析器
const resolvers = {Query: {users: () => users,user: (parent, args) => users.find(user => user.id === args.id),posts: () => posts,post: (parent, args) => posts.find(post => post.id === args.id),},Mutation: {createUser: (parent, args) => {const user = {id: String(users.length + 1),name: args.name,email: args.email};users.push(user);return user;},createPost: (parent, args, context) => {const post = {id: String(posts.length + 1),title: args.title,content: args.content,authorId: args.authorId};posts.push(post);// 发布订阅事件context.pubsub.publish('POST_ADDED', { postAdded: post });return post;}},Subscription: {postAdded: {subscribe: (parent, args, context) => context.pubsub.asyncIterator(['POST_ADDED'])}},User: {posts: (parent) => posts.filter(post => post.authorId === parent.id)},Post: {author: (parent) => users.find(user => user.id === parent.authorId)}
};// 创建服务器
async function startServer() {const app = express();const server = new ApolloServer({typeDefs,resolvers,context: ({ req }) => ({// 添加认证、数据库连接等user: req.user,pubsub: new PubSub()})});await server.start();server.applyMiddleware({ app });const PORT = 4000;app.listen(PORT, () => {console.log(`Server running at http://localhost:${PORT}${server.graphqlPath}`);});
}startServer();

2. 数据库集成 (Prisma)

// schema.prisma
generator client {provider = "prisma-client-js"
}datasource db {provider = "postgresql"url      = env("DATABASE_URL")
}model User {id        String   @id @default(cuid())email     String   @uniquename      Stringposts     Post[]createdAt DateTime @default(now())updatedAt DateTime @updatedAt
}model Post {id        String   @id @default(cuid())title     Stringcontent   String?published Boolean  @default(false)author    User     @relation(fields: [authorId], references: [id])authorId  StringcreatedAt DateTime @default(now())updatedAt DateTime @updatedAt
}
// 使用Prisma的解析器
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();const resolvers = {Query: {users: async () => {return await prisma.user.findMany({include: { posts: true }});},user: async (parent, args) => {return await prisma.user.findUnique({where: { id: args.id },include: { posts: true }});}},Mutation: {createUser: async (parent, args) => {return await prisma.user.create({data: {name: args.name,email: args.email}});},createPost: async (parent, args) => {return await prisma.post.create({data: {title: args.title,content: args.content,author: {connect: { id: args.authorId }}},include: { author: true }});}}
};

3. 认证和授权

const jwt = require('jsonwebtoken');// 中间件
const authMiddleware = (req) => {const token = req.headers.authorization?.replace('Bearer ', '');if (token) {try {const user = jwt.verify(token, process.env.JWT_SECRET);return { user };} catch (error) {throw new AuthenticationError('Invalid token');}}return {};
};// 带权限检查的解析器
const resolvers = {Query: {me: async (parent, args, context) => {if (!context.user) {throw new ForbiddenError('Authentication required');}return await prisma.user.findUnique({where: { id: context.user.id }});}},Mutation: {createPost: async (parent, args, context) => {if (!context.user) {throw new ForbiddenError('Authentication required');}return await prisma.post.create({data: {title: args.title,content: args.content,author: {connect: { id: context.user.id }}}});}}
};

客户端使用

1. Apollo Client (React)

// 安装依赖
// npm install @apollo/client graphqlimport { ApolloClient, InMemoryCache, ApolloProvider, gql, useQuery, useMutation } from '@apollo/client';// 创建客户端
const client = new ApolloClient({uri: 'http://localhost:4000/graphql',cache: new InMemoryCache(),headers: {authorization: localStorage.getItem('token') ? `Bearer ${localStorage.getItem('token')}` : ''}
});// App组件
function App() {return (<ApolloProvider client={client}><div className="App"><UserList /><CreateUser /></div></ApolloProvider>);
}// 查询组件
const GET_USERS = gql`query GetUsers {users {idnameemailposts {idtitle}}}
`;function UserList() {const { loading, error, data, refetch } = useQuery(GET_USERS);if (loading) return <p>Loading...</p>;if (error) return <p>Error: {error.message}</p>;return (<div><h2>用户列表</h2><button onClick={() => refetch()}>刷新</button>{data.users.map(user => (<div key={user.id}><h3>{user.name}</h3><p>{user.email}</p><p>文章数量: {user.posts.length}</p></div>))}</div>);
}// 变更组件
const CREATE_USER = gql`mutation CreateUser($name: String!, $email: String!) {createUser(name: $name, email: $email) {idnameemail}}
`;function CreateUser() {const [name, setName] = useState('');const [email, setEmail] = useState('');const [createUser, { loading, error }] = useMutation(CREATE_USER, {// 更新缓存update(cache, { data: { createUser } }) {const { users } = cache.readQuery({ query: GET_USERS });cache.writeQuery({query: GET_USERS,data: {users: [...users, createUser]}});}});const handleSubmit = async (e) => {e.preventDefault();try {await createUser({variables: { name, email }});setName('');setEmail('');} catch (err) {console.error(err);}};return (<form onSubmit={handleSubmit}><h2>创建用户</h2><inputtype="text"placeholder="姓名"value={name}onChange={(e) => setName(e.target.value)}required/><inputtype="email"placeholder="邮箱"value={email}onChange={(e) => setEmail(e.target.value)}required/><button type="submit" disabled={loading}>{loading ? '创建中...' : '创建用户'}</button>{error && <p>错误: {error.message}</p>}</form>);
}

2. 订阅使用

import { useSubscription } from '@apollo/client';const POST_ADDED_SUBSCRIPTION = gql`subscription PostAdded {postAdded {idtitlecontentauthor {name}createdAt}}
`;function PostSubscription() {const { data, loading, error } = useSubscription(POST_ADDED_SUBSCRIPTION);if (loading) return <p>等待新文章...</p>;if (error) return <p>订阅错误: {error.message}</p>;return (<div><h3>新文章通知</h3>{data && (<div><h4>{data.postAdded.title}</h4><p>作者: {data.postAdded.author.name}</p><p>{data.postAdded.content}</p></div>)}</div>);
}

3. 缓存管理

// 乐观更新
const [likePost] = useMutation(LIKE_POST, {optimisticResponse: {likePost: {id: postId,likesCount: post.likesCount + 1,isLiked: true,__typename: 'Post'}},update(cache, { data: { likePost } }) {cache.modify({id: cache.identify(likePost),fields: {likesCount: (existing) => likePost.likesCount,isLiked: () => likePost.isLiked}});}
});// 缓存策略
const { loading, error, data } = useQuery(GET_POSTS, {fetchPolicy: 'cache-first', // cache-first, cache-only, network-only, cache-and-networkpollInterval: 5000, // 每5秒轮询一次notifyOnNetworkStatusChange: true
});

高级特性

1. 自定义标量类型

// 服务端定义
const { GraphQLScalarType, GraphQLError } = require('graphql');
const { Kind } = require('graphql/language');const DateType = new GraphQLScalarType({name: 'Date',description: 'Date custom scalar type',serialize(value) {return value.toISOString(); // 发送给客户端},parseValue(value) {return new Date(value); // 来自客户端变量},parseLiteral(ast) {if (ast.kind === Kind.STRING) {return new Date(ast.value); // 来自查询字面量}throw new GraphQLError(`Can only parse strings to dates but got a: ${ast.kind}`);}
});const resolvers = {Date: DateType,// ... 其他解析器
};

2. DataLoader(批量加载和缓存)

const DataLoader = require('dataloader');// 创建DataLoader
const userLoader = new DataLoader(async (userIds) => {const users = await prisma.user.findMany({where: { id: { in: userIds } }});// 保持顺序return userIds.map(id => users.find(user => user.id === id));
});const postLoader = new DataLoader(async (userIds) => {const posts = await prisma.post.findMany({where: { authorId: { in: userIds } }});return userIds.map(userId => posts.filter(post => post.authorId === userId));
});// 在解析器中使用
const resolvers = {Post: {author: async (parent, args, context) => {return context.userLoader.load(parent.authorId);}},User: {posts: async (parent, args, context) => {return context.postLoader.load(parent.id);}}
};// 在context中提供DataLoader实例
const server = new ApolloServer({typeDefs,resolvers,context: () => ({userLoader: new DataLoader(batchUsers),postLoader: new DataLoader(batchPosts)})
});

3. 字段级权限控制

const { shield, rule, and, or } = require('graphql-shield');// 定义规则
const isAuthenticated = rule({ cache: 'contextual' })(async (parent, args, context) => {return context.user !== null;}
);const isOwner = rule({ cache: 'strict' })(async (parent, args, context) => {return context.user && parent.authorId === context.user.id;}
);const isAdmin = rule({ cache: 'contextual' })(async (parent, args, context) => {return context.user && context.user.role === 'ADMIN';}
);// 创建权限盾
const permissions = shield({Query: {me: isAuthenticated,users: isAdmin,},Mutation: {createPost: isAuthenticated,updatePost: and(isAuthenticated, or(isOwner, isAdmin)),deletePost: and(isAuthenticated, or(isOwner, isAdmin)),},Post: {author: isAuthenticated,}
});// 应用到服务器
const server = new ApolloServer({typeDefs,resolvers,middlewares: [permissions]
});

4. 查询复杂度分析

const depthLimit = require('graphql-depth-limit');
const costAnalysis = require('graphql-query-complexity');const server = new ApolloServer({typeDefs,resolvers,validationRules: [depthLimit(10), // 限制查询深度costAnalysis({maximumCost: 1000,defaultCost: 1,scalarCost: 1,objectCost: 1,listFactor: 10,introspectionCost: 1000,complexityScalarMap: {DateTime: 2,Upload: 1000,},fieldConfigMap: {User: {posts: { complexity: ({ args, childComplexity }) => {return childComplexity * (args.first || 10);}}}}})]
});

5. 缓存策略

const responseCachePlugin = require('apollo-server-plugin-response-cache');// 响应缓存插件
const server = new ApolloServer({typeDefs,resolvers,plugins: [responseCachePlugin({sessionId: (requestContext) => {return requestContext.request.http.headers.get('session-id') || null;},})],cacheControl: {defaultMaxAge: 60, // 默认缓存60秒}
});// 在Schema中设置缓存提示
const typeDefs = gql`type Query {posts: [Post!]! @cacheControl(maxAge: 300) # 缓存5分钟user(id: ID!): User @cacheControl(maxAge: 60, scope: PRIVATE)}type Post @cacheControl(maxAge: 300) {id: ID!title: String!author: User! @cacheControl(maxAge: 60)}
`;

性能优化

1. N+1 查询问题解决

// 错误的方式 - 导致N+1查询
const resolvers = {Post: {author: async (parent) => {return await prisma.user.findUnique({where: { id: parent.authorId }});}}
};// 正确的方式 - 使用DataLoader
const resolvers = {Post: {author: async (parent, args, context) => {return context.userLoader.load(parent.authorId);}}
};// 或者在查询时包含关联数据
const resolvers = {Query: {posts: async () => {return await prisma.post.findMany({include: { author: true } // 一次性获取作者信息});}},Post: {author: (parent) => parent.author // 直接返回已加载的数据}
};

2. 分页优化

// 游标分页实现
const resolvers = {Query: {posts: async (parent, args) => {const { first = 10, after } = args;const posts = await prisma.post.findMany({take: first + 1, // 多取一个判断是否还有更多cursor: after ? { id: after } : undefined,skip: after ? 1 : 0,orderBy: { createdAt: 'desc' }});const hasNextPage = posts.length > first;const edges = posts.slice(0, first).map(post => ({node: post,cursor: post.id}));return {edges,pageInfo: {hasNextPage,startCursor: edges[0]?.cursor,endCursor: edges[edges.length - 1]?.cursor}};}}
};

3. 字段级缓存

const Redis = require('redis');
const redis = Redis.createClient();const resolvers = {User: {postsCount: async (parent, args, context) => {const cacheKey = `user:${parent.id}:postsCount`;// 尝试从缓存获取let count = await redis.get(cacheKey);if (count === null) {// 缓存未命中,从数据库查询count = await prisma.post.count({where: { authorId: parent.id }});// 缓存结果,5分钟过期await redis.setEx(cacheKey, 300, count.toString());}return parseInt(count);}}
};

4. 查询优化

// 使用投影减少数据传输
const resolvers = {Query: {users: async (parent, args, info) => {// 分析查询字段const selectedFields = getFieldsFromInfo(info);const select = {};if (selectedFields.includes('name')) select.name = true;if (selectedFields.includes('email')) select.email = true;if (selectedFields.includes('posts')) {select.posts = {select: {id: true,title: true}};}return await prisma.user.findMany({ select });}}
};// 辅助函数
function getFieldsFromInfo(info) {return info.fieldNodes[0].selectionSet.selections.map(selection => selection.name.value);
}

实战项目

博客系统完整实现

1. Schema设计
# 完整的博客系统Schema
type User {id: ID!username: String! @uniqueemail: String! @uniquedisplayName: String!bio: Stringavatar: Stringrole: UserRole!posts: [Post!]!comments: [Comment!]!followers: [User!]!following: [User!]!followersCount: Int!followingCount: Int!postsCount: Int!createdAt: DateTime!updatedAt: DateTime!
}type Post {id: ID!title: String!slug: String! @uniquecontent: String!excerpt: StringfeaturedImage: Stringstatus: PostStatus!viewsCount: Int!likesCount: Int!commentsCount: Int!author: User!tags: [Tag!]!categories: [Category!]!comments: [Comment!]!likes: [Like!]!isLiked: Boolean!readingTime: Int!createdAt: DateTime!updatedAt: DateTime!publishedAt: DateTime
}type Comment {id: ID!content: String!author: User!post: Post!parent: Commentreplies: [Comment!]!repliesCount: Int!likesCount: Int!isLiked: Boolean!createdAt: DateTime!updatedAt: DateTime!
}type Tag {id: ID!name: String! @uniqueslug: String! @uniquedescription: StringpostsCount: Int!posts: [Post!]!
}type Category {id: ID!name: String! @uniqueslug: String! @uniquedescription: Stringparent: Categorychildren: [Category!]!postsCount: Int!posts: [Post!]!
}enum UserRole {USERMODERATORADMIN
}enum PostStatus {DRAFTPUBLISHEDARCHIVED
}type Query {# 用户查询me: Useruser(username: String!): Userusers(first: Int = 20after: Stringsearch: Stringrole: UserRole): UserConnection!# 文章查询post(slug: String!): Postposts(first: Int = 20after: Stringstatus: PostStatus = PUBLISHEDauthorId: IDtagIds: [ID!]categoryIds: [ID!]search: StringsortBy: PostSortInput): PostConnection!popularPosts(limit: Int = 10): [Post!]!recentPosts(limit: Int = 10): [Post!]!# 标签和分类tags: [Tag!]!tag(slug: String!): Tagcategories: [Category!]!category(slug: String!): Category# 搜索search(query: String!, type: SearchType = ALL): SearchResult!# 统计stats: BlogStats!
}type Mutation {# 认证login(email: String!, password: String!): AuthPayload!register(input: RegisterInput!): AuthPayload!logout: Boolean!refreshToken: AuthPayload!# 用户操作updateProfile(input: UpdateProfileInput!): User!uploadAvatar(file: Upload!): User!followUser(userId: ID!): FollowPayload!unfollowUser(userId: ID!): FollowPayload!# 文章操作createPost(input: CreatePostInput!): Post!updatePost(id: ID!, input: UpdatePostInput!): Post!deletePost(id: ID!): Boolean!publishPost(id: ID!): Post!unpublishPost(id: ID!): Post!likePost(postId: ID!): LikePayload!unlikePost(postId: ID!): LikePayload!# 评论操作createComment(input: CreateCommentInput!): Comment!updateComment(id: ID!, input: UpdateCommentInput!): Comment!deleteComment(id: ID!): Boolean!likeComment(commentId: ID!): LikePayload!unlikeComment(commentId: ID!): LikePayload!# 标签和分类createTag(input: CreateTagInput!): Tag!updateTag(id: ID!, input: UpdateTagInput!): Tag!deleteTag(id: ID!): Boolean!createCategory(input: CreateCategoryInput!): Category!updateCategory(id: ID!, input: UpdateCategoryInput!): Category!deleteCategory(id: ID!): Boolean!
}type Subscription {# 实时通知postPublished: Post!commentAdded(postId: ID!): Comment!userFollowed(userId: ID!): FollowNotification!postLiked(postId: ID!): LikeNotification!# 在线状态userOnline(userId: ID!): UserOnlineStatus!
}
2. 服务端完整实现
const { ApolloServer } = require('apollo-server-express');
const { PrismaClient } = require('@prisma/client');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const { createWriteStream } = require('fs');
const { finished } = require('stream/promises');
const path = require('path');const prisma = new PrismaClient();// 认证中间件
const getUser = async (token) => {try {if (token) {const decoded = jwt.verify(token, process.env.JWT_SECRET);const user = await prisma.user.findUnique({where: { id: decoded.userId }});return user;}return null;} catch (error) {return null;}
};// 解析器实现
const resolvers = {Query: {me: async (parent, args, context) => {if (!context.user) {throw new AuthenticationError('Not authenticated');}return context.user;},posts: async (parent, args) => {const {first = 20,after,status = 'PUBLISHED',authorId,tagIds,categoryIds,search,sortBy} = args;const where = { status };if (authorId) where.authorId = authorId;if (search) {where.OR = [{ title: { contains: search, mode: 'insensitive' } },{ content: { contains: search, mode: 'insensitive' } }];}if (tagIds?.length) {where.tags = { some: { id: { in: tagIds } } };}if (categoryIds?.length) {where.categories = { some: { id: { in: categoryIds } } };}const orderBy = sortBy ? [sortBy] : [{ createdAt: 'desc' }];const posts = await prisma.post.findMany({where,orderBy,take: first + 1,cursor: after ? { id: after } : undefined,skip: after ? 1 : 0,include: {author: true,tags: true,categories: true,_count: {select: {comments: true,likes: true}}}});const hasNextPage = posts.length > first;const edges = posts.slice(0, first).map(post => ({node: post,cursor: post.id}));return {edges,pageInfo: {hasNextPage,startCursor: edges[0]?.cursor,endCursor: edges[edges.length - 1]?.cursor}};},popularPosts: async (parent, args) => {return await prisma.post.findMany({where: { status: 'PUBLISHED' },orderBy: [{ likesCount: 'desc' },{ viewsCount: 'desc' }],take: args.limit || 10,include: {author: true,tags: true}});}},Mutation: {register: async (parent, args) => {const { username, email, password, displayName } = args.input;// 检查用户是否已存在const existingUser = await prisma.user.findFirst({where: {OR: [{ email }, { username }]}});if (existingUser) {throw new UserInputError('User already exists');}// 加密密码const hashedPassword = await bcrypt.hash(password, 12);// 创建用户const user = await prisma.user.create({data: {username,email,password: hashedPassword,displayName,role: 'USER'}});// 生成JWTconst token = jwt.sign({ userId: user.id },process.env.JWT_SECRET,{ expiresIn: '7d' });return {token,user};},login: async (parent, args) => {const { email, password } = args;const user = await prisma.user.findUnique({where: { email }});if (!user) {throw new AuthenticationError('Invalid credentials');}const valid = await bcrypt.compare(password, user.password);if (!valid) {throw new AuthenticationError('Invalid credentials');}const token = jwt.sign({ userId: user.id },process.env.JWT_SECRET,{ expiresIn: '7d' });return {token,user};},createPost: async (parent, args, context) => {if (!context.user) {throw new AuthenticationError('Not authenticated');}const { title, content, excerpt, tagIds, categoryIds } = args.input;// 生成slugconst slug = title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');const post = await prisma.post.create({data: {title,slug,content,excerpt,author: { connect: { id: context.user.id } },tags: tagIds ? { connect: tagIds.map(id => ({ id })) } : undefined,categories: categoryIds ? { connect: categoryIds.map(id => ({ id })) } : undefined,status: 'DRAFT'},include: {author: true,tags: true,categories: true}});return post;},publishPost: async (parent, args, context) => {if (!context.user) {throw new AuthenticationError('Not authenticated');}const post = await prisma.post.findUnique({where: { id: args.id },include: { author: true }});if (!post) {throw new UserInputError('Post not found');}if (post.author.id !== context.user.id && context.user.role !== 'ADMIN') {throw new ForbiddenError('Not authorized');}const updatedPost = await prisma.post.update({where: { id: args.id },data: {status: 'PUBLISHED',publishedAt: new Date()},include: {author: true,tags: true,categories: true}});// 发布订阅事件context.pubsub.publish('POST_PUBLISHED', { postPublished: updatedPost });return updatedPost;},likePost: async (parent, args, context) => {if (!context.user) {throw new AuthenticationError('Not authenticated');}const existingLike = await prisma.like.findFirst({where: {userId: context.user.id,postId: args.postId}});if (existingLike) {throw new UserInputError('Already liked');}await prisma.$transaction([prisma.like.create({data: {userId: context.user.id,postId: args.postId}}),prisma.post.update({where: { id: args.postId },data: {likesCount: { increment: 1 }}})]);return {success: true,post: await prisma.post.findUnique({where: { id: args.postId },include: { author: true }})};}},// 字段解析器Post: {isLiked: async (parent, args, context) => {if (!context.user) return false;const like = await prisma.like.findFirst({where: {userId: context.user.id,postId: parent.id}});return !!like;},readingTime: (parent) => {const wordsPerMinute = 200;const wordCount = parent.content.split(/\s+/).length;return Math.ceil(wordCount / wordsPerMinute);},commentsCount: (parent) => {return parent._count?.comments || 0;},likesCount: (parent) => {return parent._count?.likes || parent.likesCount || 0;}},User: {postsCount: async (parent) => {return await prisma.post.count({where: {authorId: parent.id,status: 'PUBLISHED'}});},followersCount: async (parent) => {return await prisma.follow.count({where: { followingId: parent.id }});},followingCount: async (parent) => {return await prisma.follow.count({where: { followerId: parent.id }});}}
};// 创建服务器
const server = new ApolloServer({typeDefs,resolvers,context: async ({ req }) => {const token = req.headers.authorization?.replace('Bearer ', '');const user = await getUser(token);return {user,prisma,pubsub: new PubSub()};}
});
3. 客户端实现 (React)
// hooks/useAuth.js
import { useState, useEffect, createContext, useContext } from 'react';
import { useApolloClient } from '@apollo/client';const AuthContext = createContext();export const useAuth = () => useContext(AuthContext);export const AuthProvider = ({ children }) => {const [user, setUser] = useState(null);const [token, setToken] = useState(localStorage.getItem('token'));const client = useApolloClient();useEffect(() => {if (token) {// 验证token并获取用户信息fetchUser();}}, [token]);const login = async (email, password) => {const { data } = await client.mutate({mutation: LOGIN_MUTATION,variables: { email, password }});const { token: newToken, user: newUser } = data.login;setToken(newToken);setUser(newUser);localStorage.setItem('token', newToken);};const logout = () => {setToken(null);setUser(null);localStorage.removeItem('token');client.resetStore();};return (<AuthContext.Provider value={{ user, token, login, logout }}>{children}</AuthContext.Provider>);
};// components/PostList.js
import { useQuery } from '@apollo/client';
import { GET_POSTS } from '../graphql/queries';export const PostList = () => {const { data, loading, error, fetchMore } = useQuery(GET_POSTS, {variables: { first: 10 }});const loadMore = () => {fetchMore({variables: {after: data.posts.pageInfo.endCursor}});};if (loading) return <div>Loading...</div>;if (error) return <div>Error: {error.message}</div>;return (<div>{data.posts.edges.map(({ node: post }) => (<article key={post.id}><h2>{post.title}</h2><p>{post.excerpt}</p><div><span>By {post.author.displayName}</span><span>{post.likesCount} likes</span><span>{post.commentsCount} comments</span></div></article>))}{data.posts.pageInfo.hasNextPage && (<button onClick={loadMore}>Load More</button>)}</div>);
};// components/CreatePost.js
import { useState } from 'react';
import { useMutation } from '@apollo/client';
import { CREATE_POST_MUTATION } from '../graphql/mutations';export const CreatePost = () => {const [formData, setFormData] = useState({title: '',content: '',excerpt: ''});const [createPost, { loading, error }] = useMutation(CREATE_POST_MUTATION, {onCompleted: (data) => {console.log('Post created:', data.createPost);// 重定向到文章页面}});const handleSubmit = async (e) => {e.preventDefault();await createPost({variables: { input: formData }});};return (<form onSubmit={handleSubmit}><inputtype="text"placeholder="Title"value={formData.title}onChange={(e) => setFormData({ ...formData, title: e.target.value })}required/><textareaplaceholder="Excerpt"value={formData.excerpt}onChange={(e) => setFormData({ ...formData, excerpt: e.target.value })}/><textareaplaceholder="Content"value={formData.content}onChange={(e) => setFormData({ ...formData, content: e.target.value })}required/><button type="submit" disabled={loading}>{loading ? 'Creating...' : 'Create Post'}</button>{error && <p>Error: {error.message}</p>}</form>);
};

总结

GraphQL是一个功能强大的API查询语言,它提供了:

  1. 精确的数据获取: 客户端可以指定需要的确切数据
  2. 强类型系统: 完整的类型定义和运行时验证
  3. 单一端点: 所有操作通过一个URL处理
  4. 实时功能: 内置订阅支持实时数据更新
  5. 开发者体验: 优秀的工具支持和自省功能

最佳实践总结

  • 设计清晰的Schema,使用适当的类型和命名规范
  • 实现适当的认证和授权机制
  • 使用DataLoader解决N+1查询问题
  • 实施查询复杂度限制和速率限制
  • 合理使用缓存策略提升性能
  • 遵循GraphQL规范和社区最佳实践

GraphQL已经被越来越多的公司采用,掌握GraphQL将为你的职业发展带来很大帮助。通过本指南的学习和实践,你应该能够熟练使用GraphQL构建现代化的API。

http://www.dtcms.com/a/591312.html

相关文章:

  • 淘宝客如何免费做网站做一组静态页面网站多少钱
  • 建网站没有公司地址怎么办mvc5 网站开发美學 pdf
  • 建站开发软件网站设计概述500字
  • 江苏华江建设集团有限公司网站宁夏自治区建设厅官方网站
  • 文本怎样做阅读链接网站网站优化比较好的公司
  • 保定网站建设公司哪家好响应式网站代码
  • 成都网站内容策划wordpress简码
  • wordpress 获取优酷优化大师手机版
  • 开发建设网站重庆网站建设套餐
  • 做视频网站了几百万以鹦鹉做头像的网站
  • 一线城市做网站工资有多少钱wordpress注册页
  • 北京网站建设公司文字排版友情链接代码美化
  • 珠海斗门建设局网站wordpress安装500
  • 唐四薪php网站开发答案wordpress 只有英文版
  • 怎样进行网站开发婚庆网站建设公司
  • 域名如何做网站大连住建部官网
  • 公司网站建设情况说明书网络公司有什么职位
  • 酒店网站的建设方案谷歌google浏览器
  • 网站 图片 自动往右移没有网站域名备案信息吗
  • 网站开发都需要学什么工业设计产品图
  • 网站建设对教育解决方案自己怎么做外贸网站
  • 大一做家教的网站专业网站建设代理
  • 怎样在百度做网站表白网页设计与制作课程结构
  • 阎良做网站WordPress仿百度百家主题
  • 做网站的怎么找客户西安网站建设g
  • 做一个网站赚钱吗蔡甸城乡建设局网站
  • 专做国外旅游的网站iis网站后台登不进
  • 网站下的源代码和自己做的区别网站app下载平台怎么做
  • 网站一次性链接怎么做企业网站 备案
  • C语言应用实例:做不完的畅通工程(并查集)