货柜项目总结
一、参与设计
介绍:我之前做的是一个智能货柜的项目,部署在在商场、小区等场所。该货柜使用的流程是,用户通过扫码确认免密支付后,货柜打开,用户拿完东西后关闭货柜,自动扣减钱
二、核心流程梳理
2. 1 具体购买流程

用户扫码,后端会对参数进行校验,判断用户是不是在黑名单中;校验成功后,通过加分布式锁将订单落库,再请求到支付宝/微信做一个预约下单,授权预约下单后会给前端发送一个签约的package包,用户同意后,我们会收到支付宝/微信的回调,之后会调用设备服务开门,在开门之前会记录所有货仓的重量,用户卖完东西后,再次记录所有货仓的重量,用这两个做差值得出用户买了什么商品;通过MQ发送消息调用账户服务结算,释放掉分布式锁,在账户服务中会涉及到优惠券、积分、余额的处理,同时这个消息会发送给一个触发补货的服务,这个服务会去查看设备是否需要补货,如果需要,通知调度人员去补货
更具体地流程
数据库表

2.2 设备上线

设备服务收到设备登陆服务器的请求,会查看是否有该设备:
- 如果有该设备:修改设备表的设备状态为在线,更新设备缓存状态,更新设备服务中设备通信信息,更新通信服务中设备、Channel信息
- 如果没有该设备:添加该设备,修改设备表的设备状态为初始化,添加设备缓存
2.3 心跳/下线流程
最开始的设计是
- 每个设备每30s向Redis存入一条消息,key:sn码,value:null,过期时间设置为60s
- 如果key过期了,就判断设备下线
但这存在一个问题:设备可能因为网络波动频繁下线
于是我们又引入了心跳保护机制,为了避免因为网络波动问题,导致的大面积设备下线,造成误判
- 设备每 30s 发送一次心跳,设备服务器收到设备发来的心跳,对缓存中的心跳计数器+1,存入心跳表,然后更新缓存中设备最后一次收到心跳的时间
- 开启一个定时任务每一分钟去扫描设备服务最后一次心跳时间,如果最后一次心跳时间距离现在相差一分钟,就会判断是否满足心跳保护机制,决定设备是否下线
- 心跳保护机制:我们的设备有2500台左右,一分钟正常能收到5000次心跳,我们设置了一个阈值80%,如果一分钟收到的心跳总数小于5000 * 80% = 4000 次,就会认为是服务器故障,不会让设备下线,通知运维人员检查
- 如果不满足心跳保护机制,将该服务下线,更新数据库设备的状态,更新缓存状态
2. 4 重量

- 仓门没有打开之前是每60s上传一次重量信息,在仓门打开5s之后,每300ms上传一次重量
- 发送的数据
{大key:重量,小key:sn码 ,值:时间} - 服务器接收到数据后,更新缓存,发送MQ,目的是进行削峰,通过MQ再落库
- 我们还有一个服务式定期删除这些数据,只会保留七天的重量信息
2.5 设备补货、调度流程

- 每一次设备服务向MQ发送消息结单,对商品的库存扣减的同时,会触发货道是否需要补货
- 如果达到补货的阈值,会通过飞书通知补货人员前去补货
- 补货人员先检查设备状态,再通过h5页面扫描二维码调用补货的接口,加分布式锁,创建补货订单并落库,设备服务调用开门,缓存开关门的重量来判断商品数量,增加库存,更新补货订单状态
2.6 对账流程

外部对账:
每天第三方支付公司 支付宝 / 微信 都会给我们一个账单,叫做结算账单。结算账单会告诉我们昨天结了多少钱,什么时候到账,到账到哪里,那个支付公司都会给我们。同时支付公司还会给我们一个交易账单,需要我们每一笔都去对,主要是对交易订单和系统订单的订单状态,看支付公司和我们数据库记录的状态是否一致;对订单金额;对他收取的手续费是否一致
内部对账:
每天晚上0点看一次重量,算出库存量,加上白天的补货计算出【库存的减少量】与【订单的商品数】是否一致,如果不对,会调取监控、查看一些重量变更记录
2.7 生成报表流程
报表就是账单
- 通过
线程池 + CountDownLatch,生成不同维度的报表,每个线程处理一个维度,等每一个线程都处理完,再进行汇总,处理接下来的任务
不同的维度
- 以商品数据
- 以地区统计数据
- 不同时间的数据
1 | ExecutorService pool = Executors.newFixedThreadPool(3); |
2.8 营销体系

- 优惠券分类
- 优惠券大概分为三种,满减券、现金券、团购券;满减券就是达到多少金额就可以减一部分钱;现金券就是无门槛卷;团购券是购买指定商品不需要钱
- 优惠券的获取方式
- 我们可以通过活动,秒杀、新人用户、拉新人的方式获取优惠券
- 优惠券的使用规则
- 优惠券的使用规则可以简单配置,比如团购券可以一次性多使用几张,而现金券、满减券的配置规则只能一次使用一张
2.9 优惠券秒杀

优惠券的秒杀功能主要使用Redis减少数据库的压力,mq异步消峰处理落库
在秒杀之前,向Redis中加一个set结构的数据做缓存预热,key就是秒杀券id,value就是库存量
前端会采用滑动验证防止用户连续点击,后端采用Redis的decrement命令扣减Redis的库存,返回值如果 $\ge 0$表示抢购成功,同时往Redis的Set集合中加一条用户抢到的记录,限制一人一单,发送mq进行后续业务操作。
2.10 充值流程

2.11 退款流程

一般退款都是因为商品质量问题,或者说拿了别人放错货架的商品,导致扣款扣多了,系统仅支持七天内退款(数据库只会存七天内的重量信息),退款需要客户联系客服,我们后台进行退款
- 解决超退
- 一个订单多次退款会有限制:where 条件 已退金额 + 本次退金额 $<$ 支付金额
- 退优惠券
- 我们采用的是先退优惠券再退钱
- 退积分
- 我们会按照比例扣除积分,如果积分已经被用户使用了,那就只能算给用户的补偿,因为大多数退款都是因为商品的问题
2.12 扫码登陆流程
小程序如何获取用户唯一标识(openid)?
前端组装appid调用微信接口得到jscode,把jscode给后端,后端用jscode请求wx可以得到用户的唯一标识(openid)
支付宝文档

微信文档

2.13 区域价格设置
分散剂来存储商品价格
- 货柜价格表(一级):货柜sn码——货道id——商品id——价格
- 区域价格表(二级):区域——商品id——价格
- 商品价格表(三级):商品——价格
查询价格时,先从一级价格表查询,如果没有该商品的价格的话再去二级商品区间表查询,如果还没有的话,就去查询三级价格表(三级价格表式一定有的)
2.14 MQTT协议

设备服务调用通信服务给设备发指令:

设备给通信服务发送报文:

2.15 设备发送命令不稳定处理

2.16 xxl-job兜底
用户扫码登录确认授权的同时会发送延时消息,一分钟后检查该订单是否授权,还没授权就视为取消订单, mq 延迟消息发失败了怎么办
- xxl-job 定时任务兜底,看哪些订单长时间未授权,取消掉。
三、数据库
设备表
1 | CREATE TABLE `device_info` ( |
仓门表
1 |




