AWS Lambda笔记-数据持久化DynamoDB-10【含源代码】

之前认证部分的数据都是硬编码来判断如果用户输入serverless即通过授权,这次认证是从数据库读取数据进行判断。数据的存储使用AWS的无结构化数据引擎DynamoDB。AWS的DynamoDB是一个全托管的NoSQL数据库服务,提供快速可预测的性能,具有无缝可扩展性。

  1. 创建DynamoDB
  2. 配置数据映射类
  3. 配置Lambda环境变量
  4. 用户数据层操作
  5. 用户业务层逻辑
  6. 用户授权功能
  7. 用户注册功能

源代码

代码下载地址:https://pan.baidu.com/s/1VgsY2vELb9aGKzNipVLAgQ
提取码:4z4h

工程说明

工程是基于数据库查询的用户授权,并且增加用户注册功能。
repository-dynamodb工程:主要实现DynamoDB数据库动态表名解析器和映射器,为数据库层提供基础DynamoDBMapper类。
service-user工程:数据层实现简单的CURD操作并做了数据映射,业务层实现用户授权和用户注册基本功能。
lambda-authorizer工程:用户授权功能,通过token请求业务层查询对应的用户是否存在,决定拒绝还是通过。
lambda-userregistration工程:用户注册功能,接受用户名和邮箱完成注册,由业务层对用户数据进行验证通过后提交给数据层持久化,成功返回201状态码,异常返回4xx状态码。

工程间关联服务

工程关系图

1. 创建DynamoDB

DynamoDB数据库创建方法比较多。
1)使用 aws dynamodb create-table 本地命令行方式创建。
2)访问 DynamoDB Console :https://console.aws.amazon.com/dynamodb/home,在界面点击“创建表”按步骤创建。
3)使用CloudFormation方式,在cloudformation.template模版中创建,这种方式便于自动化部署。
我们使用方法三创建两张表UserTable和TokenTable,用于存储用户和Token信息。
注意:在下面代码中,没有明确指定TableName属性(即没有明确表的名称)如果不指定名称,则 AWS CloudFormation会生成一个唯一的物理ID并将该ID用于表名称。例如:表名"serverlessbook-TokenTable-1F7YKEBK8X0L4","serverlessbook-UserTable-TQKVRRLPX2EN"。

cloudformation.template添加以下代码:

"UserTable": {
      "Type": "AWS::DynamoDB::Table",
      "Properties": {
        //表和索引键属性,必须字段
        "AttributeDefinitions": [
          {
            "AttributeName": "UserId",
            //S表示String,N表示数字,B表示Blob,M表示Map
            "AttributeType": "S" 
          },
          {
            "AttributeName": "Username",
            "AttributeType": "S"
          },
          {
            "AttributeName": "Email",
            "AttributeType": "S"
          }
        ],
        //KeySchema:指定组成表主键的属性
        //KeySchema属性字段还必须在AttributeDefinitions属性中定义
        "KeySchema": [
          {
            "AttributeName": "UserId",
            "KeyType": "HASH" //主键为Hash类型,DynamoDB中没有自增属性
          }
        ],
        //创建的全局二级索引。可以创建最多 20 个全局二级索引
        //注意一次只能新增一个。
        "GlobalSecondaryIndexes": [
          {
            "IndexName": "UsernameIndex",
            "KeySchema": [
              {
                "AttributeName": "Username",
                "KeyType": "HASH"
              }
            ],
            //表示从表复制(投影)到本地二级索引的属性。
            "Projection": {
                //所有表属性都投影到索引中。
                "ProjectionType": "ALL" 
            },
            //指定的表的吞吐量
            "ProvisionedThroughput": {
              "ReadCapacityUnits": 1,
              "WriteCapacityUnits": 1
            }
          },
          {
            "IndexName": "EmailIndex",
            "KeySchema": [
              {
                "AttributeName": "Email",
                "KeyType": "HASH"
              }
            ],
            "Projection": {
              "ProjectionType": "ALL"
            },
            "ProvisionedThroughput": {
              "ReadCapacityUnits": 1,
              "WriteCapacityUnits": 1
            }
          }
        ],
        //指定的表的吞吐量
        "ProvisionedThroughput": {
            //在DynamoDB返回ThrottlingException 之前,每秒使用的最大强一致性读取数。
            "ReadCapacityUnits": 1,
            //在 DynamoDB 返回 ThrottlingException 之前,每秒使用的最大写入数。
            "WriteCapacityUnits": 1
        }
      }
    },
    "TokenTable": {
      "Type": "AWS::DynamoDB::Table",
      "Properties": {
        "AttributeDefinitions": [
          {
            "AttributeName": "Token",
            "AttributeType": "S"
          }
        ],
        "KeySchema": [
          {
            "AttributeName": "Token",
            "KeyType": "HASH"
          }
        ],
        "ProvisionedThroughput": {
          "ReadCapacityUnits": 1,
          "WriteCapacityUnits": 1
        }
      }
    }

