之前认证部分的数据都是硬编码来判断如果用户输入serverless即通过授权,这次认证是从数据库读取数据进行判断。数据的存储使用AWS的无结构化数据引擎DynamoDB。AWS的DynamoDB是一个全托管的NoSQL数据库服务,提供快速可预测的性能,具有无缝可扩展性。
- 创建DynamoDB
- 配置数据映射类
- 配置Lambda环境变量
- 用户数据层操作
- 用户业务层逻辑
- 用户授权功能
- 用户注册功能
源代码
代码下载地址: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”,通过验证,否则拒绝。
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