资讯专栏INFORMATION COLUMN

react+graphql起手和特性介绍(二)

NikoManiac / 1702人阅读

摘要:紧接第一篇文章,起手和特性介绍一,我们接下来实现,和自定义请求上下文,来完成创建用户,发帖,查看所有帖子的功能首先,我们进行自定义请求上下文,来模拟数据库和会话,保存我们的用户数据,帖子数据,登录状态。

紧接第一篇文章,react+graphql起手和特性介绍(一),我们接下来实现resolver,和自定义请求上下文,来完成创建用户,发帖,查看所有帖子的功能
首先,我们进行自定义请求上下文,来模拟数据库和会话,保存我们的用户数据,帖子数据,登录状态。在server目录下创建context.js文件。

</>复制代码

  1. // server/context.js
  2. const store = new Map(); // 模拟数据库,保存注册用户,创建的帖子数据
  3. const sessionStore = { // 模拟session会话,保存用户登录状态数据
  4. key: 0,
  5. };
  6. module.exports = (context) => {
  7. const { ctx } = context; // 我们是将graphql与koa整合在一起,
  8. // 这里的ctx就是koa提供的请求上下文
  9. // 我们为 graphql 的 context 添加 session 和 store
  10. context.session = {
  11. get() {
  12. const cookieValue = ctx.cookies.get("user_login");
  13. return sessionStore[cookieValue];
  14. },
  15. set(value) {
  16. const cookieValue = ++sessionStore.key;
  17. ctx.cookies.set("user_login", cookieValue);
  18. sessionStore[cookieValue] = value;
  19. }
  20. };
  21. context.store = store;
  22. return context;
  23. }

接下来我们实现user的reslover和对应的schema

</>复制代码

  1. // server/resolver/user.js
  2. const idsKey = "user_ids";
  3. let idCount = 0;
  4. const genId = () => {
  5. idCount++;
  6. return "user_id_" + idCount;
  7. };
  8. module.exports = {
  9. Query: {
  10. // 查询登录用户
  11. user(root, query, ctx) {
  12. const { session } = ctx;
  13. return session.get();
  14. },
  15. // 查询所有用户
  16. users(root, query, { store }) {
  17. const ids = store.get(idsKey) || [];
  18. const res = [];
  19. ids.forEach(id => {
  20. res.push(store.get(id));
  21. });
  22. return res;
  23. },
  24. // 用户登录
  25. login(root, { id }, { store, session }) {
  26. const user = store.get(id);
  27. if (user) session.set(user);
  28. return user;
  29. }
  30. },
  31. Gender: {
  32. MALE: 1,
  33. FEMALE: 2
  34. },
  35. // Mutation 是与Query一样的根节点,与Query没有什么区别,只有语义上的区分,
  36. // 对数据进行修改和新增的操作都放在 Mutation 中
  37. Mutation: {
  38. // 创建用户
  39. createUser(root, { data }, { session, store }) {
  40. data.id = genId();
  41. let userIds = store.get(idsKey);
  42. if (!userIds) userIds = [];
  43. userIds.push(data.id);
  44. store.set(data.id, data);
  45. store.set(idsKey, userIds);
  46. session.set(data);
  47. return data;
  48. }
  49. }
  50. }

</>复制代码

  1. # server/schema/user.graphql
  2. ...
  3. extend type Query {
  4. user: User
  5. users: [User]
  6. login(id: ID!): User
  7. }
  8. # input 代表输入type,需要输入的类型需要用input进行定义。
  9. # 比如创建用户的json数据,其结构需要用input定义,才能使用
  10. input UserInput {
  11. name: String
  12. age: Int
  13. available: Boolean
  14. money: Float
  15. gender: Gender
  16. birthday: Date
  17. }
  18. extend type Mutation {
  19. # 使用 UserInput 作为输入结构类型,! 表示不能为空
  20. createUser(data: UserInput!): User
  21. }

为使我们返回的自定义类型数据生效,修改下对mock进行如下修改

</>复制代码

  1. // server/mock.js
  2. module.exports = {
  3. Date(root, args, ctx, info) {
  4. // info代表解析信息,可以取到当前访问的字段名,我们对返回数据root进行判断,
  5. // 如果为null,则创建新的对象,否则使用返回的数据
  6. if (!root[info.fieldName]) return new Date();
  7. return root[info.fieldName];
  8. }
  9. }

好了,我们的用户相关功能已经实现完成,现在启动服务,我们创建一个用户,登录,并查询

在mutation上我们先定义$user变量,语法规定需以$开头,它的类型是UserInput!,对应我们的schema定义,然后在createUser查询中使用此变量$user,它对应的schema解析变量是data,data就是我们在reslover中访问请求参数的变量名。具体的请求数据,我们通过query variables进行定义,它是json格式的数据,"user"对应我们的$user变量,里面的结构与UserInput!一一对应,并输入值。创建完用户之后会将用户数据返回,并有对应的id值。
登录用户

查询所有用户

根据我们的定义,查询出来的是数组类型
让我们继续完成帖子的功能

