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

使用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

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

https://api.example.com/v1/zoos
https://api.example.com/v1/animals
https://api.example.com/v1/employees

下面是一些例子。

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

在这里插入图片描述


2 GraphQL

学习中文官网


在这里插入图片描述

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

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

例如这个查询:

{
  me {
    name
  }
}

会产生这样的JSON结果:

{
  "me": {
    "name": "Luke Skywalker"
  }
}

2.1 Fields 字段:请求参数

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

{
  hero {
    name
    # 查询可以有备注!
    friends {
      name
    }
  }
}

{
  "data": {
    "hero": {
      "name": "R2-D2",
      "friends": [
        {
          "name": "Luke Skywalker"
        },
        {
          "name": "Han Solo"
        },
        {
          "name": "Leia Organa"
        }
      ]
    }
  }
}

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
  }
}

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
    }
  }
}

2.2.3 元字段

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

{
  search(text: "an") {
    __typename
    ... on Human {
      name
    }
}

{
  "data": {
    "search": [
      {
        "__typename": "Human",
        "name": "Han Solo"
      }
}

2.3 请求类型

操作类型可以是 querymutationsubscription

query HeroNameAndFriends {
  hero {
    name
    friends {
      name
    }
  }
}

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!"
    }
  }
}

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, //处理解析器
}

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: "姓名"},
}

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: "修改人"},
	},
})

3.1.4 schema

graphql.SchemaConfig

type SchemaConfig struct {
	Query        *Object
	Mutation     *Object
	Subscription *Object
	Types        []Type
	Directives   []*Directive
	Extensions   []Extension
}
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",
	},
)

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
}

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: "数据列表"},
	}})

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
}

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)
}

3.4 执行流程

在这里插入图片描述

3.5 文件结构

|
|---facade
|  	  |-router
|	  |-controller
|
|--assembler
|  	  |-schema:定义Fields、入参、出参
|     |-resolver:定义解析器逻辑
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值