☰
共享雨伞租赁系统开发:SpringBoot后端与微信小程序实战要点
2026/10/10 20:46:43 网站建设 项目流程

简介:这是基于Spring Boot与微信小程序平台的共享雨伞租赁系统设计文档,面向Java Web开发学习者、毕业设计或课程设计选题学生。系统覆盖用户管理、雨伞类型管理、归还点管理、雨伞管理、租赁订单管理、归还信息管理、租赁费用管理、留言板管理、系统管理及个人信息管理等模块,完整呈现了共享雨伞租借与归还的业务流程。文档采用Java语言、Spring Boot框架和MySQL数据库,对各个功能模块的实现思路与数据结构进行了说明,可帮助读者快速掌握小程序后端接口设计与数据库表设计方法。资源包内共1个文件,为docx格式,大小3.07MB,属于文字说明类资料,适合用来梳理系统架构、撰写设计说明书或准备项目答辩。目前已有62人浏览学习,对于想要参考完整系统设计思路的开发者来说,这份文档具备一定借鉴价值。

1. 共享雨伞租赁系统:SpringBoot + 微信小程序能做什么?

共享雨伞真正烧钱的不是伞,是“还伞”这个动作。地铁口雨天伞桩满、雨停伞桩空,波动特别大,订单频率高但单笔金额小。用 SpringBoot 做后端、微信小程序做前端的共享雨伞租赁系统,不是为了做花哨商城,而是把“借得出去、还得回来、费用算得清”这条链路完整跑通。它能解决三件事:微信登录与身份识别、扫码借还伞与订单状态流转、按时间计费与押金结算。适合三类人:想把共享租赁方向做成简历项目或毕设的同学,要在园区、校园落地轻量租赁的小团队,以及已经买了伞桩硬件、缺一套后端和小程序的开发者。下面按我实际做过的一个园区租赁 Demo 的架构,从数据表讲到接口,再落到小程序端和最容易翻车的地方。

2. 业务模式与数据建模:先定借还流程,再落 5 张表

2.1 固定桩与无桩:共享雨伞租赁系统的两种实现取舍

共享雨伞硬件上分固定桩和无桩两种。固定桩模式的伞架在伞桩上,用户扫码开锁取伞,还伞时必须插回任意空桩位;无桩模式的伞自带密码锁和定位模块,用户拍到哪儿还到哪儿,依赖 GPS 和小程序定位。从后端开发角度看,固定桩明显更容易落地,因为伞和伞桩的状态都能收敛到数据库里,不需要和硬件做复杂的双向通信。无桩模式对硬件要求高,伞状态靠设备定位上报,服务端只能被动接受,出问题很难排查。

我一般建议第一版做固定桩。伞桩上线时录入编号和经纬度,每把伞绑定一个桩位,订单状态跟着桩位走。用户扫伞桩上的二维码,后端把伞状态从空闲改成借出;还伞时扫空闲桩,后端把伞状态改回空闲。这样业务上最难的“伞在哪里”就变成了两个 UPDATE 语句,SpringBoot 后端可以集中精力把计费、支付、异常订单处理好。如果你手里已经有蓝牙锁或 LoRa 设备,再考虑把开锁动作接进来,但第一版别让硬件状态卡住业务流程。

2.2 数据表设计:用户、雨伞、伞桩、订单、资金流水一张不能少

我做过的最小可用表结构是 5 张:用户表、雨伞表、伞桩表、订单表、资金流水表。用户表承接微信 openid,伞桩表和雨伞表维护位置与状态,订单表记录借还时间与费用,资金流水表记录每一笔押金、租金、退款的去向。这样业务对账时不用去翻微信支付账单,本地就有明细。

先看用户表和伞桩表:

CREATE TABLE t_user ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT '用户ID', openid VARCHAR(64) NOT NULL COMMENT '微信openid', nickname VARCHAR(64) DEFAULT '' COMMENT '昵称', status TINYINT NOT NULL DEFAULT 0 COMMENT '0正常 1冻结', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_openid (openid) ) COMMENT '用户表'; CREATE TABLE t_station ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT '伞桩ID', station_no VARCHAR(32) NOT NULL COMMENT '桩位编号,二维码内容', name VARCHAR(64) DEFAULT '' COMMENT '投放点名称', address VARCHAR(255) DEFAULT '' COMMENT '地址描述', lat DECIMAL(10,6) DEFAULT 0 COMMENT '纬度', lng DECIMAL(10,6) DEFAULT 0 COMMENT '经度', current_umbrella_id BIGINT DEFAULT NULL COMMENT '当前伞ID', status TINYINT NOT NULL DEFAULT 0 COMMENT '0空闲 1占用 2维护', PRIMARY KEY (id), UNIQUE KEY uk_station_no (station_no) ) COMMENT '伞桩表';

这两个表的关键点:openid 和 station_no 都建唯一索引。openid 唯一是防止同一微信号重复建账号;station_no 唯一是给扫码借伞提供稳定入口。伞桩表里的 current_umbrella_id 在设计时允许为空,因为伞被借走后桩位就是空的。小程序端展示“有空伞”时,查的就是这个字段不为空的伞桩。

雨伞表和订单表是整条借还链路的核心。订单表我单独说几个字段为什么这么设计:request_id 是前端每次借伞生成的唯一请求编号,用于防止重复下单;deposit_amount 和 rental_fee 分开存,因为押金是先收的,租金是还伞时才计算出来的。订单状态我用 0 待支付、1 使用中、2 归还中、3 已完成、4 已取消五个状态,其中“归还中”专门留给退款还没到账的中间态,后面避坑章节会细说。

CREATE TABLE t_umbrella ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT '伞ID', umbrella_no VARCHAR(32) NOT NULL COMMENT '伞编号', rfid_no VARCHAR(32) DEFAULT '' COMMENT 'RFID标识', status TINYINT NOT NULL DEFAULT 0 COMMENT '0空闲 1借出 2维修', station_id BIGINT DEFAULT NULL COMMENT '当前所在伞桩ID', PRIMARY KEY (id), UNIQUE KEY uk_umbrella_no (umbrella_no) ) COMMENT '雨伞表'; CREATE TABLE t_order ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT '订单ID', order_no VARCHAR(32) NOT NULL COMMENT '订单号', user_id BIGINT NOT NULL COMMENT '用户ID', umbrella_id BIGINT NOT NULL COMMENT '伞ID', station_borrow_no VARCHAR(32) NOT NULL COMMENT '借伞桩编号', station_return_no VARCHAR(32) DEFAULT NULL COMMENT '还伞桩编号', status TINYINT NOT NULL COMMENT '0待支付 1使用中 2归还中 3已完成 4已取消', request_id VARCHAR(64) NOT NULL COMMENT '幂等键', deposit_amount DECIMAL(10,2) NOT NULL COMMENT '押金', rental_fee DECIMAL(10,2) DEFAULT 0 COMMENT '租金', borrow_time DATETIME DEFAULT NULL COMMENT '借出时间', return_time DATETIME DEFAULT NULL COMMENT '归还时间', PRIMARY KEY (id), UNIQUE KEY uk_request_id (request_id), UNIQUE KEY uk_order_no (order_no) ) COMMENT '租赁订单表';

订单表上加 uk_request_id 是防重复下单的关键。很多项目一开始没加,用户借伞时手抖点两下,后端就收到两条请求,结果扣了两笔押金。这个唯一索引能挡住绝大多数重复提交,前提是后端在插入订单前查一下 request_id 是否已存在。订单号也要唯一,我习惯用时间戳加随机数生成,但光靠随机数有极低概率重复,所以又把 uk_order_no 建上了。

资金流水表不需要太复杂,记录 order_no、交易类型、金额、交易流水号、状态即可。它主要用来对账,排查“用户说扣了钱但订单没生成”这类问题,以及给定时任务做补偿提供依据。