使用 ./gradlew deploy 发布工程,肯定会失败,且出现“Cannot perform more than one GSI creation or deletion in a single update”这是因为GlobalSecondaryIndexes 一次只能新增一个,在创建UsernameIndex和UsernameIndex需要分两次发布,可以先去掉“UsernameIndex”代码块。
数据库创建完成,我们可以使用一下命令查看和操作DynamoDB。

查询所有的表show tables

aws dynamodb list-tables --region us-east-1

插入 Token 数据,注意:表名来自上一条命令的查询结果

aws dynamodb put-item \
--region us-east-1 \
--table-name serverlessbook-TokenTable-IJO5534VDQLM \
--item '{"UserId": {"S":"1234"},"Token":{"S":"serverless"}}'

插入 User 数据

aws dynamodb put-item \
--region us-east-1 \
--table-name serverlessbook-UserTable-13PDTJ3N02BUR \
--item '{"UserId": {"S":"1234"},"Username":{"S":"Test User"},"Email":{"S":"[email protected]"}}'

查询全表数据

aws dynamodb scan \
--region us-east-1 \
--table-name serverlessbook-UserTable-13PDTJ3N02BUR

查询UserID = 1234的用户

aws dynamodb query \
--region us-east-1 \
--table-name serverlessbook-UserTable-13PDTJ3N02BUR  \
--key-conditions '{"UserId":{"AttributeValueList":[{"S":"1234"}],"ComparisonOperator":"EQ"}}'

删除UserID = 1234的用户,建议不要删除,后续授权功能需要一条测试数据

aws dynamodb delete-item \
--region us-east-1 \
--table-name serverlessbook-UserTable-13PDTJ3N02BUR  \
--key '{"UserId":{"S":"1234"}}'

2. 配置数据映射类

DynamoDB提供了非常简单的对象映射功能,类似JPA,并提供了一组注释对类进行装饰。如果属性设置了GSI,需要使用DynamoDBIndexHashKey注释。
注意:使用@DynamoDBIndexHashKey,若表中没有设置GlobalSecondaryIndexes,则系统会提示异常“@DynamoDBIndexHashKey must specify one of HASH GSI name/names”
两个对象的映射类Token和User。

public class Token {
    //属性名称,类似关系型数据的字段
    @DynamoDBHashKey(attributeName = "Token")
    private String token;
    //属性名
    @DynamoDBAttribute(attributeName = "UserId")
    private String userId;
    //省略getter/setter
}
public class User {
    @DynamoDBHashKey(attributeName = "UserId")
    private String id;
    //GSI设置
    @DynamoDBIndexHashKey(globalSecondaryIndexName = "UsernameIndex", attributeName = "Username")
    private String username;
    @DynamoDBIndexHashKey(globalSecondaryIndexName = "EmailIndex", attributeName = "Email")
    private String email;
    //省略getter/setter
}

在DynamoDB中注释表名可以使用@DynamoDBTable,不过这边将表名的创建交给了CloudFormation,所以我们采用动态获取表名的方法,即表名会作为环境变量注入Lambda函数中,同时通过配置DynamoDB客户端,可以解析环境变量中的对象来获取动态表名,我们继续看配置Lambda环境变量。

