

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 将 Aurora Serverless v2 与 AWS AppSync
<a name="tutorial-rds-resolvers"></a>

使用将您的 GraphQL API 连接到 Aurora 无服务器数据库。 AWS AppSync借助这种集成，您可以通过 GraphQL 查询、变更和订阅来执行 SQL 语句，从而为您提供与关系数据交互的灵活方式。

**注意**  
本教程使用 `US-EAST-1` 区域。

**优势**
+ GraphQL 和关系数据库之间的无缝集成
+ 能够通过 GraphQL 接口执行 SQL 操作
+ Aurora Serverless v2 的无服务器可扩展性
+ 通过 Secr AWS ets Manager 安全访问数据
+ 通过输入清理来防止 SQL 注入
+ 灵活的查询功能，包括筛选和范围操作

**常见使用案例**
+ 根据关系数据要求构建可扩展的应用程序
+  APIs 既需要 GraphQL 灵活性又需要 SQL 数据库功能的创作
+ 通过 GraphQL 变更和查询来管理数据操作
+ 实施安全的数据库访问模式

在本教程中，您将学习以下内容。
+ 设置 Aurora Serverless v2 集群
+ 启用数据 API 功能
+ 创建和配置数据库结构
+ 为数据库操作定义 GraphQL 架构
+ 实施查询和变更的解析器
+ 通过适当的输入清理来保护您的数据访问
+ 通过 GraphQL 接口执行各种数据库操作