2.3 伞状态扣减:数据库行锁与 Redis 各自承担的角色

第一版做伞状态扣减,别把 Redis 当唯一数据源。共享雨伞场景并发不像抢购那么极端,一个伞桩最多同时被一个用户借,数据库行锁已经足够。常见错误是“先查一下伞桩状态,如果空闲再 UPDATE”,两个操作之间有间隔,并发请求会同时读到空闲,最后都执行 UPDATE,造成一伞多借。正确做法是用 UPDATE 条件判断,让数据库保证原子性:

UPDATE t_umbrella SET status = 1, station_id = NULL WHERE id = #{umbrellaId} AND status = 0;

这条 SQL 的影响行数为 1,才算抢伞成功;为 0 说明伞已经被借走。SpringBoot 里配合 @Transactional,把伞状态更新、伞桩状态更新、订单插入放在同一个事务中,任何一步失败都整体回滚。Redis 在这个项目里的定位是缓存:缓存附近的伞桩列表、缓存伞桩实时空位数量,展示可以容忍短暂不一致,但业务流程必须认数据库。等订单量上来后再考虑把伞状态热点放进 Redis,并用 Lua 脚本做扣减,那是后话。

提示:开发阶段可以把日志级别调到 DEBUG,观察事务里两条 UPDATE 的先后顺序。很多人排查半天发现不是 SQL 写错,而是事务没包住第二条语句,导致伞桩状态变了、订单没建成功。

3. SpringBoot 接口实现:登录、借伞、还伞与定时清理

3.1 微信登录接口:code 换 openid,JWT 管后续鉴权

小程序端通过 wx.login() 拿到一个临时 code,后端拿 code 换 openid 和 session_key。这个 code 有效期只有 5 分钟,而且只能用一次,所以后端接口要做成无状态:换到 openid 后查用户表,存在就发 token,不存在就创建用户再发 token。token 我用的 JWT,里面只放 userId 和 openid,过期时间设 7 天,小程序端每次请求带着 Authorization 头即可。

