3.6.3 GraphQL 服务端实现

3.6.3 GraphQL 服务端实现 #

在理解了 GraphQL 的基础概念和 Schema 设计后,本节将详细介绍如何使用 Go 语言构建完整的 GraphQL 服务器。我们将使用流行的 GraphQL Go 库来实现查询、变更、订阅等功能。

GraphQL Go 库选择 #

Go 生态系统中有几个优秀的 GraphQL 库:

  1. graphql-go/graphql - 功能完整的 GraphQL 实现
  2. 99designs/gqlgen - 代码生成优先的 GraphQL 库
  3. graph-gophers/graphql-go - 轻量级的 GraphQL 实现

本节主要使用 graphql-go/graphql 库进行演示。

项目初始化 #

首先创建项目结构并安装依赖:

mkdir graphql-server
cd graphql-server
go mod init graphql-server

# 安装依赖
go get github.com/graphql-go/graphql
go get github.com/graphql-go/handler
go get github.com/gin-gonic/gin
go get gorm.io/gorm
go get gorm.io/driver/sqlite

项目结构:

graphql-server/
├── main.go
├── models/
│   ├── user.go
│   ├── post.go
│   └── comment.go
├── schema/
│   ├── schema.go
│   ├── types.go
│   ├── queries.go
│   ├── mutations.go
│   └── subscriptions.go
├── resolvers/
│   ├── user.go
│   ├── post.go
│   └── comment.go
├── database/
│   └── database.go
└── utils/
    └── auth.go

数据模型定义 #

首先定义数据模型:

// models/user.go
package models

import (
    "time"
    "gorm.io/gorm"
)

type User struct {
    ID        uint           `json:"id" gorm:"primaryKey"`
    Name      string         `json:"name" gorm:"not null"`
    Email     string         `json:"email" gorm:"uniqueIndex;not null"`
    Avatar    string         `json:"avatar"`
    Role      string         `json:"role" gorm:"default:user"`
    IsActive  bool           `json:"isActive" gorm:"default:true"`
    CreatedAt time.Time      `json:"createdAt"`
    UpdatedAt time.Time      `json:"updatedAt"`
    DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`

    // 关联
    Posts    []Post    `json:"posts" gorm:"foreignKey:AuthorID"`
    Comments []Comment `json:"comments" gorm:"foreignKey:AuthorID"`
    Profile  *UserProfile `json:"profile" gorm:"foreignKey:UserID"`
}

type UserProfile struct {
    ID       uint   `json:"id" gorm:"primaryKey"`
    UserID   uint   `json:"userId" gorm:"uniqueIndex"`
    Bio      string `json:"bio"`
    Website  string `json:"website"`
    Location string `json:"location"`

    User User `json:"user" gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
}

// models/post.go
package models

import (
    "time"
    "gorm.io/gorm"
)

type Post struct {
    ID        uint           `json:"id" gorm:"primaryKey"`
    Title     string         `json:"title" gorm:"not null"`
    Content   string         `json:"content" gorm:"type:text"`
    Status    string         `json:"status" gorm:"default:draft"`
    AuthorID  uint           `json:"authorId" gorm:"not null"`
    CreatedAt time.Time      `json:"createdAt"`
    UpdatedAt time.Time      `json:"updatedAt"`
    DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`

    // 关联
    Author   User      `json:"author" gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
    Comments []Comment `json:"comments" gorm:"foreignKey:PostID"`
    Tags     []Tag     `json:"tags" gorm:"many2many:post_tags;"`
}

type Tag struct {
    ID    uint   `json:"id" gorm:"primaryKey"`
    Name  string `json:"name" gorm:"uniqueIndex;not null"`
    Posts []Post `json:"posts" gorm:"many2many:post_tags;"`
}

// models/comment.go
package models

import (
    "time"
    "gorm.io/gorm"
)

