Nodejs,AWS Dynamodb如何在创建新记录之前检查用户是不是已经存在
Posted
技术标签:
【中文标题】Nodejs,AWS Dynamodb如何在创建新记录之前检查用户是不是已经存在【英文标题】:Nodejs , AWS Dynamodb how to check if user already exists before creating new recordNodejs,AWS Dynamodb如何在创建新记录之前检查用户是否已经存在 【发布时间】:2022-01-20 04:58:03 【问题描述】:我正在创建 AWS lamda 函数来创建客户(潜在客户),但在创建客户之前,我需要检查用户是否已经存在。客户/潜在客户可以通过电子邮件和电话号码的组合来识别。
这是我的表定义: dynamodb-tables.ts
export default
LeadsTable:
Type: "AWS::DynamoDB::Table",
DeletionPolicy: "Retain",
Properties:
TableName: "$self:provider.environment.LEADS_TABLE",
AttributeDefinitions: [ AttributeName: "id", AttributeType: "S" ],
KeySchema: [ AttributeName: "id", KeyType: "HASH" ],
ProvisionedThroughput:
ReadCapacityUnits: "$self:custom.table_throughput",
WriteCapacityUnits: "$self:custom.table_throughput",
,
,
,
InterestsTable:
Type: "AWS::DynamoDB::Table",
DeletionPolicy: "Retain",
Properties:
TableName: "$self:provider.environment.INTERESTS_TABLE",
AttributeDefinitions: [
AttributeName: "id", AttributeType: "S" ,
AttributeName: "leadId", AttributeType: "S" ,
],
KeySchema: [
AttributeName: "id", KeyType: "HASH" ,
AttributeName: "leadId", KeyType: "RANGE" ,
],
ProvisionedThroughput:
ReadCapacityUnits: "$self:custom.table_throughput",
WriteCapacityUnits: "$self:custom.table_throughput",
,
GlobalSecondaryIndexes: [
IndexName: "lead_index",
KeySchema: [ AttributeName: "leadId", KeyType: "HASH" ],
Projection:
// attributes to project into the index
ProjectionType: "ALL", // (ALL | KEYS_ONLY | INCLUDE)
,
ProvisionedThroughput:
ReadCapacityUnits: "$self:custom.table_throughput",
WriteCapacityUnits: "$self:custom.table_throughput",
,
,
],
,
,
;
这是我的模型:lead.model.ts
import v4 as UUID from "uuid";
// Interfaces
interface IProps
id?: string;
email: string;
phone: string;
firstName: string;
lastName: string;
interface ILeadInterface extends IProps
createdAt: Date;
updatedAt: Date;
export default class LeadModel
private _id: string;
private _email: string;
private _phone: string;
private _firstName: string;
private _lastName: string;
constructor(
id = UUID(),
email = "",
phone = "",
firstName = "",
lastName = "",
: IProps)
this._id = id;
this._email = email;
this._phone = phone;
this._firstName = firstName;
this._lastName = lastName;
/**
* Set Id
* @param value
*/
setId(value: string)
this._id = value !== "" ? value : null;
/**
* Get Id
* @return string|*
*/
getId()
return this._id;
/**
* Set Email
* @param value
*/
setEmail(value: string)
this._email = value !== "" ? value : null;
/**
* Get Email
* @return string|*
*/
getEmail()
return this._email;
/**
* Set Phone
* @param value
*/
setPhone(value: string)
this._phone = value !== "" ? value : null;
/**
* Get Phone
* @return string|*
*/
getPhone()
return this._phone;
/**
* Set First Name
* @param value
*/
setFirstName(value: string)
this._firstName = value !== "" ? value : null;
/**
* Get First Name
* @return string|*
*/
getFirstName()
return this._firstName;
/**
* Set Last Name
* @param value
*/
setLastName(value: string)
this._lastName = value !== "" ? value : null;
/**
* Get Last Name
* @return string|*
*/
getLastName()
return this._lastName;
/**
* Get Base entity mappings
* @return ILeadInterface
*/
getEntityMappings(): ILeadInterface
return
id: this.getId(),
email: this.getEmail(),
phone: this.getPhone(),
firstName: this.getFirstName(),
lastName: this.getLastName(),
createdAt: new Date(),
updatedAt: new Date(),
;
我的操作/功能:create-lead.action.ts
import
APIGatewayProxyHandler,
APIGatewayEvent,
Context,
APIGatewayProxyResult,
from "aws-lambda";
import "source-map-support/register";
// Models
import LeadModel from "../../models/lead.model";
import ResponseModel from "../../models/response.model";
// Services
import DatabaseService from "../../services/database.service";
// utils
import validateAgainstConstraints from "../../utils/util";
// Define the request constraints
import requestConstraints from "../../constraints/lead/create.constraint.json";
// Enums
import StatusCode from "../../enums/status-code.enum";
import ResponseMessage from "../../enums/response-message.enum";
/***
* Create lead and insert into database
*
* @api post /lead/create
* @apiName Create lead
* @apiGroup lead
* @apiDescription Create lead
*
* @apiParam string email The email id of the lead
* @apiParam string phone The phone number of the lead
* @apiParam string firstName The first name of the lead
* @apiParam string lastName The last name of the lead
*
* @apiSuccess object data
* @apiSuccess string message The response message
* @apiSuccess string status The response status
*
* @apiParamExample json Request-Example:
*
* "email": "jj@mailinator.com",
* "phone": "+7352726252",
* "firstName":"jj",
* "lastName":"jo"
*
*
* @apiSuccessExample json Success-Response:
* HTTP/1.1 200 OK
*
* "data": "leadId": "468c8094-a756-4000-a919-974a64b5be8e" ,
* "message": "Lead successfully created"
* "status": "success"
*
* *
* @apiErrorExample json Error-Response: Validation Errors
* HTTP/1.1 400 Bad Request
*
* "data":
* "validation":
"email": [
"Email can't be blank"
]
* ,
* "message": "required fields are missing",
* "status": "bad request"
*
*
* @apiErrorExample json Error-Response: Unknown Error
* HTTP/1.1 500 Internal Server Error
*
* "data": ,
* "message": "Unknown error",
* "status": "error"
*
*/
export const createLead: APIGatewayProxyHandler = async (
event: APIGatewayEvent,
_context: Context
): Promise<APIGatewayProxyResult> =>
// Initialize response variable
let response;
// Parse request parameters
const requestData = JSON.parse(event.body);
// Validate against constraints
return validateAgainstConstraints(requestData, requestConstraints)
.then(async () =>
// Initialise database service
const databaseService = new DatabaseService();
// Initialise and hydrate model
const leadModel = new LeadModel(requestData);
// Get model data
const data = leadModel.getEntityMappings();
// Initialise DynamoDB PUT parameters
const params =
TableName: process.env.LEADS_TABLE,
Item:
id: data.id,
email: data.email,
phone: data.phone,
firstName: data.firstName,
lastName: data.lastName,
createdAt: data.createdAt,
updatedAt: data.updatedAt,
,
;
// check if lead is uneque
const unequeCheckParams =
TableName: process.env.LEADS_TABLE,
FilterExpression: "#email = :emailval OR #phone = :phoneval",
ExpressionAttributeNames:
"#email": "email",
"#phone": "phone",
,
ExpressionAttributeValues:
":emailval": data.email,
":phoneval": data.phone,
,
;
const isLead = await databaseService.query(unequeCheckParams);
if (isLead)
throw new ResponseModel(
,
409,
`create-error: $ResponseMessage.CREATE_LEAD_FAIL_DUPLICATE`
);
// Inserts item into DynamoDB table
await databaseService.create(params);
return data.id;
)
.then((leadId) =>
// Set Success Response
response = new ResponseModel(
leadId ,
StatusCode.OK,
ResponseMessage.CREATE_LEAD_SUCCESS
);
)
.catch((error) =>
// Set Error Response
response =
error instanceof ResponseModel
? error
: new ResponseModel(
,
StatusCode.ERROR,
ResponseMessage.CREATE_LEAD_FAIL
);
)
.then(() =>
// Return API Response
return response.generate();
);
;
我的数据库服务如下所示:database.service.ts
/* eslint-disable no-await-in-loop */
import * as AWS from "aws-sdk";
// Models
import ResponseModel from "../models/response.model";
// Interfaces
import IConfig from "../interfaces/config.interface";
// Enums
import StatusCode from "../enums/status-code.enum";
import ResponseMessage from "../enums/response-message.enum";
// Put
type PutItem = AWS.DynamoDB.DocumentClient.PutItemInput;
type PutItemOutput = AWS.DynamoDB.DocumentClient.PutItemOutput;
// Batch write
type BatchWrite = AWS.DynamoDB.DocumentClient.BatchWriteItemInput;
type BatchWriteOutPut = AWS.DynamoDB.DocumentClient.BatchWriteItemOutput;
// Update
type UpdateItem = AWS.DynamoDB.DocumentClient.UpdateItemInput;
type UpdateItemOutPut = AWS.DynamoDB.DocumentClient.UpdateItemOutput;
// Query
type QueryItem = AWS.DynamoDB.DocumentClient.QueryInput;
type QueryItemOutput = AWS.DynamoDB.DocumentClient.QueryOutput;
// Get
type GetItem = AWS.DynamoDB.DocumentClient.GetItemInput;
type GetItemOutput = AWS.DynamoDB.DocumentClient.GetItemOutput;
// Delete
type DeleteItem = AWS.DynamoDB.DocumentClient.DeleteItemInput;
type DeleteItemOutput = AWS.DynamoDB.DocumentClient.DeleteItemOutput;
type Item = [index: string]: string ;
const
STAGE,
DYNAMODB_LOCAL_STAGE,
DYNAMODB_LOCAL_ACCESS_KEY_ID,
DYNAMODB_LOCAL_SECRET_ACCESS_KEY,
DYNAMODB_LOCAL_ENDPOINT,
= process.env;
const config: IConfig = region: "eu-west-1" ;
if (STAGE === DYNAMODB_LOCAL_STAGE)
config.accessKeyId = DYNAMODB_LOCAL_ACCESS_KEY_ID; // local dynamodb accessKeyId
config.secretAccessKey = DYNAMODB_LOCAL_SECRET_ACCESS_KEY; // local dynamodb secretAccessKey
config.endpoint = DYNAMODB_LOCAL_ENDPOINT; // local dynamodb endpoint
AWS.config.update(config);
const documentClient = new AWS.DynamoDB.DocumentClient();
export default class DatabaseService
getItem = async ( key, hash, hashValue, tableName : Item) =>
const params =
TableName: tableName,
Key:
id: key,
,
;
if (hash)
params.Key[hash] = hashValue;
const results = await this.get(params);
if (Object.keys(results).length)
return results;
console.error("Item does not exist");
throw new ResponseModel(
id: key ,
StatusCode.BAD_REQUEST,
ResponseMessage.INVALID_REQUEST
);
;
create = async (params: PutItem): Promise<PutItemOutput> =>
try
return await documentClient.put(params).promise();
catch (error)
console.error(`create-error: $error`);
throw new ResponseModel(, 500, `create-error: $error`);
;
batchCreate = async (params: BatchWrite): Promise<BatchWriteOutPut> =>
try
return await documentClient.batchWrite(params).promise();
catch (error)
console.error(`batch-write-error: $error`);
throw new ResponseModel(, 500, `batch-write-error: $error`);
;
update = async (params: UpdateItem): Promise<UpdateItemOutPut> =>
try
// result.Attributes
return await documentClient.update(params).promise();
catch (error)
console.error(`update-error: $error`);
throw new ResponseModel(, 500, `update-error: $error`);
;
query = async (params: QueryItem): Promise<QueryItemOutput> =>
try
return await documentClient.query(params).promise();
catch (error)
console.error(`query-error: $error`);
throw new ResponseModel(, 500, `query-error: $error`);
;
get = async (params: GetItem): Promise<GetItemOutput> =>
console.log("DB GET - STAGE: ", STAGE);
console.log("DB GET - params.TableName: ", params.TableName);
console.log("DB GET - params.Key: ", params.Key);
try
return await documentClient.get(params).promise();
catch (error)
console.error(`get-error - TableName: $params.TableName`);
console.error(`get-error: $error`);
throw new ResponseModel(, 500, `get-error: $error`);
;
delete = async (params: DeleteItem): Promise<DeleteItemOutput> =>
try
return await documentClient.delete(params).promise();
catch (error)
console.error(`delete-error: $error`);
throw new ResponseModel(, 500, `delete-error: $error`);
;
getAllData = async (params: QueryItem) =>
try
const _getAllData = async (params, startKey) =>
if (startKey)
params.ExclusiveStartKey = startKey;
return documentClient.query(params).promise();
;
let lastEvaluatedKey = null;
let rows = [];
do
const result = await _getAllData(params, lastEvaluatedKey);
rows = rows.concat(result.Items);
lastEvaluatedKey = result.LastEvaluatedKey;
while (lastEvaluatedKey);
return rows;
catch (error)
console.error(`get-error: $error`);
throw new ResponseModel(, 500, `get-error: $error`);
;
我的创建操作遇到以下错误:
查询错误:ValidationException:必须在请求中指定 KeyConditions 或 KeyConditionExpression 参数
我该如何解决这个问题,或者有更好的方法
【问题讨论】:
【参考方案1】:我建议使用Conditional Put
。
AWS 文档:https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ConditionExpressions.html#Expressions.ConditionExpressions.PreventingOverwrites
PutItem 操作使用相同的键(如果存在)覆盖项目。如果您想避免这种情况,请使用条件表达式。这允许写入仅在有问题的项目还没有相同的密钥时继续进行。
aws dynamodb put-item \
--table-name ProductCatalog \
--item file://item.json \
--condition-expression "attribute_not_exists(Id)"
如果条件表达式的计算结果为 false,DynamoDB 将返回以下错误消息:条件请求失败。
使用您的代码,它看起来像这样:
const params =
TableName: ...,
Item: ...,
ConditionExpression: 'attribute_not_exists(#a)',
ExpressionAttributeNames: '#a': '...' ,
;
create = async (params: PutItem): Promise<PutItemOutput> =>
try
return await documentClient.put(params).promise();
catch (error)
if (error is 'The conditional request failed') return; // pseudo code
console.error(`create-error: $error`);
throw new ResponseModel(, 500, `create-error: $error`);
;
【讨论】:
感谢您的回答,但如果我想同时检查电子邮件和电话的组合会发生什么ConditionExpression: "attribute_not_exists(email) OR attribute_not_exists(phone)",
没有帮助。它不适用于常规属性。以上是关于Nodejs,AWS Dynamodb如何在创建新记录之前检查用户是不是已经存在的主要内容,如果未能解决你的问题,请参考以下文章
亚马逊 dynamodb(通过 aws sdk)是不是有一个很好的对象映射器,可以在 nodejs 中使用?
在哪里可以找到 AWS-SDK for dynamodb 在 NodeJS 打字稿中的所有异常类?