解决并行保存到 mongodb

Solve parallel saves to mongodb

所以我最近尝试创建一个预订网站,您可以在其中 select 您想要预订的日期。然后您将向 /api/v1/reserve 发送一个请求,该请求将检查数据是否有效。生成条带支付意向,将预订保存到数据库,并将支付意向id发送给前端(确认)。我的问题是,如果两个客户同时点击发送,他们都会为同一天付款,这是不应该发生的。

router.post("/reservation", async (req, res) => {

    //Check if everything is in the reqest
    if (!req.body?.name || !req.body?.phone || !req.body?.date) return res.sendStatus(400);
    if (!req.body.phone.match(/^[+]{0,1}[4]{1,2}[8][\s0-9]*$/)) return res.status(400).json({
        type: ErrorTypes.PHONE
    });


    //Validating date
    const reservationDate = new Date(req.body.date);
    if (reservationDate < new Date()) return res.status(400).json({
        type: ErrorTypes.DATE
    });
    //Checking if date is already reserved
    if (await checkIfDateReserved(reservationDate)) return res.status(400).json({
        type: ErrorTypes.RESERVED
    })
    //if reservation date is weekend raise the price 
    const estimatedPrice = reservationDate.getDay() == 6 || reservationDate.getDay() == 0 ? 3500 : 2000;
    try {
        //Create payment intet
        const intent = await stripe.paymentIntents.create({
            amount: estimatedPrice,
            currency: "PLN",
            payment_method_types: ['card']

        });

        const document = new ReservationModel({
            name: req.body.name,
            reservationDate: reservationDate,
            phone: req.body.phone,
            complete: false,
            intentId: intent.id
        });
        await document.save();
        //If everything went smoothley, send the secret to the client
        //Send the client the intent secret to confirm the payment
        res.json({ clientSecret: intent.client_secret });


    } catch (error) {
        res.status(500);
    }

});

基本上,两个进程 运行 在不同的线程中并行,它们检查日期是否同时保留并同时保留。我怎样才能让数据库等到第一个保存请求完成后再处理另一个? reservationDate 字段是唯一的,但似乎 MongoDB 仍然忽略它。

PS: 另外,我考虑过使用事务,但我真的不知道它们在这里如何应用。

所以我最终使用了 question thanks to 0x1C1B 中的答案。互斥量最终完美地工作。如果有人需要,请留下代码。

const mutex = new Mutex();

router.post("/reservation", async (req, res) => {

    //Check if everything is in the reqest
    if (!req.body?.name || !req.body?.phone || !req.body?.date) return res.sendStatus(400);
    if (!req.body.phone.match(/^[\s0-9]{9,11}/)) return res.status(400).json({
        type: ErrorTypes.PHONE
    });


    //Validating date
    const reservationDate = new Date(req.body.date);
    if (reservationDate < new Date()) return res.status(400).json({
        type: ErrorTypes.DATE
    });
    await mutex.lock(); //Lock the mutex to avoid processing two reservations at once
    //Checking if date is already reserved
    if (await checkIfDateReserved(reservationDate)) {
        mutex.release(); //Release the mutex so other requests can continue
        return res.status(400).json({
            type: ErrorTypes.RESERVED
        })
    }
    //if reservation date is weekend raise the price 
    const estimatedPrice = reservationDate.getDay() == 6 || reservationDate.getDay() == 0 ? 3500 : 2000;
    try {
        //Create payment intet
        const intent = await stripe.paymentIntents.create({
            amount: estimatedPrice,
            currency: "PLN",
            payment_method_types: ['card']

        });

        const document = new ReservationModel({
            name: req.body.name,
            reservationDate: reservationDate,
            phone: req.body.phone,
            complete: false,
            intentId: intent.id
        });
        await document.save();
        //If everything went smoothley, send the secret to the client
        //Send the client the intent secret to confirm the payment
        res.json({ clientSecret: intent.client_secret }); //Send the client_secret required for payment confirmation
        //If the confirmation doesn't go through in 5 minutes the reservation is canceled due to "createdAt" field in the schema (see ../../schemas/Reservation.ts)
        //If the payment goes through the automatic deletion is cancelled (see index.ts:59)
        mutex.release(); //Release mutex after reservation has been saved to db
        //So other users can't reserve it

    } catch (error) {
        res.status(500);
        mutex.release();
    }

});