Nodejs,AWS Dynamodb如何在创建新记录之前检查用户是否已经存在



我正在创建AWS lamda函数来创建客户(潜在客户(,但在创建客户之前,我需要检查用户是否已经存在。客户/潜在客户可以通过电子邮件和电话号码的组合来识别。

这是我的表格定义:发电机数据库表.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(),
};
}
}

我的操作/功能:创建潜在客户。操作。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参数

我该如何解决这个问题,或者有更好的方法吗

我建议使用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}`);
}
};

最新更新