订单30分钟未支付自动取消怎么实现?

920 阅读3分钟

今天闲逛的时候,看到掘金上面有一个订单30分钟未支付,自动取消的一个需求。这个需求平时也会在美团的外卖下单、拼多多等下单的时候可以看到,但是之前一直也没有思考过,怎么去实现这个需求。所以,今天就把这个需求实现一遍。

准备

  • 使用语言 golang

了解需求

这个需求是属于开发中的延时任务的一个功能。平常开发中会遇到的生成了订单但是没有支付,30分钟以后订单就会自动关闭。

思路

使用数据库轮序技术。该方案通常是在小型项目中使用,即通过开启一个线程定时的去扫描数据库,通过订单时间来判断是否有超时的订单,然后进行修改和删除等操作。

需要考虑的方面

  1. 定义订单超时时间:需要确定订单超时时间,例如订单30分钟未支付则自动取消。
  2. 监听订单状态变化:需要在程序中监听订单状态变化,例如订单状态为待支付,且下单时间超过订单超时时间,则自动取消订单。
  3. 更新订单状态:需要在程序中更新订单状态,将订单状态从待支付修改为取消。

简单思路

  1. 在数据库中创建订单表,包含订单id、下单时间和订单状态等字段。
  2. 创建一个goroutine用于监听订单状态变化,每隔一段时间查询数据库中所有待支付订单,判断是否超时,如果超时则将订单状态修改为取消。
  3. 在程序启动时启动该goroutine,保持一直运行。
  4. 在用户下单时,在数据库中插入新订单记录,将订单状态设置为待支付。
  5. 当用户支付成功后,在数据库中将订单状态修改为已支付。

技术实现

package main

import (
   "database/sql"
   "fmt"
   "time"

   _ "github.com/go-sql-driver/mysql"
)

type Order struct {
   ID         int
   CreatedAt  time.Time
   Status     string
   PayTimeout time.Duration
}

func main() {
   // 连接数据库
   db, err := sql.Open("mysql", "root:root@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local")
   if err != nil {
      panic(any(err))
   }
   defer db.Close()

   // 创建订单表
   if _, err := db.Exec(`
        CREATE TABLE IF NOT EXISTS orders (
            id int(11) NOT NULL AUTO_INCREMENT,
            created_at datetime NOT NULL,
            status varchar(10) NOT NULL DEFAULT '',
            PRIMARY KEY (id)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
    `); err != nil {
      panic(any(err))
   }

   // 定义订单超时时间为30分钟
   // payTimeout := 30 * time.Minute
   payTimeout := 1 * time.Minute

   // 启动goroutine监听订单状态变化
   go func() {
      for {
         // 查询所有待支付订单
         rows, err := db.Query("SELECT id, created_at FROM orders WHERE status = 'pending'")
         if err != nil {
            fmt.Println("Failed to query orders:", err)
            continue
         }

         // 遍历订单记录
         for rows.Next() {
            var orderID int
            var createdAt time.Time
            if err := rows.Scan(&orderID, &createdAt); err != nil {
               fmt.Println("Failed to scan order:", err)
               continue
            }

            // 判断订单是否超时
            if time.Since(createdAt) >= payTimeout {
               // 更新订单状态为取消
               if _, err := db.Exec("UPDATE orders SET status = 'cancelled' WHERE id = ?", orderID); err != nil {
                  fmt.Println("Failed to update order:", err)
                  continue
               }

               fmt.Printf("Order %d cancelled\n", orderID)
            }
         }

         // 关闭rows
         rows.Close()

         // 休眠5秒钟
         time.Sleep(5 * time.Second)
      }
   }()

   // 用户下单
   orderID := placeOrder(db)

   // 模拟支付
   fmt.Println("Paying for order", orderID)
   time.Sleep(10 * time.Minute)

   // 查询订单状态
   order := getOrder(db, orderID)
   fmt.Println("Order status:", order.Status)
}

// 下单
func placeOrder(db *sql.DB) int {
   now := time.Now()

   // 插入订单记录
   result, err := db.Exec("INSERT INTO orders (created_at, status) VALUES (?, 'pending')", now)
   if err != nil {
      panic(any(err))
   }

   orderID, err := result.LastInsertId()
   if err != nil {
      panic(any(err))
   }

   fmt.Println("Placed order", orderID)

   return int(orderID)
}

// 查询订单
func getOrder(db *sql.DB, orderID int) *Order {
   row := db.QueryRow("SELECT id, created_at, status FROM orders WHERE id = ?", orderID)

   var order Order
   if err := row.Scan(&order.ID, &order.CreatedAt, &order.Status); err != nil {
      panic(any(err))
   }

   return &order
}

其他方案

使用 Redis 和 Golang 实现订单 30 分钟未支付自动取消的功能(后期会补充这边文章)