首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >基于UniApp + ThinkPHP实现抢单功能的技术方案

基于UniApp + ThinkPHP实现抢单功能的技术方案

作者头像
编程小白狼
发布2025-08-24 08:33:10
发布2025-08-24 08:33:10
2980
举报
文章被收录于专栏:编程小白狼编程小白狼

前言

在O2O平台、物流配送、外卖服务等应用场景中,抢单功能已成为核心业务模块之一。本文将详细介绍如何使用UniApp作为前端框架,ThinkPHP作为后端API服务,实现一个高并发场景下的抢单系统。

技术选型

  • 前端:UniApp(跨端开发框架,一套代码多端发布)
  • 后端:ThinkPHP 6.x(高性能PHP框架)
  • 数据库:MySQL 5.7+
  • 缓存:Redis(用于高并发控制)

系统设计

数据库设计
代码语言:javascript
复制
-- 订单表
CREATE TABLE `orders` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `order_sn` varchar(20) NOT NULL COMMENT '订单编号',
  `user_id` int(11) NOT NULL COMMENT '发布用户ID',
  `title` varchar(255) NOT NULL COMMENT '订单标题',
  `content` text COMMENT '订单详情',
  `price` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '订单金额',
  `status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '0:待抢单,1:已抢单,2:已完成',
  `create_time` datetime DEFAULT NULL,
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_status` (`status`),
  KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 抢单记录表
CREATE TABLE `order_grab` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `order_id` int(11) NOT NULL COMMENT '订单ID',
  `grab_user_id` int(11) NOT NULL COMMENT '抢单用户ID',
  `grab_time` datetime DEFAULT NULL COMMENT '抢单时间',
  `status` tinyint(1) DEFAULT '0' COMMENT '抢单状态',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_order_id` (`order_id`), -- 唯一约束防止重复抢单
  KEY `idx_grab_user_id` (`grab_user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
业务流程
  1. 用户发布订单,状态为"待抢单"
  2. 多个配送员看到订单列表,点击抢单
  3. 系统通过并发控制确保只有一个配送员抢单成功
  4. 抢单成功后订单状态更新为"已抢单"
  5. 配送员完成服务后,订单状态更新为"已完成"

核心实现

前端UniApp实现
订单列表页面
代码语言:javascript
复制
<template>
  <view class="container">
    <view class="order-list">
      <view v-for="item in orderList" :key="item.id" class="order-item">
        <view class="order-info">
          <text class="title">{{ item.title }}</text>
          <text class="price">¥{{ item.price }}</text>
        </view>
        <view class="order-content">{{ item.content }}</view>
        <button 
          v-if="item.status === 0" 
          class="grab-btn" 
          @click="grabOrder(item.id)"
          :loading="grabLoading"
        >抢单</button>
        <text v-else class="status-text">
          {{ item.status === 1 ? '已抢单' : '已完成' }}
        </text>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      orderList: [],
      grabLoading: false
    }
  },
  onLoad() {
    this.loadOrderList()
  },
  methods: {
    // 加载订单列表
    async loadOrderList() {
      try {
        const res = await this.$api.get('/order/list', {
          status: 0 // 只获取待抢单的订单
        })
        this.orderList = res.data
      } catch (error) {
        uni.showToast({
          title: '加载失败',
          icon: 'none'
        })
      }
    },
    
    // 抢单操作
    async grabOrder(orderId) {
      this.grabLoading = true
      try {
        const res = await this.$api.post('/order/grab', {
          order_id: orderId
        })
        
        if (res.code === 200) {
          uni.showToast({
            title: '抢单成功',
            icon: 'success'
          })
          // 刷新列表
          this.loadOrderList()
        } else {
          uni.showToast({
            title: res.msg || '抢单失败',
            icon: 'none'
          })
        }
      } catch (error) {
        uni.showToast({
          title: '网络错误',
          icon: 'none'
        })
      } finally {
        this.grabLoading = false
      }
    }
  }
}
</script>
后端ThinkPHP实现
抢单API控制器
代码语言:javascript
复制
<?php
namespace app\controller;

use app\BaseController;
use think\facade\Db;
use think\facade\Cache;