3. 配置Lambda环境变量

在AuthorizerLambda中添加环境变量,即User表和Token表。
注意:下面代码的DynamoDbTokenTable和DynamoDbUserTable这两个属性,他们的组成是DynamoDB+映射器类名+Table,这个规律将在下面的EnvironmentVariableTableNameResolver类中动态获取表名中使用到。

    "AuthorizerLambda": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "Handler": "com.serverlessbook.lambda.authorizer.Handler",
        "Runtime": "java8",
        "Timeout": "300",
        "MemorySize": "1024",
        "Description": "Test lambda",
        //执行期间可从函数代码访问的环境变量。
        "Environment": {
          "Variables": { //环境变量键值对
            //EnvironmentVariableTableNameResolver.java 代码中对表名进行拼接
            //片段:"DynamoDb" + clazz.getSimpleName() + "Table"
            //例如:DynamoDbTokenTable,获得Ref的TokenTable动态表
            "DynamoDbTokenTable": {
              "Ref": "TokenTable"
            },
            "DynamoDbUserTable": {
              "Ref": "UserTable"
            }
          }
        }
      }
    }

我们需要知道所有的DynamoDB操作都是由DynamoDBmapper的实例执行。这个类是可扩展,可配置的,其中一个配置是表名解析器类,实现DynamoDBMapperConfig.TabNameResolver接口。

表名解析器

public class EnvironmentVariableTableNameResolver implements TableNameResolver {
    //通过获得类名称,拼接成 "DynamoDb+类名+Table“ 名称
    @Override
    public String getTableName(Class clazz, DynamoDBMapperConfig config) {
        //规律在这里
        String environmentVariableName = "DynamoDb" + clazz.getSimpleName() + "Table";
        String tableName = System.getenv(environmentVariableName);
        if (tableName == null) {
            throw new DynamoDBMappingException("DynamoDB table name for " +
             clazz + " cannot be determined. " + environmentVariableName + 
            " environment variable should be set.");
        }
        return tableName;
    }
}

我们再创建一个DynamoDBMapper的子类DynamoDBMapperWithCustomTableName,并把EnvironmentVariableTableNameResolver注入到DynamoDBMapper的子类中。DynamoDB的所有操作都是由DynamoDBmapper的实例执行。

自定义 DynamoDBMapper

public class DynamoDBMapperWithCustomTableName extends DynamoDBMapper {
    //需要注入dynamodb客户端
    @Inject
    public DynamoDBMapperWithCustomTableName(AmazonDynamoDBClient amazonDynamoDBClient) {
        this(amazonDynamoDBClient, new EnvironmentVariableTableNameResolver());
    }

    //使用dynamodb客户端和表名解析器构建自定义dynamodbmapper
    public DynamoDBMapperWithCustomTableName(AmazonDynamoDBClient amazonDynamoDBClient, 
                        DynamoDBMapperConfig.TableNameResolver tableNameResolver) {
        super(amazonDynamoDBClient,
                DynamoDBMapperConfig
                        .builder()
                        .withTableNameResolver(tableNameResolver)
                        .build());
    }
}

同时我们需要在使用到数据操作的Lambda的AbstractModule中添加注入依赖关系。即lambda-authorizer和lambda-userregistration工程中添加。

@Override
protected void configure() {
    bind(DynamoDBMapper.class).to(DynamoDBMapperWithCustomTableName.class);
}

4. 用户数据层操作

这边主要是通过DynamoDBMapper操作数据的CURD,无其他复杂逻辑。

用户数据操作层接口

public interface UserRepository {
    //根据token查询用户
    Optional getUserByToken(String token);
    //更具email查询用户
    Optional getUserByEmail(String email);
    //根据username查询用户
    Optional getUserByUsername(String username);
    //保存用户
    void saveUser(User user);
}

用户数据操作层实现类

public class UserRepositoryDynamoDB implements UserRepository {
    private final DynamoDBMapper dynamoDBMapper;
    @Inject
    public UserRepositoryDynamoDB(DynamoDBMapper dynamoDBMapper) {
        this.dynamoDBMapper = dynamoDBMapper;
    }

    public Optional getUserByToken(String token) {
        //根据分区键获取Token数据
        Token foundTokenInDynamoDB = dynamoDBMapper.load(Token.class, token);
        if (foundTokenInDynamoDB != null) {
            String userId = foundTokenInDynamoDB.getUserId();
            // 找到Token,根据Token中的Userid,在User表中找User信息
            return Optional.ofNullable(dynamoDBMapper.load(User.class, userId));
        }
        // 没找到Token直接返回空
        return Optional.empty();
    }

    @Override
    public void saveUser(User user) {
        dynamoDBMapper.save(user);
    }

    @Override
    public Optional getUserByEmail(String email) {
        return getUserByCriteria("EmailIndex", new User().setEmail(email));
    }

    @Override
    public Optional getUserByUsername(String username) {
        return getUserByCriteria("UsernameIndex", new User().setUsername(username));
    }

    //获取索引名称,并查询对象查询
    public Optional getUserByCriteria(String indexName, User hashKeyValues) {
        //构建查询表达式
        DynamoDBQueryExpression expression = new DynamoDBQueryExpression()
                .withIndexName(indexName)
                .withConsistentRead(false)
                .withHashKeyValues(hashKeyValues);
        //查询结构
        QueryResultPage result = dynamoDBMapper.queryPage(User.class, expression);

        if (result.getCount() > 0) {
            return Optional.of(result.getResults().get(0));
        }
        return Optional.empty();
    }
}

5. 用户业务层逻辑

用户业务逻辑层主要是用户token信息获取和用户注册业务,用户注册里面包含用户名不能重复,邮箱必须合法且不能重复验证逻辑。

public interface UserService {
    //根据toke获取用户信息
    User getUserByToken(String token) throws UserNotFoundException;
    //注册用户,里面包含用户名和邮箱唯一性验证以及邮箱合法性验证
    User registerNewUser(String username, String email) throws UserRegistrationException;
}
public class UserServiceImpl implements UserService {
    private final UserRepository userRepository;
    //构造函数,传入用户存储服务
    @Inject
    public UserServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
        Objects.requireNonNull(userRepository);
    }

    @Override
    public User getUserByToken(String token) throws UserNotFoundException {
         return userRepository.getUserByToken(token).orElseThrow(UserNotFoundException::new);
    }
    
    //注册用户
    @Override
    public User registerNewUser(String username, String email) throws UserRegistrationException {
        checkEmailValidity(email);  //邮箱合法性验证
        checkEmailUniqueness(email);    //邮箱重复性验证
        checkUsernameUniqueness(username);  //用户名重复性验证
        User newUser = new User()
                .setId(UUID.randomUUID().toString())
                .setUsername(username)
                .setEmail(email);
        //保存用户信息
        userRepository.saveUser(newUser);
        return newUser;
    }
    //邮箱合法性验证
    private void checkEmailValidity(String email) throws InvalidMailAddressException {
        final String emailPattern = "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$";
        if (!Pattern.compile(emailPattern).matcher(email).matches()) {
            throw new InvalidMailAddressException();
        }
    }
    //去数据库验证邮箱不重复
    void checkEmailUniqueness(String email) throws AnotherUserWithSameEmailExistsException {
        if (userRepository.getUserByEmail(email).isPresent()) {
            throw new AnotherUserWithSameEmailExistsException();
        }
    }
    //去数据库验证用户名不重复
    void checkUsernameUniqueness(String username) throws AnotherUserWithSameUsernameExistsException {
        if (userRepository.getUserByUsername(username).isPresent()) {
            throw new AnotherUserWithSameUsernameExistsException();
        }
    }
}

6. 用户授权功能

该功能相关的API Gateway基于AWS Lambda笔记-简单授权-8
的cloudformation.template配置,添加dynamodb数据库操作权限策略,即在cloudformation.template的LambdaCustomPolicy的Action元素和Resource元素中添加以下内容。