**Topics**
+ [设置数据库集群](#create-cluster)
+ [启用数据 API](#enable-data-api)
+ [创建数据库和表](#create-database-and-table)
+ [GraphQL 架构](#graphql-schema)
+ [将您的 API 连接到数据库操作](#configuring-resolvers)
+ [通过 API 修改数据](#run-mutations)
+ [检索数据](#run-queries)
+ [保护您的数据访问](#input-sanitization)

## 设置数据库集群
<a name="create-cluster"></a>

在向添加 Amazon RDS 数据源之前 AWS AppSync，必须先在 Aurora Serverless v2 集群上启用数据 API，然后**使用配置密钥**。*AWS Secrets Manager*您可以使用 AWS CLI创建 Aurora Serverless v2 集群：

```
aws rds create-db-cluster \
    --db-cluster-identifier appsync-tutorial \
    --engine aurora-mysql \
    --engine-version 8.0 \
    --serverless-v2-scaling-configuration MinCapacity=0,MaxCapacity=1 \
    --master-username USERNAME \
    --master-user-password COMPLEX_PASSWORD \
    --enable-http-endpoint
```

这将为集群返回一个 ARN。

创建集群后，必须使用以下命令添加一个 Aurora Serverless v2 实例。

```
aws rds create-db-instance \
    --db-cluster-identifier appsync-tutorial \
    --db-instance-identifier appsync-tutorial-instance-1 \
    --db-instance-class db.serverless \
    --engine aurora-mysql
```

**注意**  
这些端点需要一段时间才会活跃。您可以在 Amazon RDS 控制台中相应集群的**连接和安全**选项卡中查看其状态。您也可以使用以下 AWS CLI 命令检查集群的状态。  

```
aws rds describe-db-clusters \
    --db-cluster-identifier appsync-tutorial \
    --query "DBClusters[0].Status"
```

您可以使用 AWS Secrets Manager 控制台创建 S *ecre* t，也可以使用上 AWS CLI 一步中的`USERNAME`和`COMPLEX_PASSWORD`使用输入文件创建 Secret，如下所示。

```
{
    "username": "USERNAME",
    "password": "COMPLEX_PASSWORD"
}
```

将其作为参数传递给 AWS CLI：

```
aws secretsmanager create-secret --name HttpRDSSecret --secret-string file://creds.json --region us-east-1
```

这将为密钥返回 ARN。

 **记下您的 Aurora Serverless 集群的 ARN** 和密钥，以便以后在创建数据源时在 AppSync 控制台中使用。

## 启用数据 API
<a name="enable-data-api"></a>

您可以通过[按照 RDS 文档中的说明操作](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/data-api.html)来在您的集群上启用数据 API。在添加为数据源之前，必须启用 AppSync 数据 API。

## 创建数据库和表
<a name="create-database-and-table"></a>

在启用数据 API 后，您可以确保它在 AWS CLI中使用 `aws rds-data execute-statement` 命令。这将确保您的 Aurora 无服务器集群在添加到 AppSync API 之前已正确配置该集群。首先，使用 `--sql` 参数创建一个名为 *TESTDB* 的数据库，如下所示：

```
aws rds-data execute-statement --resource-arn "arn:aws:rds:us-east-1:123456789000:cluster:http-endpoint-test" \
--schema "mysql"  --secret-arn "arn:aws:secretsmanager:us-east-1:123456789000:secret:testHttp2-AmNvc1"  \
--region us-east-1 --sql "create DATABASE TESTDB"
```

如果运行无误，请使用*创建表* 命令添加一个表：

```
aws rds-data execute-statement --resource-arn "arn:aws:rds:us-east-1:123456789000:cluster:http-endpoint-test" \
 --schema "mysql"  --secret-arn "arn:aws:secretsmanager:us-east-1:123456789000:secret:testHttp2-AmNvc1" \
 --region us-east-1 \
 --sql "create table Pets(id varchar(200), type varchar(200), price float)" --database "TESTDB"
```

如果一切运行顺利，则可以继续在 AppSync API 中添加集群作为数据源。

## GraphQL 架构
<a name="graphql-schema"></a>

现在，您的 Aurora Serverless 数据 API 已通过表启动并运行，我们将创建一个 GraphQL 架构并附加用于执行变更和订阅的解析器。在 AWS AppSync 控制台中创建新 API 并导航到 **Schema** 页面，然后输入以下内容：

```
type Mutation {
    createPet(input: CreatePetInput!): Pet
    updatePet(input: UpdatePetInput!): Pet
    deletePet(input: DeletePetInput!): Pet
}

input CreatePetInput {
    type: PetType
    price: Float!
}

input UpdatePetInput {
id: ID!
    type: PetType
    price: Float!
}

input DeletePetInput {
    id: ID!
}

type Pet {
    id: ID!
    type: PetType
    price: Float
}

enum PetType {
    dog
    cat
    fish
    bird
    gecko
}

type Query {
    getPet(id: ID!): Pet
    listPets: [Pet]
    listPetsByPriceRange(min: Float, max: Float): [Pet]
}

schema {
    query: Query
    mutation: Mutation
}
```

 **保存**您的架构并导航到**数据来源**页面，然后创建新的数据来源。为数据来源类型选择**关系数据库**，并提供一个友好名称。使用您在上一步中创建的数据库名称以及用来创建它的**集群 ARN**。对于该**角色**，您可以 AppSync 创建一个新角色，也可以使用类似于以下内容的策略创建一个角色：

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "rds-data:BatchExecuteStatement",
                "rds-data:BeginTransaction",
                "rds-data:CommitTransaction",
                "rds-data:ExecuteStatement",
                "rds-data:RollbackTransaction"
            ],
            "Resource": [
                "arn:aws:rds:us-east-1:111122223333:cluster:mydbcluster",
                "arn:aws:rds:us-east-1:111122223333:cluster:mydbcluster:*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "secretsmanager:GetSecretValue"
            ],
            "Resource": [
            "arn:aws:secretsmanager:us-east-1:111122223333:secret:mysecret",
            "arn:aws:secretsmanager:us-east-1:111122223333:secret:mysecret:*"
            ]
        }
    ]
}
```

------

请注意，此策略中有两个需要获得角色访问权的**语句**。第一个**资源**是您的 Aurora 无服务器集群，第二个资源是您的 AR AWS Secrets Manager N。在单击 “**创建**” 之前，您需要 ARNs 在 AppSync 数据源配置中**同时提供两**者。

将其作为参数传递给 AWS CLI。

```
aws secretsmanager create-secret \
  --name HttpRDSSecret \
  --secret-string file://creds.json \
  --region us-east-1
```

这将为密钥返回 ARN。在控制台中创建数据源时，请记下您的 Aurora Serverless 集群的 ARN 和密钥，以备日后使用。 AWS AppSync 

### 构建您的数据库结构
<a name="create-database-and-table"></a>

在启用数据 API 后，您可以确保它在 AWS CLI中使用 `aws rds-data execute-statement` 命令。这将确保您的 Aurora Serverless v2 集群在添加到 API 之前已正确配置。 AWS AppSync 首先，使用 `--sql` 参数创建名为 *TESTDB* 的数据库，如下所示。

```
aws rds-data execute-statement \
                --resource-arn "arn:aws:rds:us-east-1:111122223333:cluster:appsync-tutorial" \
                --secret-arn "arn:aws:secretsmanager:us-east-1:111122223333:secret:appsync-tutorial-rds-secret"  \
                --region us-east-1 \
                --sql "create DATABASE TESTDB"
```

如果运行无误，请使用以下 *create table* 命令添加一个表。

```
aws rds-data execute-statement \
      --resource-arn "arn:aws:rds:us-east-1:111122223333:cluster:http-endpoint-test" \
      --secret-arn "arn:aws:secretsmanager:us-east-1:111122223333:secret:testHttp2-AmNvc1" \
      --region us-east-1 \
      --sql "create table Pets(id varchar(200), type varchar(200), price float)" \
      --database "TESTDB"
```

### 设计您的 API 接口
<a name="graphql-schema"></a>

Aurora Serverless v2 数据 API 已通过表启动并运行之后，创建一个 GraphQL 架构并附加用于执行变更和订阅的解析器。在 AWS AppSync 控制台中创建新 API，然后在控制台中导航到**架构**页面，然后输入以下内容。

```
type Mutation {
        createPet(input: CreatePetInput!): Pet
        updatePet(input: UpdatePetInput!): Pet
        deletePet(input: DeletePetInput!): Pet
    }
    
    input CreatePetInput {
        type: PetType
        price: Float!
    }
    
    input UpdatePetInput {
        id: ID!
        type: PetType
        price: Float!
    }
    
    input DeletePetInput {
        id: ID!
    }
    
    type Pet {
        id: ID!
        type: PetType
        price: Float
    }
    
    enum PetType {
        dog
        cat
        fish
        bird
        gecko
    }
    
    type Query {
        getPet(id: ID!): Pet
        listPets: [Pet]
        listPetsByPriceRange(min: Float, max: Float): [Pet]
    }
    
    schema {
        query: Query
        mutation: Mutation
    }
```

 **保存**您的架构并导航到**数据来源**页面，然后创建新的数据来源。为**数据来源**类型选择**关系数据库**，并提供一个易于理解和记忆的名称。使用您在上一步中创建的数据库名称以及用来创建它的**集群 ARN**。对于该**角色**，您可以 AWS AppSync 创建一个新角色，也可以使用类似于以下内容的策略创建一个角色。

------
#### [ JSON ]

****  

```
{
        "Version":"2012-10-17",		 	 	 
        "Statement": [
            {
                "Effect": "Allow",
                "Action": [
                    "rds-data:BatchExecuteStatement",
                    "rds-data:BeginTransaction",
                    "rds-data:CommitTransaction",
                    "rds-data:ExecuteStatement",
                    "rds-data:RollbackTransaction"
                ],
                "Resource": [
                    "arn:aws:rds:us-east-1:111122223333:cluster:mydbcluster",
                    "arn:aws:rds:us-east-1:111122223333:cluster:mydbcluster:*"
                ]
            },
            {
                "Effect": "Allow",
                "Action": [
                    "secretsmanager:GetSecretValue"
                ],
                "Resource": [
                "arn:aws:secretsmanager:us-east-1:111122223333:secret:mysecret",
                "arn:aws:secretsmanager:us-east-1:111122223333:secret:mysecret:*"
                ]
            }
        ]
    }
```

------

请注意，此策略中有两个需要获得角色访问权的**语句**。第一个**资源**是你的 Aurora Serverless v2 集群，第二个资源是你的 AR AWS Secrets Manager N。在单击 “**创建**” 之前，您需要 ARNs 在 AWS AppSync 数据源配置中**同时提供两**者。

## 将您的 API 连接到数据库操作
<a name="configuring-resolvers"></a>

现在，我们有一个有效的 GraphQL 架构和一个 RDS 数据来源，您可以将解析器附加到架构中的 GraphQL 字段。我们的 API 将提供以下功能：

1. 使用 *Mutation.createPet* 字段创建宠物

1. 使用 *Mutation.updatePet* 字段更新宠物

1. 使用 *Mutation.deletePet* 字段删除宠物

1. 使用 *Query.getPet* 字段获取单个宠物

1. 使用 *Query.listPets* 字段列出所有宠物

1. 使用*查询列出价格区间内的宠物。 listPetsByPriceRange*字段

### Mutation.createPet
<a name="mutation-createpet"></a>

在 AWS AppSync 控制台的架构编辑器中，在右侧选择 Att **ach Resolver fo** `createPet(input: CreatePetInput!): Pet` r。选择您的 RDS 数据来源。在**请求映射模板**部分，添加以下模板：

```
#set($id=$utils.autoId())
{
"version": "2018-05-29",
    "statements": [
        "insert into Pets VALUES (:ID, :TYPE, :PRICE)",
        "select * from Pets WHERE id = :ID"
    ],
    "variableMap": {
        ":ID": "$ctx.args.input.id",
        ":TYPE": $util.toJson($ctx.args.input.type),
        ":PRICE": $util.toJson($ctx.args.input.price)
    }
}
```

系统会根据**语句**数组中的顺序依次执行 SQL 语句。结果将以相同的顺序返回。由于这是一个变更，您将在 *insert* 后面运行 *select* 语句来检索提交的值，从而填充 GraphQL 响应映射模板。

在**响应映射模板**部分，添加以下模板：

```
$utils.toJson($utils.rds.toJsonObject($ctx.result)[1][0])
```

由于*语句*有两个 SQL 查询，我们需要指定从具有 `$utils.rds.toJsonString($ctx.result))[1][0])` 的数据库返回的矩阵中的第二个结果。

### Mutation.updatePet
<a name="mutation-updatepet"></a>

在 AWS AppSync 控制台的架构编辑器中，为其选择**附加解析器**。`updatePet(input: UpdatePetInput!): Pet`选择您的 **RDS 数据来源**。在**请求映射模板**部分，添加以下模板。

```
{
"version": "2018-05-29",
    "statements": [
        $util.toJson("update Pets set type=:TYPE, price=:PRICE WHERE id=:ID"),
        $util.toJson("select * from Pets WHERE id = :ID")
    ],
    "variableMap": {
        ":ID": "$ctx.args.input.id",
        ":TYPE": $util.toJson($ctx.args.input.type),
        ":PRICE": $util.toJson($ctx.args.input.price)
    }
}
```

在**响应映射模板**部分，添加以下模板。

```
$utils.toJson($utils.rds.toJsonObject($ctx.result)[1][0])
```

### Mutation.deletePet
<a name="mutation-deletepet"></a>

在 AWS AppSync 控制台的架构编辑器中，为其选择**附加解析器**。`deletePet(input: DeletePetInput!): Pet`选择您的 **RDS 数据来源**。在**请求映射模板**部分，添加以下模板。

```
{
"version": "2018-05-29",
    "statements": [
        $util.toJson("select * from Pets WHERE id=:ID"),
        $util.toJson("delete from Pets WHERE id=:ID")
    ],
    "variableMap": {
        ":ID": "$ctx.args.input.id"
    }
}
```

在**响应映射模板**部分，添加以下模板。

```
$utils.toJson($utils.rds.toJsonObject($ctx.result)[0][0])
```

### Query.getPet
<a name="query-getpet"></a>

既然已为您的架构创建变更，现在将连接三个查询以展示如何获取各个项目和列表以及应用 SQL 筛选。在 AWS AppSync 控制台的**架构编辑器**中，为其选择**附加解析器**。`getPet(id: ID!): Pet`选择您的 **RDS 数据来源**。在**请求映射模板**部分，添加以下模板。

```
{
"version": "2018-05-29",
        "statements": [
            $util.toJson("select * from Pets WHERE id=:ID")
    ],
    "variableMap": {
        ":ID": "$ctx.args.id"
    }
}
```

在**响应映射模板**部分，添加以下模板：

```
$utils.toJson($utils.rds.toJsonObject($ctx.result)[0][0])
```

### Query.listPets
<a name="query-listpets"></a>

在 AWS AppSync 控制台的架构编辑器中，在右侧选择 Att **ach Resolver fo** `getPet(id: ID!): Pet` r。选择您的 **RDS 数据来源**。在**请求映射模板**部分，添加以下模板。

```
{
    "version": "2018-05-29",
    "statements": [
        "select * from Pets"
    ]
}
```

在**响应映射模板**部分，添加以下模板。

```
$utils.toJson($utils.rds.toJsonObject($ctx.result)[0])
```

### 查询。 listPetsByPriceRange
<a name="query-listpetsbypricerange"></a>

在 AWS AppSync 控制台的架构编辑器中，在右侧选择 Att **ach Resolver fo** `getPet(id: ID!): Pet` r。选择您的 **RDS 数据来源**。在**请求映射模板**部分，添加以下模板。

```
{
    "version": "2018-05-29",
    "statements": [
            "select * from Pets where price > :MIN and price < :MAX"
    ],

    "variableMap": {
        ":MAX": $util.toJson($ctx.args.max),
        ":MIN": $util.toJson($ctx.args.min)
    }
}
```

在**响应映射模板**部分，添加以下模板：

```
$utils.toJson($utils.rds.toJsonObject($ctx.result)[0])
```

## 通过 API 修改数据
<a name="run-mutations"></a>

现在，您已使用 SQL 语句配置所有解析器并将 GraphQL API 连接到 Serverless Aurora 数据 API，可以开始执行变更和查询。在 AWS AppSync 控制台中，选择 **Quer** ies 选项卡并输入以下内容来创建 Pet：

```
mutation add {
    createPet(input : { type:fish, price:10.0 }){
        id
        type
        price
    }
}
```

该响应包含 *id*、*类型* 和*价格*，如下所示：

```
{
  "data": {
    "createPet": {
      "id": "c6fedbbe-57ad-4da3-860a-ffe8d039882a",
      "type": "fish",
      "price": "10.0"
    }
  }
}
```

您可以通过运行 *updatePet* 变更来修改此项目：

```
mutation update {
    updatePet(input : {
        id: ID_PLACEHOLDER,
        type:bird,
        price:50.0
    }){
        id
        type
        price
    }
}
```

请注意，我们使用了以前从 *createPet* 操作返回的 *id*。当解析器利用 `$util.autoId()` 时，这将是您的记录的唯一值。您可以通过类似的方式删除记录：

```
mutation delete {
    deletePet(input : {id:ID_PLACEHOLDER}){
        id
        type
        price
    }
}
```

使用包含不同的*价格* 值的第一个变更创建几条记录，然后运行几个查询。

## 检索数据
<a name="run-queries"></a>

同样，在控制台的**查询**选项卡中，使用以下语句列出您创建的所有记录。

```
query allpets {
    listPets {
        id
        type
        price
    }
}
```

利用我们的映射模板`where price > :MIN and price < :MAX`中的 SQL WH *ER* E 谓词进行*查询。 listPetsByPriceRange*使用以下 GraphQL 查询：

```
query petsByPriceRange {
    listPetsByPriceRange(min:1, max:11) {
        id
        type
        price
    }
}
```

您应仅看到包含高于 1 美元或低于 10 美元的*价格* 的记录。最后，您可以执行查询以检索各个记录，如下所示：

```
query onePet {
    getPet(id:ID_PLACEHOLDER){
        id
        type
        price
    }
}
```

## 保护您的数据访问
<a name="input-sanitization"></a>

SQL 注入是数据库应用程序中的一个安全漏洞。当攻击者通过用户输入字段插入恶意 SQL 代码时，就会发生注入。通过注入可允许未经授权访问数据库数据。我们建议您在使用 `variableMap` 进行处理之前仔细验证和清理所有用户输入，以防范 SQL 注入攻击。如果不使用变量映射，则由您负责清理其 GraphQL 操作的参数。执行此操作的一种方法是在对数据 API 执行 SQL 语句之前，在请求映射模板中提供特定于输入的验证步骤。让我们看看如何修改 `listPetsByPriceRange` 示例的请求映射模板。您可以执行以下操作，而不是仅依赖于用户输入：

```
#set($validMaxPrice = $util.matches("\d{1,3}[,\\.]?(\\d{1,2})?",$ctx.args.maxPrice))

#set($validMinPrice = $util.matches("\d{1,3}[,\\.]?(\\d{1,2})?",$ctx.args.minPrice))


#if (!$validMaxPrice || !$validMinPrice)
    $util.error("Provided price input is not valid.")
#end
{
    "version": "2018-05-29",
    "statements": [
            "select * from Pets where price > :MIN and price < :MAX"
    ],

    "variableMap": {
        ":MAX": $util.toJson($ctx.args.maxPrice),
        ":MIN": $util.toJson($ctx.args.minPrice)
    }
}
```

在对数据 API 执行解析器时防止恶意输入的另一种方法是，将预编译语句与存储过程和参数化输入一起使用。例如，在 `listPets` 的解析器中，定义以下将 *select* 作为预编译语句执行的过程：

```
CREATE PROCEDURE listPets (IN type_param VARCHAR(200))
  BEGIN
     PREPARE stmt FROM 'SELECT * FROM Pets where type=?';
     SET @type = type_param;
     EXECUTE stmt USING @type;
     DEALLOCATE PREPARE stmt;
  END
```

在您的 Aurora Serverless v2 实例中创建。

```
aws rds-data execute-statement --resource-arn "arn:aws:rds:us-east-1:xxxxxxxxxxxx:cluster:http-endpoint-test" \
--schema "mysql"  --secret-arn "arn:aws:secretsmanager:us-east-1:xxxxxxxxxxxx:secret:httpendpoint-xxxxxx"  \
--region us-east-1  --database "DB_NAME" \
--sql "CREATE PROCEDURE listPets (IN type_param VARCHAR(200)) BEGIN PREPARE stmt FROM 'SELECT * FROM Pets where type=?'; SET @type = type_param; EXECUTE stmt USING @type; DEALLOCATE PREPARE stmt; END"
```

由于我们现在只是调用了存储过程，因此简化了为 listPets 生成的解析器代码。至少，任何字符串输入都应该有单引号进行[转义](#escaped)。

```
#set ($validType = $util.isString($ctx.args.type) && !$util.isNullOrBlank($ctx.args.type))
#if (!$validType)
    $util.error("Input for 'type' is not valid.", "ValidationError")
#end

{
    "version": "2018-05-29",
    "statements": [
        "CALL listPets(:type)"
    ]
    "variableMap": {
        ":type": $util.toJson($ctx.args.type.replace("'", "''"))
    }
}
```

### 使用转义字符串
<a name="escaped"></a>

使用单引号来标记 SQL 语句中字符串文字的开始和结束，例如 `'some string value'`。要允许在字符串中使用具有一个或多个单引号字符 ( `'`) 的字符串值，必须用两个单引号 (`''`) 替换每个字符串值。例如，如果输入字符串是 `Nadia's dog`，您可以针对 SQL 语句将其转义，例如

```
update Pets set type='Nadia''s dog' WHERE id='1'
```