摘要:紧接第一篇文章,起手和特性介绍一,我们接下来实现,和自定义请求上下文,来完成创建用户,发帖,查看所有帖子的功能首先,我们进行自定义请求上下文,来模拟数据库和会话,保存我们的用户数据,帖子数据,登录状态。
紧接第一篇文章,react+graphql起手和特性介绍(一),我们接下来实现resolver,和自定义请求上下文,来完成创建用户,发帖,查看所有帖子的功能
首先,我们进行自定义请求上下文,来模拟数据库和会话,保存我们的用户数据,帖子数据,登录状态。在server目录下创建context.js文件。
// server/context.js const store = new Map(); // 模拟数据库,保存注册用户,创建的帖子数据 const sessionStore = { // 模拟session会话,保存用户登录状态数据 key: 0, }; module.exports = (context) => { const { ctx } = context; // 我们是将graphql与koa整合在一起, // 这里的ctx就是koa提供的请求上下文 // 我们为 graphql 的 context 添加 session 和 store context.session = { get() { const cookieValue = ctx.cookies.get("user_login"); return sessionStore[cookieValue]; }, set(value) { const cookieValue = ++sessionStore.key; ctx.cookies.set("user_login", cookieValue); sessionStore[cookieValue] = value; } }; context.store = store; return context; }
接下来我们实现user的reslover和对应的schema
// server/resolver/user.js const idsKey = "user_ids"; let idCount = 0; const genId = () => { idCount++; return "user_id_" + idCount; }; module.exports = { Query: { // 查询登录用户 user(root, query, ctx) { const { session } = ctx; return session.get(); }, // 查询所有用户 users(root, query, { store }) { const ids = store.get(idsKey) || []; const res = []; ids.forEach(id => { res.push(store.get(id)); }); return res; }, // 用户登录 login(root, { id }, { store, session }) { const user = store.get(id); if (user) session.set(user); return user; } }, Gender: { MALE: 1, FEMALE: 2 }, // Mutation 是与Query一样的根节点,与Query没有什么区别,只有语义上的区分, // 对数据进行修改和新增的操作都放在 Mutation 中 Mutation: { // 创建用户 createUser(root, { data }, { session, store }) { data.id = genId(); let userIds = store.get(idsKey); if (!userIds) userIds = []; userIds.push(data.id); store.set(data.id, data); store.set(idsKey, userIds); session.set(data); return data; } } }
# server/schema/user.graphql ... extend type Query { user: User users: [User] login(id: ID!): User } # input 代表输入type,需要输入的类型需要用input进行定义。 # 比如创建用户的json数据,其结构需要用input定义,才能使用 input UserInput { name: String age: Int available: Boolean money: Float gender: Gender birthday: Date } extend type Mutation { # 使用 UserInput 作为输入结构类型,! 表示不能为空 createUser(data: UserInput!): User }
为使我们返回的自定义类型数据生效,修改下对mock进行如下修改
// server/mock.js module.exports = { Date(root, args, ctx, info) { // info代表解析信息,可以取到当前访问的字段名,我们对返回数据root进行判断, // 如果为null,则创建新的对象,否则使用返回的数据 if (!root[info.fieldName]) return new Date(); return root[info.fieldName]; } }
好了,我们的用户相关功能已经实现完成,现在启动服务,我们创建一个用户,登录,并查询
在mutation上我们先定义$user变量,语法规定需以$开头,它的类型是UserInput!,对应我们的schema定义,然后在createUser查询中使用此变量$user,它对应的schema解析变量是data,data就是我们在reslover中访问请求参数的变量名。具体的请求数据,我们通过query variables进行定义,它是json格式的数据,"user"对应我们的$user变量,里面的结构与UserInput!一一对应,并输入值。创建完用户之后会将用户数据返回,并有对应的id值。
登录用户
查询所有用户
根据我们的定义,查询出来的是数组类型
让我们继续完成帖子的功能
// server/resolver/post.js const idsKey = "post_ids"; let idCount = 0; const genId = () => { idCount++; return "post_id_" + idCount; }; module.exports = { Query: { post(root, query, { store }) { return store.get(query.id) }, posts(root, query, { store }) { const ids = store.get(idsKey) || []; const res = []; ids.forEach(id => { res.push(store.get(id)); }); return res; } }, Post: { // 在返回post数据时有个user字段是User类型,我们并不需要每次返回时都在post查询的 // resolver中查出对应的user数据,graphql的特性是,如果reslover返回的数据没有某个 // 定义了类型的字段值,就会找类型字段的具体定义reslover并执行,其root值就是上次查询 // 出来的对应类型值,然后将此reslover返回值拼接到原始对象中并返回。 // 在这里具体的执行流程会在下面示例中说明 user(root, query, { store }) { if (root && root.userId) { return store.get(root.userId) } } }, Mutation: { createPost(root, { data }, { store, session }) { // 如果用户没有登录,将无法创建帖子 if (!session.get()) throw new Error("no permission"); data.id = genId(); data.userId = session.get().id; let ids = store.get(idsKey); if (!ids) ids = []; ids.push(data.id); store.set(data.id, data); store.set(idsKey, ids); return data; } } }
为了格式化错误,在创建服务时,自定义formatError
// server/index.js ... const server = new ApolloServer({ ... formatError: error => { // 删除 extensions 字段,删除异常的堆栈,不暴露服务器发生错误的文件 delete error.extensions; return error; }, }); ...
继续完善post schema
# server/schema/post.graphql ... extend type Query { post(id: ID!): Post @auth(role: ONE) posts: [Post] @auth(role: ALL) } input PostInput { title: String! content: String! } extend type Mutation { createPost(data: PostInput!): Post }
如果会话有异常,没有cookie信息,修改下graphql gui客户端的配置
修改 "request.credentials": "omit" 为 "request.credentials": "include"
下面我们进行创建帖子和查询帖子的操作
可以看到,我们在代码createPost和posts代码中并没有查询user,这里也会返回user数据,是因为我们定义了Post的user字段对应的reslover方法,在返回类型为Post时,posts/createPost返回的数据user字段为空,graphql就会自动调用user的reslover方法,并且之前posts/createPost返回的数据会作为user的reslover中root参数传入,这样我们就可以从root数据中获取userId,然后对user数据的查询只用放在一个地方执行就可以。graphql很好地分化了类型数据的处理逻辑,使每个resolver只关注处理此层对应的数据,剩下的数据拼接graphql会帮我们处理好。
最后我们将用自定义指令,来实现服务端鉴权操作
创建文件directive.js
// server/directive.js const { SchemaDirectiveVisitor } = require("apollo-server-koa"); class AuthDirective extends SchemaDirectiveVisitor { visitFieldDefinition(field) { // 对用户年龄进行校验 const age = this.args.age; // 指令的实现方式是将resolvoer进行hack,因此指令本质也是resolver const realResolve = field.resolve; field.resolve = async function (root, query, context, info) { const user = context.session.get(); if (user && user.age >= age) { return await realResolve.call(this, root, query, context, info); } else { throw Error("no permission"); } }; } } module.exports = { auth: AuthDirective }
在schema中定义指令
# server/schema/schema.graphql ... # 使用directive关键子定义指令, auth 指令名,age为此指令接收的参数 directive @auth( age: Int, ) on FIELD_DEFINITION # FIELD_DEFINITION 表示此指令应用于字段定义
使用指令
# server/schema/post.graphql ... extend type Query { post(id: ID!): Post @auth(age: 18) posts: [Post] @auth(age: 20) } ...
现在我们再进行查询,发现指令已经生效,用户age小于20是不能查出posts数据的,而post是可以查出数据的
到此,我们graphql后端服务的搭建和特性就介绍完了,后面我们会介绍前端react如何整合graphql
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/99637.html
摘要:如果你对这系列文章有疑问或发现有错误的地方,欢迎在下方留言讨论。 紧接上篇react+graphql起手和特性介绍(二),介绍完graphql与koa的服务搭建和graphql的一些常用特性,接下来我们介绍下在react中如何使用graphql我们使用create-react-app创建react应用: npm i -g create-react-app mkdir react-gra...
摘要:感谢王下邀月熊分享的前端每周清单,为方便大家阅读,特整理一份索引。王下邀月熊大大也于年月日整理了自己的前端每周清单系列,并以年月为单位进行分类,具体内容看这里前端每周清单年度总结与盘点。 感谢 王下邀月熊_Chevalier 分享的前端每周清单,为方便大家阅读,特整理一份索引。 王下邀月熊大大也于 2018 年 3 月 31 日整理了自己的前端每周清单系列,并以年/月为单位进行分类,具...
摘要:前端每周清单年度总结与盘点在过去的八个月中,我几乎只做了两件事,工作与整理前端每周清单。本文末尾我会附上清单线索来源与目前共期清单的地址,感谢每一位阅读鼓励过的朋友,希望你们能够继续支持未来的每周清单。 showImg(https://segmentfault.com/img/remote/1460000010890043); 前端每周清单年度总结与盘点 在过去的八个月中,我几乎只做了...
摘要:新闻热点国内国外,前端最新动态发布近日,正式发布新版本中提供了一系列的特性与问题修复。而近日正式发布,其能够帮助开发者快速构建应用。 前端每周清单第 10 期:Firefox53、React VR发布、JS测试技术概述、Microsoft Edge现代DOM树构建及性能之道 为InfoQ中文站特供稿件,首发地址为这里;如需转载,请与InfoQ中文站联系。从属于笔者的 Web 前端入门...
阅读 3317·2019-08-29 16:17
阅读 1971·2019-08-29 15:31
阅读 2644·2019-08-29 14:09
阅读 2547·2019-08-26 13:52
阅读 743·2019-08-26 12:21
阅读 2124·2019-08-26 12:08
阅读 990·2019-08-23 17:08
阅读 1922·2019-08-23 16:59