go+typescript+graphQL+react构建简书网站(二) 编写GraphQL API

栏目: IT技术 · 发布时间: 5年前

内容简介:项目地址:二话不说,先贴官网:

项目地址: https://github.com/unrotten/hello-world-web

开始之前,关于GraphQL的介绍

二话不说,先贴官网: https://graphql.org

go+typescript+graphQL+react构建简书网站(二) 编写GraphQL API

如图所见,GraphQL可以分为三个部分:一是对数据的描述,称为类型系统,通过定义不同的type,来描述数据之间的关系。这其实很容易理解,可以直接类比为我们再 Go 中定义的struct,不同的是,在GraphQL中,每一个字段都是一个type。二是请求;在之前,我们定义了三个type,query,matutation和subscription,其实就是请求的方式。这里可以发现,其实我们所请求的东西,就是type,即我们对数据的描述。那么请求的内容也显而易见,应该是我们定义好的type。三是返回的结果;GraphQL是对数据的描述,从定义,到请求,再到结果,都不外乎是GraphQL中的type。

通过以上的三部分,不难发现,GraphQL的整一个流程,无非就是开发者定义好一系列的type,使用者拿到这些type,然后根据自己所需,去请求所要的type,最终获得返回。那么这里就体现了GraphQL的宗旨,即由调用方去决定请求什么数据,而提供方只需按照GraphQL的方式,将他所拥有的数据,描述出来即可。

GraphQL库使用入门

我们首先来看作者的示例程序:

package main

import (
    "encoding/json"
    "fmt"
    "log"

    "github.com/graphql-go/graphql"
)

func main() {
    // Schema
    fields := graphql.Fields{
        "hello": &graphql.Field{
            Type: graphql.String,
            Resolve: func(p graphql.ResolveParams) (interface{}, error) {
                return "world", nil
            },
        },
    }
    rootQuery := graphql.ObjectConfig{Name: "RootQuery", Fields: fields}
    schemaConfig := graphql.SchemaConfig{Query: graphql.NewObject(rootQuery)}
    schema, err := graphql.NewSchema(schemaConfig)
    if err != nil {
        log.Fatalf("failed to create new schema, error: %v", err)
    }

    // Query
    query := `
        {
            hello
        }
    `
    params := graphql.Params{Schema: schema, RequestString: query}
    r := graphql.Do(params)
    if len(r.Errors) > 0 {
        log.Fatalf("failed to execute graphql operation, errors: %+v", r.Errors)
    }
    rJSON, _ := json.Marshal(r)
    fmt.Printf("%s \n", rJSON) // {"data":{"hello":"world"}}
}

我们从底部看起,可以看到我们编写的请求hello通过graphql.Params构建了一个请求参数,并交由graphql.Do方法执行。而Params中,定义了一个Schema。再往上看,可以知道Schema由graphql.NewSchema(schemaConfig)声明,而schemaConfig的Query参数接收了一个Object指针。这个Object就是我们对数据的定义,它在rootQuery中被描述为,名称为RootQuery,字段为hello的类型。其中字段hello就是我们具体的数据,其类型为Field。对于每个字段,我们至少需要定义他的类型Type,在这里hello的类型是一个String。Resolve是对字段的解析函数,字段的值由Resolve函数返回。

定义用户数据

首先我们需要分析,关于用户,我们需要什么数据,以下列了几个最基础的:

  1. 用户注册:用户名,邮箱,密码
  2. 用户登录:用户名/邮箱,密码
  3. 获取当前登录用户信息:当前登录用户详细信息
  4. 获取指定用户信息: 指定用户详细信息
  5. 获取用户列表:用户列表
  6. 修改用户信息:用户详细信息

controller 目录下新建 user.go 文件:

package controller

import (
    "github.com/graphql-go/graphql"
    "github.com/graphql-go/relay"
)

