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