作者选择了 Diversity in Tech Fund以作为 Write for Donations计划的一部分接受捐款。
介绍
对于许多应用程序来说,一个可取的功能是用户上传个人资料图像的能力,但是,构建此功能对于新的 GraphQL 开发者来说可能是一个挑战,因为它没有内置的文件上传支持。
在此教程中, 您会直接从您的后端应用程序中将图像上传到第三方存储服务 。 您将构建一个使用 S3 兼容 [AWS GO] (https://docs.aws.amazon.com/sdk-for-go/api) 的图形QL API 。 SDK从一个Go后端应用程序上传到DigitalOcean Spaces,这是一种高度可扩展的对象存储服务. Go后端应用程序将曝光一个GraphQL API,并将用户数据存储在由DigitalOcean的管理数据库服务提供的PotsgreSQL数据库中.
在本教程结束时,您将使用 Golang 构建一个 GraphQL API,可以从 多部分 HTTP 请求接收媒体文件,并将该文件上传到 DigitalOcean Spaces。
前提条件
要遵循本教程,您将需要:
- 数字海洋账户。 没有账户的,签入新账户。 您将在此教程中使用 DigitalOcean 的 [Spaces] (https://www.digitalocean.com/products/spaces/) 和 [管理数据库] (https://www.digitalocean.com/products/managed-databases/) 。
- 数字海洋空间,带有访问密钥和访问密钥,您可以通过遵循教程来创建,如何创建数字海洋空间和API密钥. 也可以看到[如何管理行政访问空间]的产品文档(https://docs.digitalocean.com/products/spaces/how-to/manage-access/)。
- 联合国 安装在您的本地机器上, 您可以跟踪我们的系列, [如何安装和设置 Go 本地编程环境] ([ LINK5] ) 。 此教程使用了 Go 版本 1. 17.1.
- [Golang] (https://go.dev/learn/)的基本知识,你可以从我们的[How To Code in Go] (https://www.digitalocean.com/community/tutorial_series/how-to-code-in-go)系列中获得这些知识. 教程[如何写出您的第一个 Go (https://andsky.com/tech/tutorials/how-to-write-your-first-program-in-go),为戈兰语编程语言提供了很好的入门.
- 联合国 对GraphQL的理解,可在我们的教程中找到,GraphQL的介绍]. .
步骤 1 — 启动 Golang GraphQL API
在此步骤中, 您将使用 [Gqlgen] (https://github.com/99designs/gqlgen] 库来拖曳 GraphQL API 。 Gqlgen 是用于构建 GraphQL API 的 Go 库 。 Gqglen提供的两个重要特征是先行计划方法和代码生成. 使用 schema- first 方法,您首先使用 GraphQL Schema定义语言定义API的数据模型. 然后从定义的图中生成API的锅炉板代码. 使用代码生成特性,您不需要为 API 自动创建查询和突变解析器.
要开始,请执行下面的命令来安装gqlgen:
1go install github.com/99designs/gqlgen@latest
接下来,创建一个名为digitalocean
的项目目录来存储该项目的文件:
1mkdir digitalocean
转到数字海洋
项目目录:
1cd digitalocean
从您的项目目录中运行以下命令来创建一个 go.mod
文件,该文件在 digitalocean
项目中管理 modules:
1go mod init digitalocean
接下来,使用 nano 或您最喜欢的文本编辑器,在项目目录中创建一个名为tools.go
的文件:
1nano tools.go
将以下行列添加到 tools.go
文件中作为一个 tool的项目:
1// +build tools
2
3 package tools
4
5 import _ "github.com/99designs/gqlgen"
接下来,执行 tidy
命令来安装在 tools.go
文件中引入的 gqlgen 依赖性:
1go mod tidy
最后,使用已安装的 Gqlgen 库,生成 GraphQL API 所需的 boilerplate 文件:
1gqlgen init
上面的gqlgen
命令会生成一个server.go
文件来运行 GraphQL 服务器和一个包含包含 GraphQL API 的 Schema 定义的schema.graphqls
文件的graph
目录。
在此步骤中,您使用了 Gqlgen 库来启动 GraphQL API. 接下来,您将定义 GraphQL 应用程序的方案。
步骤 2 — 定义 GraphQL 应用程序方案
在此步骤中,您将通过修改在运行gqlgen init
命令时自动生成的schema.graphqls
文件来定义 GraphQL 应用程序的方案。
导航到graf
目录并打开schema.graphqls
文件,该文件定义了 GraphQL 应用程序的方案。 用以下代码块代替 boilerplate 方案,该代码块定义了用户
类型,用Query
来检索所有用户数据和Mutation
来插入数据:
1[label schema.graphqls]
2
3scalar Upload
4
5type User {
6 id: ID!
7 fullName: String!
8 email: String!
9 img_uri: String!
10 DateCreated: String!
11}
12
13type Query {
14 users: [User]!
15}
16
17input NewUser {
18 fullName: String!
19 email: String!
20 img_uri: String
21 DateCreated: String
22}
23
24input ProfileImage {
25 userId: String
26 file: Upload
27}
28
29type Mutation {
30 createUser(input: NewUser!): User!
31 uploadProfileImage(input: ProfileImage!): Boolean!
32}
代码块定义了两个突变
类型和一个单一的查询
类型来检索所有用户。 mutation 用于在 GraphQL 应用程序中插入或突变现有数据,而 query 用于检索数据,类似于 REST API 中的GET
HTTP 词汇。
上面的代码块中的方案使用了 GraphQL Schema Definition Language来定义包含CreateUser
类型的突变
,它接受NewUser
输入作为参数并返回单个用户
。
注意:Gqlgen自动定义上传尺度类型,并定义文件的属性. 要使用它,您只需要在方案文件的顶部声明它,就像在上面的代码块中一样。
在此时,您已经定义了应用程序的数据模型结构,下一步是使用Gqlgen的代码生成功能生成方案的查询和突变解析函数。
步骤3 - 生成应用程序解决方案
在此步骤中,您将使用 Gqlgen 的代码生成功能自动生成基于您在前一步创建的方案的 GraphQL 解析器. resolver 是解决或返回 GraphQL 字段的值的函数。
Gqlgen 套件是基于一个先于方案的方法。Gqlgen 的一个节省时间的功能是基于您在schema.graphqls
文件中的定义方案生成您的应用程序的解析器的能力. 有了这个功能,您不需要手动写解析器板代码,这意味着您可以专注于执行定义的解析器。
若要使用代码生成功能,请在项目目录中执行下面的命令,以生成 GraphQL API 模型文件和解析器:
1gqlgen generate
執行「gqlgen」命令後會發生幾件事情. 將打印兩個與「schema.resolvers.go」檔案相關的驗證錯誤,產生一些新檔案,並且您的項目將具有新的文件夾結構。
运行树命令以查看新添加到您的项目的文件。
1tree *
目前的目录结构将类似于此:
1[secondary_label Output]
2
3go.mod
4go.sum
5gqlgen.yml
6graph
7 ├── db.go
8 ├── generated
9 │ └── generated.go
10 ├── model
11 │ └── models_gen.go
12 ├── resolver.go
13 ├── schema.graphqls
14 └── schema.resolvers.go
15server.go
16tmp
17 ├── build-errors.log
18 └── main
19tools.go
20
212 directories, 8 files
在项目文件中,一个重要的文件是schema.resolvers.go. 它包含执行在schema.graphqls文件中之前定义的突变
和请求
类型的方法。
若要修复验证错误,请在schema.resolvers.go
文件的底部删除CreateTodo
和Todos
方法,Gqlgen将方法移动到文件的底部,因为在schema.graphqls
文件中修改了类型定义。
1[label schema.resolvers.go]
2
3package graph
4
5// This file will be automatically regenerated based on the schema, any resolver implementations
6// will be copied through when generating and any unknown code will be moved to the end.
7
8import (
9 "context"
10 "digitalocean/graph/generated"
11 "digitalocean/graph/model"
12 "fmt"
13)
14
15func (r *mutationResolver) CreateUser(ctx context.Context, input model.NewUser) (*model.User, error) {
16 panic(fmt.Errorf("not implemented"))
17}
18
19func (r *mutationResolver) UploadProfileImage(ctx context.Context, input model.ProfileImage) (bool, error) {
20 panic(fmt.Errorf("not implemented"))
21}
22
23func (r *queryResolver) User(ctx context.Context) (*model.User, error) {
24 panic(fmt.Errorf("not implemented"))
25}
26
27// Mutation returns generated.MutationResolver implementation.
28func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} }
29
30// Query returns generated.QueryResolver implementation.
31func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }
32
33type mutationResolver struct{ *Resolver }
34type queryResolver struct{ *Resolver }
35
36// !!! WARNING !!!
37// The code below was going to be deleted when updating resolvers. It has been copied here so you have
38// one last chance to move it out of harms way if you want. There are two reasons this happens:
39// - When renaming or deleting a resolver the old code will be put in here. You can safely delete
40// it when you're done.
41// - You have helper methods in this file. Move them out to keep these resolver files clean.
42
43func (r *mutationResolver) CreateTodo(ctx context.Context, input model.NewTodo) (*model.Todo, error) {
44 panic(fmt.Errorf("not implemented"))
45}
46func (r *queryResolver) Todos(ctx context.Context) ([]*model.Todo, error) {
47 panic(fmt.Errorf("not implemented"))
48}
如schema.graphqls
文件所定义,Gqlgen的代码生成器创建了两个突变和一个查询解析方法,这些解析器为以下目的服务:
- `创造用户 ' : 这个突变解析器将一个新的用户记录插入到连接的Postgres数据库.
- `上载ProfileImage': 这个突变解析器上传了从一个[多部分 HTTP 请求(https://swagger.io/docs/specification/describing-request-body/multipart-requests/)收到的媒体文件,并将文件上传到数字海洋空间内部的一桶. 文件上传后,上传文件的URL被插入到之前创建的用户的"img_uri"字段.
- " 用户 " : 这个查询解析器查询所有现有用户的数据库,并作为查询结果返回. .
通过从突变和查询类型生成的方法,你会注意到它们会引起(https://golang.org/src/runtime/panic.go)与执行时未实施
错误的恐慌。 这表明它们仍然是自动生成的 boilerplate 代码。 在本教程中,您将返回schema.resolver.go
文件来实施这些生成的方法。
在此时,您将基于 schema.graphqls
文件的内容生成此应用程序的解析器. 您现在将使用 Managed Databases 服务创建一个数据库,该数据库将存储传递给突变解析器创建用户的数据。
第4步:在DigitalOcean上提供和使用管理数据库实例
在此步骤中,您将使用 DigitalOcean 控制台访问 Managed Databases 服务,并创建一个 PostgreSQL 数据库来存储来自该应用程序的数据。
虽然应用程序不会直接在数据库中存储图像,但它仍然需要一个数据库来插入每个用户的记录。
用户的记录将包括一个 Fullname ,** email** ,** dateCreated,** 和一个** img_uri** 字段的字符串数据类型.** img_uri** 字段包含指向用户通过此GraphQL API上传的图像文件的URL,并存储在DigitalOcean Spaces的桶中。
使用您的 DigitalOcean 仪表板,导航到控制台的数据库部分,创建一个新的数据库集群,然后从提供的数据库列表中选择 PostgreSQL 留下所有其他设置以其默认值,并使用底部的按钮创建此集群。
数据库集群创建过程需要几分钟才能完成。
创建集群后,请遵循数据库集群页面上的 Getting Started 步骤来设置集群用于使用。
在 Getting Started 指南的第二步中,单击** Continue, I will do this later** text to proceed. 默认情况下,数据库集群对所有连接开放。
<$>[注] 注: 在生产准备的情况下,第二步中的** 添加可信源** 输入字段只应包含可信 IP 地址,例如运行应用程序的 DigitalOcean Droplet 的 IP 地址。
点击允许这些输入源
按钮来保存并继续到下一步。
在下一个步骤中,将显示群集的连接细节,您还可以通过单击 行动 下载单元来查找群集凭证,然后选择** 连接细节** 选项。
在此屏幕截图中,右侧的灰色框显示了创建的演示群集的连接凭证。
在digitalocean
项目目录中,创建一个.env
文件,并在以下格式中添加您的群集凭证,确保您用自己的凭证替换突出的位置持有人内容:
1[label .env]
2
3 DB_PASSWORD=YOUR_DB_PASSWORD
4 DB_PORT=PORT
5 DB_NAME=YOUR_DATABASE_NAME
6 DB_ADDR=HOST
7 DB_USER=USERNAME
随着连接详细信息安全地存储在.env 文件中,下一步将是获取这些凭据并将数据库集群连接到您的项目。
开始前,您需要数据库驱动程序,以便在连接 Postgres 数据库时与 Golang 的本地 SQL 软件包合作 。 [go-pg] (https://github.com/go-pg/pg)是一个戈兰格文库,用于将ORM([对象-关系映射 (https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping))查询翻译成SQL查询,用于Postgres数据库. [godotenv] (https://github.com/joho/godotenv)是一个戈兰语库,用于将环境证书从`.env'文件装入您的应用程序。 最后,go.uuid为每个用户的记录生成一个UUID(普遍独特的标识符),将插入数据库.
运行此命令来安装这些:
1go get github.com/go-pg/pg/v10 github.com/joho/godotenv github.com/satori/go.uuid
然后,导航到)连接。
首先,将代码块的内容添加到 db.go
文件中. 此函数( createSchema
)在建立连接到数据库后立即在 Postgres 数据库中创建用户表。
1[label db.go]
2package graph
3
4import (
5 "github.com/go-pg/pg/v10"
6 "github.com/go-pg/pg/v10/orm"
7 "digitalocean/graph/model"
8)
9
10func createSchema(db *pg.DB) error {
11 for _, models := range []interface{}{(*model.User)(nil)}{
12 if err := db.Model(models).CreateTable(&orm.CreateTableOptions{
13 IfNotExists: true,
14 }); err != nil {
15 panic(err)
16 }
17 }
18
19 return nil
20}
使用)转移到)客户端或 GUI 手动创建表格,createSchema 函数则负责创建表格。
接下来,将下面的代码块的内容添加到db.go
文件中,以建立连接到Postgres数据库,并在成功建立连接时执行上面的createSchema
函数:
1[label db.go]
2
3import (
4 // ...
5
6 "fmt"
7 "os"
8 )
9
10func Connect() *pg.DB {
11 DB_PASSWORD := os.Getenv("DB_PASSWORD")
12 DB_PORT := os.Getenv("DB_PORT")
13 DB_NAME := os.Getenv("DB_NAME")
14 DB_ADDR := os.Getenv("DB_ADDR")
15 DB_USER := os.Getenv("DB_USER")
16
17 connStr := fmt.Sprintf(
18 "postgresql://%v:%v@%v:%v/%v?sslmode=require",
19 DB_USER, DB_PASSWORD, DB_ADDR, DB_PORT, DB_NAME )
20
21 opt, err := pg.ParseURL(connStr); if err != nil {
22 panic(err)
23 }
24
25 db := pg.Connect(opt)
26
27 if schemaErr := createSchema(db); schemaErr != nil {
28 panic(schemaErr)
29 }
30
31 if _, DBStatus := db.Exec("SELECT 1"); DBStatus != nil {
32 panic("PostgreSQL is down")
33 }
34
35 return db
36}
当执行时,上面的代码块中导出的)连接到 Postgres 数据库。
- 首先,您在 root
.env
文件中存储的数据库凭据被检索,然后创建一个变量以存储与检索的凭据格式化的字符串. 此变量在连接到数据库时将被用作连接 URI。 * 接下来,创建的连接字符串被分析,以查看已格式化的凭据是否有效。
要使用导出的连接
函数,您需要将该函数添加到server.go
文件中,以便在应用程序启动时执行。
要在应用程序启动后立即使用先前创建的连接
函数从图形
包中,并将来自.env
文件的凭证加载到应用程序中,请在您偏好的代码编辑器中打开server.go
文件,并添加下面的行:
<$>[注] 注: 请确保在server.go
文件中替换现有的srv
变量以下面突出的srv
变量。
1[label server.go]
2 package main
3
4import (
5 "log"
6 "net/http"
7 "os"
8 "digitalocean/graph"
9 "digitalocean/graph/generated"
10
11 "github.com/99designs/gqlgen/graphql/handler"
12 "github.com/99designs/gqlgen/graphql/playground"
13 "github.com/joho/godotenv"
14)
15
16const defaultPort = "8080"
17
18func main() {
19 err := godotenv.Load(); if err != nil {
20 log.Fatal("Error loading .env file")
21 }
22
23 // ...
24
25 Database := graph.Connect()
26 srv := handler.NewDefaultServer(
27 generated.NewExecutableSchema(
28 generated.Config{
29 Resolvers: &graph.Resolver{
30 DB: Database,
31 },
32 }),
33 )
34
35 // ...
36}
在此代码片段中,您通过 [Load()
] 函数(https://github.com/joho/godotenv/blob/c40e9c6392b05ba58e6fea50091ce35a1ef020e7/godotenv.go# L42) 加载了存储在 .env 中的凭证。您从
db包中调用了
Connect函数,并创建了
Resolver对象,存储在
DB` 字段中的数据库连接。 (存储的数据库连接将在本教程中稍后由解析者访问)。
目前,在 resolver.go
文件中的 boilerplate Resolver
结构不包含您在上面的代码中存储数据库连接的 DB
字段。
在图表
目录中,打开resolver.go
文件,并修改Resolver
结构,以便有一个DB
字段,其类型为go-pg
指针,如下所示:
1[label resolver.go]
2package graph
3
4import "github.com/go-pg/pg/v10"
5
6// This file will not be regenerated automatically.
7//
8// It serves as dependency injection for your app, add any dependencies you require here.
9
10type Resolver struct {
11 DB *pg.DB
12}
现在,每次运行server.go
输入文件时都会建立数据库连接,而go-pg
包可以用作ORM来执行从解析函数对数据库的操作。
在此步骤中,您使用 DigitalOcean 上的 Managed Database 服务创建了一个 PostgreSQL 数据库,您还创建了一个具有连接
函数的db.go
文件,在应用程序启动时建立与 PostgreSQL 数据库的连接。
步骤5 - 实施生成解决方案
在此步骤中,您将在schema.resolvers.go
文件中实施方法,该文件作为突变和查询解析器,实施的突变解析器将创建用户并上传用户的个人资料图像,而查询解析器将检索所有存储的用户细节。
实施突变解决方法
在schema.graphqls
文件中,产生了两个突变解析器,一个是用来插入用户的记录,另一个是处理上传的个人资料图像,但是这些突变尚未实现,因为它们是锅板代码。
打开schema.resolvers.go
文件. 用突出的行修改导入和CreateUser
突变,将包含用户详细信息输入的新行插入到数据库中:
1[label schema.resolvers.go]
2package graph
3
4import (
5 "context"
6 "fmt"
7 "time"
8
9 "digitalocean/graph/generated"
10 "digitalocean/graph/model"
11 "github.com/satori/go.uuid"
12)
13
14func (r *mutationResolver) CreateUser(ctx context.Context, input model.NewUser) (*model.User, error) {
15 user := model.User{
16 ID: fmt.Sprintf("%v", uuid.NewV4()),
17 FullName: input.FullName,
18 Email: input.Email,
19 ImgURI: "https://bit.ly/3mCSn2i",
20 DateCreated: time.Now().Format("01-02-2006"),
21 }
22
23 _, err := r.DB.Model(&user).Insert(); if err != nil {
24 return nil, fmt.Errorf("error inserting user: %v", err)
25 }
26
27 return &user, nil
28}
在)。 其次,每个行中的ImgURI
字段都有一个定位图像URL作为默认值。 这将是所有记录的默认值,并且在用户上传新图像时会更新。
接下来,您将测试此时已构建的应用程序. 从项目目录中,使用以下命令运行 server.go
文件:
1go run ./server.go
现在,通过您的 Web 浏览器导航到 http://localhost:8080
以访问您的 GraphQL API 内置的 GraphQL 游戏场地. 将下面的代码块中的 GraphQL 突变粘贴到游戏场地编辑器中以插入新的用户记录。
1[label graphql]
2
3mutation createUser {
4 createUser(
5 input: {
6 email: "[email protected]"
7 fullName: "John Doe"
8 }
9 ) {
10 id
11 }
12}
右侧的输出将看起来像这样:
您执行了CreateUser
突变,以创建一个名为John Doe
的测试用户,并作为突变结果返回了新插入的用户记录的id
。
<$>[注] 注: 复制从执行的 GraphQL 查询中返回的 id
值。
在此时,您有第二个UploadProfileImage
突变解析函数要执行,但在执行此函数之前,您需要先执行查询解析器,因为每个上传都与特定用户相关联,这就是为什么您在上传图像之前检索了特定用户的ID。
应用 Query Resolver 方法
正如schema.resolvers.graphqls
文件中所定义的那样,一个查询解析器被生成以获取所有创建的用户。
打开scheme.resolvers.go
并用突出的行修改生成的用户
查询解决方案.下面的用户
方法中的新代码将查询所有用户行的 Postgres 数据库并返回结果。
1[label schema.resolvers.go]
2package graph
3
4func (r *queryResolver) Users(ctx context.Context) ([]*model.User, error) {
5 var users []*model.User
6
7 err := r.DB.Model(&users).Select()
8 if err != nil {
9 return nil, err
10 }
11
12 return users, nil
13}
在上面的用户
解析函数中,通过在用户
模型上使用 go-pg 的选择
方法来获取用户表中的所有记录,而不会将WHERE
或LIMIT
条款传入查询中。
注意:对于一个较大的应用程序,从查询中返回许多记录,重要的是考虑页化返回的数据以提高性能。
要从您的浏览器中测试此查询解决方案,请导航到 http://localhost:8080
访问 GraphQL 播放场。 将下面的 GraphQL 查询粘贴到播放场编辑器中,以获取所有创建的用户记录。
1[label graphql]
2
3query fetchUsers {
4 users {
5 fullName
6 id
7 img_uri
8 }
9}
右侧的输出将看起来像这样:
在返回的结果中,您可以看到一个具有数组值的用户
对象被返回。 目前,仅在用户
数组中返回了先前创建的用户,因为这是表中唯一的记录。 如果您用新的细节执行CreateUser
突变,则在用户
数组中会返回更多的用户。
在这一点上,您现在已经实现了) SDK 将所接收的图像上传到 DigitalOcean Spaces 的桶中。
步骤6 — 将图像上传到数字海洋空间
在此步骤中,您将使用第二个UploadProfileImage
突变中的强大的API来上传图像到您的空间。
要开始,请导航到您的 DigitalOcean 主机的空间部分,在那里您将创建一个新的桶来存储从您的后端应用程序上传的文件。
点击创建新空间
按钮,将设置留在默认值,并为新空间指定一个独特的名称:
创建新空间后,导航到设置卡并复制空间的终端点、名称和区域,并将其添加到 GraphQL 项目中的 .env
文件中,以此格式:
1[label .env]
2SPACE_ENDPOINT=BUCKET_ENDPOINT
3DO_SPACE_REGION=DO_SPACE_REGION
4DO_SPACE_NAME=DO_SPACE_NAME
例如,下面的屏幕截图显示了):
作为前提的一部分,您为您的空间创建了一个空间访问密钥和秘密密钥,将您的访问密钥和秘密密密钥粘贴到 GraphQL 应用程序中的 .env
文件中,以以下格式:
1[label .env]
2ACCESS_KEY=YOUR_SPACE_ACCESS_KEY
3SECRET_KEY=YOUR_SPACE_SECRET_KEY
在此时,您需要使用CTRL + C 键
组合来停止 GraphQL 服务器,然后执行下面的命令,以重新启动 GraphQL 应用程序,并将新的凭证加载到应用程序中。
1go run ./server.go
现在你的空间凭据已被加载到应用程序中,你将创建上传逻辑在) SDK 以连接到您的 DigitalOcean Space。
在 Spaces中,您可以通过使用兼容的 AWS SDK 来编程操作。 AWS Go SDK 是一个开发套件,提供了一组图书馆,可供 Go 开发人员使用。 SDK 提供的图书馆可由 Go 编写的应用程序在执行与 AWS 资源的操作时使用,例如将文件传输到 S3 库。
The DigitalOcean Spaces documentation提供了您可以使用 AWS SDK 在 Spaces API 上执行的操作列表,我们将使用 aws-sdk-go SDK 连接到您的 DigitalOcean Space。
运行go get
命令,将aws-sdk-go
SDK 安装到应用程序中:
1go get github.com/aws/aws-sdk-go
在接下来的几块代码中,您将逐步在UploadProfileImage
突变解析器中组合上传逻辑。
首先,打开schema.resolvers.go
文件. 添加突出的行以配置 AWS SDK 与存储的凭据,并与您的 DigitalOcean Space 建立连接:
注意:下面的代码块中的代码不完整,因为你正在逐步将上传逻辑整合在一起。
1[label schema.resolvers.go]
2package graph
3
4import (
5 ...
6
7 "os"
8
9 "github.com/aws/aws-sdk-go/aws"
10 "github.com/aws/aws-sdk-go/aws/credentials"
11 "github.com/aws/aws-sdk-go/aws/session"
12 "github.com/aws/aws-sdk-go/service/s3"
13)
14
15func (r *mutationResolver) UploadProfileImage(ctx context.Context, input model.ProfileImage) (bool, error) {
16
17 SpaceRegion := os.Getenv("DO_SPACE_REGION")
18 accessKey := os.Getenv("ACCESS_KEY")
19 secretKey := os.Getenv("SECRET_KEY")
20
21 s3Config := &aws.Config{
22 Credentials: credentials.NewStaticCredentials(accessKey, secretKey, ""),
23 Endpoint: aws.String(os.Getenv("SPACE_ENDPOINT")),
24 Region: aws.String(SpaceRegion),
25 }
26
27 newSession := session.New(s3Config)
28 s3Client := s3.New(newSession)
29
30}
现在 SDK 已配置,下一步是将发送的文件上传到 多部分 HTTP 请求。
处理发送的文件的一种方法是读取多部分请求的内容,暂时将内容保存到内存中的新文件中,使用aws-SDK-go
库上传临时文件,然后在上传后删除它。
要做到这一点,在schema.resolvers.go
文件中的UploadProfileImage
突变解析器中的现有代码中添加了突出的行:
1[label schema.resolvers.go]
2
3package graph
4
5import (
6 ...
7
8 "io/ioutil"
9 "bytes"
10
11)
12
13func (r *mutationResolver) UploadProfileImage(ctx context.Context, input model.ProfileImage) (bool, error) {
14...
15
16SpaceName := os.Getenv("DO_SPACE_NAME")
17
18...
19
20 userFileName := fmt.Sprintf("%v-%v", input.UserID, input.File.Filename)
21 stream, readErr := ioutil.ReadAll(input.File.File)
22 if readErr != nil {
23 fmt.Printf("error from file %v", readErr)
24 }
25
26 fileErr := ioutil.WriteFile(userFileName, stream, 0644); if fileErr != nil {
27 fmt.Printf("file err %v", fileErr)
28 }
29
30 file, openErr := os.Open(userFileName); if openErr != nil {
31 fmt.Printf("Error opening file: %v", openErr)
32 }
33
34 defer file.Close()
35
36 buffer := make([]byte, input.File.Size)
37
38_, _ = file.Read(buffer)
39
40 fileBytes := bytes.NewReader(buffer)
41
42 object := s3.PutObjectInput{
43 Bucket: aws.String(SpaceName),
44 Key: aws.String(userFileName),
45 Body: fileBytes,
46 ACL: aws.String("public-read"),
47 }
48
49 if _, uploadErr := s3Client.PutObject(&object); uploadErr != nil {
50 return false, fmt.Errorf("error uploading file: %v", uploadErr)
51 }
52
53 _ = os.Remove(userFileName)
54
55return true, nil
56}
使用 ReadAll
方法从上面的代码块中的 io
包中,您首先读取了添加到发送到GraphQL API的多部分请求的文件内容,然后创建了一个临时文件,将此内容投放到其中。
接下来,使用 PutObjectInput
结构,您创建了要上传的文件结构,通过指定 Bucket
、 Key
、 ACL
和 Body
字段为临时存储的文件的内容。
<$>[注] 注: 访问控制列表 (ACL
)字段在 PutObjectInput
struct 有一个 public-read
值,使所有上传的文件可通过互联网查看。 如果您的应用程序要求上传的数据保持私密,您可以删除此字段。
创建PutObjectInput
结构后,使用PutObject
方法进行PUT
操作,将PutObjectInput
结构的值发送到桶中。
要测试上传突变解析器,你可以使用Sammy the Shark的图像,DigitalOcean的马斯科特。
1wget https://html.sammy-codes.com/images/small-profile.jpeg
接下来,执行下面的 cURL 命令,将 HTTP 请求发送到 GraphQL API 以上传 Sammy 的图像,该图像已被添加到请求表单体中。
注意: 如果您使用的是 Windows 操作系统,建议您使用 Git Bash壳执行 cURL命令,因为背后逃避。
1curl localhost:8080/query -F operations='{ "query": "mutation uploadProfileImage($image: Upload! $userId : String!) { uploadProfileImage(input: { file: $image userId : $userId}) }", "variables": { "image": null, "userId" : "12345" } }' -F map='{ "0": ["variables.image"] }' -F 0=@small-profile.jpeg
注意:我们在上面的请求中使用随机的 userId
值,因为更新用户记录的过程尚未实现。
输出将类似于此,表明文件上传成功:
1[secondary_label Output]
2{"data": { "uploadProfileImage": true }}
在 DigitalOcean 控制台的空间部分中,您将找到从您的终端上传的图像:
在此时,应用程序内部的文件上传正在工作;然而,这些文件与执行上传的用户相关联,每个文件上传的目标是将文件上传到存储桶,然后通过更新用户的img_uri
字段来链接到用户。
在图表
目录中打开resolver.go
文件并添加下面的代码块,它包含两种方法:一种是通过指定字段从数据库中获取用户,另一种是更新用户的记录。
1[label resolver.go]
2
3import (
4...
5
6 "digitalocean/graph/model"
7 "fmt"
8)
9
10...
11
12func (r *mutationResolver) GetUserByField(field, value string) (*model.User, error) {
13 user := model.User{}
14
15 err := r.DB.Model(&user).Where(fmt.Sprintf("%v = ?", field), value).First()
16
17 return &user, err
18}
19
20func (r *mutationResolver) UpdateUser(user *model.User) (*model.User, error) {
21 _, err := r.DB.Model(user).Where("id = ?", user.ID).Update()
22 return user, err
23}
上面的第一个GetUserByField
函数接受一个字段
和一个值
参数,两者都是字符串类型。
代码块中的第二个) 方法,在UPDATE
语句中添加一个包含条件的WHERE
条款,仅更新具有相同ID
传入函数的行。
现在你可以使用UploadProfileImage
突变中的两种方法,将下面的突出代码块的内容添加到UploadProfileImage
突变中,在schema.resolvers.go
文件中。
<$>[注] 注: 将突出的代码放置在现有返回声明上方的行上,在UploadProfileImage
突变中。
1[label schema.resolvers.go]
2
3package graph
4
5func (r *mutationResolver) UploadProfileImage(ctx context.Context, input model.ProfileImage) (bool, error) {
6 _ = os.Remove(userFileName)
7
8 user, userErr := r.GetUserByField("ID", *input.UserID)
9
10 if userErr != nil {
11 return false, fmt.Errorf("error getting user: %v", userErr)
12 }
13
14 fileUrl := fmt.Sprintf("https://%v.%v.digitaloceanspaces.com/%v", SpaceName, SpaceRegion, userFileName)
15
16 user.ImgURI = fileUrl
17
18 if _, err := r.UpdateUser(user); err != nil {
19 return false, fmt.Errorf("err updating user: %v", err)
20 }
21
22 return true, nil
23}
从新代码添加到schema.resolvers.go
文件中,将一个ID
字符串和用户ID传递到GetUserByField
辅助函数,以获取执行突变的用户的记录。
然后创建一个新的变量,并给出一个字符串的值格式化,以使最近上传的文件的链接在 https://BUCKET_NAME.SPACE_REGION.digitaloceanspaces.com/USER_ID-FILE_NAME
的格式中。
将下面的 curl 命令粘贴到您的终端中,并在命令中替换突出的 USER_ID 位存者,以通过 GraphQL 游戏场所创建的用户userId
。
1curl localhost:8080/query -F operations='{ "query": "mutation uploadProfileImage($image: Upload! $userId : String!) { uploadProfileImage(input: { file: $image userId : $userId}) }", "variables": { "image": null, "userId" : "USER_ID" } }' -F map='{ "0": ["variables.image"] }' -F 0=@small-profile.jpeg
结果将看起来像这样:
1[secondary_label Output]
2{"data": { "uploadProfileImage": true }}
为了进一步确认用户的img_uri
已更新,您可以使用浏览器中的fetchUsers
查询来获取用户的详细信息。如果更新成功,您将看到img_uri
字段中的https://bit.ly/3mCSn2i
默认位置地址已更新到上传图像的值。
右侧的输出将看起来像这样:
在返回的结果中,从查询返回的第一个用户对象中的img_uri
具有相当于在 DigitalOcean Spaces 中上传到桶的文件的值。
要通过 ACL 选项测试上传文件的权限,您可以在浏览器中打开img_uri
链接. 由于上传图像上的默认元数据,它将自动下载到您的计算机作为图像文件。
在img_uri
链接上的图像将是从命令行上传的相同图像,表明resolver.go
文件中的方法被正确执行,并且UploadProfileImage
突变中的整个文件上传逻辑按预期工作。
在此步骤中,您使用UploadProfileImage
突变解决方案的 AWS SDK for Go 将图像上传到 DigitalOcean Space。
结论
在本教程中,您使用Golang的AWS SDK从GraphQL应用程序中的突变解析器上传到DigitalOcean Space上的创建桶。
作为下一步,您可以部署本教程内构建的应用程序。 Go Dev 指南提供了一个初学者友好的指南,如何部署一个 Golang 应用程序到 DigitalOcean 的 应用平台,这是构建,部署和管理您的应用程序从各种编程语言的完全管理的解决方案。