</>复制代码

  1. // server/resolver/post.js
  2. const idsKey = "post_ids";
  3. let idCount = 0;
  4. const genId = () => {
  5. idCount++;
  6. return "post_id_" + idCount;
  7. };
  8. module.exports = {
  9. Query: {
  10. post(root, query, { store }) {
  11. return store.get(query.id)
  12. },
  13. posts(root, query, { store }) {
  14. const ids = store.get(idsKey) || [];
  15. const res = [];
  16. ids.forEach(id => {
  17. res.push(store.get(id));
  18. });
  19. return res;
  20. }
  21. },
  22. Post: {
  23. // 在返回post数据时有个user字段是User类型,我们并不需要每次返回时都在post查询的
  24. // resolver中查出对应的user数据,graphql的特性是,如果reslover返回的数据没有某个
  25. // 定义了类型的字段值,就会找类型字段的具体定义reslover并执行,其root值就是上次查询
  26. // 出来的对应类型值,然后将此reslover返回值拼接到原始对象中并返回。
  27. // 在这里具体的执行流程会在下面示例中说明
  28. user(root, query, { store }) {
  29. if (root && root.userId) {
  30. return store.get(root.userId)
  31. }
  32. }
  33. },
  34. Mutation: {
  35. createPost(root, { data }, { store, session }) {
  36. // 如果用户没有登录,将无法创建帖子
  37. if (!session.get()) throw new Error("no permission");
  38. data.id = genId();
  39. data.userId = session.get().id;
  40. let ids = store.get(idsKey);
  41. if (!ids) ids = [];
  42. ids.push(data.id);
  43. store.set(data.id, data);
  44. store.set(idsKey, ids);
  45. return data;
  46. }
  47. }
  48. }

为了格式化错误,在创建服务时,自定义formatError

</>复制代码

  1. // server/index.js
  2. ...
  3. const server = new ApolloServer({
  4. ...
  5. formatError: error => {
  6. // 删除 extensions 字段,删除异常的堆栈,不暴露服务器发生错误的文件
  7. delete error.extensions;
  8. return error;
  9. },
  10. });
  11. ...

继续完善post schema

</>复制代码

  1. # server/schema/post.graphql
  2. ...
  3. extend type Query {
  4. post(id: ID!): Post @auth(role: ONE)
  5. posts: [Post] @auth(role: ALL)
  6. }
  7. input PostInput {
  8. title: String!
  9. content: String!
  10. }
  11. extend type Mutation {
  12. createPost(data: PostInput!): Post
  13. }

如果会话有异常,没有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

</>复制代码

  1. // server/directive.js
  2. const { SchemaDirectiveVisitor } = require("apollo-server-koa");
  3. class AuthDirective extends SchemaDirectiveVisitor {
  4. visitFieldDefinition(field) {
  5. // 对用户年龄进行校验
  6. const age = this.args.age;
  7. // 指令的实现方式是将resolvoer进行hack,因此指令本质也是resolver
  8. const realResolve = field.resolve;
  9. field.resolve = async function (root, query, context, info) {
  10. const user = context.session.get();
  11. if (user && user.age >= age) {
  12. return await realResolve.call(this, root, query, context, info);
  13. } else {
  14. throw Error("no permission");
  15. }
  16. };
  17. }
  18. }
  19. module.exports = {
  20. auth: AuthDirective
  21. }

在schema中定义指令

</>复制代码

  1. # server/schema/schema.graphql
  2. ...
  3. # 使用directive关键子定义指令, auth 指令名,age为此指令接收的参数
  4. directive @auth(
  5. age: Int,
  6. ) on FIELD_DEFINITION
  7. # FIELD_DEFINITION 表示此指令应用于字段定义

使用指令

</>复制代码

  1. # server/schema/post.graphql
  2. ...
  3. extend type Query {
  4. post(id: ID!): Post @auth(age: 18)
  5. posts: [Post] @auth(age: 20)
  6. }
  7. ...

现在我们再进行查询,发现指令已经生效,用户age小于20是不能查出posts数据的,而post是可以查出数据的

到此,我们graphql后端服务的搭建和特性就介绍完了,后面我们会介绍前端react如何整合graphql

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/99637.html

相关文章

  • react+graphql手和特性介绍(三)

    摘要:如果你对这系列文章有疑问或发现有错误的地方,欢迎在下方留言讨论。 紧接上篇react+graphql起手和特性介绍(二),介绍完graphql与koa的服务搭建和graphql的一些常用特性,接下来我们介绍下在react中如何使用graphql我们使用create-react-app创建react应用: npm i -g create-react-app mkdir react-gra...

    soasme 评论0 收藏0
  • 王下邀月熊_Chevalier的前端每周清单系列文章索引

    摘要:感谢王下邀月熊分享的前端每周清单,为方便大家阅读,特整理一份索引。王下邀月熊大大也于年月日整理了自己的前端每周清单系列,并以年月为单位进行分类,具体内容看这里前端每周清单年度总结与盘点。 感谢 王下邀月熊_Chevalier 分享的前端每周清单,为方便大家阅读,特整理一份索引。 王下邀月熊大大也于 2018 年 3 月 31 日整理了自己的前端每周清单系列,并以年/月为单位进行分类,具...

    2501207950 评论0 收藏0
  • 前端每周清单年度总结与盘点

    摘要:前端每周清单年度总结与盘点在过去的八个月中,我几乎只做了两件事,工作与整理前端每周清单。本文末尾我会附上清单线索来源与目前共期清单的地址,感谢每一位阅读鼓励过的朋友,希望你们能够继续支持未来的每周清单。 showImg(https://segmentfault.com/img/remote/1460000010890043); 前端每周清单年度总结与盘点 在过去的八个月中,我几乎只做了...

    jackwang 评论0 收藏0
  • 前端每周清单第 10 期:Firefox53、React VR发布、Microsoft Edge现代

    摘要:新闻热点国内国外,前端最新动态发布近日,正式发布新版本中提供了一系列的特性与问题修复。而近日正式发布,其能够帮助开发者快速构建应用。 前端每周清单第 10 期:Firefox53、React VR发布、JS测试技术概述、Microsoft Edge现代DOM树构建及性能之道 为InfoQ中文站特供稿件,首发地址为这里;如需转载,请与InfoQ中文站联系。从属于笔者的 Web 前端入门...

    MingjunYang 评论0 收藏0

发表评论

0条评论

NikoManiac

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<