"LambdaCustomPolicy": {
  "Type": "AWS::IAM::Policy",
  "Properties": {
    "PolicyName": "LambdaCustomPolicy",
    "PolicyDocument": {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": [
            "s3:ListBuckets",
            "dynamodb:BatchGetItem",
            "dynamodb:BatchWriteItem",
            "dynamodb:DeleteItem",
            "dynamodb:GetItem",
            "dynamodb:GetRecords",
            "dynamodb:GetShardIterator",
            "dynamodb:ListTables",
            "dynamodb:PutItem",
            "dynamodb:Query",
            "dynamodb:Scan",
            "dynamodb:UpdateItem"
          ],
          "Resource": [
          {
            "Fn::Sub": "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${TokenTable}*"
          },
          {
            "Fn::Sub": "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${UserTable}*"
          }
        ]
        }
      ]
    }

发布工程./gradlew deploy ,认认真真添加两条记录。

//insert Token 数据,注意:表名来自 aws dynamodb list-tables --region us-east-1 命令的返回值。
aws dynamodb put-item \
--region us-east-1 \
--table-name serverlessbook-TokenTable-1F7YKEBK8X0L4 \
--item '{"UserId": {"S":"1234"},"Token":{"S":"serverless"}}'

//insert User 数据
aws dynamodb put-item \
--region us-east-1 \
--table-name serverlessbook-UserTable-TQKVRRLPX2EN \
--item '{"UserId": {"S":"1234"},"Username":{"S":"Test User"},"Email":{"S":"[email protected]"}}'

这时候我们访问 https://serverless.xxx.com/test?value=hello+world,添加Authorization头信息为“Bearer serverless”,通过验证,否则拒绝。

通过验证后,访问/test方法返回记录

拒绝访问

7. 用户注册功能

用户注册功能依赖于user-service工程,UserRepository类实现用户的数据保存操作,UserService类实现用户注册操作,包含用户名和邮箱唯一性验证以及邮箱合法性验证。接下来需要配置注册Lambda和用户注册API Gateway。

1) 创建用户注册Lambda

按照工程结构图中lambda-userregistration创建目录及java文件,并工程的build.gradle添加依赖。

dependencies {
    compile group: 'com.google.inject', name: 'guice', version: guiceVersion
    compile project(':services-user')
}

创建Handler类,继承LambdaHandler

public class Handler extends LambdaHandler {
    //内部类
    public static class RegistrationInput {
        @JsonProperty("username")
        private String username;
        @JsonProperty("email")
        private String email;
        //getter/setter
    }
    //注册成功跳转地址
    public static class RegistrationOutput {
        private final String resourceUrl;
        public RegistrationOutput(User user) {
            resourceUrl = "/user/" + user.getId();
        }
        @JsonGetter("resourceUrl")
        public String getResourceUrl() {
            return resourceUrl;
        }
    }
    //注入器
    private static final Injector INJECTOR = Guice.createInjector(new DependencyInjectionModule());
    //用户service
    private UserService userService;

    @Inject
    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    public Handler() {
        INJECTOR.injectMembers(this);
        Objects.requireNonNull(userService);
    }

    @Override
    public RegistrationOutput handleRequest(RegistrationInput input, Context context) {
        User createdUser = userService.registerNewUser(input.username, input.email);
        //返回生成user的原始URL
        return new RegistrationOutput(createdUser);
    }
}
2) 配置Lambda的函数
 "UserRegistrationLambda": {
  "Type": "AWS::Lambda::Function",
  "Properties": {
    "Handler": "com.serverlessbook.lambda.userregistration.Handler",
    "Runtime": "java8",
    "Timeout": "300",
    "MemorySize": "1024",
    "Description": "User registration Lambda",
    "Role": {
      "Fn::GetAtt": [
        "LambdaExecutionRole",
        "Arn"
      ]
    },
    "Code": {
      "S3Bucket": {
        "Ref": "DeploymentBucket"
      },
      "S3Key": {
        "Fn::Sub": "artifacts/lambda-userregistration/${ProjectVersion}/${DeploymentTime}.jar"
      }
    },
    "Environment": {
      "Variables": {
        "DynamoDbTokenTable": {
          "Ref": "TokenTable"
        },
        "DynamoDbUserTable": {
          "Ref": "UserTable"
        }
      }
    }
  }
},
3) 创建用户注册API网关

