Solo  当前访客:6 开始使用

Express + GraphQL构建简单的cms框架DEMO


1. 技术选择

Express + GraphQL + VUE + mysql

gitHub地址

gitee地址 

2. 实现目标

a. 实现创建cms系统及相应业务界面

b. 实现组件关联

c. 实现页面可编辑

 

3. 插件选择

a. express-graphql - 用于集成graphql到express框架中

b. vue-apollo & vue-apollo - 用于在client端调用graphql的接口

c. graphql-tools - 帮助我们使用js更方便的构建graphql schame

 

4. demo运行示意图

 

5. 具体实现分解

1. 首先分析graphql的特点

主要包含以下内容 1个schame, 5种基本类型+3中扩展类型+自定义类型, 2种主要操作query&mutation

2.  如何构建一个可用的schame,详情见第3步

如果使用官方的graphql库,示例操作如下

npm install graphql

构建schame如下

var {
  GraphQLList,
  GraphQLObjectType,
  GraphQLSchema,
  GraphQLString,
  GraphQLInt,
  GraphQLFloat,
  GraphQLEnumType,
  GraphQLNonNull,
  GraphQLInterfaceType,
  GraphQLInputObjectType
} = require('graphql');

const componentsSchema = require('./components');
const themesSchema = require('./themes');

const testSchema = require('./test');

const Query=new GraphQLObjectType({
name:'CommonQuery',
description:'通用查询',
fields:()=>(Object.assign({},
componentsSchema.query,
themesSchema.query,
testSchema.query
))
});
const Mutation=new GraphQLObjectType({
name:'CommonMutation',
description:'通用编辑',
fields:()=>(Object.assign({},
componentsSchema.mutation
))
});
const schema = new GraphQLSchema({
query: Query,
mutation: Mutation
});

module.exports = schema;

 

或者

var { graphql, buildSchema } = require('graphql');

var schema = buildSchema(type Query { hello: String });

 

再配合express-graphql

var graphqlHTTP = require('express-graphql');

app.use('/graphql2', graphqlHTTP({
schema: schemas,
graphiql: true
}));

 

测试访问 http://localhost:9004/graphql2

 

3. 基于业务特点构建通用的schame模板

a. 基于单表的增删改查

 我们已经了解到graphql实现的是类似controller/action层的功能, 那单表增删改查操作其实就是普通的数据库操作了,利用mysql库,可以轻松实现

b. 使用第2步介绍的库构建schema

首先定义一张组件表

 c. 定义针对组件表的查询、新增功能

var daoUtil = new BaseDao();
const tableName = "m_components";

const Components = new GraphQLObjectType({
name:'Components',
description:"组件对象POJO",
fields: () => {
return ({
id: {type: new GraphQLNonNull(GraphQLInt)},
name: {type: new GraphQLNonNull(GraphQLString)},
theme: {type: new GraphQLNonNull(GraphQLInt)},
frontShow: {type: GraphQLString},
backShow: {type: GraphQLString},
createAt: {type: GraphQLString},
createBy: {type: new GraphQLNonNull(GraphQLInt)},
});
}
});

module.exports = {
query: {
components_list: {
type: new GraphQLList(Components),
description: '查询组件列表',
resolve: async function(source) {
return (await daoUtil.queryAll(tableName));
}
}
},
mutation: {
components_add: {
type: Components,
description: '添加组件',
args: {
id: {type: GraphQLInt},
name: {type: new GraphQLNonNull(GraphQLString)},
theme: {type: new GraphQLNonNull(GraphQLString)},
},
resolve: async function(source, {id, name, theme}) {
var user = {
name: name,
theme: theme,
};
return await daoUtil.insert(tableName, user);
}
}
}
}

 

d. 使用自带的graphql编辑器测试

使用方法见:graphiql

4. 使用模板自动构建schame

常见的schame构建如上, 基于此,我们抽取出公共的部分, 使用模板生成对应对象的所有操作