var gender = graphql.NewEnum(graphql.EnumConfig{
    Name: "Gender",
    Values: graphql.EnumValueConfigMap{
        "man":     {Value: "man", Description: "男"},
        "woman":   {Value: "woman", Description: "女"},
        "unknown": {Value: "unknown", Description: "保密"},
    },
    Description: "性别",
})
var userState = graphql.NewEnum(graphql.EnumConfig{
    Name: "UserState",
    Values: graphql.EnumValueConfigMap{
        "unsign":    {Value: "unsign", Description: "未认证"},
        "normal":    {Value: "normal", Description: "正常"},
        "forbidden": {Value: "forbidden", Description: "禁止发言"},
        "freeze":    {Value: "freeze", Description: "冻结"},
    },
    Description: "用户状态",
})
var userType = graphql.NewObject(graphql.ObjectConfig{
    Name: "User",
    Fields: graphql.Fields{
        "id":        relay.GlobalIDField("User", nil),
        "username":  {Type: graphql.String, Description: "用户名"},
        "email":     {Type: graphql.String, Description: "邮箱"},
        "avatar":    {Type: graphql.String, Description: "头像"},
        "gender":    {Type: gender, Description: "性别"},
        "introduce": {Type: graphql.String, Description: "个人简介"},
        "state":     {Type: userState, Description: "状态"},
        "root":      {Type: graphql.Boolean, Description: "管理员"},
    },
    Description: "用户数据",
})
var userConnectionDefinition = relay.ConnectionDefinitions(relay.ConnectionConfig{
    Name:     "User",
    NodeType: userType,
})

func registerUserType() {
    queryType.AddFieldConfig("GetUserList", &graphql.Field{
        Type:        userConnectionDefinition.ConnectionType,
        Args:        relay.ConnectionArgs,
        Resolve:     nil,
        Description: "用户列表",
    })
    queryType.AddFieldConfig("GetUser", &graphql.Field{
        Type: userType,
        Args: graphql.FieldConfigArgument{
            "id":       {Type: graphql.ID, Description: "ID"},
            "username": {Type: graphql.String, Description: "用户名"},
        },
        Resolve:     nil,
        Description: "获取用户信息",
    })
    queryType.AddFieldConfig("CurrentUser", &graphql.Field{
        Type:        userType,
        Resolve:     nil,
        Description: "获取当前登录用户信息",
    })
    mutationType.AddFieldConfig("CreatUser", &graphql.Field{
        Type: userType,
        Args: graphql.FieldConfigArgument{
            "username": {Type: graphql.NewNonNull(graphql.String), Description: "用户名"},
            "email":    {Type: graphql.NewNonNull(graphql.String), Description: "邮箱"},
            "password": {Type: graphql.NewNonNull(graphql.String), Description: "密码"},
        },
        Resolve:     nil,
        Description: "注册新用户",
    })
    mutationType.AddFieldConfig("SignIn", &graphql.Field{
        Type: userType,
        Args: graphql.FieldConfigArgument{
            "username": {Type: graphql.NewNonNull(graphql.String), Description: "用户名"},
            "password": {Type: graphql.NewNonNull(graphql.String), Description: "密码"},
        },
        Resolve:     nil,
        Description: "用户登录",
    })
    mutationType.AddFieldConfig("UpdateUser", &graphql.Field{
        Type: userType,
        Args: graphql.FieldConfigArgument{
            "username":  {Type: graphql.String, Description: "用户名"},
            "email":     {Type: graphql.String, Description: "邮箱"},
            "avatar":    {Type: graphql.String, Description: "头像"},
            "gender":    {Type: gender, Description: "性别"},
            "introduce": {Type: graphql.String, Description: "个人简介"},
        },
        Resolve:     nil,
        Description: "修改用户信息",
    })
}

首先我们需要知道,Field在GraphQL中的定义:

type Field struct {
    Name              string              `json:"name"` // used by graphlql-relay
    Type              Output              `json:"type"`
    Args              FieldConfigArgument `json:"args"`
    Resolve           FieldResolveFn      `json:"-"`
    DeprecationReason string              `json:"deprecationReason"`
    Description       string              `json:"description"`
}

