ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

FastAdmin+UniApp租赁商城小程序:押金租金链路与库存并发处理

FastAdmin+UniApp租赁商城小程序:押金租金链路与库存并发处理 简介基于thinkphp与uniapp开发的租赁小程序源码面向需要搭建租借、归还、押金管理等场景的开发者与企业。系统以小程序为前端载体后端采用ThinkPHP框架支持多角色平台管理内置装修、门店、商品、订单、财务、优惠券、会员与配置中心等核心模块并支持二级分销体系。压缩包共2000个文件以js、vue、css等前端代码为主同时包含html页面、json配置、md说明文档以及SQL数据库脚本整体约25.14MB资源结构较完整。功能上商品模块支持多规格单品和组合套餐后台可灵活调整库存与价格订单模块可管理租赁与充值订单员工端可执行出库、归还操作财务模块提供提现申请、充值套餐和余额明细管理。完整源码附带了数据库脚本和可运行的小程序页面有助于快速理解ThinkPHP接口与UniApp前端交互逻辑。目前已有348人学习浏览适合具备PHP与前端基础的开发者参考。1. 租赁业务和普通商城最大的区别不在功能表里做过商城系统的人拿到这套源码第一反应通常是先找商品、订单、支付这三个模块。但租赁场景下真正决定系统能不能跑起来的是「押金 租金周期 出库归还」这条链路。普通电商订单付款即结束租赁订单却在付款后拉开一个完整生命周期商品要出库、按天计费、到期归还、验货退款。这套基于 ThinkPHP 5.1FastAdmin 框架加 UniApp 的租赁商城小程序源码把这条链路完整拆成了用户端、员工端、后台管理端三套交互。小程序端面向 C 端用户浏览下单员工端处理出库与归还后台配置商品价格策略、会员余额和优惠券。它不追求大而全的中台设计胜在业务闭环清晰——每个角色该干什么、订单走到哪一步该做什么表结构和权限都给你铺好了。适合三类人有实体物资想做起租业务的商家比如工具租赁、礼服租赁、电子设备租赁接外包需要快速交付租赁类小程序的开发者以及想研究 ThinkPHP 多端项目结构的后端工程师。下面从架构链路讲到部署避坑最后落在库存并发的处理技巧上。2. FastAdmin 底座与 UniApp 三端这套源码的项目结构2.1 为什么是 ThinkPHP 5.1 FastAdmin而不是从零搭接口这套源码的后端没有自己手搓框架而是基于 FastAdmin 二次开发。FastAdmin 是 ThinkPHP 5.1 生态里常用的后台开发框架自带 RBAC 权限管理、后台模板、插件机制和一键生成 CRUD 的能力。选择它做租赁小程序底座最直接的好处是后台管理界面不用从头写菜单、管理员、角色权限这些通用能力开箱即用。你在后台看到的门店管理、员工权限、财务提现等模块本质上都是在 FastAdmin 的控制器、模型、视图三层结构上扩展出来的业务。对于小程序端UniApp 的价值在于一套代码可编译到微信小程序、H5 和 App。但要注意这套源码的实际业务重心在微信小程序端H5 和 App 属于附带能力App 打包后如果要上架安卓应用市场还需要额外处理软著和隐私合规这部分后面部署章节会说。请求链路按 FastAdmin 的默认路由走小程序端uni.request发起 HTTP 请求经 Nginx 转发到public/index.php入口文件根据路由参数定位到对应的 API 控制器。控制器里有两个默认行为检测管理员登录态和客户端用户登录态。FastAdmin 的 API 模块把这两个登录态分得很开后台管理员走Admin中间件小程序端用户走User中间件二者 Token 互不通用。实际开发时最容易犯的错就是把用户 Token 塞到后台请求头里然后拿不到数据还找不到原因。这套源码的目录结构大致如下application/ admin/ # 后台管理控制器门店、商品、订单、财务 api/ # 小程序端接口控制器用户登录、商品列表、下单 extend/ fast/ # FastAdmin 核心扩展 public/ assets/ # 后台静态资源bootstrap.css、fastadmin.css 等 addons/ # 插件目录后台静态文件里出现了bootstrap.min.css、fastadmin.min.css这类文件说明后台界面是 Bootstrap 3 加 FastAdmin 自研样式组合前端交互少不了 jQuery 和 RequireJS。这套组合对后端开发者很友好——不需要懂 Vue 也能维护后台页面。但也意味着后台页面和 Uniapp 小程序前端是两套技术栈改动后台页面需要直接操作 PHP 模板文件。2.2 后台登录态与小程序端用户态的隔离设计FastAdmin 后台的登录是基于think\facade\Session的服务端会话管理员登录后 Session 里记录admin_id访问控制器时通过后台基类的_initialize方法做鉴权。而小程序端的用户态走的是无状态 Token 机制用户通过微信登录拿到code后端调用code2Session接口换取openid然后签发一个自定义 Token 返回给前端。前端每次请求在 Header 里带token后端用中间件解析并写入当前用户信息。// application/api/controller/User.php 登录逻辑示意 public function login() { $code $this-request-post(code); $wxRes $this-wechat-login($code); // 小程序 code 换 openid $user UserModel::where(openid, $wxRes[openid])-find(); if (!$user) { $user UserModel::create([ openid $wxRes[openid], nickname 微信用户, ]); } $token md5(uniqid() . $user-id); cache(user_token_ . $token, $user-id, 7200); // Token 存缓存2小时过期 return json([token $token, user_id $user-id]); }这段逻辑里最关键的是 Token 写入缓存而不是数据库。用cache()函数存储 Token 映射关系请求进来时从缓存读过期自动失效不需要额外建表。如果要延长登录态有效期把第三个参数从 7200 改成 259200030天即可。注意每次请求必须校验token这个校验逻辑通常写在 API 基类的_initialize方法里不要在每个控制器里重复写否则后续维护时漏了一个接口就等于把用户信息裸奔出去。2.3 表结构设计的业务重心在订单和库存记录这套源码的核心数据表可以从后台菜单反推出来。门店管理对应store表公告对应store_notice表员工权限直接复用 FastAdmin 的auth_group和admin表商品相关的是goods和goods_sku多规格表租赁订单是rent_order和rent_order_items用户余额记录是user_money_log。下面用表格梳理关键表和它们承担的业务职责表名业务职责关键字段store门店基础信息、公告、地址定位name、notice、latitude、longitudegoods租赁商品主表status、store_id、deposit、rent_typegoods_sku商品规格库存价格goods_id、spec、stock、day_price、month_pricerent_order租赁主订单order_no、user_id、total_deposit、rent_fee、statusrent_order_items订单里的明细商品order_id、goods_id、sku_id、start_date、end_dateuser_money_log余额变动流水user_id、amount、typedistribution_log分销佣金记录user_id、order_id、level、amount租赁业务里经常把「押金」和「租金」混在一个字段里但这套系统的设计是拆开的。deposit字段在归还验收前都是冻结状态rent_fee按租赁天数计算结算时要分开算。rent_order_items里的start_date和end_date直接决定了租金的计算区间而不是用下单时间替代——这是租赁业务和卖货业务在数据设计上最大的差异。3. 租赁订单状态机与押金/租金结算逻辑3.1 订单状态如何在用户、员工、后台间流转租赁订单的状态流不是简单的「待付款→已完成」而是多了一个「租用中」和「已归还待结算」的中间态。用户下单付款后订单是待提货员工在线下把实物交给用户时在员工端点击确认出库订单才进入租用中用户归还商品后员工确认收货并检查商品损伤订单变为已归还此时押金才进入退款审核。下面这张状态表对应的是后台订单列表里看到的状态字段状态值含义触发方式押金状态0待付款用户下单未支付未收1待提货支付成功等待员工出库已收2租用中员工端确认出库计时开始冻结中3待归还用户申请归还或租期到期冻结中4已归还员工确认收货等待结算待退还5已完成押金退还完成已退还6已取消用户主动取消或超时未支付无前端状态展示直接拿这个数字做映射。员工端的出库操作对应状态 1 到 2归还操作对应状态 3 到 4后台的退款操作对应 4 到 5。这个链路的精髓在于「每个状态变更都强制绑定一个角色的操作」而不是系统自动流转这样出了纠纷能定位到是谁在什么时间动的手。要在后台增加一个「强制归还」的操作按钮只需要在订单管理控制器里加一个自定义方法把订单状态直接改到 3并记录操作日志。源码的 FastAdmin 后台列表页通过addtabs和ajax请求来实现操作不需要刷新整个页面。3.2 租金自动计算的两种维度按日租和按月租商品发布时有「日租价」和「月租价」两个价格档次下单时用户选择起止时间后端根据所选区间判断该按哪个价格计算。计算规则写在一个公共方法里这样员工端和用户端调用的是同一套逻辑不会出现前端展示价和后端实收价不一致的情况。// 计算租金$startDate 和 $endDate 为日期字符串格式 Y-m-d public function calcRent($sku, $startDate, $endDate) { $days (strtotime($endDate) - strtotime($startDate)) / 86400 1; if ($days 0) { throw new \Exception(结束日期不能早于开始日期); } // 超过30天按整月折算不足整月按日租价补齐 $months intdiv($days, 30); $remainDays $days % 30; $fee $months * $sku[month_price] $remainDays * $sku[day_price]; // 单独计算押金押金不计入租金 $deposit $sku[deposit]; return [ days $days, months $months, rent_fee $fee, deposit $deposit, ]; }这套算法属于业务上最常见、也最容易说服用户的方案租期越长越便宜但不会出现「租 31 天比租 30 天还便宜」的荒谬结果。比如一件商品日租 20 元、月租 500 元租 31 天时结果为 500 20 520 元租 30 天是 500 元多一天多 20 元符合直觉。如果要改成按自然月结算需要把months的判断改成检查日期是否跨月但那套逻辑更适合长期租赁短租场景用天数折算反而简单可靠。3.3 员工端出库与归还的接口约定员工端本质上是嵌在用户端小程序里的一个隐藏入口——员工用分配给自己的手机号登录后小程序通过角色判断渲染出工作台菜单。出库操作提交的参数只有order_no和员工操作人id后端校验订单状态为待提货后更新库存并把状态置为租用中。// 员工出库接口 public function outbound() { $orderNo $this-request-post(order_no); $adminId $this-request-post(admin_id); $order RentOrderModel::where(order_no, $orderNo)-find(); if ($order[status] ! 1) { return json([code 0, msg 当前状态不可出库]); } Db::startTrans(); try { // 扣减实际库存 GoodsSkuModel::where(id, $order[sku_id]) -dec(stock, 1) -update(); // 订单流转到租用中 $order-status 2; $order-outbound_admin_id $adminId; $order-outbound_time time(); $order-save(); Db::commit(); return json([code 1, msg 出库成功]); } catch (\Exception $e) { Db::rollback(); return json([code 0, msg 出库失败]); } }这里用事务把库存扣减和订单状态变更绑在一起保证不会出现「库存扣了但订单状态没改」的不一致情况。dec是 ThinkPHP 的原子自减方法比select update两步操作更安全在线下门店同时有多人扫码出库的场景下能避免超卖。等待用户归还时逆向流程调用归还接口库存做inc自增状态从 3 变到 4。归还接口里还可以增加一个「损坏赔偿金额」字段由员工填写系统自动从押金里扣除后再走退款。4. UniApp 小程序端的页面结构与角色化路由4.1 从pages.json看小程序端的功能划分UniApp 的页面结构和路由都写在pages.json里。这套源码的小程序端页面可以分成三组用户端常规页面、支付与订单页面、员工端工作台页面。用户端首页和商城页是装修模块的动态渲染容器商品详情页负责展示规格选择起租时间订单确认页整合了余额和微信支付两种支付方式。员工端则独立在staff目录下和用户端页面完全分离。{ pages: [ { path: pages/index/index, style: { navigationBarTitleText: 租赁首页 } }, { path: pages/goods/detail, style: { navigationBarTitleText: 商品详情 } }, { path: pages/order/confirm, style: { navigationBarTitleText: 确认订单 } }, { path: pages/staff/dashboard, style: { navigationBarTitleText: 员工工作台 } }, { path: pages/staff/scan, style: { navigationBarTitleText: 扫码出库 } } ] }页面拆分的核心思路是「员工端和用户端不混在同一个页面里」。虽然二者共用一套登录态但页面入口、底部 TabBar 和操作按钮完全不同。staff/scan页面使用uni.scanCode扫码接口扫描用户出示的订单二维码然后把码内携带的order_no传给后端出库接口。这样员工不用手动输入订单号降低线下操作出错率。4.2 装修模块的动态渲染机制后台装修模块改的不只是图片和文案而是整个首页的组件结构和主题色。后台把装修配置以 JSON 格式存到config表小程序端首页请求接口拿到 JSON 后动态渲染。JSON 结构大致如下{ theme_color: #ff5f17, components: [ { type: banner, data: { images: [https://xxx/banner1.png] } }, { type: notice, data: { text: 新用户首租立减20元 } }, { type: goods_list, data: { category_id: 3, limit: 10 } } ] }前面的/后面我在前面已经写过了注意 JSON 值里的https://xxx在替换成实际域名时需要在小程序后台配置 downloadFile 合法域名纯 http 在内网可以调试但真机预览会直接白图。小程序端拿到theme_color后通过 CSS 变量赋值全局按钮、导航栏、价格文字都会跟着变。// 动态设置主题色 const app getApp(); app.globalData.themeColor res.data.theme_color; uni.setNavigationBarColor({ frontColor: #ffffff, backgroundColor: res.data.theme_color }); document.documentElement.style.setProperty(--theme-color, res.data.theme_color);这里document只在 H5 端可用在微信小程序里要换思路组件样式中直接使用 CSS 变量而page根节点的style由 JS 动态设置。小程序端的做法是给每个页面的根 view 绑定:style{ --theme-color: themeColor }这样子组件里写color: var(--theme-color)就能全局生效。后台保存装修后会生成一个版本号小程序端本地缓存接口返回的配置下次进入先渲染缓存再对比版本号做增量更新避免首页每次打开都白屏等待。4.3 登录态与前端路由守卫的配合小程序端登录流程不再是传统的账号密码而是调用uni.login获取临时code交给后端换openid并返回业务token。开发者工具里可以模拟但真机调试必须用真实 AppID否则 code 换不到 openid。用户信息接口返回的数据里包含is_staff字段前端根据这个字段决定是否渲染员工工作台的入口。// 路由守卫进入员工页面前校验角色 uni.request({ url: /api/user/info, header: { token: uni.getStorageSync(token) }, success: (res) { if (res.data.is_staff 1) { uni.navigateTo({ url: /pages/staff/dashboard }); } else { uni.showToast({ title: 无权限访问, icon: none }); } } });后端user表里存了is_staff字段员工账号由后台门店模块创建并绑定到门店。这样员工离职后只要后台把账号禁用小程序端该账号立即失去工作台权限不需要重新发版。如果要做更细的权限控制比如仓库员工只能看出库单不能操作退款可以再建一张staff_auth表和admin表做映射FastAdmin 后台的管理员表天然支持分组权限扩展起来不难。5. 部署上线伪静态、微信支付配置与库存并发扣减技巧5.1 LNMP 环境的 Nginx 配置要点这套源码是 ThinkPHP 5.x 项目PHP 版本建议 7.4。有些 ThinkPHP 历史版本在 PHP 8.0 下会出现 DEPRECATED 警告甚至直接白屏主要是each()、create_function()这类函数被移除导致。如果你只拿到了源码包没有说明文档部署时报 500 错误先打开runtime/log里的日志文件绝大多数原因集中在目录权限不够和 PHP 扩展缺失特别是fileinfo和redis。Nginx 伪静态配置如下server { listen 80; server_name yourdomain.com; root /var/www/rent/public; index index.php index.html; location / { if (!-e $request_filename) { rewrite ^(.*)$ /index.php?s$1 last; } } location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } location ~ \.(js|css|png|jpg)$ { expires 30d; } }root指向的是public目录而不是项目根目录这是 ThinkPHP 安全部署的标准做法——入口文件外露数据库配置和业务代码都在上一级目录即使某个静态文件解析异常攻击者也拿不到application/database.php。部署完成后记得改config/database.php里的连接信息并把后台入口从admin.php改成随机字符串能挡掉大部分扫描后台路径的脚本。ThinkPHP 历史上出过多次远程代码执行漏洞公网环境务必开启app_debug false关闭错误回显。5.2 小程序发布前的微信支付与域名配置小程序端上传代码前要在manifest.json的小程序配置里填入自己申请的 AppID同时确认后台 API 请求地址是 HTTPS 域名而不是 IP。微信支付配置分散在两个地方小程序后台的「微信支付」关联商户号后端支付参数配置在后台的配置中心需要填mch_id、api_key和证书路径。这里要特别提醒退款和微信支付回调验签必须用 API v2 的 HMAC-SHA256 方式很多源码用的是 MD5 方式的旧接口微信侧 2024 年后已逐步收紧如果退款一直报签名错误检查是否升级到 v2 的 HMAC-SHA256。测试环境常见的坑是「真机预览请求失败」。开发工具勾选「不校验合法域名」后接口能通但真机上由于没有关闭域名校验HTTPS 证书必须是由正规 CA 签发的不能是自签名。同时微信公众平台要把request合法域名和downloadFile合法域名都配置完整后者管的是用户头像、商品图片、装修 banner 的加载。5.3 高并发下租赁库存扣减的 Redis 原子操作最后收在一个具体技巧上多门店同时出库时dec方法虽然保证单次操作的原子性但遇到秒杀场景还是要上 Redis。源码里的库存字段在 MySQL可以先同步一份到 Redis出库时改用decr# 商品 SKU 初始化库存 SET goods_sku_stock_123 100 # 出库扣减 DECR goods_sku_stock_123 # 归还回补 INCR goods_sku_stock_123Redis 的DECR是原子操作两个员工同时出库不会出现扣减覆盖。扣减成功后用消息队列异步把结果写回 MySQLMySQL 表里存一个version字段做乐观锁更新时WHERE id 123 AND version 旧版本号不匹配就重新读取重试。这方案比单纯 MySQL 的dec在高并发下表现更稳而且归还操作走了INCR回补Redis 里的值和 MySQL 最终一致。如果后续要做「租期到期自动扣费提醒」用定时任务扫描end_date now()的待归还订单提前一天给用户推订阅消息这套表结构已经足够支撑。本文还有配套的精品资源点击获取
返回列表