创建新的REST资源:http://domain.com/users

     "UsersResource": {
      "Type": "AWS::ApiGateway::Resource",
      "Properties": {
        //user路径
        "PathPart": "users",
        "RestApiId": {
          "Ref": "RestApi"
        },
        "ParentId": {
          "Fn::GetAtt": [
            "RestApi",
            "RootResourceId"
          ]
        }
      }
    }

创建网关,并使用正则表达式来更改Http状态码。当执行成功,会通知API网关返回一个内容为空的响应,并将Location header 赋值为resourceUrl属性值作为输出。

"UsersPostMethod": {
    "Type": "AWS::ApiGateway::Method",
    "Properties": {
        "HttpMethod": "POST",
        //API Gateway从中创建方法的RestApi资源的 ID
        "RestApiId": { 
            "Ref": "RestApi"
        },
        //API Gateway资源的ID
        "ResourceId": {
            "Ref": "UsersResource"
        },
        //不需要认证
        "AuthorizationType": "NONE",
        //API Gateway接受的请求参数,这里不需要。
        "RequestParameters": {
        },
        //可发送到调用方法的客户端的响应
        "MethodResponses": [
            {   
                //201状态吗,返回Location头,例如:/user/f89fb152-c67c-4f2f-a7c7-28a537b5ebe2
                "StatusCode": "201",
                "ResponseParameters": {
                    "method.response.header.Location": "true"
                }
            },
            {
                "StatusCode": "400"
            },
            {
                "StatusCode": "409"
            }
        ],
        //该方法在收到请求时调用的后端系统
        "Integration": {
            "Type": "AWS",
            "Uri": {
                "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${UserRegistrationLambda.Arn}/invocations"
            },
            "IntegrationHttpMethod": "POST",
            "RequestParameters": {
            },
            //json格式数据
            "RequestTemplates": {
                "application/json": "{\"username\": $input.json('$.username'),\"email\": $input.json('$.email')}"
            },
            //指示 API Gateway 将请求传递到目标后端的时间
            "PassthroughBehavior": "NEVER",
            //API Gateway 在方法的后端处理完请求后提供的响应。
            "IntegrationResponses": [
                {
                    //一个正则表达式,用于指定来自后端的哪些错误字符串或状态代码将映射到集成响应。
                    "SelectionPattern": ".*",
                    //API Gateway 用于将集成响应映射到 MethodResponse 状态代码的状态代码。
                    "StatusCode": "201",
                    //来自 API Gateway 发送到方法响应的后端响应的响应参数。
                    "ResponseParameters": {
                        "method.response.header.Location": "integration.response.body.resourceUrl"
                    },
                    //用于转换集成响应正文的模板。
                    //将目标指定为键值对 (字符串-字符串映射),其中内容类型作为键,模板作为值。
                    "ResponseTemplates": {
                        "application/json": "#set($inputRoot = $input.path('$'))"
                    }
                },
                {
                    "SelectionPattern": ".*not valid.*",
                    "StatusCode": "400",
                    "ResponseTemplates": {
                        "application/json": "{\"code\": 400, \"errorMessage\":\"$input.path('$.errorMessage')\"}"
                    }
                },
                {
                    "SelectionPattern": ".*already exists.*",
                    "StatusCode": "409",
                    "ResponseTemplates": {
                        "application/json": "{\"code\": 409, \"errorMessage\":\"$input.path('$.errorMessage')\"}"
                    }
                }
            ]
        }
    }
}

添加API网关可以执行Lambda的权限

 "UsersPostLambdaPermission": {
      "Type": "AWS::Lambda::Permission",
      "Properties": {
        "Action": "lambda:InvokeFunction",
        "FunctionName": {
          "Ref": "UserRegistrationLambda"
        },
        "Principal": "apigateway.amazonaws.com",
        "SourceArn": {
          "Fn::Sub": "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${RestApi}/*"
        }
      }
    }

