• GraphQL & Go,graphql基本知识,go-graphql使用


    1 RESTful

    1.1 基本概念

    概念:Restful就是一个资源定位及资源操作的风格。不是标准也不是协议,只是一种风格。

    功能

    资源:互联网所有的事物都可以被抽象为资源.每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。一般来说,数据库中的表都是同种记录的"集合"(collection),所以API中的名词也应该使用复数。

    资源操作:使用POST、DELETE、PUT、GET,使用不同方法对资源进行操作

    1.2 HTTP VS REST

    传统方式操作资源 :通过不同的参数来实现不同的效果!方法单一,post 和 get

    http://127.0.0.1/item/queryItem?id=1 查询,GET
    http://127.0.0.1/item/addItem新增,POST
    http://127.0.0.1/item/updateItem 更新,POST
    http://127.0.0.1/item/deleteItem?id=1 删除,GET或POST
    
    • 1
    • 2
    • 3
    • 4

    使用RESTful操作资源 :可以通过不同的请求方式来实现不同的效果

    http://127.0.0.1/item/1 查询,GET
    http://127.0.0.1/item 新增,POST
    http://127.0.0.1/item 更新,PUT
    http://127.0.0.1/item/1 删除,DELETE
    
    • 1
    • 2
    • 3
    • 4

    举例来说,有一个API提供动物园(zoo)的信息,还包括各种动物和雇员的信息,则它的路径应该设计成下面这样。

    https://api.example.com/v1/zoos
    https://api.example.com/v1/animals
    https://api.example.com/v1/employees
    
    • 1
    • 2
    • 3

    下面是一些例子。

    GET /zoos:列出所有动物园
    POST /zoos:新建一个动物园
    GET /zoos/ID:获取某个指定动物园的信息
    PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)
    PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)
    DELETE /zoos/ID:删除某个动物园
    GET /zoos/ID/animals:列出某个指定动物园的所有动物
    DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物
    
     URL组成 : 协议://IP地址:端口/路径?参数1=1&参数2=2... 
     传统带参访问方式:
         接口:/employees  
         访问路径 http://localhost:8080/employee?id=1&name=tom&age=23
         
     使用参数路径访问方式:
         接口:/employees/{id}/{name}/{age}   
         http://localhost:8080/employee/12/tom/23
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在这里插入图片描述


    2 GraphQL

    学习中文官网


    在这里插入图片描述

    背景:REST接口无法满足变化的需求,一遍就得重新改

    GraphQL 服务:每个类型的每个字段都由一个 resolver 函数支持,当一个字段被执行时,相应的 resolver 被调用以产生下一个值。如果字段产生标量值,例如字符串或数字,则执行完成。如果一个字段产生一个对象,则该查询将继续执行该对象对应字段的解析器,直到生成标量值

    例如这个查询:

    {
      me {
        name
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    会产生这样的JSON结果:

    {
      "me": {
        "name": "Luke Skywalker"
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.1 Fields 字段:请求参数

    如果类型是object,就可以无限套娃

    {
      hero {
        name
        # 查询可以有备注!
        friends {
          name
        }
      }
    }
    
    {
      "data": {
        "hero": {
          "name": "R2-D2",
          "friends": [
            {
              "name": "Luke Skywalker"
            },
            {
              "name": "Han Solo"
            },
            {
              "name": "Leia Organa"
            }
          ]
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    2.2 参数

    2.2.1 给字段传递参数的能力,定参

    在类似 REST 的系统中,你只能传递一组简单参数 —— 请求中的 query 参数和 URL 段。但是在 GraphQL 中,每一个字段和嵌套对象都能有自己的一组参数,从而使得 GraphQL 可以完美替代多次 API 获取请求

    GraphQL 自带一组默认标量类型:

    • Int:有符号 32 位整数。
    • Float:有符号双精度浮点值。
    • String:UTF‐8 字符序列。
    • Booleantrue 或者 false
    • ID:ID 标量类型表示一个唯一标识符,通常用以重新获取对象或者作为缓存中的键。ID 类型使用和 String 一样的方式序列化;然而将其定义为 ID 意味着并不需要人类可读型。
    {
      human(id: "1000") {
        name
        height
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2.2.2 变量:动态的参数

    1. 使用 $variableName 替代查询中的静态值。
    2. 声明 $variableName 为查询接受的变量之一。
    3. variableName: value 通过传输专用(通常是 JSON)的分离的变量字典中
    # { "graphql": true, "variables": { "episode": JEDI } }
    query HeroNameAndFriends($episode: Episode) {
      hero(episode: $episode) {
        name
        friends {
          name
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2.2.3 元字段

    你并不知道你将从 GraphQL 服务获得什么类型,这时候你就需要一些方法在客户端来决定如何处理这些数据。GraphQL 允许你在查询的任何位置请求 __typename,一个元字段,以获得那个位置的对象类型名称

    {
      search(text: "an") {
        __typename
        ... on Human {
          name
        }
    }
    
    {
      "data": {
        "search": [
          {
            "__typename": "Human",
            "name": "Han Solo"
          }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    2.3 请求类型

    操作类型可以是 querymutationsubscription

    query HeroNameAndFriends {
      hero {
        name
        friends {
          name
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2.3.1 mutation:修改请求,类似post

    # 定义
    mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
      createReview(episode: $ep, review: $review) {
        stars
        commentary
      }
    }
    
    # 请求,相当于post
    {
      "ep": "JEDI",
      "review": {
        "stars": 5,
        "commentary": "This is a great movie!"
      }
    }
    
    # response
    {
      "data": {
        "createReview": {
          "stars": 5,
          "commentary": "This is a great movie!"
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    3 go-graphql

    GO文档:https://pkg.go.dev/github.com/graphql-go/graphql

    schema:定义了对象中的哪些字段是我们期望得到的

    resolve:解析器函数,每个特定的field被请求时触发执行这个函数


    3.1 schema

    3.1.1 graphql.Field

    graphql的操作逻辑单位,名字、描述、接收参数、返回参数、解析器

    type Field struct {
    	Name              string              `json:"name"` // used by graphlql-relay
    	Type              Output              `json:"type"`
    	Args              FieldConfigArgument `json:"args"`
    	Resolve           FieldResolveFn      `json:"-"`
    	Subscribe         FieldResolveFn      `json:"-"`
    	DeprecationReason string              `json:"deprecationReason"`
    	Description       string              `json:"description"`
    }
    
    var DoQueryTasks = &graphql.Field{
    	Name:        "Tasks list page",
    	Description: "query task list page",
    	Type:        TaskListType,               //返回类型(列表)
    	Args:        taskArgsNoTaskManageId,     //接收参数
    	Resolve:     resolver.QueryTaskListPage, //处理解析器
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    3.1.2 输入参数定制

    graphql.ArgumentConfig,接收的参数只有类型,没有处理逻辑,所以对应的是config类,不需要嵌套Field

    // 参数配置
    type ArgumentConfig struct {
    	Type         Input       `json:"type"`
    	DefaultValue interface{} `json:"defaultValue"`
    	Description  string      `json:"description"`
    }
    // 参数定义
    type FieldConfigArgument map[string]*ArgumentConfig
    
    // 实现了入参的定义
    graphql.FieldConfigArgument{
    	"id":                 &graphql.ArgumentConfig{Type: graphql.Int, Description: "任务ID"},
    	"name":  			 &graphql.ArgumentConfig{Type: graphql.String, Description: "姓名"},
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    3.1.3 输出参数定制

    graphql.NewObject,嵌套里面还会有嵌套,所以需要套娃式Fields

    Object类型,用于描述层级或者树形数据结构。对于树形数据结构来说,叶子字段的类型都是标量数据类型。几乎所有 GraphQL 类型都是对象类型。Object 类型有一个 name 字段,以及一个很重要的 fields 字段。fields 字段可以描述出一个完整的数据结构。

    func NewObject(config ObjectConfig) *Object
    
    type ObjectConfig struct {
    	Name        string      `json:"name"`
    	Interfaces  interface{} `json:"interfaces"`
    	Fields      interface{} `json:"fields"`
    	IsTypeOf    IsTypeOfFn  `json:"isTypeOf"`
    	Description string      `json:"description"`
    }
    
    // 定制输出结果
    var TaskListType = graphql.NewObject(graphql.ObjectConfig{
    	Name: "TaskListPage",
    	Fields: graphql.Fields{
    		"total": &graphql.Field{Type: graphql.Int, Description: "总数"},
    		"list":  &graphql.Field{Type: graphql.NewList(OutPutType), Description: "数据列表"},
    }})
    
    var OutPutType = graphql.NewObject(graphql.ObjectConfig{
    	Name: "OutPutType",
    	Fields: graphql.Fields{
    		"id":           &graphql.Field{Type: graphql.Int, Description: "ID"},
    		"createdAt":    &graphql.Field{Type: scalars.CustomTime,Description: "创建时间"},
    		"createdBy":    &graphql.Field{Type: graphql.String,Description: "创建人"},
    		"modifiedAt":   &graphql.Field{Type: scalars.CustomTime,Description: "修改时间"},
    		"modifiedBy":   &graphql.Field{Type: graphql.String,Description: "修改人"},
    	},
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    3.1.4 schema

    graphql.SchemaConfig

    type SchemaConfig struct {
    	Query        *Object
    	Mutation     *Object
    	Subscription *Object
    	Types        []Type
    	Directives   []*Directive
    	Extensions   []Extension
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    var Schema graphql.Schema
    
    // init 初始化GraphQL
    func init() {
    	Schema, _ = graphql.NewSchema(graphql.SchemaConfig{
    		Query:    rootQuery,
    		Mutation: rootMutation,
    	})
    }
    
    // 查询操作graphql
    var rootQuery = graphql.NewObject(
    	graphql.ObjectConfig{
    		Name: "Query",
    		Fields: graphql.Fields{
    			"tasks":               	schemas.DoQueryTasks,               //查询任务列表
    			"users":                schemas.DoQueryUsers,                //查询用户列表
    		},
    		Description: "RootQuery",
    	},
    )
    // 修改操作graphql
    var rootMutation = graphql.NewObject(
    	graphql.ObjectConfig{
    		Name: "Mutation",
    		Fields: graphql.Fields{
    			"updateTask":              	 schemas.DoUpdateTask,             	    //更新任务
    			"createTask":                schemas.DoCreateTask,                  //创建任务
    			"deleteTask":                schemas.DoDeleteTask,                  //删除任务
    		},
    		Description: "RootMutation",
    	},
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    3.2 解析器 ——处理函数

    3.2.1 graphql.ResolveParams 透传的参数

    // 请求结构
    type ResolveParams struct {
    	// Source is the source value
    	Source interface{}
    
    	// Args is a map of arguments for current GraphQL request
    	Args map[string]interface{}
    
    	// Info is a collection of information about the current execution state.
    	Info ResolveInfo
    
    	// Context argument is a context value that is provided to every resolve function within an execution.
    	// It is commonly
    	// used to represent an authenticated user, or request-specific caches.
    	Context context.Context
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    3.2.2 Resolve

    // 绑定参数并解析
    func QueryListById(p graphql.ResolveParams) (interface{}, error) {
        // 定义参数结构体
    	opts := entity.ListPageOptions{}
        // 解析前端传递参数
    	jsonStr, _ := json.Marshal(p.Args)
    	if err := json.Unmarshal(jsonStr, &opts); err != nil {
    		return nil, err
    	}
    	// 执行处理逻辑
    	list, total, err := service.QueryListPage(&opts)
    	if err != nil {
    		return nil, err
    	}
        // 返回前端所需结构
    	return dto.PageData{Total: total, List: dto.GenListDTOs(list)}, nil
    }
    
    // 解析器会对应解析成输出的graphql结构
    var ListType = graphql.NewObject(graphql.ObjectConfig{
    	Name: "ListPage",
    	Fields: graphql.Fields{
    		"total": &graphql.Field{Type: graphql.Int, Description: "总数"},
    		"list":  &graphql.Field{Type: graphql.NewList(OutPutType), Description: "数据列表"},
    	}})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    3.3 路由控制

    3.3.1 GraphqlHandler

    graphql.Params、graphql.Do、graphql.Result

    // 执行动作
    func Do(p Params) *Result
    
    // 执行结果
    type Result struct {
    	Data       interface{}                `json:"data"`
    	Errors     []gqlerrors.FormattedError `json:"errors,omitempty"`
    	Extensions map[string]interface{}     `json:"extensions,omitempty"`
    }
    
    // 参数的对齐
    type Params struct {
    	// The GraphQL type system to use when validating and executing a query.
    	Schema Schema
    
    	// A GraphQL language formatted string representing the requested operation.
    	RequestString string
    
    	// The value provided as the first argument to resolver functions on the top
    	// level type (e.g. the query object type).
    	RootObject map[string]interface{}
    
    	// A mapping of variable name to runtime value to use for all variables
    	// defined in the requestString.
    	VariableValues map[string]interface{}
    
    	// The name of the operation to use if requestString contains multiple
    	// possible operations. Can be omitted if requestString contains only
    	// one operation.
    	OperationName string
    
    	// Context may be provided to pass application-specific per-request
    	// information to resolve functions.
    	Context context.Context
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    3.3.2 对接前端

    // graphql请求
    query HeroNameAndFriends {
      hero(id:1001) {
        name
        friends {
          name
        }
      }
    }
    
    // QueryRequest GraphQL 查询语法字段
    type QueryRequest struct {
    	Query     string                 `json:"query" form:"query"`          //GraphQL query 语句
    	Variables map[string]interface{} `json:"variables"  form:"variables"` //变量
    }
    
    func GraphQLHandler(c *gin.Context) {
    	var reqData dto.QueryRequest
    	if err := c.Bind(&reqData); err != nil {
    		return
    	}
    
    	//解析参数
    	result := graphql.Do(graphql.Params{
    		Schema:         schema.Schema,
    		RequestString:  reqData.Query,
    		Context:        c,
    		VariableValues: reqData.Variables,
    	})
    	if result.HasErrors() {
    		return
    	}
    	httprsp.Success(c, result.Data)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    3.4 执行流程

    在这里插入图片描述

    3.5 文件结构

    |
    |---facade
    |  	  |-router
    |	  |-controller
    |
    |--assembler
    |  	  |-schema:定义Fields、入参、出参
    |     |-resolver:定义解析器逻辑
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
  • 相关阅读:
    详解Vue中的render: h => h(App)
    ThinkPHP8学习笔记
    C#非托管泄漏中HEAP_ENTRY的Size对不上是怎么回事?
    Java异常try{}catch{}中的return机制
    c++模式之单例模式详解
    双链笔记软件 Roam Edit 的优点、缺点、评价及学习资源
    【0116】PostgreSQL/MVCC
    流程自动化(RPA)的好处有哪些?
    时序数据库 InfluxDB 2.2 初探
    外贸独立站SEO技巧
  • 原文地址:https://blog.csdn.net/qq_42647903/article/details/127808221