@RestController @RequestMapping("/api/wx") public class WxAuthController { @PostMapping("/login") public Result<String> login(@RequestBody LoginRequest request) { // code 换 openid,session_key 只在服务端保留,不返回前端 String openid = wxService.code2Session(request.getCode()); Long userId = userService.ensureUserByOpenid(openid); // 生成 JWT,7 天过期 String token = jwtUtil.createToken(userId, openid); return Result.ok(token); } }

这里要注意 session_key 不要返回给小程序。共享雨伞系统里不需要解密手机号,也就没必要让前端拿到 session_key。JWT 生成后,后续接口通过拦截器解析 token 拿到 userId,不要再每次查微信接口。开发时最容易踩的坑是把 appid 和 secret 直接写在配置里推到 Git 仓库,正确做法是用 Spring 的 @ConfigurationProperties 读取环境变量或配置中心。

3.2 借伞接口:扫码、开锁、状态机与幂等一次说清

借伞接口是整个系统最需要小心的地方。它要做四件事:幂等检查、锁伞、改状态、建订单。前端在用户点击“借伞”时生成一个 requestId,后端先查 request_id 是否存在,存在就直接返回原订单,避免重复扣押金;不存在才继续走锁伞流程。锁伞用 select ... for update 锁定伞桩行,再对伞执行 UPDATE ... WHERE status = 0,确保同一把伞不会被并发借走。

@Transactional(rollbackFor = Exception.class) public BorrowResult borrow(String stationNo, Long userId, String requestId) { // 1. 幂等检查:相同 requestId 直接返回已有订单 Order exist = orderMapper.selectByRequestId(requestId); if (exist != null) { return toBorrowResult(exist); } // 2. 锁伞桩,防止并发借同一把伞 Station station = stationMapper.selectByNoForUpdate(stationNo); if (station.getStatus() != 0 || station.getCurrentUmbrellaId() == null) { throw new BizException("当前伞桩无空闲伞"); } // 3. 伞置为借出,伞桩清空 int rows = umbrellaMapper.updateStatus(station.getCurrentUmbrellaId(), 0, 1); if (rows == 0) { throw new BizException("伞已被借出"); } stationMapper.clearCurrentUmbrella(stationNo); // 4. 创建使用中订单 Order order = new Order(); order.setOrderNo(generateOrderNo()); order.setUserId(userId); order.setUmbrellaId(station.getCurrentUmbrellaId()); order.setStationBorrowNo(stationNo); order.setStatus(OrderStatus.USING.getCode()); order.setRequestId(requestId); order.setDepositAmount(new BigDecimal("20")); order.setBorrowTime(new Date()); orderMapper.insert(order); return toBorrowResult(order); }

逻辑说明:第 2 步用 selectByNoForUpdate 锁定的是伞桩这行,锁住之后第二个请求会阻塞,直到第一个事务提交。第 3 步的 UPDATE 影响行数也必须判断,因为伞桩上有伞不代表伞一定空闲,可能有后台把伞标记成维修了。参数说明:requestId 由前端生成,格式建议是时间戳加随机串,长度控制在 64 字符以内;押金金额在订单里写死 20 元,后续如果要调整,单独做一个押金配置表比改代码更灵活。

3.3 还伞结算接口:费用计算、退款与订单闭环

还伞时用户扫空闲伞桩,提交 stationNo 和 orderId。后端先锁订单行,判断订单状态,避免用户重复提交还伞请求。然后计算用时和费用,更新订单为“归还中”,把伞挂到伞桩上。这里我特意不在本地事务里直接调微信退款,因为本地事务只能保证数据库回滚,管不了远程微信退款的结果。把退款放在事务之外,通过消息或异步任务处理,回调成功后再把订单改成已完成,这才是可靠的还伞结算方式。

@Transactional(rollbackFor = Exception.class) public ReturnResult returnUmbrella(Long orderId, String returnStationNo) { Order order = orderMapper.selectByIdForUpdate(orderId); if (order.getStatus() == OrderStatus.COMPLETED.getCode()) { throw new BizException("该订单已归还"); } if (order.getStatus() != OrderStatus.USING.getCode()) { throw new BizException("订单状态异常"); } // 计算租金:首小时 1 元,超时后每小时 0.5 元,单日封顶 10 元 long minutes = Duration.between(order.getBorrowTime(), new Date()).toMinutes(); BigDecimal fee = feeStrategy.calculate(minutes); // 伞桩落伞、伞状态回空闲、订单进入归还中 stationMapper.bindUmbrella(returnStationNo, order.getUmbrellaId()); umbrellaMapper.updateStatus(order.getUmbrellaId(), 1, 0); order.setRentalFee(fee); order.setStationReturnNo(returnStationNo); order.setStatus(OrderStatus.RETURN_PENDING.getCode()); order.setReturnTime(new Date()); orderMapper.updateById(order); // 事务提交后再发起退款,微信退款结果走回调 refundService.refundDeposit(order.getId(), order.getDepositAmount().subtract(fee)); return ReturnResult.of(fee, order.getDepositAmount().subtract(fee)); }

这个接口的关键不是计算逻辑,而是把“本地状态更新”和“外部资金操作”拆开。如果你把 refundDeposit 写进事务里,退款调用失败会导致整个事务回滚,用户伞已经还了,数据库却还显示借出。到时候用户和运营都会被这个玄学问题折磨。费用策略我用了一个 FeeStrategy 接口,方便以后改成按天计费或会员折扣,而不用动还伞主流程。

3.4 定时任务:超时订单回收与状态自愈

小程序端可能发生借伞后没支付押金、或者支付了但没取伞的情况。这类订单不能一直占着伞桩。SpringBoot 里用 @Scheduled 做轻量定时任务就够了,不用一开始就上分布式任务调度平台。我通常配两个任务:一个清理创建后超过 30 分钟仍未支付的订单,另一个扫描状态停留在“归还中”超过 15 分钟的订单并主动查微信支付结果。

@Scheduled(cron = "0 0/5 * * * ?") public void cancelExpiredBorrowOrders() { List<Order> expired = orderMapper.selectExpiredPending(30); for (Order order : expired) { try { // 回滚伞和伞桩状态,订单改成已取消 umbrellaMapper.updateStatus(order.getUmbrellaId(), 1, 0); stationMapper.bindUmbrella(order.getStationBorrowNo(), order.getUmbrellaId()); orderMapper.updateStatusById(order.getId(), OrderStatus.CANCELED.getCode()); } catch (Exception e) { log.error("取消超时订单失败, orderId={}", order.getId(), e); } } }

参数说明:cron 表达式0 0/5 * * * ?表示每 5 分钟执行一次,对共享雨伞这种低频场景足够。多个实例部署时要注意 @Scheduled 会在每个节点都执行一遍,轻量做法是引入一个 Redis 分布式锁,抢到锁的节点才执行任务,避免订单被重复扫描处理。

4. 微信小程序端:扫码入口、请求封装与附近伞桩

4.1 小程序目录与请求封装:统一携带 token 和错误码

小程序端不需要复杂的工程结构,页面控制在四五个以内就够:首页、借伞页、订单列表页、我的页。代码目录我习惯按功能分,pages 下放页面,utils 下放请求封装和公共函数。请求封装是第一个要写的东西,因为所有接口都要带 token,都要处理 401 和业务错误码,不能每个页面都复制一遍 wx.request。

const request = (url, method = 'GET', data = {}) => { const token = wx.getStorageSync('token') return new Promise((resolve, reject) => { wx.request({ url: `${getApp().globalData.baseUrl}${url}`, method, data, header: { Authorization: `Bearer ${token}` }, success: (res) => { if (res.data.code === 401) { wx.removeStorageSync('token') wx.reLaunch({ url: '/pages/login/login' }) return } if (res.data.code !== 0) { wx.showToast({ title: res.data.msg, icon: 'none' }) reject(res.data) return } resolve(res.data.data) }, fail: reject }) }) } module.exports = { request }

逻辑说明:所有接口返回统一结构 { code, msg, data },code 0 表示成功。401 单独处理,清 token 并回登录页。业务错误直接弹 toast,页面里只需要关心成功分支。参数说明:baseUrl 放在 getApp().globalData 里,方便测试环境切换;token 的 key 建议统一叫 token,和登录接口的返回字段保持一致。

4.2 扫码借伞页面:wx.scanCode 与手动输入的兜底

扫码是共享雨伞小程序的主入口。二维码内容我建议直接放 stationNo,比如“STATION-001”,后端按编号查伞桩。这样用户扫的不是某个产品链接,而是业务编号,省去小程序端再解析参数。开发时要注意一个边界:部分安卓机型扫码偶尔会失败,或者用户摄像头权限没开,所以页面上必须保留手动输入伞桩编号的输入框。

async handleScanBorrow() { try { const scanRes = await wx.scanCode({ onlyFromCamera: true }) // 二维码内容约定为 stationNo,例如 STATION-001 this.borrowByStationNo(scanRes.result) } catch (err) { wx.showToast({ title: '扫码失败,请手动输入编号', icon: 'none' }) } } borrowByStationNo(stationNo) { const requestId = `${Date.now()}_${Math.random().toString(36).slice(2)}` wx.showLoading({ title: '借伞中...' }) request('/api/order/borrow', 'POST', { stationNo, requestId }) .then((data) => { wx.showToast({ title: '借伞成功', icon: 'success' }) // 跳转到订单详情页 }) .finally(() => wx.hideLoading()) }

逻辑说明:requestId 在点击借伞时生成,同一页面重复点击会生成不同 requestId,后端靠唯一索引拒绝重复插入,而不是靠前端按钮禁用。这是双重保险。参数说明:onlyFromCamera 让用户直接调起摄像头而不是从相册选图,省一步操作;扫码失败时提示手动输入,输入框要加一个简单的格式校验,比如正则匹配^STATION-\d{3,}$。

4.3 附近伞桩列表:Haversine 距离排序不依赖地图服务

附近伞桩列表不需要引入额外地图 SDK。小程序端用 wx.getLocation 拿到当前经纬度,再请求后端拿到伞桩列表,前端计算距离并排序。这样实现最简单,也不容易踩地图组件的渲染性能坑。距离计算用 Haversine 公式,精度足够共享雨伞这种场景。

function getDistance(lat1, lng1, lat2, lng2) { const rad = (d) => (d * Math.PI) / 180 const R = 6371 // 地球半径,单位 km const dLat = rad(lat2 - lat1) const dLng = rad(lng2 - lng1) const a = Math.sin(dLat / 2) ** 2 + Math.cos(rad(lat1)) * Math.cos(rad(lat2)) * Math.sin(dLng / 2) ** 2 return 2 * R * Math.asin(Math.sqrt(a)) } function sortStations(stations, lat, lng) { return stations .map((item) => ({ ...item, distance: getDistance(lat, lng, item.lat, item.lng) })) .sort((a, b) => a.distance - b.distance) .slice(0, 10) }

逻辑说明:地图组件在列表项多时会出现卡顿,而且小程序里使用地图组件需要授权和隐私声明,纯列表展示反而更轻。参数说明:lat 和 lng 是数字类型,接口返回的 DECIMAL 字段转成 JavaScript 数字时注意别让字符串参与运算;排序后截断前 10 个伞桩,这是为了避免一次渲染太多节点导致首屏白屏。如果后续要画地图撒点,再单独做 Map 页面也不迟。

5. 避坑指南:共享雨伞系统最容易翻车的 5 个地方

5.1 现象:伞桩显示有空伞,扫码却提示借出失败

伞桩列表来自 Redis 缓存或接口实时查询,展示的是“有伞”;但用户扫码那一刻,后端查到伞已经在别人订单里。原因有两个:一是缓存没有及时失效,伞桩空位信息滞后;二是并发下两个请求同时读到同一把伞空闲,后执行的 UPDATE 影响行数为 0。解决办法是借伞接口绝对不能信任前端传过来的状态,必须以 UPDATE 条件判断的结果为准。展示层允许有偏差,但流程层必须让数据库做最终裁决。运营后台应提供手动刷新伞桩状态的按钮,不然维护人员巡检时看到的空位数据一样会误导用户。

5.2 现象:同一笔订单被提交两次,重复扣押金

用户借伞时点击两次“借伞”,或小程序在弱网环境下自动重试,后端收到两条一模一样业务含义的请求。如果接口先查订单再插入,两个请求同时通过查询后插入,就会生成两笔订单,押金扣两次。解决分两层:前端生成 requestId 并在每次点击时刷新;后端 t_order 表对 request_id 建唯一索引,插入前先 selectByRequestId,查到就直接返回原订单,查不到再走插入逻辑。就算两个请求同时走到插入,唯一索引也会让其中一个插入失败,再用 try-catch 捕获 DuplicateKeyException,转成“订单已存在”返回给用户。这个坑不做后端幂等,光靠前端禁按钮是堵不住的。

5.3 现象:小程序审核被拒,原因总是卡在类目或隐私协议

共享租赁类小程序涉及位置信息、相机权限,还涉及押金支付,审核容易被驳回。常见错误是类目选成“工具-效率”,但业务实际属于“生活服务-共享服务”,同时没有在小程序后台配置用户隐私保护指引。解决路径是:先在代码里把 wx.getLocation、wx.scanCode 的调用场景写明,在小程序管理后台的隐私保护指引里声明“用于查找附近伞桩和扫码借伞”;再在 app.json 里配置 requiredPrivateInfos 声明 getLocation。开发阶段用体验版多走几遍授权流程,不要等提审才发现隐私弹窗没生效。这类问题一旦被驳回,整个发布周期会耽误好几天,而且审核反馈里不会告诉你具体改哪里,只能自己一项项对照。

5.4 现象:微信支付回调丢失,订单状态一直停在“使用中”

用户还伞后,后端发起了退款,但微信支付回调没有及时到达,订单一直停在“归还中”,用户侧看到伞已归还、钱没退回来。原因有两类:回调接口没有做幂等和异常兜底,处理过程中抛异常导致返回失败,微信按策略重试几次后放弃;或者本地事务异常导致回调处理中断,却没有记录原始报文。解决方法是回调接口第一件事就是验签,验签通过后把报文原样存库,再做业务更新;同时定时任务扫描停留在“归还中”超过 15 分钟的订单,调用微信支付查询接口主动补齐状态。记住一个原则:状态不能只靠回调驱动,必须有一个反向主动查单的兜底任务。

5.5 现象:还伞后伞状态还是“借出”,伞桩数量对不上

还伞接口把伞状态更新、伞桩落伞、退款调用写在一个事务里,退款因余额不足或网络超时失败,整个事务回滚。最终用户伞已经插进桩位,数据库里订单却还是使用中。这是典型的把外部调用塞进本地事务导致的坑。解决办法在实现还伞接口时就该避开:本地事务只更新伞、伞桩、订单状态,事务提交后再异步发起退款,退款结果通过回调更新订单为已完成。如果退款失败,定时任务负责重试或告警。这样至少保证了“伞还了”这个事实先落库,资金问题可以后续补偿,不要因为退款问题让物理世界和数据库状态长期对不上。

6. 上线前的验证思路:并发测试、真机联调与灰度放量

6.1 上线前的最小验证清单

共享雨伞系统上线前,至少要把下面这张清单跑一遍,不要直接拿线上环境当测试环境。重点不是功能能不能点通,而是异常分支是否可控。

模块验证点通过标准
登录新用户自动注册、老用户直接登录同一 openid 不重复建账号
借伞并发 20 个请求借同一把伞只有一个成功,其余提示“已被借出”
借伞幂等相同 requestId 请求两次返回同一订单,不重复扣押金
还伞正常还伞、重复还伞第一次成功,第二次提示“已归还”
支付回调模拟回调失败、超时定时任务能主动补齐订单状态
取消订单30 分钟未支付订单伞和伞桩状态正确回滚

6.2 并发回归:接口幂等与库存扣减

借伞接口的并发回归可以用一段简单的 bash 脚本模拟,不用急着上 JMeter。脚本向借伞接口同时发 20 个请求,target 是同一把伞,最后看返回结果中成功数量是否为 1。

for i in $(seq 1 20); do curl -s -X POST http://localhost:8080/api/order/borrow \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d "{\"stationNo\":\"STATION-001\",\"requestId\":\"test_$i\"}" & done wait

这个脚本每个请求的 requestId 不同,所以能验证的是并发扣减而不是幂等。要验证幂等,就把 requestId 固定成同一个,再跑一遍,预期所有请求返回同一订单号。注意脚本里&和wait的用法,不加 wait 会导致脚本提前退出,看不到完整结果。

6.3 灰度放量的三个层级

小程序不像 App 那样可以自行灰度安装,但可以分三步走。第一步,在开发者工具里上传代码,设为体验版,让内部几个人真机扫码,重点测扫码借伞和还伞退款;第二步,把部分伞桩的二维码替换成新系统的体验版路径,线上用户扫到会先进小程序,再引导体验,这样即使出问题也只影响一小批设备;第三步,所有伞桩切换正式版,同时盯支付回调成功率和订单取消率。我做类似租赁项目学到最深的教训是:宁可让 5% 的用户多等两天,也不要让 50% 的用户遇到还伞后押金不退回。上线当天把日志级别调到 INFO,把微信支付回调的原始报文单独打一条日志,排查时能省一半时间。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询