Name是字段名,Type是所属类型,Args即获取本字段所需的参数,Resolve是解析函数。

我们首先定义了两个枚举类型,分别是gender和userState。

在userType中,我们使用了 relay 库,并将字段id定义为GlobalIDField,这是为了便于使用relay提供的分页功能。在registerUserType函数中,我们向queryType注册了两个字段GetUserList和GetUser,分别用于获取用户列表和获取单个用户的信息。其中参数relay.ConnectionArgs定义为:

var ConnectionArgs = graphql.FieldConfigArgument{
    "before": &graphql.ArgumentConfig{
        Type: graphql.String,
    },
    "after": &graphql.ArgumentConfig{
        Type: graphql.String,
    },
    "first": &graphql.ArgumentConfig{
        Type: graphql.Int,
    },
    "last": &graphql.ArgumentConfig{
        Type: graphql.Int,
    },
}

我们定义了一个mutationType的字段CreateUser,用于新用户的注册。

可以看到,所有定义的类型中,解析函数Resolve目前均为空,后面我们会将具体的处理逻辑加上去。

修改 graphql.go 文件,调用registerUserType注册用户类型字段:

queryType = graphql.NewObject(graphql.ObjectConfig{Name: "Query", Fields: graphql.Fields{}})
    mutationType = graphql.NewObject(graphql.ObjectConfig{Name: "Mutation", Fields: graphql.Fields{}})
    subscriptType = graphql.NewObject(graphql.ObjectConfig{Name: "Subscription", Fields: graphql.Fields{
        "test": {
            Name: "test",
            Type: graphql.String,
            Resolve: func(p graphql.ResolveParams) (interface{}, error) {
                return "test", nil
            },
            Description: "test",
        },
    }})
    registerUserType()
    schemaConfig := graphql.SchemaConfig{
        Query:        queryType,
        Mutation:     mutationType,
        Subscription: subscriptType,
    }

启动项目,进入GraphiQL:

go+typescript+graphQL+react构建简书网站(二) 编写GraphQL API

定义文章数据

照例我们需要分析关于文章,我们需要什么数据:

  1. 获取指定文章内容:文章的标题,内容
  2. 获取文章列表:文章的标题列表,也可以包含简介
  3. 新增文章:文章标题,内容,作者,标签
  4. 修改文章:文章标题,内容,作者,标签,ID
  5. 删除文章:文章ID

我们在 controller 目录下新建文件 article.go

package controller

import (
    "github.com/graphql-go/graphql"
    "github.com/graphql-go/relay"
)

var articleState = graphql.NewEnum(graphql.EnumConfig{
    Name: "ArticleState",
    Values: graphql.EnumValueConfigMap{
        "unaudited": {Value: "unaudited", Description: "未审核"},
        "online":    {Value: "online", Description: "已上线"},
        "offline":   {Value: "offline", Description: "已下线"},
        "deleted":   {Value: "deleted", Description: "已删除"},
    },
    Description: "文章状态",
})
var articleType = graphql.NewObject(graphql.ObjectConfig{
    Name: "Article",
    Fields: graphql.Fields{
        "id":      relay.GlobalIDField("Article", nil),
        "sn":      {Type: graphql.NewNonNull(graphql.String), Description: "序号"},
        "title":   {Type: graphql.NewNonNull(graphql.String), Description: "标题"},
        "uid":     {Type: graphql.NewNonNull(graphql.ID), Description: "作者ID"},
        "cover":   {Type: graphql.String, Description: "封面"},
        "content": {Type: graphql.String, Description: "文章内容"},
        "tags":    {Type: graphql.NewList(graphql.String), Description: "标签"},
        "state":   {Type: graphql.NewNonNull(articleState), Description: "状态"},
    },
    Description: "文章",
})
var articleConnectionDefinition = relay.ConnectionDefinitions(relay.ConnectionConfig{
    Name:     "Article",
    NodeType: articleType,
})

