簡體   English   中英

Nodejs,AWS Dynamodb如何在創建新記錄之前檢查用戶是否已經存在

[英]Nodejs , AWS Dynamodb how to check if user already exists before creating new record

我正在創建 AWS lamda function 來創建客戶(潛在客戶),但在創建客戶之前,我需要檢查用戶是否已經存在。 客戶/潛在客戶可以通過 email 和電話號碼的組合來識別。

這是我的表定義: 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}",
          },
        },
      ],
    },
  },
};

這是我的 model: 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(),
    };
  }
}

我的行動/ function: 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": "joeljosephchalakudy@gmail.com",
 *      "phone": "+919809198160",
 *      "firstName":"Joel",
 *      "lastName":"Joseph"
 *    }
 *
 * @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}`);
    }
  };

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM