請注意當前的環境,老版本的
egg
可能配置有差別
egg-graphql
npm i --save egg-graphql
/config/plugin.js
exports.graphql = { enable: true, package: 'egg-graphql' }
/config/config.default.js
// add your config here config.middleware = ['graphql'] // graphql config.graphql = { router: '/graphql', // 是否加載到 app 上,默認開啓 app: true, // 是否加載到 agent 上,默認關閉 agent: false, // 是否加載開發者工具 graphiql, 默認開啓。路由同 router 字段。使用瀏覽器打開該可見。 graphiql: true, // graphQL 路由前的攔截器 onPreGraphQL: function* (ctx) {}, // 開發工具 graphiQL 路由前的攔截器,建議用於作權限操做(如只提供開發者使用) onPreGraphiQL: function* (ctx) {}, }
egg-graphql
代碼結構. ├── graphql | graphql 代碼 │ ├── common | 通用類型定義 │ │ ├── resolver.js | 合併全部全局類型定義 │ │ ├── scalars | 自定義類型定義 │ │ │ └── date.js | 日期類型實現 │ │ └── schema.graphql | schema 定義 │ ├── mutation | 全部的更新 │ │ └── schema.graphql | schema 定義 │ ├── query | 全部的查詢 │ │ └── schema.graphql | schema 定義 │ └── user | 用戶業務 │ ├── connector.js | 鏈接數據服務 │ ├── resolver.js | 類型實現 │ └── schema.graphql | schema 定義
graphql 目錄下,有 4 種代碼vue
common
全局類型定義query
查詢代碼mutation
更新操做代碼4 業務
實現代碼node
connector
鏈接數據服務resolver
類型實現schema
定義common
全局類型common/schema.graphql
scalar Date
common/scalars/date.js
const { GraphQLScalarType } = require('graphql'); const { Kind } = require('graphql/language'); module.exports = new GraphQLScalarType({ name: 'Date', description: 'Date custom scalar type', parseValue(value) { return new Date(value); }, serialize(value) { return value.getTime(); }, parseLiteral(ast) { if (ast.kind === Kind.INT) { return parseInt(ast.value, 10); } return null; }, });
common/resolver.js
module.exports = { Date: require('./scalars/date'), // eslint-disable-line };
在egg node
下仍是用require
,若是語言偏好用import
會損失轉換性能,不推薦
user
業務user/schema.graphql
# 用戶 type User { # 流水號 id: ID! # 用戶名 name: String! # token token: String }
user/connector.js
'use strict' const DataLoader = require('dataloader') class UserConnector { constructor(ctx) { this.ctx = ctx this.loader = new DataLoader(this.fetch.bind(this)) } fetch(id) { const user = this.ctx.service.user return new Promise(function(resolve, reject) { const users = user.findById(id) resolve(users) }) } fetchById(id) { return this.loader.load(id) } // 用戶登陸 fetchByNamePassword(username, password) { let user = this.ctx.service.user.findByUsernamePassword(username, password) return user } // 用戶列表 fetchAll() { let user = this.ctx.service.user.findAll() return user } // 用戶刪除 removeOne(id) { let user = this.ctx.service.user.removeUser(id) return user } } module.exports = UserConnector
dataloader
是N+1
問題
user/resolver.js
'use strict' module.exports = { Query: { user(root, {username, password}, ctx) { return ctx.connector.user.fetchByNamePassword(username, password) }, users(root, {}, ctx) { return ctx.connector.user.fetchAll() } }, Mutation: { removeUser(root, { id }, ctx) { return ctx.connector.user.removeOne(id) }, } }
query
查詢query/schema.graphql
type Query { # 用戶登陸 user( # 用戶名 username: String!, # 密碼 password: String! ): User # 用戶列表 users: [User!] }
mutation
更新mutation/schema.graphql
type Mutation { # User # 刪除用戶 removeUser ( # 用戶ID id: ID!): User }
cros
跨域訪問config/plugin.js
exports.cors = { enable: true, package: 'egg-cors' }
config/config.default.js
// cors config.cors = { origin: '*', allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH' } // csrf config.security = { csrf: { ignore: () => true } }
做爲API
服務,順手把csrf
關掉
jwt
受權config/config.default.js
// easy-mock 模擬數據地址 config.baseURL = 'https://www.easy-mock.com/mock/59801fd8a1d30433d84f198c/example' // jwt config.jwt = { jwtSecret: 'shared-secret', jwtExpire: '14 days', WhiteList: ['UserLogin'] }
util/request.js
'use strict' const _options = { dataType: 'json', timeout: 30000 } module.exports = { createAPI: (_this, url, method, data) => { let options = { ..._options, method, data } return _this.ctx.curl( `${_this.config.baseURL}${url}`, options ) } }
service/user.js
const Service = require('egg').Service const {createAPI} = require('../util/request') const jwt = require('jsonwebtoken') class UserService extends Service { // 用戶詳情 async findById(id) { const result = await createAPI(this, '/user', 'get', { id }) return result.data } // 用戶列表 async findAll() { const result = await createAPI(this, '/user/all', 'get', {}) return result.data } // 用戶登陸、jwt token async findByUsernamePassword(username, password) { const result = await createAPI(this, '/user/login', 'post', { username, password }) let user = result.data user.token = jwt.sign({uid: user.id}, this.config.jwt.jwtSecret, { expiresIn: this.config.jwt.jwtExpire }) return user } // 用戶刪除 async removeUser(id) { const result = await createAPI(this, '/user', 'delete', { id }) return result.data } } module.exports = UserService
token
驗證中間件config/config.default.js
config.middleware = ['auth', 'graphql'] config.bodyParser = { enable: true, jsonLimit: '10mb' }
開啓內置
bodyParser
服務
middleware/auth.js
const jwt = require('jsonwebtoken') module.exports = options => { return async function auth(ctx, next) { // 開啓 GraphiQL IDE 調試時,全部的請求放過 if (ctx.app.config.graphql.graphiql) { await next() return } const body = ctx.request.body if (body.operationName !== 'UserLogin') { let token = ctx.request.header['authorization'] if (token === undefined) { ctx.body = {message: '令牌爲空,請登錄獲取!'} ctx.status = 401 return } token = token.replace(/^Bearer\s/, '') try { let decoded = jwt.verify(token, ctx.app.config.jwt.jwtSecret, { expiresIn: ctx.app.config.jwt.jwtExpire }) await next() } catch (err) { ctx.body = {message: '訪問令牌鑑權無效,請從新登錄獲取!'} ctx.status = 401 } } else { await next() } } }
若是開啓GraphiQL IDE
工具,token
驗證將失效,令牌數據是寫在request.header[authorization]
,這個調試IDE
不支持設置header