func registerArticleType() {
    queryType.AddFieldConfig("GetArticle", &graphql.Field{
        Type:        articleType,
        Args:        graphql.FieldConfigArgument{"id": {Type: graphql.NewNonNull(graphql.ID), Description: "ID"}},
        Resolve:     nil,
        Description: "获取指定文章",
    })
    queryType.AddFieldConfig("Articles", &graphql.Field{
        Type: articleConnectionDefinition.ConnectionType,
        Args: relay.NewConnectionArgs(graphql.FieldConfigArgument{
            "title":   {Type: graphql.String, Description: "标题"},
            "uid":     {Type: graphql.ID, Description: "作者ID"},
            "content": {Type: graphql.String, Description: "内容"},
            "tags":    {Type: graphql.NewList(graphql.String), Description: "标签"},
        }),
        Resolve:     nil,
        Description: "获取文章列表",
    })
    mutationType.AddFieldConfig("CreateArticle", &graphql.Field{
        Type: articleType,
        Args: graphql.FieldConfigArgument{
            "title":   {Type: graphql.NewNonNull(graphql.String), Description: "标题"},
            "cover":   {Type: graphql.String, Description: "封面"},
            "content": {Type: graphql.String, Description: "文章内容"},
            "tags":    {Type: graphql.NewList(graphql.String), Description: "标签"},
        },
        Resolve:     nil,
        Description: "新增文章",
    })
    mutationType.AddFieldConfig("UpdateArticle", &graphql.Field{
        Type: articleType,
        Args: graphql.FieldConfigArgument{
            "id":      {Type: graphql.NewNonNull(graphql.ID), Description: "ID"},
            "title":   {Type: graphql.NewNonNull(graphql.String), Description: "标题"},
            "cover":   {Type: graphql.String, Description: "封面"},
            "content": {Type: graphql.String, Description: "文章内容"},
            "tags":    {Type: graphql.NewList(graphql.String), Description: "标签"},
        },
        Resolve:     nil,
        Description: "修改文章",
    })
    mutationType.AddFieldConfig("DeleteArticle", &graphql.Field{
        Type:        articleType,
        Args:        graphql.FieldConfigArgument{"id": {Type: graphql.NewNonNull(graphql.ID), Description: "ID"}},
        Resolve:     nil,
        Description: "删除文章",
    })
}

修改 graphql.go 文件:

registerUserType()
    registerArticleType()
    schemaConfig := graphql.SchemaConfig{
        Query:        queryType,
        Mutation:     mutationType,
        Subscription: subscriptType,
    }
    var err error
    schema, err = graphql.NewSchema(schemaConfig)
    if err != nil {
        panic(err)
    }

    h := handler.New(&handler.Config{
        Schema:     &schema,
        Pretty:     true,
        GraphiQL:   false,
        Playground: true,
    })

在这里我们不仅新注册了文章的type,同时也将GraphQL的调试界面,从GrapiQL换成了Playground,效果如下:

go+typescript+graphQL+react构建简书网站(二) 编写GraphQL API

到这里对于GraphQL API的定义方式都有一定了解了。剩余未定义的包括:评论,评论回复,赞,标签,用户计数,文章扩展等数据尚未定义,可以自己动手尝试。我们定义完成之后,可以从Playground中,下载graphql库根据定义,生成的.graphql文件:

type Article {
  content: String
  count: ArticleEx
  cover: String
  id: ID!
  sn: String!
  state: ArticleState!
  tags: [String]
  title: String!
  uid: ID!
}

type ArticleConnection {
  edges: [ArticleEdge]
  pageInfo: PageInfo!
}

type ArticleEdge {
  cursor: String!
  node: Article
}

type ArticleEx {
  aid: ID!
  cmtNum: Int!
  viewNum: Int!
  zanNum: Int!
}

enum ArticleState {
  unaudited
  online
  offline
  deleted
}