type Comment struct {
    ID        uint           `json:"id" gorm:"primaryKey"`
    Content   string         `json:"content" gorm:"not null"`
    PostID    uint           `json:"postId" gorm:"not null"`
    AuthorID  uint           `json:"authorId" gorm:"not null"`
    CreatedAt time.Time      `json:"createdAt"`
    UpdatedAt time.Time      `json:"updatedAt"`
    DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`

    // 关联
    Post   Post `json:"post" gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
    Author User `json:"author" gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
}

数据库初始化 #

// database/database.go
package database

import (
    "log"
    "graphql-server/models"
    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
    "gorm.io/gorm/logger"
)

var DB *gorm.DB

func InitDatabase() {
    var err error
    DB, err = gorm.Open(sqlite.Open("test.db"), &gorm.Config{
        Logger: logger.Default.LogMode(logger.Info),
    })
    if err != nil {
        log.Fatal("Failed to connect to database:", err)
    }

    // 自动迁移
    err = DB.AutoMigrate(
        &models.User{},
        &models.UserProfile{},
        &models.Post{},
        &models.Comment{},
        &models.Tag{},
    )
    if err != nil {
        log.Fatal("Failed to migrate database:", err)
    }

    // 创建测试数据
    seedData()
}

func seedData() {
    // 创建用户
    users := []models.User{
        {
            Name:     "John Doe",
            Email:    "[email protected]",
            Avatar:   "https://example.com/john.jpg",
            Role:     "admin",
            IsActive: true,
        },
        {
            Name:     "Jane Smith",
            Email:    "[email protected]",
            Avatar:   "https://example.com/jane.jpg",
            Role:     "user",
            IsActive: true,
        },
    }

    for _, user := range users {
        var existingUser models.User
        if err := DB.Where("email = ?", user.Email).First(&existingUser).Error; err != nil {
            DB.Create(&user)
        }
    }

    // 创建文章
    var john, jane models.User
    DB.Where("email = ?", "[email protected]").First(&john)
    DB.Where("email = ?", "[email protected]").First(&jane)

    posts := []models.Post{
        {
            Title:    "GraphQL Introduction",
            Content:  "GraphQL is a query language for APIs...",
            Status:   "published",
            AuthorID: john.ID,
        },
        {
            Title:    "Go Programming Tips",
            Content:  "Here are some useful Go programming tips...",
            Status:   "published",
            AuthorID: jane.ID,
        },
    }

    for _, post := range posts {
        var existingPost models.Post
        if err := DB.Where("title = ?", post.Title).First(&existingPost).Error; err != nil {
            DB.Create(&post)
        }
    }
}

GraphQL 类型定义 #

// schema/types.go
package schema

import (
    "time"
    "github.com/graphql-go/graphql"
    "github.com/graphql-go/graphql/language/ast"
)

// 自定义标量类型
var DateType = graphql.NewScalar(graphql.ScalarConfig{
    Name:        "Date",
    Description: "Date custom scalar type",
    Serialize: func(value interface{}) interface{} {
        switch v := value.(type) {
        case time.Time:
            return v.Format(time.RFC3339)
        case *time.Time:
            return v.Format(time.RFC3339)
        default:
            return nil
        }
    },
    ParseValue: func(value interface{}) interface{} {
        switch v := value.(type) {
        case string:
            t, err := time.Parse(time.RFC3339, v)
            if err != nil {
                return nil
            }
            return t
        default:
            return nil
        }
    },
    ParseLiteral: func(valueAST ast.Value) interface{} {
        switch valueAST := valueAST.(type) {
        case *ast.StringValue:
            t, err := time.Parse(time.RFC3339, valueAST.Value)
            if err != nil {
                return nil
            }
            return t
        default:
            return nil
        }
    },
})

// 枚举类型
var UserRoleEnum = graphql.NewEnum(graphql.EnumConfig{
    Name:        "UserRole",
    Description: "User role enumeration",
    Values: graphql.EnumValueConfigMap{
        "ADMIN": &graphql.EnumValueConfig{
            Value:       "admin",
            Description: "Administrator role",
        },
        "USER": &graphql.EnumValueConfig{
            Value:       "user",
            Description: "Regular user role",
        },
    },
})

var PostStatusEnum = graphql.NewEnum(graphql.EnumConfig{
    Name:        "PostStatus",
    Description: "Post status enumeration",
    Values: graphql.EnumValueConfigMap{
        "DRAFT": &graphql.EnumValueConfig{
            Value:       "draft",
            Description: "Draft post",
        },
        "PUBLISHED": &graphql.EnumValueConfig{
            Value:       "published",
            Description: "Published post",
        },
    },
})

// 对象类型声明(避免循环依赖)
var UserType *graphql.Object
var UserProfileType *graphql.Object
var PostType *graphql.Object
var CommentType *graphql.Object
var TagType *graphql.Object

// 输入类型
var CreateUserInputType = graphql.NewInputObject(graphql.InputObjectConfig{
    Name:        "CreateUserInput",
    Description: "Input for creating a user",
    Fields: graphql.InputObjectConfigFieldMap{
        "name": &graphql.InputObjectFieldConfig{
            Type:        graphql.NewNonNull(graphql.String),
            Description: "User name",
        },
        "email": &graphql.InputObjectFieldConfig{
            Type:        graphql.NewNonNull(graphql.String),
            Description: "User email",
        },
        "avatar": &graphql.InputObjectFieldConfig{
            Type:        graphql.String,
            Description: "User avatar URL",
        },
        "role": &graphql.InputObjectFieldConfig{
            Type:         UserRoleEnum,
            DefaultValue: "user",
            Description:  "User role",
        },
    },
})

var UpdateUserInputType = graphql.NewInputObject(graphql.InputObjectConfig{
    Name:        "UpdateUserInput",
    Description: "Input for updating a user",
    Fields: graphql.InputObjectConfigFieldMap{
        "name": &graphql.InputObjectFieldConfig{
            Type:        graphql.String,
            Description: "User name",
        },
        "email": &graphql.InputObjectFieldConfig{
            Type:        graphql.String,
            Description: "User email",
        },
        "avatar": &graphql.InputObjectFieldConfig{
            Type:        graphql.String,
            Description: "User avatar URL",
        },
        "role": &graphql.InputObjectFieldConfig{
            Type:        UserRoleEnum,
            Description: "User role",
        },
        "isActive": &graphql.InputObjectFieldConfig{
            Type:        graphql.Boolean,
            Description: "User active status",
        },
    },
})

var CreatePostInputType = graphql.NewInputObject(graphql.InputObjectConfig{
    Name:        "CreatePostInput",
    Description: "Input for creating a post",
    Fields: graphql.InputObjectConfigFieldMap{
        "title": &graphql.InputObjectFieldConfig{
            Type:        graphql.NewNonNull(graphql.String),
            Description: "Post title",
        },
        "content": &graphql.InputObjectFieldConfig{
            Type:        graphql.NewNonNull(graphql.String),
            Description: "Post content",
        },
        "status": &graphql.InputObjectFieldConfig{
            Type:         PostStatusEnum,
            DefaultValue: "draft",
            Description:  "Post status",
        },
        "tags": &graphql.InputObjectFieldConfig{
            Type:        graphql.NewList(graphql.String),
            Description: "Post tags",
        },
    },
})

解析器实现 #

// resolvers/user.go
package resolvers

import (
    "context"
    "errors"
    "strconv"
    "graphql-server/database"
    "graphql-server/models"
    "github.com/graphql-go/graphql"
)

type UserResolver struct{}

func (r *UserResolver) GetUser(p graphql.ResolveParams) (interface{}, error) {
    id, ok := p.Args["id"].(string)
    if !ok {
        return nil, errors.New("invalid user ID")
    }

    userID, err := strconv.ParseUint(id, 10, 32)
    if err != nil {
        return nil, errors.New("invalid user ID format")
    }

    var user models.User
    if err := database.DB.Preload("Profile").First(&user, uint(userID)).Error; err != nil {
        return nil, err
    }

    return &user, nil
}

func (r *UserResolver) GetUsers(p graphql.ResolveParams) (interface{}, error) {
    var users []models.User
    query := database.DB.Preload("Profile")

    // 处理过滤参数
    if role, ok := p.Args["role"].(string); ok && role != "" {
        query = query.Where("role = ?", role)
    }

    if search, ok := p.Args["search"].(string); ok && search != "" {
        query = query.Where("name LIKE ? OR email LIKE ?", "%"+search+"%", "%"+search+"%")
    }

    if err := query.Find(&users).Error; err != nil {
        return nil, err
    }

    return users, nil
}

func (r *UserResolver) CreateUser(p graphql.ResolveParams) (interface{}, error) {
    input, ok := p.Args["input"].(map[string]interface{})
    if !ok {
        return nil, errors.New("invalid input")
    }

    user := models.User{
        Name:     input["name"].(string),
        Email:    input["email"].(string),
        Role:     input["role"].(string),
        IsActive: true,
    }

    if avatar, ok := input["avatar"].(string); ok {
        user.Avatar = avatar
    }

    if err := database.DB.Create(&user).Error; err != nil {
        return nil, err
    }

    return &user, nil
}

func (r *UserResolver) UpdateUser(p graphql.ResolveParams) (interface{}, error) {
    id, ok := p.Args["id"].(string)
    if !ok {
        return nil, errors.New("invalid user ID")
    }

    userID, err := strconv.ParseUint(id, 10, 32)
    if err != nil {
        return nil, errors.New("invalid user ID format")
    }

    input, ok := p.Args["input"].(map[string]interface{})
    if !ok {
        return nil, errors.New("invalid input")
    }

    var user models.User
    if err := database.DB.First(&user, uint(userID)).Error; err != nil {
        return nil, err
    }

    // 更新字段
    updates := make(map[string]interface{})
    if name, ok := input["name"].(string); ok {
        updates["name"] = name
    }
    if email, ok := input["email"].(string); ok {
        updates["email"] = email
    }
    if avatar, ok := input["avatar"].(string); ok {
        updates["avatar"] = avatar
    }
    if role, ok := input["role"].(string); ok {
        updates["role"] = role
    }
    if isActive, ok := input["isActive"].(bool); ok {
        updates["is_active"] = isActive
    }

    if err := database.DB.Model(&user).Updates(updates).Error; err != nil {
        return nil, err
    }

    return &user, nil
}

func (r *UserResolver) DeleteUser(p graphql.ResolveParams) (interface{}, error) {
    id, ok := p.Args["id"].(string)
    if !ok {
        return nil, errors.New("invalid user ID")
    }

    userID, err := strconv.ParseUint(id, 10, 32)
    if err != nil {
        return nil, errors.New("invalid user ID format")
    }

    if err := database.DB.Delete(&models.User{}, uint(userID)).Error; err != nil {
        return nil, err
    }

    return true, nil
}

// 关联字段解析器
func (r *UserResolver) GetUserPosts(p graphql.ResolveParams) (interface{}, error) {
    user, ok := p.Source.(*models.User)
    if !ok {
        return nil, errors.New("invalid user source")
    }

    var posts []models.Post
    if err := database.DB.Where("author_id = ?", user.ID).Find(&posts).Error; err != nil {
        return nil, err
    }

    return posts, nil
}

func (r *UserResolver) GetUserComments(p graphql.ResolveParams) (interface{}, error) {
    user, ok := p.Source.(*models.User)
    if !ok {
        return nil, errors.New("invalid user source")
    }

    var comments []models.Comment
    if err := database.DB.Where("author_id = ?", user.ID).Find(&comments).Error; err != nil {
        return nil, err
    }

    return comments, nil
}

func (r *UserResolver) GetUserProfile(p graphql.ResolveParams) (interface{}, error) {
    user, ok := p.Source.(*models.User)
    if !ok {
        return nil, errors.New("invalid user source")
    }

    var profile models.UserProfile
    if err := database.DB.Where("user_id = ?", user.ID).First(&profile).Error; err != nil {
        return nil, nil // 返回 nil 而不是错误,因为 profile 是可选的
    }

    return &profile, nil
}
// resolvers/post.go
package resolvers

import (
    "errors"
    "strconv"
    "strings"
    "graphql-server/database"
    "graphql-server/models"
    "github.com/graphql-go/graphql"
)

type PostResolver struct{}

func (r *PostResolver) GetPost(p graphql.ResolveParams) (interface{}, error) {
    id, ok := p.Args["id"].(string)
    if !ok {
        return nil, errors.New("invalid post ID")
    }

    postID, err := strconv.ParseUint(id, 10, 32)
    if err != nil {
        return nil, errors.New("invalid post ID format")
    }

    var post models.Post
    if err := database.DB.Preload("Author").Preload("Tags").First(&post, uint(postID)).Error; err != nil {
        return nil, err
    }

    return &post, nil
}

func (r *PostResolver) GetPosts(p graphql.ResolveParams) (interface{}, error) {
    var posts []models.Post
    query := database.DB.Preload("Author").Preload("Tags")

    // 处理过滤参数
    if status, ok := p.Args["status"].(string); ok && status != "" {
        query = query.Where("status = ?", status)
    }

    if authorID, ok := p.Args["authorId"].(string); ok && authorID != "" {
        query = query.Where("author_id = ?", authorID)
    }

    if err := query.Find(&posts).Error; err != nil {
        return nil, err
    }

    return posts, nil
}

func (r *PostResolver) CreatePost(p graphql.ResolveParams) (interface{}, error) {
    input, ok := p.Args["input"].(map[string]interface{})
    if !ok {
        return nil, errors.New("invalid input")
    }

    // 从上下文获取当前用户ID(假设已经通过认证中间件设置)
    ctx := p.Context
    userID, ok := ctx.Value("userID").(uint)
    if !ok {
        return nil, errors.New("authentication required")
    }

    post := models.Post{
        Title:    input["title"].(string),
        Content:  input["content"].(string),
        Status:   input["status"].(string),
        AuthorID: userID,
    }

    if err := database.DB.Create(&post).Error; err != nil {
        return nil, err
    }

    // 处理标签
    if tagNames, ok := input["tags"].([]interface{}); ok {
        var tags []models.Tag
        for _, tagName := range tagNames {
            if name, ok := tagName.(string); ok {
                var tag models.Tag
                // 查找或创建标签
                if err := database.DB.Where("name = ?", name).FirstOrCreate(&tag, models.Tag{Name: name}).Error; err != nil {
                    continue
                }
                tags = append(tags, tag)
            }
        }

        // 关联标签
        if len(tags) > 0 {
            database.DB.Model(&post).Association("Tags").Append(tags)
        }
    }

    // 重新加载完整的文章数据
    database.DB.Preload("Author").Preload("Tags").First(&post, post.ID)

    return &post, nil
}

// 关联字段解析器
func (r *PostResolver) GetPostAuthor(p graphql.ResolveParams) (interface{}, error) {
    post, ok := p.Source.(*models.Post)
    if !ok {
        return nil, errors.New("invalid post source")
    }

    var user models.User
    if err := database.DB.First(&user, post.AuthorID).Error; err != nil {
        return nil, err
    }

    return &user, nil
}

func (r *PostResolver) GetPostComments(p graphql.ResolveParams) (interface{}, error) {
    post, ok := p.Source.(*models.Post)
    if !ok {
        return nil, errors.New("invalid post source")
    }

    var comments []models.Comment
    if err := database.DB.Preload("Author").Where("post_id = ?", post.ID).Find(&comments).Error; err != nil {
        return nil, err
    }

    return comments, nil
}

func (r *PostResolver) GetPostTags(p graphql.ResolveParams) (interface{}, error) {
    post, ok := p.Source.(*models.Post)
    if !ok {
        return nil, errors.New("invalid post source")
    }

    var tags []models.Tag
    if err := database.DB.Model(&post).Association("Tags").Find(&tags); err != nil {
        return nil, err
    }

    return tags, nil
}

GraphQL Schema 构建 #

// schema/schema.go
package schema

import (
    "graphql-server/resolvers"
    "github.com/graphql-go/graphql"
)

var userResolver = &resolvers.UserResolver{}
var postResolver = &resolvers.PostResolver{}
var commentResolver = &resolvers.CommentResolver{}

func init() {
    // 定义对象类型
    UserProfileType = graphql.NewObject(graphql.ObjectConfig{
        Name:        "UserProfile",
        Description: "User profile object",
        Fields: graphql.Fields{
            "id": &graphql.Field{
                Type:        graphql.NewNonNull(graphql.ID),
                Description: "Profile ID",
            },
            "userId": &graphql.Field{
                Type:        graphql.NewNonNull(graphql.ID),
                Description: "User ID",
            },
            "bio": &graphql.Field{
                Type:        graphql.String,
                Description: "User bio",
            },
            "website": &graphql.Field{
                Type:        graphql.String,
                Description: "User website",
            },
            "location": &graphql.Field{
                Type:        graphql.String,
                Description: "User location",
            },
        },
    })

    TagType = graphql.NewObject(graphql.ObjectConfig{
        Name:        "Tag",
        Description: "Tag object",
        Fields: graphql.Fields{
            "id": &graphql.Field{
                Type:        graphql.NewNonNull(graphql.ID),
                Description: "Tag ID",
            },
            "name": &graphql.Field{
                Type:        graphql.NewNonNull(graphql.String),
                Description: "Tag name",
            },
        },
    })

    CommentType = graphql.NewObject(graphql.ObjectConfig{
        Name:        "Comment",
        Description: "Comment object",
        Fields: graphql.Fields{
            "id": &graphql.Field{
                Type:        graphql.NewNonNull(graphql.ID),
                Description: "Comment ID",
            },
            "content": &graphql.Field{
                Type:        graphql.NewNonNull(graphql.String),
                Description: "Comment content",
            },
            "createdAt": &graphql.Field{
                Type:        graphql.NewNonNull(DateType),
                Description: "Comment creation date",
            },
            "author": &graphql.Field{
                Type:        graphql.NewNonNull(UserType),
                Description: "Comment author",
                Resolve:     commentResolver.GetCommentAuthor,
            },
            "post": &graphql.Field{
                Type:        graphql.NewNonNull(PostType),
                Description: "Comment post",
                Resolve:     commentResolver.GetCommentPost,
            },
        },
    })

    PostType = graphql.NewObject(graphql.ObjectConfig{
        Name:        "Post",
        Description: "Post object",
        Fields: graphql.Fields{
            "id": &graphql.Field{
                Type:        graphql.NewNonNull(graphql.ID),
                Description: "Post ID",
            },
            "title": &graphql.Field{
                Type:        graphql.NewNonNull(graphql.String),
                Description: "Post title",
            },
            "content": &graphql.Field{
                Type:        graphql.NewNonNull(graphql.String),
                Description: "Post content",
            },
            "status": &graphql.Field{
                Type:        graphql.NewNonNull(PostStatusEnum),
                Description: "Post status",
            },
            "createdAt": &graphql.Field{
                Type:        graphql.NewNonNull(DateType),
                Description: "Post creation date",
            },
            "updatedAt": &graphql.Field{
                Type:        graphql.NewNonNull(DateType),
                Description: "Post update date",
            },
            "author": &graphql.Field{
                Type:        graphql.NewNonNull(UserType),
                Description: "Post author",
                Resolve:     postResolver.GetPostAuthor,
            },
            "comments": &graphql.Field{
                Type:        graphql.NewList(graphql.NewNonNull(CommentType)),
                Description: "Post comments",
                Resolve:     postResolver.GetPostComments,
            },
            "tags": &graphql.Field{
                Type:        graphql.NewList(graphql.NewNonNull(TagType)),
                Description: "Post tags",
                Resolve:     postResolver.GetPostTags,
            },
        },
    })

    UserType = graphql.NewObject(graphql.ObjectConfig{
        Name:        "User",
        Description: "User object",
        Fields: graphql.Fields{
            "id": &graphql.Field{
                Type:        graphql.NewNonNull(graphql.ID),
                Description: "User ID",
            },
            "name": &graphql.Field{
                Type:        graphql.NewNonNull(graphql.String),
                Description: "User name",
            },
            "email": &graphql.Field{
                Type:        graphql.NewNonNull(graphql.String),
                Description: "User email",
            },
            "avatar": &graphql.Field{
                Type:        graphql.String,
                Description: "User avatar URL",
            },
            "role": &graphql.Field{
                Type:        graphql.NewNonNull(UserRoleEnum),
                Description: "User role",
            },
            "isActive": &graphql.Field{
                Type:        graphql.NewNonNull(graphql.Boolean),
                Description: "User active status",
            },
            "createdAt": &graphql.Field{
                Type:        graphql.NewNonNull(DateType),
                Description: "User creation date",
            },
            "updatedAt": &graphql.Field{
                Type:        graphql.NewNonNull(DateType),
                Description: "User update date",
            },
            "profile": &graphql.Field{
                Type:        UserProfileType,
                Description: "User profile",
                Resolve:     userResolver.GetUserProfile,
            },
            "posts": &graphql.Field{
                Type:        graphql.NewList(graphql.NewNonNull(PostType)),
                Description: "User posts",
                Resolve:     userResolver.GetUserPosts,
            },
            "comments": &graphql.Field{
                Type:        graphql.NewList(graphql.NewNonNull(CommentType)),
                Description: "User comments",
                Resolve:     userResolver.GetUserComments,
            },
        },
    })
}

// 查询根类型
var QueryType = graphql.NewObject(graphql.ObjectConfig{
    Name:        "Query",
    Description: "Root query type",
    Fields: graphql.Fields{
        "user": &graphql.Field{
            Type:        UserType,
            Description: "Get user by ID",
            Args: graphql.FieldConfigArgument{
                "id": &graphql.ArgumentConfig{
                    Type:        graphql.NewNonNull(graphql.ID),
                    Description: "User ID",
                },
            },
            Resolve: userResolver.GetUser,
        },
        "users": &graphql.Field{
            Type:        graphql.NewList(graphql.NewNonNull(UserType)),
            Description: "Get all users",
            Args: graphql.FieldConfigArgument{
                "role": &graphql.ArgumentConfig{
                    Type:        UserRoleEnum,
                    Description: "Filter by user role",
                },
                "search": &graphql.ArgumentConfig{
                    Type:        graphql.String,
                    Description: "Search in name and email",
                },
            },
            Resolve: userResolver.GetUsers,
        },
        "post": &graphql.Field{
            Type:        PostType,
            Description: "Get post by ID",
            Args: graphql.FieldConfigArgument{
                "id": &graphql.ArgumentConfig{
                    Type:        graphql.NewNonNull(graphql.ID),
                    Description: "Post ID",
                },
            },
            Resolve: postResolver.GetPost,
        },
        "posts": &graphql.Field{
            Type:        graphql.NewList(graphql.NewNonNull(PostType)),
            Description: "Get all posts",
            Args: graphql.FieldConfigArgument{
                "status": &graphql.ArgumentConfig{
                    Type:        PostStatusEnum,
                    Description: "Filter by post status",
                },
                "authorId": &graphql.ArgumentConfig{
                    Type:        graphql.ID,
                    Description: "Filter by author ID",
                },
            },
            Resolve: postResolver.GetPosts,
        },
    },
})

// 变更根类型
var MutationType = graphql.NewObject(graphql.ObjectConfig{
    Name:        "Mutation",
    Description: "Root mutation type",
    Fields: graphql.Fields{
        "createUser": &graphql.Field{
            Type:        graphql.NewNonNull(UserType),
            Description: "Create a new user",
            Args: graphql.FieldConfigArgument{
                "input": &graphql.ArgumentConfig{
                    Type:        graphql.NewNonNull(CreateUserInputType),
                    Description: "User input data",
                },
            },
            Resolve: userResolver.CreateUser,
        },
        "updateUser": &graphql.Field{
            Type:        graphql.NewNonNull(UserType),
            Description: "Update an existing user",
            Args: graphql.FieldConfigArgument{
                "id": &graphql.ArgumentConfig{
                    Type:        graphql.NewNonNull(graphql.ID),
                    Description: "User ID",
                },
                "input": &graphql.ArgumentConfig{
                    Type:        graphql.NewNonNull(UpdateUserInputType),
                    Description: "User update data",
                },
            },
            Resolve: userResolver.UpdateUser,
        },
        "deleteUser": &graphql.Field{
            Type:        graphql.NewNonNull(graphql.Boolean),
            Description: "Delete a user",
            Args: graphql.FieldConfigArgument{
                "id": &graphql.ArgumentConfig{
                    Type:        graphql.NewNonNull(graphql.ID),
                    Description: "User ID",
                },
            },
            Resolve: userResolver.DeleteUser,
        },
        "createPost": &graphql.Field{
            Type:        graphql.NewNonNull(PostType),
            Description: "Create a new post",
            Args: graphql.FieldConfigArgument{
                "input": &graphql.ArgumentConfig{
                    Type:        graphql.NewNonNull(CreatePostInputType),
                    Description: "Post input data",
                },
            },
            Resolve: postResolver.CreatePost,
        },
    },
})

// 创建 Schema
var Schema, _ = graphql.NewSchema(graphql.SchemaConfig{
    Query:    QueryType,
    Mutation: MutationType,
})

服务器启动 #

// main.go
package main

import (
    "context"
    "log"
    "net/http"

    "graphql-server/database"
    "graphql-server/schema"

    "github.com/gin-gonic/gin"
    "github.com/graphql-go/graphql"
    "github.com/graphql-go/handler"
)

func main() {
    // 初始化数据库
    database.InitDatabase()

    // 创建 Gin 路由器
    r := gin.Default()

    // CORS 中间件
    r.Use(func(c *gin.Context) {
        c.Header("Access-Control-Allow-Origin", "*")
        c.Header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
        c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization")

        if c.Request.Method == "OPTIONS" {
            c.AbortWithStatus(204)
            return
        }

        c.Next()
    })

    // 认证中间件(简化版)
    authMiddleware := func(c *gin.Context) {
        // 这里应该实现真正的JWT验证
        // 为了演示,我们假设用户ID为1
        c.Set("userID", uint(1))
        c.Next()
    }

    // GraphQL 处理器
    h := handler.New(&handler.Config{
        Schema:   &schema.Schema,
        Pretty:   true,
        GraphiQL: true,
        Playground: true,
    })

    // GraphQL 端点
    r.POST("/graphql", authMiddleware, func(c *gin.Context) {
        // 将用户信息添加到上下文
        ctx := context.WithValue(c.Request.Context(), "userID", c.GetUint("userID"))
        c.Request = c.Request.WithContext(ctx)

        h.ServeHTTP(c.Writer, c.Request)
    })

    r.GET("/graphql", func(c *gin.Context) {
        h.ServeHTTP(c.Writer, c.Request)
    })

    // 健康检查端点
    r.GET("/health", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "status": "ok",
            "message": "GraphQL server is running",
        })
    })

    log.Println("GraphQL server is running on http://localhost:8080/graphql")
    log.Println("GraphiQL is available at http://localhost:8080/graphql")

    if err := r.Run(":8080"); err != nil {
        log.Fatal("Failed to start server:", err)
    }
}

测试查询示例 #

启动服务器后,可以在 GraphiQL 界面中测试以下查询:

查询用户列表 #

query {
  users {
    id
    name
    email
    role
    createdAt
    posts {
      id
      title
      status
    }
  }
}

创建用户 #

mutation {
  createUser(
    input: {
      name: "Alice Johnson"
      email: "[email protected]"
      role: USER
      avatar: "https://example.com/alice.jpg"
    }
  ) {
    id
    name
    email
    role
    createdAt
  }
}

创建文章 #

mutation {
  createPost(
    input: {
      title: "My First GraphQL Post"
      content: "This is the content of my first GraphQL post."
      status: PUBLISHED
      tags: ["graphql", "go", "tutorial"]
    }
  ) {
    id
    title
    content
    status
    author {
      name
      email
    }
    tags {
      id
      name
    }
  }
}

复杂查询 #

query {
  posts(status: PUBLISHED) {
    id
    title
    createdAt
    author {
      name
      email
      profile {
        bio
        website
      }
    }
    comments {
      id
      content
      author {
        name
      }
    }
    tags {
      name
    }
  }
}

通过这个完整的实现,你已经构建了一个功能完整的 GraphQL 服务器,支持查询、变更和复杂的关联数据获取。在下一节中,我们将学习如何开发 GraphQL 客户端应用。