a. 模板分析

模板中应该具备对象的可操作列表, 入参、返回

type Query {
 {{each $data}}
  {{$value.name}}_all: [{{$value.modal}}]
  {{$value.name}}_page(page: Int!,limit: Int!, where:[where_query]): {{$value.modal}}_page
  {{$value.name}}_byId(id: Int!): {{$value.modal}}
  {{$value.name}}_byField({{each $value.columns}}  {{$value.Field}}:{{$value.fieldType}}, {{/each}}): [{{$value.modal}}]
 {{/each}}
}

type Mutation {
{{each $data}}
{{$value.name}}_add( {{each $value.columns}} {{if !$value.unUserInMutation}} {{$value.Field}}:{{$value.Type}},{{/if}} {{/each}} ): {{$value.modal}}
{{$value.name}}_batch(list: [{{$value.modal}}_mutation]!): batch_result
{{$value.name}}_delete(id: Int!): batch_result
{{$value.name}}_updateById({{each $value.columns}} {{$value.Field}}:{{$value.fieldType}}, {{/each}}): {{$value.modal}}
{{/each}}
}

以上是对外提供的query和Mutation。 $data为所有对象的list

{{$value.modal}} 对应了创建的每个对象在schame中对应的可操作对象

type {{$value.modal}} {
  {{each $value.columns }}
    {{$value.Field}}:{{$value.Type}}
  {{/each}}
  {{each $value.relations relation}}
    {{relation.modal}}:{{if relation.type == 'o2o'}} {{relation.modal}} {{/if}}{{if relation.type == 'o2m'}} [{{relation.modal}}] {{/if}}
  {{/each}}
}

 {{$value.modal}}_page 对应了构建的分页对象在schame中对应的对象

type {{$value.modal}}_page {
  page: Int!,
  limit: Int!,
  list: [{{$value.modal}}],
  total: Int!
}

最后,通过以下代码封装操作

schema {
  query: Query
  mutation: Mutation
}

b. 调用模板方法渲染方法,同时构建schame对象

var gql = template(this.artPath, data);
      // console.log(gql)
      let typeDefs = `${gql}`
      let jsSchema = makeExecutableSchema({
        typeDefs,
        resolvers,
      });

5. 其他

a. 如何实现关联查询

通过定义一个配置文件, 管理对象间的关系

"m_components": [{
      "type": "o2m",
      "field": "id",
      "targetTable": "m_component_props",
      "targetField": "componentId"
    },{
      "type": "o2o",
      "field": "theme",
      "targetTable": "m_theme",
      "targetField": "id"
    }]

通过schame绑定查询关系

{{each $value.relations relation}}
    {{relation.modal}}:{{if relation.type == 'o2o'}} {{relation.modal}} {{/if}}{{if relation.type == 'o2m'}} [{{relation.modal}}] {{/if}}
  {{/each}}

b. 如何解决N+1查询问题

由于graphql会根据schame,一层层解析,最终返回结果,导致关联查询时,会出现N+1查询的问题

解决N+1查询的基本方法就是讲查询条件集中到一个sql中

在我们的范例中, 可以利用resolver中context对象在各个resolver间传递, 结合关系查询配置, 在父节点中集成查询,然后传递到子resolver中返回

resolver中接受参数如下

  • obj 上一级对象,如果字段属于根节点查询类型通常不会被使用。
  • args 可以提供在 GraphQL 查询中传入的参数。
  • context 会被提供给所有解析器,并且持有重要的上下文信息比如当前登入的用户或者数据库访问对象。
  • info 一个保存与当前查询相关的字段特定信息以及 schema 详细信息的值

 

============================完结==============

 


标题:Express + GraphQL构建简单的cms框架DEMO
作者:hugh0524
地址:https://blog.uproject.cn/articles/2019/04/03/1554276742410.html

, , , , 0 0