发布程序 ./gradlew deploy 就可以测试用户注册功能。截图显示注册成功和注册失败两种结果。


注册成功

用户信息注册失败

工程中的依赖包

根目录的build.gradle中添加
ext {
guiceVersion = '4.1.+'
awsSdkVersion = '1.11.+'
}
使用到dynamodb的工程需要添加,即repository-dynamodb工程
dependencies {
compile group: 'com.amazonaws', name: 'aws-java-sdk-dynamodb', version: awsSdkVersion
}

异常一:DynamoDBIndexHashKey must specify one of HASH GSI name/names

@DynamoDBIndexHashKey must specify one of HASH GSI name/names: com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMappingException
com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMappingException: @DynamoDBIndexHashKey must specify one of HASH GSI name/names
at com.amazonaws.services.dynamodbv2.datamodeling.StandardAnnotationMaps$FieldMap.globalSecondaryIndexNames(StandardAnnotationMaps.java:345)
at com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperFieldModel$Properties$Immutable.(DynamoDBMapperFieldModel.java:459)
at com.amazonaws.services.dynamodbv2.datamodeling.StandardBeanProperties$Bean.(StandardBeanProperties.java:92)
at com.amazonaws.services.dynamodbv2.datamodeling.StandardBeanProperties$Bean.(StandardBeanProperties.java:86)
at com.amazonaws.services.dynamodbv2.datamodeling.StandardBeanProperties$BeanMap.putOrFlatten(StandardBeanProperties.java:217)
at com.amazonaws.services.dynamodbv2.datamodeling.StandardBeanProperties$BeanMap.putAll(StandardBeanProperties.java:207)
at com.amazonaws.services.dynamodbv2.datamodeling.StandardBeanProperties$BeanMap.(StandardBeanProperties.java:198)
at com.amazonaws.services.dynamodbv2.datamodeling.StandardBeanProperties$CachedBeans.getBeans(StandardBeanProperties.java:55)
at com.amazonaws.services.dynamodbv2.datamodeling.StandardBeanProperties$CachedBeans.access$100(StandardBeanProperties.java:48)
at com.amazonaws.services.dynamodbv2.datamodeling.StandardBeanProperties.of(StandardBeanProperties.java:42)
at com.amazonaws.services.dynamodbv2.datamodeling.StandardModelFactories$TableBuilder.(StandardModelFactories.java:132)
at com.amazonaws.services.dynamodbv2.datamodeling.StandardModelFactories$TableBuilder.(StandardModelFactories.java:116)
at com.amazonaws.services.dynamodbv2.datamodeling.StandardModelFactories$StandardTableFactory.getTable(StandardModelFactories.java:107)
at com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper.getTableModel(DynamoDBMapper.java:409)
at com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper.load(DynamoDBMapper.java:447)
at com.amazonaws.services.dynamodbv2.datamodeling.AbstractDynamoDBMapper.load(AbstractDynamoDBMapper.java:80)
at com.serverlessbook.services.user.repository.UserRepositoryDynamoDB.getUserByToken(UserRepositoryDynamoDB.java:26)
at com.serverlessbook.services.user.UserServiceImpl.getUserByToken(UserServiceImpl.java:21)
at com.serverlessbook.lambda.authorizer.Handler.handleRequest(Handler.java:56)
at com.serverlessbook.lambda.authorizer.Handler.handleRequest(Handler.java:23)
at com.serverlessbook.lambda.LambdaHandler.handleRequest(LambdaHandler.java:38)

这个异常是,Java类注释中添加@DynamoDBIndexHashKey,实际没有为DynamoDB的表创建GSI,解决方法,在对应的表中创建GSI即可。

源代码

代码下载地址:https://pan.baidu.com/s/1VgsY2vELb9aGKzNipVLAgQ
提取码:4z4h

你可能感兴趣的:(AWS Lambda笔记-数据持久化DynamoDB-10【含源代码】)