class Order extends BaseController
{
    /**
     * 抢单接口
     */
    public function grab()
    {
        $orderId = $this->request->post('order_id');
        $userId = $this->getUserId(); // 获取当前用户ID
        
        if (empty($orderId)) {
            return json(['code' => 400, 'msg' => '参数错误']);
        }
        
        // 使用Redis锁防止并发抢单
        $lockKey = 'order_grab_lock:' . $orderId;
        $lock = Cache::store('redis')->set($lockKey, 1, ['nx', 'ex' => 5]);
        
        if (!$lock) {
            return json(['code' => 500, 'msg' => '操作太频繁,请稍后重试']);
        }
        
        try {
            // 开启事务
            Db::startTrans();
            
            // 检查订单是否存在且可抢
            $order = Db::name('orders')
                ->where('id', $orderId)
                ->lock(true)
                ->find();
                
            if (!$order) {
                throw new \Exception('订单不存在');
            }
            
            if ($order['status'] != 0) {
                throw new \Exception('订单已被抢');
            }
            
            // 检查用户是否已抢过该订单
            $existGrab = Db::name('order_grab')
                ->where('order_id', $orderId)
                ->where('grab_user_id', $userId)
                ->find();
                
            if ($existGrab) {
                throw new \Exception('您已抢过该订单');
            }
            
            // 更新订单状态
            Db::name('orders')
                ->where('id', $orderId)
                ->update([
                    'status' => 1,
                    'update_time' => date('Y-m-d H:i:s')
                ]);
                
            // 创建抢单记录
            Db::name('order_grab')
                ->insert([
                    'order_id' => $orderId,
                    'grab_user_id' => $userId,
                    'grab_time' => date('Y-m-d H:i:s'),
                    'status' => 1
                ]);
                
            // 提交事务
            Db::commit();
            
            return json(['code' => 200, 'msg' => '抢单成功']);
            
        } catch (\Exception $e) {
            // 回滚事务
            Db::rollback();
            return json(['code' => 500, 'msg' => $e->getMessage()]);
        } finally {
            // 释放锁
            Cache::store('redis')->delete($lockKey);
        }
    }
    
    /**
     * 获取订单列表
     */
    public function list()
    {
        $status = $this->request->get('status', 0);
        $page = $this->request->get('page', 1);
        $limit = $this->request->get('limit', 10);
        
        $where = [];
        if ($status !== '') {
            $where[] = ['status', '=', $status];
        }
        
        $list = Db::name('orders')
            ->where($where)
            ->page($page, $limit)
            ->order('create_time', 'desc')
            ->select();
            
        return json(['code' => 200, 'data' => $list]);
    }
    
    /**
     * 获取当前用户ID
     */
    private function getUserId()
    {
        // 实际项目中根据Token获取用户ID
        // 这里简化为直接返回
        return 1;
    }
}

高并发处理策略

在高并发场景下,抢单功能需要特别注意以下几个问题:

1. 数据库乐观锁
代码语言:javascript
复制
// 使用版本号实现乐观锁
$order = Db::name('orders')
    ->where('id', $orderId)
    ->where('status', 0)
    ->find();

if ($order) {
    $update = Db::name('orders')
        ->where('id', $orderId)
        ->where('version', $order['version']) // 检查版本号
        ->update([
            'status' => 1,
            'version' => $order['version'] + 1, // 版本号+1
            'update_time' => date('Y-m-d H:i:s')
        ]);
        
    if (!$update) {
        throw new \Exception('抢单失败,请重试');
    }
}
2. Redis队列削峰

对于极高并发场景,可以使用Redis队列缓解数据库压力:

代码语言:javascript
复制
// 将抢单请求放入队列
public function grabQueue()
{
    $orderId = $this->request->post('order_id');
    $userId = $this->getUserId();
    
    $data = [
        'order_id' => $orderId,
        'user_id' => $userId,
        'time' => time()
    ];
    
    // 将抢单请求加入队列
    Cache::store('redis')->lPush('order_grab_queue', json_encode($data));
    
    return json(['code' => 200, 'msg' => '抢单请求已提交,请等待结果']);
}

// 使用定时任务处理队列
public function processGrabQueue()
{
    // 每次处理10个请求
    for ($i = 0; $i < 10; $i++) {
        $data = Cache::store('redis')->rPop('order_grab_queue');
        if (!$data) break;
        
        $data = json_decode($data, true);
        // 处理抢单逻辑
        $this->doGrab($data['order_id'], $data['user_id']);
    }
}
3. 限流控制

使用Redis实现简单限流:

代码语言:javascript
复制
// 检查用户抢单频率
public function checkRateLimit($userId)
{
    $key = 'grab_rate_limit:' . $userId;
    $limit = 10; // 10次/分钟
    
    $current = Cache::store('redis')->get($key);
    if ($current && $current >= $limit) {
        return false;
    }
    
    Cache::store('redis')->multi();
    Cache::store('redis')->incr($key);
    Cache::store('redis')->expire($key, 60); // 60秒过期
    Cache::store('redis')->exec();
    
    return true;
}

总结

本文介绍了基于UniApp和ThinkPHP实现抢单功能的完整方案,包括:

  1. 前后端技术选型和数据库设计
  2. 核心业务逻辑的实现代码
  3. 高并发场景下的处理策略

实际项目中还需要考虑更多的细节,如用户认证、权限控制、异常处理、日志记录等。抢单功能作为核心业务,需要保证数据的一致性和系统的高可用性,合理使用数据库事务、锁机制和缓存技术是关键。

希望本文能为需要实现类似功能的开发者提供参考和思路。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-08-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 技术选型
  • 系统设计
    • 数据库设计
    • 业务流程
  • 核心实现
    • 前端UniApp实现
      • 订单列表页面
    • 后端ThinkPHP实现
      • 抢单API控制器
  • 高并发处理策略
    • 1. 数据库乐观锁
    • 2. Redis队列削峰
    • 3. 限流控制
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档