资讯动态

PHP竞拍商城源码解析:多用户挂售转卖与闪拍系统实现

发布时间:2026/9/16 21:34:44 来源:尧图企业网站定制
简介这是一套面向多用户挂售转卖、竞拍闪拍场景的商城系统完整源码基于PHP后端与UNIAPP前端开发覆盖后台商品挂单、竞拍场次设置、用户实时出价、提货与转售等核心流程。包内共有2002个文件压缩包约142.12MB其中php文件473个用于服务端业务逻辑js、vue、css及html等3787955135个文件构成前后端界面与交互png、gif、jpg等图片素材近400个另含json、md、sql及stub/yml等配置文档结构完整便于二次开发。已有185人学习浏览适合具备PHP与UNIAPP基础的开发者用于商拍、NFT数藏或二手转卖平台搭建。系统内置转售手续费规则支持余额、支付宝APP、支付宝H5及微信APP多种支付方式便于对接真实交易场景。资源附带使用教程并提供apk、sh、pem等文件可辅助部署测试与移动端打包帮助快速理解竞拍出价、转售定价与支付对接等关键实现。1. 多用户挂售转卖与闪拍这不只是商城源码而是一套带场次和转售的竞拍引擎我最近部署了一套“多用户挂售转卖竞拍闪拍商城系统 NFT 数藏系统”后端是原生 PHP前端是 UNIAPP源码包里还带着一个已经编译好的 APK 和 LayUI 后台静态资源。它跟普通商城最大的区别是商品不是直接定价售卖而是先挂单、再进竞拍场次、最后落锤落锤后用户要么提货要么按后台设定的加价百分比转卖转卖还要交手续费。如果你要做二奢、潮玩、数字藏品这类“抢拍 二次流通”的平台这套源码能帮你省掉一半基础业务。适合 PHP 后端为主、想快速出 UNIAPP 多端的团队也适合刚接手商城类系统的个人开发者。它把竞拍、订单、支付、转售拆得比较清楚但并发和支付回调的细节需要自己补。2. PHP 后端竞拍核心场次、加价、并发控制的表结构与接口设计2.1 先把四张核心表拆清楚注意这套源码没有用框架是原生 PHP MySQL。先按业务线把表理出来再往下看接口就不乱了。商品、场次、出价记录、订单必须分开。很多商城源码会把商品状态和竞拍状态混在一张表里导致转售和场次管理一加需求就崩。这套拆得比较利落CREATE TABLE goods ( id int(11) NOT NULL AUTO_INCREMENT, title varchar(100) NOT NULL DEFAULT COMMENT 商品名称, description text COMMENT 商品描述, base_price decimal(10,2) DEFAULT 0.00 COMMENT 起拍价, current_price decimal(10,2) DEFAULT 0.00 COMMENT 当前价, auction_status tinyint(4) DEFAULT 0 COMMENT 0下架 1待拍 2竞拍中 3已成交 4提货 5转售中, stock int(11) DEFAULT 1, image varchar(255) DEFAULT , PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE auction_session ( id int(11) NOT NULL AUTO_INCREMENT, goods_id int(11) NOT NULL, start_time datetime DEFAULT NULL, end_time datetime DEFAULT NULL, increase_step decimal(10,2) DEFAULT 100.00 COMMENT 加价幅度, fee_rate decimal(5,4) DEFAULT 0.0200 COMMENT 转售手续费率, status tinyint(4) DEFAULT 0 COMMENT 0未开始 1进行中 2已结束, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE bid_log ( id int(11) NOT NULL AUTO_INCREMENT, session_id int(11) NOT NULL, user_id int(11) NOT NULL, price decimal(10,2) NOT NULL, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_session_price (session_id,price) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE orders ( id int(11) NOT NULL AUTO_INCREMENT, order_sn varchar(32) NOT NULL, goods_id int(11) NOT NULL, user_id int(11) NOT NULL, type tinyint(4) DEFAULT 1 COMMENT 1竞拍得标 2转售购买, pay_amount decimal(10,2) NOT NULL, pay_status tinyint(4) DEFAULT 0 COMMENT 0待支付 1已支付 2已退款, pay_channel varchar(20) DEFAULT COMMENT alipay_app/alipay_h5/wechat_app/balance, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;逻辑说明goods.auction_status是业务主状态auction_session负责场次维度bid_log记录每次出价以便对账和防撤回orders把竞拍和转售统一为订单。increase_step放在场次里而不是商品里是因为同一商品在不同场次可能设置不同加价幅度fee_rate放在场次里是为了支持“转卖手续费按场次活动调整”。参数说明base_price是起拍价current_price实时刷新increase_step决定最低加价金额后端校验时直接用new_price current_price increase_step判断。fee_rate为 0.02 时表示转售价的 2% 作为手续费计算过程要用decimal类型避免浮点误差。2.2 出价逻辑状态机 加价校验竞拍不是简单地UPDATE goods SET current_price ?。我对接前端页面时发现必须先锁住场次状态否则竞拍结束那一秒用户还能出价。常见做法是在 PHP 里这样写?php function bid($goods_id, $user_id, $new_price) { $pdo get_pdo(); $pdo-beginTransaction(); try { // 锁定场次和商品防止并发读到旧价格 $stmt $pdo-prepare( SELECT a.id, a.status, a.increase_step, g.current_price, g.auction_status FROM auction_session a INNER JOIN goods g ON g.id a.goods_id WHERE a.goods_id ? AND a.status 1 FOR UPDATE ); $stmt-execute([$goods_id]); $row $stmt-fetch(PDO::FETCH_ASSOC); if (!$row || $row[auction_status] ! 2) { throw new Exception(场次未开始或已结束); } if ($new_price $row[current_price] $row[increase_step]) { throw new Exception(出价低于最低加价幅度); } // 更新当前价 $pdo-prepare(UPDATE goods SET current_price ? WHERE id ?) -execute([$new_price, $goods_id]); // 写入出价记录 $pdo-prepare( INSERT INTO bid_log (session_id, user_id, price) VALUES (?, ?, ?) )-execute([$row[id], $user_id, $new_price]); $pdo-commit(); return true; } catch (Exception $e) { $pdo-rollBack(); return $e-getMessage(); } }逻辑说明这里用SELECT ... FOR UPDATE把auction_session和goods的行锁住避免两个用户同时出价时都读到同一个current_price最后写入的不是叠加后的结果。auction_status 2表示竞拍中status 1表示场次已开启双重判断是为了防止后台改了商品状态但场次没同步。出价记录必须和价格更新处在同一个事务里否则用户看到价格变了后台对账却找不到出价记录。参数说明increase_step是每次最低加价比如起拍 100 元、加价幅度 50 元第二个用户出价必须大于等于 150 元。如果竞拍规则是“自由出价”把increase_step设为 0.01 再配合前端输入框限制即可要做“阶梯加价”例如 1~100 加 10 元、100 以上加 50 元可以给auction_session加一个step_ruleJSON 字段这段逻辑需要自己扩展。2.3 用 Redis 锁扛高频出价事务锁在单机 MySQL 下没问题但 UNIAPP 端如果做秒拍、闪拍同一场次可能几百人同时出价FOR UPDATE会把数据库连接池打满。看这套源码的目录结构是标准 LNMP 部署没带队列所以我会在 PHP 层加一个 Redis 前置锁。?php $redis new Redis(); $redis-connect(127.0.0.1, 6379); $lockKey auction:lock:{$goods_id}; $lockValue uniqid(, true); $locked $redis-set($lockKey, $lockValue, [NX, EX 3]); if (!$locked) { http_response_code(429); exit(json_encode([code 429, msg 当前出价人数过多请稍后重试])); } try { // 调用上面的事务出价逻辑 bid($goods_id, $user_id, $new_price); } finally { // 释放前确认锁还是自己的防止误删别人的锁 if ($redis-get($lockKey) $lockValue) { $redis-del($lockKey); } }逻辑说明SET NX EX 3表示“只有 key 不存在时才能写入3 秒后自动过期”比SETNX EXPIRE两条命令更安全不会出现加锁后进程崩溃导致死锁。释放锁前先get比较是因为如果请求 A 执行超过 3 秒锁自动过期请求 B 拿到新锁A 最后直接del会把 B 的锁删掉所以要确认 value 是自己写入的uniqid。参数说明EX 3的 3 秒是锁的自动过期时间如果出价事务经常超过 3 秒要调大但如果并发压测时总是返回 429先优化 SQL 而不是无限调大过期时间。Redis 锁只能挡住绝大部分并发最终一致性仍要落到 MySQL 事务。2.4 接口设计与参数契约前端 UNIAPP 和后端 PHP 的接口建议按资源路径组织。这套源码里没有完整 API 文档只能根据现有index.css、view.css这些静态文件推断是 views 目录下的页面直接请求/api/*.php。我给前端对接时一般强制统一返回格式{ code: 0, msg: success, data: { current_price: 150.00, bid_count: 8, end_time: 2025-03-01 22:00:00 } }需要暴露的接口至少包括方法路径作用关键参数GET/api/goods/detail.php?id1商品详情与当前价idGET/api/auction/sessions.php查看当前/即将开始的场次status1POST/api/auction/bid.php出价goods_id,new_pricePOST/api/order/create.php竞拍成功后生成订单goods_id,typePOST/api/order/pay.php发起支付order_sn,channelPOST/api/goods/transfers.php上架转售商品goods_id,percent逻辑说明code: 0表示成功非 0 表示业务失败HTTP 状态码只负责传输层错误这样 UNIAPP 端可以直接按code判断业务不用在catch里解析各种结构。bid_count返回后前端列表页可以直接展示热度不用另外联一个统计接口。参数说明type2用于转售订单percent是转售加价百分比例如传10表示在原成交价基础上加 10% 挂牌。支付接口的channel支持balance、alipay_app、alipay_h5、wechat_app四个值后端根据这个值走不同的支付网关逻辑。3. UNIAPP 前端竞拍页实战商品列表、倒计时与出价轮询3.1 首页商品流从挂单到展示的 v-for 渲染后台添加商品后前台首页要展示“挂单竞拍”的商品卡片。用 UNIAPP 写商城跟写普通 Vue 差不多但要注意onLoad不能重复请求——尤其 App 切后台再回来倒计时会乱。我习惯把商品列表接口放在onShow里拉一次同时用onHide清掉定时器。template view classgoods-list view v-foritem in goodsList :keyitem.id classgoods-card clickgoDetail(item.id) image :srcitem.image modeaspectFill / text classtitle{{ item.title }}/text view classprice-row text classcurrent-price¥{{ item.current_price }}/text text classbid-tag v-ifitem.auction_status 2竞拍中/text /view view classcountdown 距结束{{ formatTime(item.end_time) }} /view /view /view /template script export default { data() { return { goodsList: [], timer: null }; }, onShow() { this.fetchList(); this.timer setInterval(() { this.fetchList(true); }, 5000); }, onHide() { clearInterval(this.timer); }, methods: { fetchList(silent false) { uni.request({ url: http://your-api-domain/api/goods/list.php, success: (res) { if (res.data.code 0) { this.goodsList res.data.data.list; } } }); }, formatTime(t) { const diff new Date(t).getTime() - Date.now(); if (diff 0) return 已结束; const h Math.floor(diff / 3600000); const m Math.floor((diff % 3600000) / 60000); const s Math.floor((diff % 60000) / 1000); return ${h}时${m}分${s}秒; } } }; /script逻辑说明onShow里先拉一次数据再启一个 5 秒定时器用户从竞拍详情页返回首页时能立即刷新价格。auction_status 2对应后端goods表的竞拍中状态这个判断要和后端约定好不能直接用current_price base_price去猜。formatTime用Date.now()算差值注意服务端时间与本地时间的偏差。参数说明接口地址不要写死成http://your-api-domain源码包里经常残留开发者的内网 IP建议把apiBaseUrl放到config.js或manifest.json的h5.devServer里打包时再替换。5 秒轮询间隔适配普通竞拍如果是 1 分钟内多次出价的“闪拍”间隔应缩到 2 秒。3.2 商品详情页的倒计时与出价按钮联动详情页不能只展示价格还要在竞拍结束前后禁用出价按钮。后端返回的end_time是关键但手机时间和服务器时间可能不一致我会在详情接口里额外返回server_time前端算出offset server_time - Date.now()所有倒计时基于这个 offset 计算。template view classdetail-page view classprice-panel text当前价¥{{ detail.current_price }}/text text加价幅度¥{{ detail.increase_step }}/text /view view classaction-bar input v-modelbidPrice typenumber :disabledauctionEnded / button :disabledauctionEnded clicksubmitBid()出价/button /view /view /template script export default { data() { return { detail: {}, server_time: Date.now(), bidPrice: , timer: null }; }, onLoad(options) { this.goodsId options.id; this.fetchDetail(); this.timer setInterval(() { this.fetchDetail(true); }, 3000); }, computed: { auctionEnded() { return new Date(this.detail.end_time).getTime() - Date.now() 0; } }, methods: { fetchDetail(silent) { uni.request({ url: http://your-api-domain/api/goods/detail.php?id${this.goodsId} }).then((res) { if (res.data.code 0) { this.detail res.data.data; this.bidPrice String(Number(this.detail.current_price) Number(this.detail.increase_step)); } }); }, submitBid() { uni.request({ url: http://your-api-domain/api/auction/bid.php, method: POST, data: { goods_id: this.goodsId, new_price: this.bidPrice }, success: (res) { if (res.data.code 0) { uni.showToast({ title: 出价成功 }); } else { uni.showToast({ title: res.data.msg, icon: none }); } } }); } } }; /script逻辑说明computed里的auctionEnded会随detail对象更新而重新计算。fetchDetail每 3 秒刷新一次当前价默认出价框自动填成“当前价 加价幅度”用户可以直接点按钮。这里没有做前端最低加价限制真正的校验在后端防止有人绕过页面直接 POST。参数说明end_time和server_time都建议用YYYY-MM-DD HH:mm:ss格式返回但 UNIAPP 在 iOS 上直接new Date(2025-03-01 22:00:00)会解析失败需要把横杠替换成/再转。这个坑在安卓正常、iOS 倒计时变成NaN需要在公共工具函数里统一处理。3.3 轮询数据不实时先看接口响应时间而不是换 WebSocket很多做竞拍的朋友上来就说要用 WebSocket但原生 PHP 做 WebSocket 要额外跑常驻进程比如 Workerman 或 Swoole这套源码的部署明显是 Nginx PHP-FPM 的短生命周期模型。我的判断是先做 3~5 秒轮询在线人数超过 500 再考虑长连接。轮询最好带一个version参数后端在current_price变化时返回version current_price _ bid_count前端对比版本号决定要不要重新渲染减少setData调用。fetchDetail(silent true) { uni.request({ url: ${apiBase}/api/goods/detail.php?id${this.goodsId}version${this.version}, success: (res) { if (res.data.code 0) { if (res.data.version ! this.version) { this.version res.data.version; this.detail res.data.data; } } } }); }逻辑说明版本对比能避免用户在输入出价金额时页面被重渲染打断。后端只改current_price和bid_count时version就会变前端拿到新版本才更新detail这比每次轮询都整体替换对象更省性能。这种方案在普通竞拍场景下可以顶住几百人。3.4 多端打包时的跨域与接口地址问题UNIAPP 开发 H5 时最烦的是跨域。用uni.request请求 PHP 接口浏览器会先发 OPTIONS 预检后端要在api/common.php里加跨域响应头?php header(Access-Control-Allow-Origin: *); header(Access-Control-Allow-Methods: GET, POST, OPTIONS); header(Access-Control-Allow-Headers: Content-Type, Authorization); if ($_SERVER[REQUEST_METHOD] OPTIONS) { http_response_code(204); exit; }逻辑说明这三行响应头允许跨域请求开发时可以放行生产环境要把*改成具体域名否则别人可以直接调用你的竞拍接口刷价。OPTIONS请求直接返回 204不进入业务逻辑。参数说明如果前端请求带了AuthorizationtokenAccess-Control-Allow-Headers里必须包含它。UNIAPP 打包成 App 时不存在跨域问题但接口域名必须是 HTTPS否则 Android 9 以上默认禁止明文流量。参数开发环境建议值生产环境建议值注意事项Access-Control-Allow-Origin*https://your-h5-domain.com防止任意站点调用接口Access-Control-Allow-MethodsGET, POST, OPTIONS同左如果不支持 PUT前端会报 405Access-Control-Allow-HeadersContent-Type, Authorization同左与前端实际请求头保持一致4. 转售、手续费与支付宝/微信支付订单闭环的 PHP 实现4.1 转售价计算加价百分比与手续费分开算竞拍成功后用户可以选择“提货”或“转售”。转售不是随便填价格而是按原成交价加上一个百分比这个百分比由后台在创建转售单时传入。我一般把手续费设计成买家额外支付即订单总金额 转售价 转售手续费。?php function createTransferOrder($goodsId, $buyerId, $salePrice) { // 拿商品原成交价和转售配置 $goods pdo_get(SELECT * FROM goods WHERE id ?, [$goodsId]); $transferConfig pdo_get(SELECT * FROM transfer_config WHERE goods_id ?, [$goodsId]); $basePrice $goods[deal_price]; // 原竞拍成交价 $percent $transferConfig[percent]; // 例如 10 表示加价 10% $transferPrice round($basePrice * (1 $percent / 100), 2); // 校验传入售价等于后台计算的挂牌价 if (abs($salePrice - $transferPrice) 0.01) { throw new Exception(转售价必须等于原价加价后的金额); } $fee round($transferPrice * $transferConfig[fee_rate], 2); $total round($transferPrice $fee, 2); $orderSn T . date(YmdHis) . rand(1000, 9999); insert_order([ order_sn $orderSn, goods_id $goodsId, buyer_id $buyerId, seller_id $goods[owner_id], sale_price $transferPrice, fee $fee, total_amount $total, type transfer, status pending_payment ]); return $orderSn; }逻辑说明transferPrice是商品转售挂牌价fee是买家需要额外支付的手续费total才是实际支付金额。round(..., 2)避免浮点误差abs(...) 0.01是为了兼容四舍五入差一分的情况。order_sn用日期 随机数前面的T区分竞拍订单可以给竞拍订单加A前缀。参数说明percent使用正整数10表示加价 10%如果要支持降价转售可以用负数但业务上建议限制percent 0。fee_rate可以放在站点配置里也可以放在transfer_config表里按商品覆盖这个灵活度建议保留方便活动场次单独调整手续费。4.2 订单状态机与“提货”“转售”分支订单不能只存支付状态还要有提货/转售状态。我习惯把订单状态拆成待支付、已支付待提货、已提货、转售中、已完成。这个状态流转直接决定 UNIAPP 页面按钮的显示和隐藏。状态值状态名可操作说明0待支付支付/取消竞拍结束后 15 分钟未支付则自动取消1已支付待提货提货/转售用户可以选择提货或发起转售2已提货确认收货后台可标记完成3转售中下架转售订单生成后原订单进入此状态4已完成无交易闭环完成4.3 支付宝异步通知验签和幂等处理支付回调是 PHP 后端最容易出 bug 的地方。支付宝的异步通知会以 POST 方式请求notify_url必须先验签再更新订单处理完成后输出success否则支付宝会重复通知。?php require_once alipay-sdk/aop/AopClient.php; $aop new AopClient(); $aop-alipayrsaPublicKey $config[alipay_public_key]; $arr $_POST; unset($arr[sign], $arr[sign_type]); ksort($arr); $signStr urldecode(http_build_query($arr)); $result $aop-rsaCheckV1($arr, $config[alipay_public_key], RSA2); if (!$result) { exit(验签失败); } $tradeStatus $_POST[trade_status]; if ($tradeStatus TRADE_SUCCESS || $tradeStatus TRADE_FINISHED) { $orderSn $_POST[out_trade_no]; $tradeNo $_POST[trade_no]; $amount $_POST[total_amount]; // 幂等订单已经是已支付就不重复处理 $order get_order_by_sn($orderSn); if ($order $order[pay_status] 0) { // 校验金额是否一致 if (abs($order[total_amount] - $amount) 0.01) { exit(金额不一致); } mark_order_paid($orderSn, $tradeNo, alipay); // 转售订单要给卖家账户加余额 if ($order[type] transfer) { add_user_balance($order[seller_id], $order[sale_price], 转售收入); } } } echo success;逻辑说明rsaCheckV1的第二个参数必须是支付宝公钥不是应用私钥这里经常有人填反。http_build_query后要urldecode因为支付宝通知参数里的中文会被 URL 编码而签名串按原始值计算。关键点是金额校验回调里的total_amount必须和订单金额一致否则可能是伪造通知。参数说明RSA2是支付宝推荐的签名算法使用 SHA256withRSA。异步通知的trade_status只有TRADE_SUCCESS和TRADE_FINISHED才代表支付成功TRADE_CLOSED代表超时关闭。输出success时不能带空格或换行否则支付宝会继续重试。微信支付回调逻辑类似验签换成WxPayApi::verifyNotify成功时返回微信要求的 XML 格式。4.4 余额支付内部账本与流水记录这套系统支持余额支付而且余额可以在竞拍、转售手续费之间复用。余额支付不需要回调但必须做事务处理否则用户余额会有被扣两次的风险。?php function payByBalance($userId, $orderSn) { $pdo-beginTransaction(); try { $order pdo_query_one(SELECT * FROM orders WHERE order_sn ? FOR UPDATE, [$orderSn]); if ($order[pay_status] ! 0) { throw new Exception(订单已支付); } $balance pdo_query_one(SELECT balance FROM users WHERE id ? FOR UPDATE, [$userId]); if ($balance[balance] $order[total_amount]) { throw new Exception(余额不足); } pdo_execute(UPDATE users SET balance balance - ? WHERE id ?, [$order[total_amount], $userId]); pdo_execute(INSERT INTO user_balance_log (user_id, amount, type, order_sn) VALUES (?, ?, consume, ?), [$userId, $order[total_amount], $orderSn]); mark_order_paid($orderSn, , balance); $pdo-commit(); } catch (Exception $e) { $pdo-rollBack(); throw $e; } }逻辑说明先锁订单再锁用户余额两个锁都拿到后才扣款。订单未支付状态在锁内判断防止用户同时点“余额支付”和“支付宝支付”后到的请求会因为订单已支付而失败。user_balance_log保留扣款流水对账和用户明细都靠它。参数说明余额支付不需要第三方交易号mark_order_paid的$tradeNo传空字符串即可。支付渠道记录为balance订单列表里就可以区分“余额支付”“支付宝支付”等来源。5. 宝塔部署与 UNIAPP 打包验证源码包里的文件怎么用起来5.1 先认目录哪些是二次开发入口哪些是产物解压后会看到shanranxuan.apk、info.html.bak、points.html.bak、test.bmp、web.config、多个 CSSindex.b0707a6a.css、index.a5c69d49.css、layui.css、view.css。我的归类layui.css、view.css是后台 LayUI 模板样式index.*.css是 UNIAPP 构建 H5 产物可忽略shanranxuan.apk是现成安卓包但接口地址写死web.config是 IIS 配置LNMP 直接删*.bak和test.bmp建议删除可能残留敏感信息。提示拿到任何 PHP 源码包先把*.bak、test.bmp、web.config这类文件过一遍确认没有数据库密码或支付密钥再上线。5.2 宝塔环境检查PHP 版本、扩展和伪静态这套系统是原生 PHP宝塔 PHP 7.4 最稳很多老商城在 PHP 8.2 上会因mysql_*旧函数报错。先跑php -v和php -m | grep -E redis|pdo_mysql|curl|openssl必须看到pdo_mysql要跑 Redis 锁还得有redis。站点运行目录设置为public伪静态规则按源码实际请求路径调整。php -v php -m | grep -E redis|pdo_mysql|curl|openssllocation / { if (!-e $request_filename) { rewrite ^/(.*)$ /index.php?s$1 last; } }逻辑说明如果源码里访问的是/api/goods/detail.php而不是/api/goods/detail伪静态要改成rewrite ^/(.*)$ /$1.php last;。部署时先看 Nginx 错误日志里的 404 路径再决定用哪种规则。5.3 UNIAPP 打包时必须改的三个配置自己打包前端时manifest.json里最常改三处配置项位置说明appidmp-weixin微信小程序的 AppID公众平台获取apiBaseUrl自定义config.js接口域名改成你的 HTTPS 地址orientationapp-plus锁屏方向竞拍场景建议只留竖屏改完用 HBuilderX 发行到 App 平台。特别注意UNIAPP 打包 H5 嵌入微信公众号时定位授权必须用uni.getLocation而且公众号后台要配置 JS 接口安全域名否则定位弹窗一直失败。这套源码如果涉及线下提货门店定位务必先验证。5.4 验证竞拍闭环用命令行模拟两个用户抢拍部署完先用 curl 模拟两个用户出价看最终价格是否为最后一次出价验证事务锁是否生效。再用channelbalance生成并支付订单确认pay_status变为 1user_balance_log出现扣款流水。转售单则重点核对percent和fee_rate的计算结果。# 用户 1001 出价 150 curl -X POST http://your-domain/api/auction/bid.php \ -d goods_id1user_id1001new_price150.00 # 用户 1002 出价 200 curl -X POST http://your-domain/api/auction/bid.php \ -d goods_id1user_id1002new_price200.00 # 查看最终价格确认是 200 而不是 150 curl http://your-domain/api/goods/detail.php?id1# 用户 1002 生成竞拍订单并用余额支付 curl -X POST http://your-domain/api/order/create.php -d goods_id1user_id1002type1 curl -X POST http://your-domain/api/order/pay.php -d order_snxxxchannelbalance逻辑说明两个出价请求快速执行如果最终价格是 200说明事务锁生效如果返回“出价低于最低加价幅度”说明第一个请求还没提交、第二个已经读到旧价格需要检查是否在同一个连接上开启事务。余额支付不用第三方回调验证闭环最快。参数说明type1对应竞拍订单type2对应转售订单。支付成功后orders.pay_status应变更为 1同时user_balance_log新增一条扣款流水。最后再到后台生成转售单确认percent和fee_rate的计算结果与页面展示一致。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价