type Comment {
  aid: String!
  content: String!
  floor: Int!
  id: ID!
  replies: [CommentReply]
  state: ArticleState!
  uid: String!
  zanNum: Int!
}

type CommentConnection {
  edges: [CommentEdge]
  pageInfo: PageInfo!
}

type CommentEdge {
  cursor: String!
  node: Comment
}

type CommentReply {
  cid: ID!
  content: String!
  id: ID!
  state: ArticleState!
  uid: ID!
}

enum Gender {
  man
  woman
  unknown
}

type Mutation {
  AddTag(name: String!): Tag
  CancelFollow(uid: ID!): UserFollow
  CancelZan(id: ID!): Boolean
  Comment(
    aid: ID!
    content: String!
  ): Comment
  CreatUser(
    username: String!
    email: String!
    password: String!
  ): User
  CreateArticle(
    title: String!
    cover: String
    content: String
    tags: [String]
  ): Article
  DeleteArticle(id: ID!): Article
  DeleteComment(id: ID!): Comment
  DeleteReply(id: ID!): CommentReply
  Follow(uid: ID!): UserFollow
  Reply(
    cid: ID!
    content: String!
  ): CommentReply
  SignIn(
    username: String!
    password: String!
  ): User
  UpdateArticle(
    id: ID!
    title: String!
    cover: String
    content: String
    tags: [String]
  ): Article
  UpdateUser(
    gender: Gender
    introduce: String
    username: String
    email: String
    avatar: String
  ): User
  Zan(
    objtype: Objtype!
    objid: ID!
  ): Zan
}

enum Objtype {
  reply
  article
  comment
}

type PageInfo {
  endCursor: String
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
  startCursor: String
}

type Query {
  Articles(
    title: String
    uid: ID
    content: String
    tags: [String]
    last: Int
    before: String
    after: String
    first: Int
  ): ArticleConnection
  Comments(
    aid: ID!
    before: String
    after: String
    first: Int
    last: Int
  ): CommentConnection
  CurrentUser: User
  GetArticle(id: ID!): Article
  GetUser(
    id: ID
    username: String
  ): User
  GetUserList(
    before: String
    after: String
    first: Int
    last: Int
  ): UserConnection
  Tag(name: String): [Tag]
}

type Subscription {
  test: String
}

type Tag {
  id: ID!
  name: String!
}

type User {
  avatar: String!
  email: String!
  fans: [User]
  follows: [User]
  gender: Gender!
  id: ID!
  introduce: String
  root: Boolean!
  state: UserState!
  userCount: UserCount
  username: String!
}

type UserConnection {
  edges: [UserEdge]
  pageInfo: PageInfo!
}

type UserCount {
  articleNum: Int!
  fansNum: Int!
  followNum: Int!
  uid: ID!
  words: Int!
  zanNum: Int!
}

type UserEdge {
  cursor: String!
  node: User
}

type UserFollow {
  fuid: ID!
  id: ID!
  uid: ID!
}

enum UserState {
  unsign
  normal
  forbidden
  freeze
}

type Zan {
  id: ID!
  objid: ID!
  objtype: Objtype!
  uid: ID!
}

其实这个时候,如果我们是前后端分开做的,已经可以开始让前端一起进行了。但是实际上——至少我不是,所以后续我们会将后端的Resolve初步完成,才会开始前端的工作。

作者个人博客地址: https://unrotten.org

作者微信公众号:

go+typescript+graphQL+react构建简书网站(二) 编写GraphQL API


以上所述就是小编给大家介绍的《go+typescript+graphQL+react构建简书网站(二) 编写GraphQL API》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

On LISP

On LISP

Paul Graham / Prentice Hall / 09 September, 1993 / $52.00

On Lisp is a comprehensive study of advanced Lisp techniques, with bottom-up programming as the unifying theme. It gives the first complete description of macros and macro applications. The book also ......一起来看看 《On LISP》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试