- 前端進(jìn)階之旅:https://interview2.poetries.top
- 公眾號(hào):「前端進(jìn)階之旅」 每天分享技術(shù)干貨
一招盲、GraphQL介紹
1.1 簡(jiǎn)介
GraphQL 是一種新的 API 的查詢語(yǔ)言鳍刷,它提供了一種更高效甸各、強(qiáng)大和靈活 API 查詢。它 是由 Facebook 開發(fā)和開源谋竖,目前由來(lái)自世界各地的大公司和個(gè)人維護(hù)兄渺。GraphQL 對(duì)你的 API 中的數(shù)據(jù)提供了一套易于理解的完整描述择浊,使得客戶端能夠準(zhǔn)確地獲得它需要的數(shù)據(jù),而且 沒有任何冗余刹碾。它彌補(bǔ)了 RESTful API(字段冗余,擴(kuò)展性差座柱、無(wú)法聚合 api迷帜、無(wú)法定義數(shù)據(jù) 類型物舒、網(wǎng)絡(luò)請(qǐng)求次數(shù)多)等不足
注意:GraphQL 是 api 的查詢語(yǔ)言,而不是數(shù)據(jù)庫(kù)戏锹。從這個(gè)意義上說冠胯,它是數(shù)據(jù)庫(kù)無(wú)關(guān)的, 而且可以在使用 API 的任何環(huán)境中有效使用景用,我們可以理解為 GraphQL 是基于 API 之上的一 層封裝涵叮,目的是為了更好,更靈活的適用于業(yè)務(wù)的需求變化
GraphQL 可以用在常見各種服務(wù)器端語(yǔ)言以及客戶端語(yǔ)言中
服務(wù)器端語(yǔ)言:C# / .NET伞插、Clojure割粮、Elixir、Erlang媚污、Go舀瓢、Groovy、Java耗美、JavaScript京髓、PHP、Python商架、 Scala堰怨、Ruby
客戶端語(yǔ)言:js、React + React Native蛇摸、Angular备图、Vue.js、Apollo Link赶袄、Native iOS揽涮、Native Android、 Scala.js
中文文檔:http://graphql.cn
GraphQL 出現(xiàn)的歷史背景
當(dāng)提起API設(shè)計(jì)的時(shí)候饿肺,大家通常會(huì)想到SOAP(一種簡(jiǎn)單的基于 XML 的協(xié)議)蒋困,RESTful 等設(shè)計(jì)方式,從 2000 年 RESTful 的理論被提出的時(shí)候敬辣,在業(yè)界引起了很大反響雪标,因?yàn)檫@種 設(shè)計(jì)理念更易于用戶的使用,所以便很快的被大家所接受溉跃。
我們知道 REST 是一種從服務(wù) 器公開數(shù)據(jù)的流行方式汰聋。當(dāng) REST 的概念被提及出來(lái)時(shí),客戶端應(yīng)用程序?qū)?shù)據(jù)的需求相 對(duì)簡(jiǎn)單喊积,而開發(fā)的速度并沒有達(dá)到今天的水平烹困。
因此 REST 對(duì)于許多應(yīng)用程序來(lái)說是非常 適合的。然而在業(yè)務(wù)越發(fā)復(fù)雜乾吻,客戶對(duì)系統(tǒng)的擴(kuò)展性有了更高的要求時(shí)髓梅,API 環(huán)境發(fā)生了巨 大的變拟蜻,RESTful 顯得心有余而力不足。比如:字段冗余枯饿,擴(kuò)展性差酝锅、無(wú)法聚合 api、無(wú)法 定義數(shù)據(jù)類型奢方、網(wǎng)絡(luò)請(qǐng)求次數(shù)多
GraphQL 的出現(xiàn)整好彌補(bǔ)了 RESTful APi 的不足
使用 GraphQL 的公司
目前已經(jīng)有很多的公司在使用 GraphQL(https://graphql.org/users/)
1.2 為什么推薦 GraphQL 而不是 RESTful API
在過去的十多年中搔扁,REST 已經(jīng)成為設(shè)計(jì) web api 的標(biāo)準(zhǔn)(雖然只是一個(gè)模糊的標(biāo)準(zhǔn))。
它提供了一些很棒的想法蟋字,比如無(wú)狀態(tài)服務(wù)器和結(jié)構(gòu)化的資源訪問稿蹲。
然而 REST api 表 現(xiàn)得過于僵化,無(wú)法跟上訪問它們的客戶的快速變化的需求
RESTful API 不足
- 擴(kuò)展性(多個(gè)終端需要返回不同的字段)鹊奖,單個(gè) RESTful 接口返回?cái)?shù)據(jù)越來(lái)越 臃腫苛聘。前端對(duì)于真正用到的字段是沒有直觀映像的,僅僅通過 url 地址忠聚,無(wú)法預(yù)測(cè)也無(wú) 法回憶返回的字段數(shù)目和字段是否有效设哗,接口返回 50 個(gè)字段,但卻只用 5 個(gè)字段两蟀,造 成字段冗余网梢,擴(kuò)展性差,單個(gè) RESTful 接口返回?cái)?shù)據(jù)越來(lái)越臃腫
- API 聚合問題赂毯,某個(gè)前端展現(xiàn)战虏,實(shí)際需要調(diào)用多個(gè)獨(dú)立的 RESTful API 才能獲 取到足夠的數(shù)據(jù),導(dǎo)致網(wǎng)絡(luò)請(qǐng)求次數(shù)多
- 前后端字段頻繁改動(dòng)欢瞪,導(dǎo)致類型不一致,錯(cuò)誤的數(shù)據(jù)類型可能會(huì)導(dǎo)致網(wǎng)站出錯(cuò) 尤其是在業(yè)務(wù)多變的場(chǎng)景中徐裸,很難在保證工程質(zhì)量的同時(shí)快速滿足業(yè)務(wù)需求
GraphQL 的優(yōu)點(diǎn)
- 吸收了 RESTful API 的特性
- 所見即所得 各種不同的前端框架和平臺(tái)可以指定自己需要的字段遣鼓。查詢的返回結(jié)果就是輸 入的查詢結(jié)構(gòu)的精確映射
客戶端可以自定義 Api 聚合
如果設(shè)計(jì)的數(shù)據(jù)結(jié)構(gòu)是從屬的,直接就能在查詢語(yǔ)句中指定;即使數(shù)據(jù)結(jié)構(gòu)是獨(dú) 立的重贺,也可以在查詢語(yǔ)句中指定上下文骑祟,只需要一次網(wǎng)絡(luò)請(qǐng)求,就能獲得資源和子 資源的數(shù)據(jù)气笙。
代碼即是文檔
GraphQL 會(huì)把 schema 定義和相關(guān)的注釋生成可視化的文檔次企,從而使得代碼的變更,直接就反映到最新的文檔上潜圃,避免 RESTful 中手工維護(hù)可能會(huì)造成代碼缸棵、 文檔不一致的問題
參數(shù)類型強(qiáng)校驗(yàn)
- RESTful 方案本身沒有對(duì)參數(shù)的類型做規(guī)定,往往都需要自行實(shí)現(xiàn)參數(shù)的校驗(yàn)機(jī)制谭期, 以確保安全堵第。
- 但 GraphQL 提供了強(qiáng)類型的 schema 機(jī)制吧凉,從而天然確保了參數(shù)類型的合法性
二、GraphQl類型系統(tǒng)
2.1 GraphQl類型
可以將GraphQL的類型系統(tǒng)分為
標(biāo)量類型
(ScalarTypes踏志,標(biāo)量類型)和其他高級(jí)數(shù)據(jù)類型
阀捅,標(biāo)量類型即可以表示最細(xì)粒度數(shù)據(jù)結(jié)構(gòu)的數(shù)據(jù)類型,可以和JavaScript的原始類型對(duì)應(yīng)
GraphQL規(guī)范目前規(guī)定支持的標(biāo)量類型有
-
Int:有符號(hào)
32
位整數(shù) --GraphQLInt
-
Float:有符號(hào)雙精度浮點(diǎn)值 --
GraphQLFloat
-
String:
UTF‐8
字符序列 --GraphQLString
-
Boolean:
true
或者false
--GraphQLBoolean
- ID(GraphQLID):ID標(biāo)量類型表示一個(gè)唯一標(biāo)識(shí)符针余,通常用以重新獲取對(duì)象或者作為緩存中的鍵饲鄙。ID類型使用和String一樣的方式序列化;然而將其定義為ID意味著并不需要可讀型圆雁。
GraphQL其他高級(jí)數(shù)據(jù)類型包括
- Object:對(duì)象(newGraphQLObjectType)
用于描述層級(jí)或者樹形數(shù)據(jù)結(jié)構(gòu)忍级。對(duì)于樹形數(shù)據(jù)結(jié)構(gòu)來(lái)說,葉子字段的類型都是標(biāo)量數(shù)據(jù)類型摸柄。幾乎所有GraphQL類型都是對(duì)象類型颤练。Object類型有一個(gè)name字段,以及一個(gè)很重要的fields字段驱负。fields字段可以描述出一個(gè)完整的數(shù)據(jù)結(jié)構(gòu)嗦玖。例如一個(gè)表示地址數(shù)據(jù)結(jié)構(gòu)的GraphQL對(duì)象為
const AddressType=newGraphQLObjectType({
name:'Address',
fields:{
street:{
type:GraphQLString
},
number:{
type:GraphQLInt
},
formatted:{
type:GraphQLString,
resolve(obj){
return obj.number+''+obj.street
}
}
}
});
-
Interface
:接口用于描述多個(gè)類型的通用字 -
Union
:聯(lián)合類型用于描述某個(gè)字段能夠支持的所有返回類型以及具體請(qǐng)求真正的返回類型 -
Enum
:枚舉用于表示可枚舉數(shù)據(jù)結(jié)構(gòu)的類型 -
InputObject
:輸入對(duì)象 -
List
:列表
列表是其他類型的封裝,通常用于對(duì)象字段的描述跃脊。例如下面PersonType類型數(shù)據(jù)的parents和children字段
const PersonType=newGraphQLObjectType({
name:'Person',
fields:()=>({
parents:{type:newGraphQLList(Person)},
children:{type:newGraphQLList(Person)},
})
})
-
Non-Null
:不能為Null
Non-Null
強(qiáng)制類型的值不能為null
宇挫,并且在請(qǐng)求出錯(cuò)時(shí)一定會(huì)報(bào)錯(cuò)±沂酰可以用于必須保證值不能為null
的字段器瘪。例如數(shù)據(jù)庫(kù)的行的id字段不能為null
const RowType=newGraphQLObjectType({
name:'Row',
fields:()=>({
id:{
type:newGraphQLNonNull(GraphQLString)
}
})
})
2.2 GraphQl查詢語(yǔ)言
GraphQL規(guī)范支持兩種操作
-
query
:僅獲取數(shù)據(jù)(fetch
)的只讀請(qǐng)求 -
mutation
:獲取數(shù)據(jù)后還有寫操作的請(qǐng)求
新版本的GraphQL還支持
subscription
,這是為了處理訂閱更新這種比較復(fù)雜的實(shí)時(shí)數(shù)據(jù)更新場(chǎng)景而設(shè)計(jì)的操作
三绘雁、Express中集成GraphQl 實(shí)現(xiàn) Server API
3.1 安裝mongodb造數(shù)據(jù)
使用mongodb
做數(shù)據(jù)庫(kù)演示橡疼,mac安裝mongodb
亮蒋,brew install mongodb-community
# 進(jìn)入mongo shell
mongo
# 創(chuàng)建數(shù)據(jù)庫(kù)
use graphql (graphql數(shù)據(jù)庫(kù)不存在會(huì)自動(dòng)創(chuàng)建)
# 創(chuàng)建nav郊楣、articlecate集合插入數(shù)據(jù)
db.nav.insert({name: "標(biāo)題1", url: "/", sort: 1, add_time: "2022-06-30"})
db.nav.insert({name: "標(biāo)題2", url: "/", sort: 1, add_time: "2022-06-30"})
db.nav.insert({name: "標(biāo)題3", url: "/", sort: 1, add_time: "2022-06-30"})
db.articlecate.insert({name: "分類1", description: "描述", keywords: "關(guān)鍵詞", status: 1})
db.articlecate.insert({name: "分類2", description: "描述", keywords: "關(guān)鍵詞", status: 1})
db.articlecate.insert({name: "分類3", description: "描述", keywords: "關(guān)鍵詞", status: 1})
或者導(dǎo)入數(shù)據(jù)庫(kù)數(shù)據(jù)
下載數(shù)據(jù)庫(kù)文件解壓并導(dǎo)入mongodb即可 https://blog.poetries.top/db/koa.zip
導(dǎo)入mongodb數(shù)據(jù)庫(kù)
mongorestore -h localhost:27017 -d koa-demo(數(shù)據(jù)庫(kù)名稱挺物,不存在會(huì)自動(dòng)創(chuàng)建) ./dump(本地?cái)?shù)據(jù)文件路徑)
3.2 express集成GraphQl
https://github.com/graphql/express-graphql
npm install express-graphql graphql--save
引入express-graphql配置中間件
app完善配置
// app.js
var express=require('express');
var DB=require('./model/db.js');
const graphqlHTTP = require('express-graphql');
const GraphQLDefaultSchema = require('./schema/default.js');
var app=express();
// 配置中間件
app.use('/graphql', graphqlHTTP({
schema: GraphQLDefaultSchema,
graphiql: true // 線上環(huán)境關(guān)閉漾月,開發(fā)環(huán)境開啟
}));
//配置路由
app.get('/',function(req,res){
res.send('hello express');
})
app.listen(3000,()=>console.log("http://localhost:3000"));
定義GraphQLSchema模型
- 新建
schema/default.js
- 定義
Schema
const DB=require('../model/db.js'); /*引入DB庫(kù)*/
const {
GraphQLObjectType,
GraphQLString,
GraphQLInt,
GraphQLSchema,
GraphQLList
} =require('graphql')
//1关斜、獲取導(dǎo)航列表 定義導(dǎo)航的schema類型
var NavSchema=new GraphQLObjectType({
name:'nav',
fields:{
_id:{
type:GraphQLString
},
title:{
type:GraphQLString
},
url:{
type:GraphQLString
},
sort:{
type:GraphQLInt
},
status:{
type:GraphQLInt
},
add_time:{
type:GraphQLString
}
}
})
var ArticleCateSchema=new GraphQLObjectType({
name:'articlecate',
fields:{
_id:{
type:GraphQLString
},
title:{
type:GraphQLString
},
description:{
type:GraphQLString
},
keywords:{
type:GraphQLString
} ,
status:{
type:GraphQLInt
}
}
})
//2波材、定義一個(gè)跟 根里面定義調(diào)用導(dǎo)航Schema類型的方法
var RootSchema=new GraphQLObjectType({
name:'root',
fields:{
oneNavList:{ //方法名稱:定義調(diào)用導(dǎo)航Schema類型的方法
type:NavSchema, //方法的類型, 方法返回的參數(shù)必須和NavSchema里面定義的類型一致
args:{id:{type:GraphQLString}}, //參數(shù)
async resolve(parent,args){ //執(zhí)行的操作
// args.id 獲取調(diào)用方法傳入的值
var id=args.id;
var navList=await DB.find('nav',{"_id":DB.getObjectId(id)});
return navList[0];
}
},
navList:{
type:GraphQLList(NavSchema),
async resolve(parent,args){
var navList=await DB.find('nav',{});
return navList;
}
},
articleCateList:{
type:GraphQLList(ArticleCateSchema),
async resolve(parent,args){
var articlecateList=await DB.find('articlecate',{});
return articlecateList;
}
},
oneArticleCateList:{
type:ArticleCateSchema,
args:{id:{type:GraphQLString}},
async resolve(parent,args){
var id=args.id;
var articlecateList=await DB.find('articlecate',{"_id":DB.getObjectId(id)});
return articlecateList[0]; //要返回一個(gè)json對(duì)象
}
}
}
})
//3沽甥、把根掛載到 GraphQLSchema
module.exports=new GraphQLSchema({
query:RootSchema
})
編寫數(shù)據(jù)庫(kù)操作方法
/**
* http://mongodb.github.io/node-mongodb-native
* http://mongodb.github.io/node-mongodb-native/3.0/api/
*/
//DB庫(kù)
var MongoDB=require('mongodb');
var MongoClient =MongoDB.MongoClient;
const ObjectID = MongoDB.ObjectID;
var Config= {
dbUrl: 'mongodb://localhost:27017/',
dbName: 'graphql' // 數(shù)據(jù)庫(kù)名
}
class Db {
static getInstance(){ /*1屯换、單例 多次實(shí)例化實(shí)例不共享的問題*/
if(!Db.instance){
Db.instance=new Db();
}
return Db.instance;
}
constructor(){
this.dbClient=''; /*屬性 放db對(duì)象*/
this.connect(); /*實(shí)例化的時(shí)候就連接數(shù)據(jù)庫(kù)*/
}
connect(){ /*連接數(shù)據(jù)庫(kù)*/
let _that=this;
return new Promise((resolve,reject)=>{
if(!_that.dbClient){ /*1杠娱、解決數(shù)據(jù)庫(kù)多次連接的問題*/
MongoClient.connect(Config.dbUrl,{ useNewUrlParser: true },(err,client)=>{
if(err){
reject(err)
}else{
_that.dbClient=client.db(Config.dbName);
resolve(_that.dbClient)
}
})
}else{
resolve(_that.dbClient);
}
})
}
/*
DB.find('user',{}) 返回所有數(shù)據(jù)
DB.find('user',{},{"title":1}) 返回所有數(shù)據(jù) 只返回一列
DB.find('user',{},{"title":1},{ 返回第二頁(yè)的數(shù)據(jù)
page:2,
pageSize:20,
sort:{"add_time":-1}
})
js中實(shí)參和形參可以不一樣 arguments 對(duì)象接收實(shí)參傳過來(lái)的數(shù)據(jù)
* */
find(collectionName,json1,json2,json3){
if(arguments.length==2){
var attr={};
var slipNum=0;
var pageSize=0;
}else if(arguments.length==3){
var attr=json2;
var slipNum=0;
var pageSize=0;
}else if(arguments.length==4){
var attr=json2;
var page=parseInt(json3.page) ||1;
var pageSize=parseInt(json3.pageSize)||20;
var slipNum=(page-1)*pageSize;
if(json3.sort){
var sortJson=json3.sort;
}else{
var sortJson={}
}
}else{
console.log('傳入?yún)?shù)錯(cuò)誤')
}
return new Promise((resolve,reject)=>{
this.connect().then((db)=>{
//var result=db.collection(collectionName).find(json);
var result =db.collection(collectionName).find(json1,{fields:attr}).skip(slipNum).limit(pageSize).sort(sortJson);
result.toArray(function(err,docs){
if(err){
reject(err);
return;
}
resolve(docs);
})
})
})
}
update(collectionName,json1,json2){
return new Promise((resolve,reject)=>{
this.connect().then((db)=>{
//db.user.update({},{$set:{}})
db.collection(collectionName).updateOne(json1,{
$set:json2
},(err,result)=>{
if(err){
reject(err);
}else{
resolve(result);
}
})
})
})
}
insert(collectionName,json){
return new Promise((resolve,reject)=>{
this.connect().then((db)=>{
db.collection(collectionName).insertOne(json,function(err,result){
if(err){
reject(err);
}else{
resolve(result);
}
})
})
})
}
remove(collectionName,json){
return new Promise((resolve,reject)=>{
this.connect().then((db)=>{
db.collection(collectionName).removeOne(json,function(err,result){
if(err){
reject(err);
}else{
resolve(result);
}
})
})
})
}
getObjectId(id){ /*mongodb里面查詢 _id 把字符串轉(zhuǎn)換成對(duì)象*/
return new ObjectID(id);
}
//統(tǒng)計(jì)數(shù)量的方法
count(collectionName,json){
return new Promise((resolve,reject)=> {
this.connect().then((db)=> {
var result = db.collection(collectionName).count(json);
result.then(function (count) {
resolve(count);
}
)
})
})
}
}
module.exports=Db.getInstance();
打開本地調(diào)試
四挽牢、Koa中集成GraphQl實(shí)現(xiàn) Server API
下載數(shù)據(jù)庫(kù)文件解壓并導(dǎo)入mongodb即可 https://blog.poetries.top/db/koa.zip
-
導(dǎo)入mongodb數(shù)據(jù)庫(kù)
mongorestore -h localhost:27017 -d koa-demo(數(shù)據(jù)庫(kù)名稱,不存在會(huì)自動(dòng)創(chuàng)建) ./dump(本地?cái)?shù)據(jù)文件路徑)
- 導(dǎo)出mongodb數(shù)據(jù)庫(kù)
mongodump -h localhost:27017 -d test(數(shù)據(jù)庫(kù)名稱) -o ./dump
npm install graphql koa-graphql koa-mount --save
實(shí)現(xiàn)導(dǎo)航列表API摊求、文章分類API禽拔、文章列表API、文章詳情API 、文章列表分頁(yè)查詢API奏赘、以及文章列表關(guān)聯(lián)文章分類實(shí)現(xiàn)聚合API
4.1 app完善配置
// app.js
var Koa=require('koa');
var router = require('koa-router')();
const mount = require('koa-mount');
const graphqlHTTP = require('koa-graphql');
var GraphQLDefaultSchema=require('./schema/default.js')
const DB=require('./model/db.js');
var app=new Koa();
//配置中間件
app.use(mount('/graphql', graphqlHTTP({
schema: GraphQLDefaultSchema,
graphiql: true
})));
router.get('/',async (ctx)=>{
ctx.body="首頁(yè)";
})
router.get('/getNavList',async (ctx)=>{
var navList=await DB.find('nav',{});
ctx.body=navList;
})
app.use(router.routes()); /*啟動(dòng)路由*/
app.use(router.allowedMethods());
app.listen(4000, ()=>console.log('http://localhost:4000'));
4.2 定義schema模型
// schema/default.js
const DB=require('../model/db.js');
//文章分類api接口 //文章列表api接口 (分頁(yè)) //文章詳情api接口(api聚合 獲取分類信息)
const {
GraphQLObjectType,
GraphQLString,
GraphQLInt,
GraphQLFloat,
GraphQLList,
GraphQLSchema,
GraphQLID
}=require('graphql')
//1寥闪、定義導(dǎo)航的schema
var NavSchema=new GraphQLObjectType({
name:'nav',
fields:{
_id:{
type:GraphQLString
},
title:{
type:GraphQLString
},url:{
type:GraphQLString
},
sort:{
type:GraphQLInt
},
status:{
type:GraphQLString
},
add_time:{
type:GraphQLString
}
}
})
//定義文章分類的schema
var ArticleCateSchema=new GraphQLObjectType({
name:'articlecate',
fields:{
_id:{type:GraphQLString},
title:{type:GraphQLString},
description:{ type: GraphQLString },
keywords:{ type: GraphQLInt },
pid:{type:GraphQLInt},
add_time:{ type: GraphQLString },
status:{ type: GraphQLInt }
}
})
//定義文章的schema
var ArticleSchema=new GraphQLObjectType({
name:'article',
fields:{
_id:{type:GraphQLID},
pid:{type:GraphQLID},
title:{ type: GraphQLString },
author:{ type: GraphQLString },
status:{type:GraphQLInt},
is_best:{ type: GraphQLInt },
is_hot:{ type: GraphQLInt },
is_new:{ type: GraphQLInt },
keywords:{ type: GraphQLString },
description:{ type: GraphQLString },
content:{ type: GraphQLString },
sort:{ type: GraphQLInt },
// 聚合查詢文章分類信息
cateInfo:{
type:ArticleCateSchema,
async resolve(parent,args){
// parent.pid 當(dāng)前新聞的分類id
console.log(parent);
var cateResult=await DB.find('articlecate',{"_id":DB.getObjectId(parent.pid)});
return cateResult[0];
}
}
}
})
//訂單商品的Schema (order_item)
var OrderItem=new GraphQLObjectType({
name:'orderitem',
fields:{
uid:{ type: GraphQLID },
order_id: { type: GraphQLID },
product_title: { type: GraphQLString },
product_id: { type: GraphQLID },
product_img: { type: GraphQLString },
product_price: { type: GraphQLFloat },
product_num: { type: GraphQLInt },
add_time: {
type: GraphQLString
}
}
})
//訂單的Schema
var OrderSchema=new GraphQLObjectType({
name:'order',
fields:{
_id:{type:GraphQLID},
uid: { type:GraphQLID},
all_price: { type: GraphQLInt },
order_id: { type: GraphQLInt },
name: { type: GraphQLString },
phone: { type: GraphQLString },
address: { type: GraphQLString },
zipcode: { type: GraphQLString },
pay_status:{ type: GraphQLInt}, // 支付狀態(tài): 0 表示未支付 1 已經(jīng)支付
pay_type:{type: GraphQLString}, // 支付類型: alipay wechat
order_status: { // 訂單狀態(tài): 0 已下單 1 已付款 2 已配貨 3、發(fā)貨 4磨淌、交易成功 5疲憋、退貨 6、取消
type: GraphQLInt
},
add_time: {
type: GraphQLString
},
// 聚合查詢訂單關(guān)聯(lián)的商品列表信息
orderItems:{
type:GraphQLList(OrderItem),
async resolve(parent,args){
//獲取當(dāng)前訂單對(duì)應(yīng)的商品 parent._id就是objectId
var orderItemList=await DB.find('order_item',{"order_id":parent._id});
return orderItemList;
}
}
}
})
//2梁只、定義一個(gè)根 配置調(diào)用Schema的方法
var RootSchema=new GraphQLObjectType({
name:'root',
fields:{
navList:{
type:GraphQLList(NavSchema),
async resolve(parent,args){
var navList=await DB.find('nav',{});
return navList;
}
},
oneNavList:{
type:NavSchema,
args:{
_id:{
type:GraphQLString
},
status:{
type:GraphQLString
}
},
async resolve(parent,args){
var oneNavList=await DB.find('nav',{"_id":DB.getObjectId(args._id),"status":args.status});
return oneNavList[0];
}
},
articleCateList:{
type:GraphQLList(ArticleCateSchema),
async resolve(parent,args){
var articlecateList=await DB.find('articlecate',{});
return articlecateList;
}
},
articleList:{
type:GraphQLList(ArticleSchema),
args:{
page:{
type:GraphQLInt
},
pageSize:{
type:GraphQLInt
}
},
// 分頁(yè)查詢文章列表
async resolve(parent,args){
var page=args.page||1;
var pageSize=args.pageSize||5;
console.log(page,pageSize);
var articleList=await DB.find('article',{},{},{
page,
pageSize:pageSize,
sort:{"add_time":-1}
});
return articleList;
}
},
// 訂單列表
orderList:{
type:GraphQLList(OrderSchema),
args:{
page:{
type:GraphQLInt
}
},
async resolve(parent,args){
var page=args.page || 1;
var orderList=await DB.find('order',{},{},{
page,
pageSize:3
});
return orderList;
}
},
// 單個(gè)訂單信息
oneOrderList:{
type:OrderSchema,
args:{
_id:{
type:GraphQLID
}
},
async resolve(parent,args){
var orderList=await DB.find('order',{"_id":DB.getObjectId(args._id)});
return orderList[0];
}
}
}
})
//3缚柳、把查詢的根 掛載到GraphQLSchema
module.exports=new GraphQLSchema({
query:RootSchema
})
4.3 編寫數(shù)據(jù)庫(kù)操作方法
// model/db.js
/**
* http://mongodb.github.io/node-mongodb-native
* http://mongodb.github.io/node-mongodb-native/3.0/api/
*/
//DB庫(kù)
var MongoDB=require('mongodb');
var MongoClient =MongoDB.MongoClient;
const ObjectID = MongoDB.ObjectID;
var Config= {
url: 'mongodb://localhost:27017',
dbName: 'koa-demo'
}
class Db{
static getInstance(){ /*1、單例 多次實(shí)例化實(shí)例不共享的問題*/
if(!Db.instance){
Db.instance=new Db();
}
return Db.instance;
}
constructor(){
this.dbClient=''; /*屬性 放db對(duì)象*/
this.connect(); /*實(shí)例化的時(shí)候就連接數(shù)據(jù)庫(kù)*/
}
connect(){ /*連接數(shù)據(jù)庫(kù)*/
let _that=this;
return new Promise((resolve,reject)=>{
if(!_that.dbClient){ /*1搪锣、解決數(shù)據(jù)庫(kù)多次連接的問題*/
MongoClient.connect(Config.dbUrl,{ useNewUrlParser: true },(err,client)=>{
if(err){
reject(err)
}else{
_that.dbClient=client.db(Config.dbName);
resolve(_that.dbClient)
}
})
}else{
resolve(_that.dbClient);
}
})
}
/*
DB.find('user',{}) 返回所有數(shù)據(jù)
DB.find('user',{},{"title":1}) 返回所有數(shù)據(jù) 只返回一列
DB.find('user',{},{"title":1},{ 返回第二頁(yè)的數(shù)據(jù)
page:2,
pageSize:20,
sort:{"add_time":-1}
})
js中實(shí)參和形參可以不一樣 arguments 對(duì)象接收實(shí)參傳過來(lái)的數(shù)據(jù)
* */
find(collectionName,json1,json2,json3){
if(arguments.length==2){
var attr={};
var slipNum=0;
var pageSize=0;
}else if(arguments.length==3){
var attr=json2;
var slipNum=0;
var pageSize=0;
}else if(arguments.length==4){
var attr=json2;
var page=parseInt(json3.page) ||1;
var pageSize=parseInt(json3.pageSize)||20;
var slipNum=(page-1)*pageSize;
if(json3.sort){
var sortJson=json3.sort;
}else{
var sortJson={}
}
}else{
console.log('傳入?yún)?shù)錯(cuò)誤')
}
return new Promise((resolve,reject)=>{
this.connect().then((db)=>{
//var result=db.collection(collectionName).find(json);
var result =db.collection(collectionName).find(json1,{fields:attr}).skip(slipNum).limit(pageSize).sort(sortJson);
result.toArray(function(err,docs){
if(err){
reject(err);
return;
}
resolve(docs);
})
})
})
}
update(collectionName,json1,json2){
return new Promise((resolve,reject)=>{
this.connect().then((db)=>{
//db.user.update({},{$set:{}})
db.collection(collectionName).updateOne(json1,{
$set:json2
},(err,result)=>{
if(err){
reject(err);
}else{
resolve(result);
}
})
})
})
}
insert(collectionName,json){
return new Promise((resolve,reject)=>{
this.connect().then((db)=>{
db.collection(collectionName).insertOne(json,function(err,result){
if(err){
reject(err);
}else{
resolve(result);
}
})
})
})
}
remove(collectionName,json){
return new Promise((resolve,reject)=>{
this.connect().then((db)=>{
db.collection(collectionName).removeOne(json,function(err,result){
if(err){
reject(err);
}else{
resolve(result);
}
})
})
})
}
getObjectId(id){ /*mongodb里面查詢 _id 把字符串轉(zhuǎn)換成對(duì)象*/
return new ObjectID(id);
}
//統(tǒng)計(jì)數(shù)量的方法
count(collectionName,json){
return new Promise((resolve,reject)=> {
this.connect().then((db)=> {
var result = db.collection(collectionName).count(json);
result.then(function (count) {
resolve(count);
}
)
})
})
}
}
module.exports=Db.getInstance();
啟動(dòng)服務(wù)
4.4 聚合查詢
聚合查詢文章分類信息秋忙,分類信息的方式要放在article的schema里面,這樣才能聚合查詢到
聚合查詢結(jié)果
查詢訂單构舟,聚合查詢訂單關(guān)聯(lián)的商品信息返回灰追,實(shí)現(xiàn)類似以下效果
// schema/default.js
//訂單商品的Schema (order_item)
var OrderItem=new GraphQLObjectType({
name:'orderitem',
fields:{
uid:{ type: GraphQLID },
order_id: { type: GraphQLID },
product_title: { type: GraphQLString },
product_id: { type: GraphQLID },
product_img: { type: GraphQLString },
product_price: { type: GraphQLFloat },
product_num: { type: GraphQLInt },
add_time: {
type: GraphQLString
}
}
})
//訂單的Schema
var OrderSchema=new GraphQLObjectType({
name:'order',
fields:{
_id:{type:GraphQLID},
uid: { type:GraphQLID},
all_price: { type: GraphQLInt },
order_id: { type: GraphQLInt },
name: { type: GraphQLString },
phone: { type: GraphQLString },
address: { type: GraphQLString },
zipcode: { type: GraphQLString },
pay_status:{ type: GraphQLInt}, // 支付狀態(tài): 0 表示未支付 1 已經(jīng)支付
pay_type:{type: GraphQLString}, // 支付類型: alipay wechat
order_status: { // 訂單狀態(tài): 0 已下單 1 已付款 2 已配貨 3、發(fā)貨 4狗超、交易成功 5弹澎、退貨 6、取消
type: GraphQLInt
},
add_time: {
type: GraphQLString
},
// 聚合查詢訂單關(guān)聯(lián)的商品列表信息
orderItems:{
type:GraphQLList(OrderItem),
async resolve(parent,args){
//獲取當(dāng)前訂單對(duì)應(yīng)的商品 parent._id就是objectId
var orderItemList=await DB.find('order_item',{"order_id":parent._id});
return orderItemList;
}
}
}
})
// 定義一個(gè)根 配置調(diào)用Schema的方法
var RootSchema=new GraphQLObjectType({
name:'root',
fields:{
orderList:{
type:GraphQLList(OrderSchema),
args:{
page:{
type:GraphQLInt
}
},
async resolve(parent,args){
var page=args.page || 1;
var orderList=await DB.find('order',{},{},{
page,
pageSize:3
});
return orderList;
}
},
oneOrderList:{
type:OrderSchema,
args:{
_id:{
type:GraphQLID
}
},
async resolve(parent,args){
var orderList=await DB.find('order',{"_id":DB.getObjectId(args._id)});
return orderList[0];
}
}
}
})
查詢訂單詳情
需要哪些字段努咐,就返回哪些字段苦蒿,編輯器會(huì)自定提示
4.5 分頁(yè)查詢
//定義文章分類的schema
var ArticleCateSchema=new GraphQLObjectType({
name:'articlecate',
fields:{
_id:{type:GraphQLString},
title:{type:GraphQLString},
description:{ type: GraphQLString },
keywords:{ type: GraphQLInt },
pid:{type:GraphQLInt},
add_time:{ type: GraphQLString },
status:{ type: GraphQLInt }
}
})
//定義文章的schema
var ArticleSchema=new GraphQLObjectType({
name:'article',
fields:{
_id:{type:GraphQLID},
pid:{type:GraphQLID},
title:{ type: GraphQLString },
author:{ type: GraphQLString },
status:{type:GraphQLInt},
is_best:{ type: GraphQLInt },
is_hot:{ type: GraphQLInt },
is_new:{ type: GraphQLInt },
keywords:{ type: GraphQLString },
description:{ type: GraphQLString },
content:{ type: GraphQLString },
sort:{ type: GraphQLInt },
// 聚合查詢文章分類信息
cateInfo:{
type:ArticleCateSchema,
async resolve(parent,args){
// parent.pid 當(dāng)前新聞的分類id
console.log(parent);
var cateResult=await DB.find('articlecate',{"_id":DB.getObjectId(parent.pid)});
return cateResult[0];
}
}
}
})
//2、定義一個(gè)根 配置調(diào)用Schema的方法
var RootSchema=new GraphQLObjectType({
name:'root',
fields:{
articleCateList:{
type:GraphQLList(ArticleCateSchema),
async resolve(parent,args){
var articlecateList=await DB.find('articlecate',{});
return articlecateList;
}
},
articleList:{
type:GraphQLList(ArticleSchema),
args:{
page:{
type:GraphQLInt
},
pageSize:{
type:GraphQLInt
}
},
// 分頁(yè)查詢文章列表
async resolve(parent,args){
var page=args.page||1;
var pageSize=args.pageSize||5;
console.log(page,pageSize);
var articleList=await DB.find('article',{},{},{
page,
pageSize:pageSize,
sort:{"add_time":-1}
});
return articleList;
}
},
}
})
4.6 實(shí)現(xiàn)數(shù)據(jù)增加渗稍、修改佩迟、刪除
// scehma/default.js
//增加 修改 刪除
// 定義根MutationRoot實(shí)現(xiàn)增刪改
var MutationSchema=new GraphQLObjectType({
name:"mutation",
fields:{
addNav:{
type:NavSchema,
args:{
title: {type: new GraphQLNonNull(GraphQLString)}, //表示title 和 url是必傳字段
url: {type: GraphQLNonNull(GraphQLString)},
sort: {type: GraphQLInt},
status: {type: GraphQLString},
add_time: {type: GraphQLString}
},
async resolve(parent, args) {
var result = await DB.insert('nav', {title:args.title,
url:args.url,
sort:args.sort,
status:args.status,
add_time:new Date().getTime()
});
console.log(result.ops[0]);
return result.ops[0];
}
},
editNav:{
type:NavSchema,
args:{
_id:{type: new GraphQLNonNull(GraphQLString)},
title: {type: new GraphQLNonNull(GraphQLString)}, //表示title 和 url是必傳字段
url: {type: GraphQLNonNull(GraphQLString)},
sort: {type: GraphQLInt},
status: {type: GraphQLString},
add_time: {type: GraphQLString}
},
async resolve(parent, args) {
var result = await DB.update('nav', {"_id":DB.getObjectId(args._id)},{title:args.title,
url:args.url,
sort:args.sort,
status:args.status,
add_time:new Date().getTime()
});
// console.log(result);
return {
_id:args._id,
title:args.title,
url:args.url,
sort:args.sort,
status:args.status,
add_time:new Date().getTime()
}
}
}
,
deleteNav:{
type:NavSchema,
args:{
_id:{type: new GraphQLNonNull(GraphQLString)},
},
async resolve(parent, args) {
var oneNavList = await DB.find('nav', { "_id": DB.getObjectId(args._id)});
var deleteResult = await DB.remove('nav', {"_id":DB.getObjectId(args._id)});
console.log(deleteResult.result.n);
if(deleteResult.result.n){
return oneNavList[0];
}else{
return {}
}
}
}
}
})
// 掛載到GraphQLSchema
module.exports=new GraphQLSchema({
// query:RootSchema,
mutation:MutationSchema
})
- 新增
可以看到必填字段不填會(huì)提示
再次查詢列表
- 修改
- 刪除
五、Vue中使用GraphQl
5.1 使用graphQl簡(jiǎn)單查詢
安裝
- 找到Vue中集成GraphQl的文檔
- 安裝相應(yīng)的模塊
ApolloBoost是一種零配置開始使用ApolloClient的方式竿屹。它包含一些實(shí)用的默認(rèn)值报强,例如我們推薦的InMemoryCache和HttpLink,它非常適合用于快速啟動(dòng)開發(fā)拱燃。將它與vue-apollo和graphql一起安裝:
npm install vue-apollo graphql apollo-boost --save
- 在
src/main.js
中引入apollo-boost
模塊并實(shí)例化ApolloClient
import ApolloClient from'apollo-boost'
const apolloClient = newApolloClient({
//你需要在這里使用絕對(duì)路徑
uri:'http://118.123.14.36:3002/graphql'
})
可以打開 http://118.123.14.36:3002/graphql 在控制臺(tái)查看查詢結(jié)果
- 在
src/main.js
配置vue-apollo
插件
import VueApollofrom'vue-apollo'
Vue.use(VueApollo);
- 創(chuàng)建
Apollo provider
Provider保存了可以在接下來(lái)被所有子組件使用的Apollo客戶端實(shí)例
const apolloProvider = newVueApollo({
defaultClient:apolloClient
})
使用apollo Provider
選項(xiàng)將它添加到你的應(yīng)用程序
new Vue({
el:'#app',
apolloProvider,
render:h=>h(App)
})
簡(jiǎn)單查詢
組件加載的時(shí)候就會(huì)去服務(wù)器請(qǐng)求數(shù)據(jù)秉溉,請(qǐng)求的數(shù)據(jù)會(huì)放在
navList
這個(gè)屬性上面,在模板中可以直接使用當(dāng)前屬性
import gql from'graphql-tag';
export default{
data(){
return { msg: '我是一個(gè) home 組件' }
},
apollo: {
// 簡(jiǎn)單的查詢扼雏,將更新 'hello' 這個(gè) vue 屬性
navList: gql`query {
navList {
title
}
}`
},
}
另一種寫法:
import gql from 'graphql-tag';
export default{
data(){
return {
msg:'我是一個(gè) home 組件'
}
},
// Apollo 具體選項(xiàng)
apollo: {
// // 帶參數(shù)的查詢
// ping: {
// // gql 查詢
// query: gql`query PingMessage($message: String!) {
// ping(message: $message)
// }`,
// // 靜態(tài)參數(shù)
// variables: {
// message: 'Meow',
// },
// },
},
apollo: {
// 注意方法名稱 和 查詢的名稱對(duì)應(yīng)
navList(){
return {
query:gql`query {
navList {
title
}
}`
}
}
}
}
完整例子
<template>
<div class="news">
<h1>{{ msg }}</h1>
<ul>
<li v-for="(item,index) of navList" :key="index">
{{item.title}}
</li>
</ul>
<br>
<hr>
<br>
<ul>
<li v-for="(item, index) of articleList" :key="index">
{{item.title}}---{{item.status}}--{{item._id}}
</li>
</ul>
</div>
</template>
<script>
import gql from 'graphql-tag';
export default {
name: 'app',
data(){
return{
msg:'我是一個(gè)首頁(yè)頁(yè)面'
}
},
apollo: {
// 簡(jiǎn)單的查詢坚嗜,將更新 'hello' 這個(gè) vue 屬性
navList: gql`{
navList{
title
}
}`,
articleList:gql`{
articleList{
title,
status,
_id
}
}`
}
}
</script>
高級(jí)查詢
<div class="news">
<h1>{{ msg }}</h1>
<ul>
<li v-for="(item,key) of articleList" v-bind:key="key">
{{item.title}}---{{item.status}}
</li>
</ul>
<button @click="getData()">
點(diǎn)擊按鈕觸發(fā)事件請(qǐng)求graphQl接口
</button>
{{navList}}
</div>
邏輯
import gql from 'graphql-tag';
var navListGql=gql`{
navList{
title
}
}`;
export default {
name: 'app',
data(){
return{
msg:'我是一個(gè)新聞頁(yè)面',
navList:[]
}
},
apollo:{
// articleList:gql`{
// articleList{
// title,
// status
// }
// }`
// 把請(qǐng)求的數(shù)據(jù)賦值給articleList
articleList:{
query:gql`query articleList($page:Int!,$pageSize:Int!){
articleList(page:$page,pageSize:$pageSize){
title,
status
}
}`,
variables:{
page:2,
pageSize:10
}
}
},
methods:{
getData(){
this.$apollo.addSmartQuery('navList',{
query:navListGql,
result(response){
console.log(response);
},error(err){
console.log(err);
}
})
}
}
}
Vue GraphQl 傳參查詢
<template>
<div class="article">
<h1>{{ msg }}</h1>
<button @click="getData()">獲取文章數(shù)據(jù)</button>
<ul>
<li v-for="(item,key) of articleList" v-bind:key="key">
{{item.title}}
</li>
</ul>
</div>
</template>
<script>
import gql from 'graphql-tag';
var articleListGql=gql`query articleList($page:Int!,$pageSize:Int!){
articleList(page:$page,pageSize:$pageSize){
title
}
}`;
export default {
name: 'app',
data(){
return{
msg:'article頁(yè)面',
articleList:[]
}
},
methods:{
getData(){
this.$apollo.addSmartQuery('articleList',{
query:articleListGql,
variables:{
page:2,
pageSize:8
},
result(response){
console.log(response)
},error(err){
console.log(err)
}
})
}
}
}
</script>
5.2 使用graphQl增加修改刪除
服務(wù)器端接口
<template>
<div class="news">
<h1>導(dǎo)航的增加修改刪除</h1>
<div class="navForm">
導(dǎo)航名稱:<input v-model="navJson.title" type="text" /> <br><br>
導(dǎo)航鏈接: <input v-model="navJson.url" type="text" /><br><br>
<button @click="doAdd()">提交數(shù)據(jù)</button>
<button @click="doEdit()">修改數(shù)據(jù)</button>
<button @click="doDele()">刪除數(shù)據(jù)</button>
</div>
</div>
</template>
<script>
import gql from 'graphql-tag';
var navMutationAddGql=gql`mutation($title:String!,$url:String!){
addNav(title:$title,url:$url){
title
}
}`;
var navMutationEditGql=gql`mutation($id:String!,$title:String!,$url:String!){
editNav(_id:$id,title:$title,url:$url){
title
}
}`;
var navMutationDelGql=gql`mutation($id:String!){
deleteNav(_id:$id){
title
}
}`;
export default {
name: 'app',
data(){
return{
navJson:{
title:"",
url:""
}
}
},
methods:{
// 提交表單
doAdd(){
// eslint-disable-next-line no-console
console.log(this.navJson.title);
this.$apollo.mutate({
mutation:navMutationAddGql,
variables: {
title: this.navJson.title,
url:this.navJson.url,
}
}).then((response)=>{
console.log(response);
}).catch((err)=>{
console.log(err);
})
},
// 修改數(shù)據(jù)
doEdit(){
this.$apollo.mutate({
mutation:navMutationEditGql,
variables: {
id:"62beaf16323cb708d06580ce",
title: this.navJson.title,
url:this.navJson.url,
}
}).then((response)=>{
console.log(response);
}).catch((err)=>{
console.log(err);
})
},
doDele(){
this.$apollo.mutate({
mutation:navMutationDelGql,
variables: {
id:"62beaf50323cb708d06580d0",
}
}).then((response)=>{
console.log(response);
}).catch((err)=>{
console.log(err);
})
}
}
}
</script>
可以看到新增成功效果
5.3 上拉分頁(yè)加載更多
npm i vue-infinite-scroll -S
// main.js配置
//配置上拉分頁(yè)加載更多
var infiniteScroll = require('vue-infinite-scroll');
Vue.use(infiniteScroll);
方法1:數(shù)據(jù)拼接
<template>
<div class="article">
<h1>{{ msg }}</h1>
<div v-infinite-scroll="loadMore" infinite-scroll-disabled="busy" infinite-scroll-distance="10">
<ul>
<li v-for="(item,key) of articleListData" v-bind:key="key">{{item.title}}</li>
</ul>
</div>
</div>
</template>
<script>
import gql from "graphql-tag";
var articleListGql = gql`
query articleList($page: Int!, $pageSize: Int!) {
articleList(page: $page, pageSize: $pageSize) {
title
}
}
`;
export default {
name: "app",
data() {
return {
msg: "上拉分頁(yè)加載更多",
articleList: [],
articleListData: [] /*實(shí)際要循環(huán)的數(shù)據(jù)*/,
page: 1,
busy: false
};
},
methods: {
loadMore() {
this.$apollo.addSmartQuery("articleList", {
query: articleListGql,
variables: {
page: this.page,
pageSize: 8
},
result(response) {
console.log(response);
this.articleListData = this.articleListData.concat(
response.data.articleList
);
this.page++;
if (response.data.articleList < 8) {
this.busy = true; //沒有數(shù)據(jù)禁用上拉分頁(yè)加載更多
}
},
error(err) {
console.log(err);
}
});
}
}
};
</script>
<style scoped>
li {
line-height: 4;
}
</style>
方法2:使用 fetchMore 實(shí)現(xiàn)分頁(yè)(推薦)
https://vue-apollo.netlify.app/zh-cn/guide/apollo/pagination.html
<template>
<div class="article">
<h1>{{ msg }}</h1>
<div v-infinite-scroll="loadMore" infinite-scroll-disabled="busy" infinite-scroll-distance="10">
<ul>
<li v-for="(item,key) of articleList" v-bind:key="key">{{item.title}}</li>
</ul>
</div>
</div>
</template>
<script>
import gql from "graphql-tag";
var articleListGql = gql`
query articleList($page: Int!, $pageSize: Int!) {
articleList(page: $page, pageSize: $pageSize) {
title
}
}
`;
export default {
name: "app",
data() {
return {
msg: "上拉分頁(yè)加載更多",
articleList: [],
page: 1,
busy: false
};
},
apollo: {
articleList() {
return {
// GraphQL 查詢
query: articleListGql,
// 初始變量
variables: {
page: this.page,
pageSize: 5
}
};
}
},
methods: {
loadMore() {
this.page++;
this.$apollo.queries.articleList.fetchMore({
// 新的變量
variables: {
page: this.page,
pageSize: 5
},
// 用新數(shù)據(jù)轉(zhuǎn)換之前的結(jié)果
updateQuery: (previousResult, { fetchMoreResult }) => {
// eslint-disable-next-line no-console
console.log(fetchMoreResult);
return {
articleList: [
...previousResult.articleList,
...fetchMoreResult.articleList
]
};
}
});
}
}
};
</script>
<style scoped>
li {
line-height: 4;
}
</style>
分頁(yè)效果
項(xiàng)目例子完整代碼下載地址 https://blog.poetries.top/assets/graphql-code.zip
六夯膀、文檔
- 中文文檔:http://graphql.cn
- Github: https://github.com/facebook/graphql
- vue-apollo文檔:https://vue-apollo.netlify.app/zh-cn/guide/apollo.html
本文由mdnice多平臺(tái)發(fā)布