摘要:緊接第一篇文章,起手和特性介紹一,我們接下來實現,和自定義請求上下文,來完成創建用戶,發帖,查看所有帖子的功能首先,我們進行自定義請求上下文,來模擬數據庫和會話,保存我們的用戶數據,帖子數據,登錄狀態。
緊接第一篇文章,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
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/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 前端入門...
閱讀 3318·2019-08-29 16:17
閱讀 1975·2019-08-29 15:31
閱讀 2645·2019-08-29 14:09
閱讀 2548·2019-08-26 13:52
閱讀 744·2019-08-26 12:21
閱讀 2125·2019-08-26 12:08
閱讀 991·2019-08-23 17:08
閱讀 1922·2019-08-23 16:59