摘要:緊接第一篇文章,起手和特性介紹一,我們接下來(lái)實(shí)現(xiàn),和自定義請(qǐng)求上下文,來(lái)完成創(chuàng)建用戶(hù),發(fā)帖,查看所有帖子的功能首先,我們進(jìn)行自定義請(qǐng)求上下文,來(lái)模擬數(shù)據(jù)庫(kù)和會(huì)話,保存我們的用戶(hù)數(shù)據(jù),帖子數(shù)據(jù),登錄狀態(tài)。
緊接第一篇文章,react+graphql起手和特性介紹(一),我們接下來(lái)實(shí)現(xiàn)resolver,和自定義請(qǐng)求上下文,來(lái)完成創(chuàng)建用戶(hù),發(fā)帖,查看所有帖子的功能
首先,我們進(jìn)行自定義請(qǐng)求上下文,來(lái)模擬數(shù)據(jù)庫(kù)和會(huì)話,保存我們的用戶(hù)數(shù)據(jù),帖子數(shù)據(jù),登錄狀態(tài)。在server目錄下創(chuàng)建context.js文件。
// server/context.js const store = new Map(); // 模擬數(shù)據(jù)庫(kù),保存注冊(cè)用戶(hù),創(chuàng)建的帖子數(shù)據(jù) const sessionStore = { // 模擬session會(huì)話,保存用戶(hù)登錄狀態(tài)數(shù)據(jù) key: 0, }; module.exports = (context) => { const { ctx } = context; // 我們是將graphql與koa整合在一起, // 這里的ctx就是koa提供的請(qǐng)求上下文 // 我們?yōu)?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; }
接下來(lái)我們實(shí)現(xiàn)user的reslover和對(duì)應(yīng)的schema
// server/resolver/user.js const idsKey = "user_ids"; let idCount = 0; const genId = () => { idCount++; return "user_id_" + idCount; }; module.exports = { Query: { // 查詢(xún)登錄用戶(hù) user(root, query, ctx) { const { session } = ctx; return session.get(); }, // 查詢(xún)所有用戶(hù) users(root, query, { store }) { const ids = store.get(idsKey) || []; const res = []; ids.forEach(id => { res.push(store.get(id)); }); return res; }, // 用戶(hù)登錄 login(root, { id }, { store, session }) { const user = store.get(id); if (user) session.set(user); return user; } }, Gender: { MALE: 1, FEMALE: 2 }, // Mutation 是與Query一樣的根節(jié)點(diǎn),與Query沒(méi)有什么區(qū)別,只有語(yǔ)義上的區(qū)分, // 對(duì)數(shù)據(jù)進(jìn)行修改和新增的操作都放在 Mutation 中 Mutation: { // 創(chuàng)建用戶(hù) 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,需要輸入的類(lèi)型需要用input進(jìn)行定義。 # 比如創(chuàng)建用戶(hù)的json數(shù)據(jù),其結(jié)構(gòu)需要用input定義,才能使用 input UserInput { name: String age: Int available: Boolean money: Float gender: Gender birthday: Date } extend type Mutation { # 使用 UserInput 作為輸入結(jié)構(gòu)類(lèi)型,! 表示不能為空 createUser(data: UserInput!): User }
為使我們返回的自定義類(lèi)型數(shù)據(jù)生效,修改下對(duì)mock進(jìn)行如下修改
// server/mock.js module.exports = { Date(root, args, ctx, info) { // info代表解析信息,可以取到當(dāng)前訪問(wèn)的字段名,我們對(duì)返回?cái)?shù)據(jù)root進(jìn)行判斷, // 如果為null,則創(chuàng)建新的對(duì)象,否則使用返回的數(shù)據(jù) if (!root[info.fieldName]) return new Date(); return root[info.fieldName]; } }
好了,我們的用戶(hù)相關(guān)功能已經(jīng)實(shí)現(xiàn)完成,現(xiàn)在啟動(dòng)服務(wù),我們創(chuàng)建一個(gè)用戶(hù),登錄,并查詢(xún)
在mutation上我們先定義$user變量,語(yǔ)法規(guī)定需以$開(kāi)頭,它的類(lèi)型是UserInput!,對(duì)應(yīng)我們的schema定義,然后在createUser查詢(xún)中使用此變量$user,它對(duì)應(yīng)的schema解析變量是data,data就是我們?cè)趓eslover中訪問(wèn)請(qǐng)求參數(shù)的變量名。具體的請(qǐng)求數(shù)據(jù),我們通過(guò)query variables進(jìn)行定義,它是json格式的數(shù)據(jù),"user"對(duì)應(yīng)我們的$user變量,里面的結(jié)構(gòu)與UserInput!一一對(duì)應(yīng),并輸入值。創(chuàng)建完用戶(hù)之后會(huì)將用戶(hù)數(shù)據(jù)返回,并有對(duì)應(yīng)的id值。
登錄用戶(hù)
查詢(xún)所有用戶(hù)
根據(jù)我們的定義,查詢(xún)出來(lái)的是數(shù)組類(lèi)型
讓我們繼續(xù)完成帖子的功能
// 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數(shù)據(jù)時(shí)有個(gè)user字段是User類(lèi)型,我們并不需要每次返回時(shí)都在post查詢(xún)的 // resolver中查出對(duì)應(yīng)的user數(shù)據(jù),graphql的特性是,如果reslover返回的數(shù)據(jù)沒(méi)有某個(gè) // 定義了類(lèi)型的字段值,就會(huì)找類(lèi)型字段的具體定義reslover并執(zhí)行,其root值就是上次查詢(xún) // 出來(lái)的對(duì)應(yīng)類(lèi)型值,然后將此reslover返回值拼接到原始對(duì)象中并返回。 // 在這里具體的執(zhí)行流程會(huì)在下面示例中說(shuō)明 user(root, query, { store }) { if (root && root.userId) { return store.get(root.userId) } } }, Mutation: { createPost(root, { data }, { store, session }) { // 如果用戶(hù)沒(méi)有登錄,將無(wú)法創(chuàng)建帖子 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; } } }
為了格式化錯(cuò)誤,在創(chuàng)建服務(wù)時(shí),自定義formatError
// server/index.js ... const server = new ApolloServer({ ... formatError: error => { // 刪除 extensions 字段,刪除異常的堆棧,不暴露服務(wù)器發(fā)生錯(cuò)誤的文件 delete error.extensions; return error; }, }); ...
繼續(xù)完善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 }
如果會(huì)話有異常,沒(méi)有cookie信息,修改下graphql gui客戶(hù)端的配置
修改 "request.credentials": "omit" 為 "request.credentials": "include"
下面我們進(jìn)行創(chuàng)建帖子和查詢(xún)帖子的操作
可以看到,我們?cè)诖acreatePost和posts代碼中并沒(méi)有查詢(xún)user,這里也會(huì)返回user數(shù)據(jù),是因?yàn)槲覀兌x了Post的user字段對(duì)應(yīng)的reslover方法,在返回類(lèi)型為Post時(shí),posts/createPost返回的數(shù)據(jù)user字段為空,graphql就會(huì)自動(dòng)調(diào)用user的reslover方法,并且之前posts/createPost返回的數(shù)據(jù)會(huì)作為user的reslover中root參數(shù)傳入,這樣我們就可以從root數(shù)據(jù)中獲取userId,然后對(duì)user數(shù)據(jù)的查詢(xún)只用放在一個(gè)地方執(zhí)行就可以。graphql很好地分化了類(lèi)型數(shù)據(jù)的處理邏輯,使每個(gè)resolver只關(guān)注處理此層對(duì)應(yīng)的數(shù)據(jù),剩下的數(shù)據(jù)拼接graphql會(huì)幫我們處理好。
最后我們將用自定義指令,來(lái)實(shí)現(xiàn)服務(wù)端鑒權(quán)操作
創(chuàng)建文件directive.js
// server/directive.js const { SchemaDirectiveVisitor } = require("apollo-server-koa"); class AuthDirective extends SchemaDirectiveVisitor { visitFieldDefinition(field) { // 對(duì)用戶(hù)年齡進(jìn)行校驗(yàn) const age = this.args.age; // 指令的實(shí)現(xiàn)方式是將resolvoer進(jìn)行hack,因此指令本質(zhì)也是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關(guān)鍵子定義指令, auth 指令名,age為此指令接收的參數(shù) directive @auth( age: Int, ) on FIELD_DEFINITION # FIELD_DEFINITION 表示此指令應(yīng)用于字段定義
使用指令
# server/schema/post.graphql ... extend type Query { post(id: ID!): Post @auth(age: 18) posts: [Post] @auth(age: 20) } ...
現(xiàn)在我們?cè)龠M(jìn)行查詢(xún),發(fā)現(xiàn)指令已經(jīng)生效,用戶(hù)age小于20是不能查出posts數(shù)據(jù)的,而post是可以查出數(shù)據(jù)的
到此,我們graphql后端服務(wù)的搭建和特性就介紹完了,后面我們會(huì)介紹前端react如何整合graphql
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/99637.html
摘要:如果你對(duì)這系列文章有疑問(wèn)或發(fā)現(xiàn)有錯(cuò)誤的地方,歡迎在下方留言討論。 緊接上篇react+graphql起手和特性介紹(二),介紹完graphql與koa的服務(wù)搭建和graphql的一些常用特性,接下來(lái)我們介紹下在react中如何使用graphql我們使用create-react-app創(chuàng)建react應(yīng)用: npm i -g create-react-app mkdir react-gra...
摘要:感謝王下邀月熊分享的前端每周清單,為方便大家閱讀,特整理一份索引。王下邀月熊大大也于年月日整理了自己的前端每周清單系列,并以年月為單位進(jìn)行分類(lèi),具體內(nèi)容看這里前端每周清單年度總結(jié)與盤(pán)點(diǎn)。 感謝 王下邀月熊_Chevalier 分享的前端每周清單,為方便大家閱讀,特整理一份索引。 王下邀月熊大大也于 2018 年 3 月 31 日整理了自己的前端每周清單系列,并以年/月為單位進(jìn)行分類(lèi),具...
摘要:前端每周清單年度總結(jié)與盤(pán)點(diǎn)在過(guò)去的八個(gè)月中,我?guī)缀踔蛔隽藘杉?,工作與整理前端每周清單。本文末尾我會(huì)附上清單線索來(lái)源與目前共期清單的地址,感謝每一位閱讀鼓勵(lì)過(guò)的朋友,希望你們能夠繼續(xù)支持未來(lái)的每周清單。 showImg(https://segmentfault.com/img/remote/1460000010890043); 前端每周清單年度總結(jié)與盤(pán)點(diǎn) 在過(guò)去的八個(gè)月中,我?guī)缀踔蛔隽?..
摘要:新聞熱點(diǎn)國(guó)內(nèi)國(guó)外,前端最新動(dòng)態(tài)發(fā)布近日,正式發(fā)布新版本中提供了一系列的特性與問(wèn)題修復(fù)。而近日正式發(fā)布,其能夠幫助開(kāi)發(fā)者快速構(gòu)建應(yīng)用。 前端每周清單第 10 期:Firefox53、React VR發(fā)布、JS測(cè)試技術(shù)概述、Microsoft Edge現(xiàn)代DOM樹(shù)構(gòu)建及性能之道 為InfoQ中文站特供稿件,首發(fā)地址為這里;如需轉(zhuǎn)載,請(qǐng)與InfoQ中文站聯(lián)系。從屬于筆者的 Web 前端入門(mén)...
閱讀 3336·2019-08-29 16:17
閱讀 1989·2019-08-29 15:31
閱讀 2660·2019-08-29 14:09
閱讀 2557·2019-08-26 13:52
閱讀 754·2019-08-26 12:21
閱讀 2154·2019-08-26 12:08
閱讀 1005·2019-08-23 17:08
閱讀 1938·2019-08-23 16:59