186-6908-0178
当前位置:首页 >程序开发
SpringBoot + 微信支付实现“扫码开门,取货自动扣款”售货柜
2026-07-29 23次浏览

大家好,我是小悟。


一、需求描述

1.1 业务场景

用户通过手机扫描售货柜上的二维码 → 选择商品 → 完成支付 → 柜门自动打开 → 用户取货 → 柜门关闭 → 系统自动扣款/确认完成


1.2 核心功能需求

模块 功能点

用户端 微信/支付宝扫码登录、商品浏览、下单支付、订单查询

货柜端 柜门状态监控、商品重量感应、温度监控、缺货预警

管理后台 商品管理、订单管理、补货管理、营收统计

支付接口 微信支付/支付宝支付集成、退款处理

1.3 技术架构

后端:Spring Boot 2.7 + MyBatis-Plus + MySQL + Redis


硬件模拟:用定时任务模拟柜门、重量传感器


支付:调用微信支付V3 API(沙箱环境)


API文档:Swagger/Knife4j


二、详细步骤

步骤1:项目初始化

创建Spring Boot项目,依赖如下:


xml代码解读复制代码<dependencies>     <dependency>       

 <groupId>org.springframework.boot</groupId>         

 <artifactId>spring-boot-starter-web</artifactId>     </dependency>     <dependency>       

   <groupId>com.baomidou</groupId>         <artifactId>mybatis-plus-boot-starter</artifactId> 

           <version>3.5.3</version>     </dependency>     <dependency>        

            <groupId>mysql</groupId>         <artifactId>mysql-connector-java</artifactId> 

                    <version>8.0.33</version>     </dependency>     <dependency>   

                          <groupId>org.springframework.boot</groupId>        

                          <artifactId>spring-boot-starter-data-redis</artifactId>  

                             </dependency>     <dependency>         

                             <groupId>com.github.wechatpay-apiv3</groupId>      

                                <artifactId>wechatpay-java</artifactId>      

                                   <version>0.2.11</version>    

                                    </dependency>     <dependency>     

                                        <groupId>com.alipay.sdk</groupId>  

                                               

                                               

                                               <artifactId>alipay-sdk-java

                                               </artifactId>        

                                               

                                                <version>4.38.157.ALL

                                                </version>     

                                                </dependency>     

                                                <dependency>         

                                                <groupId>cn.hutool

                                                </groupId>         

<artifactId>hutool-all</artifactId>         <version>5.8.20</version>     </dependency>

</dependencies> 

 

 

 


步骤2:数据库设计

sql代码解读复制代码-- 商品表 CREATE TABLE product (     id BIGINT PRIMARY KEY AUTO_INCREMENT,    

 name VARCHAR(100) NOT NULL COMMENT '商品名称',     price INT NOT NULL COMMENT '价格(分)',    

  stock INT NOT NULL COMMENT '库存',     image_url VARCHAR(500) COMMENT '商品图片',    

   cabinet_no VARCHAR(20) COMMENT '货道编号',     

   create_time DATETIME DEFAULT CURRENT_TIMESTAMP );  -- 订单表 CREATE TABLE orders

    (     id BIGINT PRIMARY KEY AUTO_INCREMENT,     order_no VARCHAR(32) NOT NULL UNIQUE COMMENT '

    订单号',     user_id VARCHAR(50) NOT NULL COMMENT '用户ID(openId)',    

     product_id BIGINT NOT NULL,     product_name VARCHAR(100),     

     amount INT NOT NULL COMMENT '金额(分)',    

      status TINYINT DEFAULT 0 COMMENT '0待支付 1已支付 2已完成 3已关闭 4退款中',    

       pay_type VARCHAR(10) COMMENT 'WECHAT/ALIPAY',     prepay_id VARCHAR(100) 

       COMMENT '微信预支付ID',     cabinet_status TINYINT COMMENT '柜门状态 0关闭 1开启',  

          create_time DATETIME DEFAULT CURRENT_TIMESTAMP,     pay_time DATETIME,    

           finish_time DATETIME );  -- 货柜状态日志表 CREATE TABLE cabinet_log 

           (     id BIGINT PRIMARY KEY AUTO_INCREMENT,     order_no VARCHAR(32),   

             door_status TINYINT COMMENT '0关闭 1开启',     

             weight_before INT COMMENT '取货前重量(g)',     

             weight_after INT COMMENT '取货后重量(g)',    

              create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); 

              

              

              


步骤3:核心代码实现

3.1 实体类

vbnet代码解读复制代码@Data @TableName("product") public class Product

 {     @TableId(type = IdType.AUTO)     private Long id;     

 private String name;     private Integer price;     private Integer stock;     

 private String imageUrl;     private String cabinetNo;     private LocalDateTime createTime; } 

  @Data @TableName("orders") public class Order {     @TableId(type = IdType.AUTO)     

  private Long id;     private String orderNo;     private String userId;     

  private Long productId;     private String productName;     private Integer amount;    

   private Integer status;     private String payType;     private String prepayId;     

   private Integer cabinetStatus;     private LocalDateTime createTime;     

   private LocalDateTime payTime;     private LocalDateTime finishTime; } 

   

   


3.2 统一响应封装

typescript代码解读复制代码@Data @AllArgsConstructor public class Result<T> 

{     private Integer code;     private String msg;     private T data;       

public static <T> Result<T> success(T data) {         return new Result<>(200, "success", data); 

    }     public static <T> Result<T> error(String msg) {         return new Result<>

    (500, msg, null);     } } 

    


3.3 商品接口

less代码解读复制代码@RestController @RequestMapping("/api/product") public class ProductController 

{       @Autowired     private IProductService productService;       @GetMapping("/list")    

 public Result<List<Product>> list() {         return Result.success(productService.list());   

   }       @GetMapping("/{id}")     public Result<Product> getById(@PathVariable Long id) 

   {         Product product = productService.getById(id);         return product != 

   null ? Result.success(product) : Result.error("商品不存在");     } } 

   

   


3.4 订单服务(核心业务)

scss代码解读复制代码@Service public class OrderService {       @Autowired     private OrderMapper 

orderMapper;     @Autowired     private ProductMapper productMapper;     @Autowired     private 

RedisTemplate<String, String> redisTemplate;       // 创建订单并生成支付二维码     @Transactional 

    public Result<Map<String, String>> createOrder(Long productId, String userId, String payType) 

    {         Product product = productMapper.selectById(productId);         if (product == 

    null || product.getStock() <= 0) {             return Result.error("商品库存不足");     

        }           String orderNo = "SH" + System.currentTimeMillis() + RandomUtil.

        randomNumbers(4);           Order order = new Order();         

        order.setOrderNo(orderNo);         order.setUserId(userId);         

        order.setProductId(productId);         order.setProductName(product.getName());     

            order.setAmount(product.getPrice());         order.setStatus(0); // 待支付  

                   order.setPayType(payType);         order.setCabinetStatus(0);  

                          order.setCreateTime(LocalDateTime.now());         

                          orderMapper.insert(order);           

                          // 调用支付接口获取二维码       

                            String qrCode = generatePayQRCode

                            (orderNo, product.getPrice(), payType);           

                            Map<String, String> result = new HashMap<>();        

                             result.put("orderNo", orderNo);         

                             result.put("qrCode", qrCode);           

                             // 订单存入Redis,15分钟过期         

                             redisTemplate.opsForValue().set

                             ("order:" + orderNo, orderNo, 15, TimeUnit.MINUTES); 

                                       return Result.success(result);     }

                                              // 支付成功回调     

                                              @Transactional     

                                              public void handlePaySuccess

                                              (String orderNo, String 

                                              transactionId) {        

                                               Order order = 

                                               orderMapper.selectByOrderNo

                                               (orderNo);         if 

                                               (order == null || 

                                               order.getStatus() != 0) 

                                               {             return; 

                                                       }           

// 更新订单状态  

                                                             

order.setStatus(1); // 已支付         order.setPayTime(LocalDateTime.now());         orderMapper.updateById(order);           // 减库存         productMapper.decreaseStock(order.getProductId());           // 发送MQ消息通知货柜开门(这里简化,直接调用)         openCabinetDoor(orderNo);     }       // 模拟开门(实际需要通过物联网协议控制硬件)     private void openCabinetDoor(String orderNo) {         // 记录开门日志         CabinetLog log = new CabinetLog();         log.setOrderNo(orderNo);         log.setDoorStatus(1);         cabinetLogMapper.insert(log);           // 更新订单柜门状态         orderMapper.updateCabinetStatus(orderNo, 1);           // 启动关门检测任务(30秒后检测)         scheduleDoorCloseCheck(orderNo);     }       // 关门检测     private void scheduleDoorCloseCheck(String orderNo) {         ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();         executor.schedule(() -> {             // 模拟重量传感器获取取货前后重量             int beforeWeight = getWeightBefore();             // 等待用户取货             try { Thread.sleep(5000); } catch (InterruptedException e) {}             int afterWeight = getWeightAfter();               int takenWeight = beforeWeight - afterWeight;             // 根据重量差判断取货是否成功             if (takenWeight > 0) {                 Order order = orderMapper.selectByOrderNo(orderNo);                 order.setStatus(2); // 已完成                 order.setFinishTime(LocalDateTime.now());                 orderMapper.updateById(order);                   // 记录重量日志                 CabinetLog log = new CabinetLog();                 log.setOrderNo(orderNo);                 log.setDoorStatus(0);                 log.setWeightBefore(beforeWeight);                 log.setWeightAfter(afterWeight);                 cabinetLogMapper.insert(log);             }         }, 30, TimeUnit.SECONDS);     }       private int getWeightBefore() { return 5000; } // 模拟重量     private int getWeightAfter() { return 4800; }       // 支付二维码生成(伪代码,实际需集成微信/支付宝SDK)     private String generatePayQRCode(String orderNo, int amount, String payType) {         // 实际调用微信支付native下单接口返回code_url         return "https://pay.example.com/qr/" + orderNo;     } }  


3.5 微信支付集成(Native支付)

typescript代码解读复制代码@Service public class WechatPayService {       @Value("${wechat.appid}")     private String appid;     @Value("${wechat.mchid}")     private String mchid;     @Value("${wechat.api-v3-key}")     private String apiV3Key;     @Value("${wechat.private-key-path}")     private String privateKeyPath;       public String nativePay(String orderNo, int amount, String description) throws Exception {         // 构建请求参数         Map<String, Object> params = new HashMap<>();         params.put("appid", appid);         params.put("mchid", mchid);         params.put("description", description);         params.put("out_trade_no", orderNo);         params.put("notify_url", "https://yourdomain.com/api/pay/wechat/notify");           Map<String, Object> amountMap = new HashMap<>();         amountMap.put("total", amount);         amountMap.put("currency", "CNY");         params.put("amount", amountMap);           // 发送请求到微信支付API         String response = HttpUtil.createPost("https://api.mch.weixin.qq.com/v3/pay/transactions/native")                 .header("Authorization", getAuthorizationHeader())                 .body(JSONUtil.toJsonStr(params))                 .execute()                 .body();           JSONObject json = JSONUtil.parseObj(response);         return json.getStr("code_url"); // 返回二维码链接     } } 


3.6 支付回调处理

less代码解读复制代码@RestController @RequestMapping("/api/pay") public class PayCallbackController {       @Autowired     private OrderService orderService;       @PostMapping("/wechat/notify")     public String wechatNotify(@RequestBody String body, @RequestHeader("Wechatpay-Signature") String signature) {         // 验签和解析(省略详细验签逻辑)         JSONObject result = JSONUtil.parseObj(body);         String orderNo = result.getByPath("resource.ciphertext.out_trade_no", String.class);         String transactionId = result.getByPath("resource.ciphertext.transaction_id", String.class);           orderService.handlePaySuccess(orderNo, transactionId);           // 返回成功响应给微信         return "{\"code\":\"SUCCESS\",\"message\":\"成功\"}";     } } 


3.7 用户扫码登录(模拟微信授权)

less代码解读复制代码@RestController @RequestMapping("/api/user") public class UserController {       @GetMapping("/wxlogin")     public Result<String> wxLogin(@RequestParam String code) {         // 实际需调用微信登录接口获取openId         String openId = "mock_openid_" + code;         String token = UUID.randomUUID().toString();         // 存入Redis,有效期2小时         redisTemplate.opsForValue().set("user:token:" + token, openId, 2, TimeUnit.HOURS);         return Result.success(token);     } } 


步骤4:配置文件

yaml代码解读复制代码server:   port: 8080  spring:   datasource:     url: jdbc:mysql://localhost:3306/vending_machine?useSSL=false&serverTimezone=Asia/Shanghai     username: root     password: 123456   redis:     host: localhost     port: 6379  wechat:   appid: wx1234567890   mchid: 1230000109   api-v3-key: your-api-v3-key   private-key-path: /cert/apiclient_key.pem  logging:   level:     com.example: DEBUG 


三、完整业务流程时序图

lua代码解读复制代码用户 → 扫描二维码 → 前端 → 后端 → 数据库 → 支付平台 → 货柜硬件   |       |          |       |         |          |   |   扫码获取token   |       |         |          |   |----------------->|       |         |          |   |   携带token请求商品列表   |         |          |   |----------------->|       |         |          |   |   选择商品下单    |       |         |          |   |----------------->|       |         |          |   |                创建订单  |         |          |   |                调用支付接口        |          |   |                返回二维码          |          |   |<----------------|       |         |          |   |   用户扫码支付    |       |         |          |   |---------------------------------->|          |   |                支付回调           |          |   |                更新订单状态       |          |   |                发送开门指令       |          |   |                                  |----> 柜门打开   |   用户取货       |                |          |   |   关门检测       |                |          |   |                重量对比验证        |          |   |                订单完成           |          | 


四、总结

4.1 核心难点与解决方案

难点 解决方案

支付安全性 使用微信V3签名、回调验签、幂等处理

柜门状态一致性 Redis分布式锁 + 数据库乐观锁 + 关门超时检测

防作弊机制 重量传感器 + 震动传感器 + 视频监控接口

高并发下单 使用Redis预扣库存 + 异步扣减DB + MQ削峰

4.2 可扩展性设计

多支付渠道:策略模式封装WechatPayService、AliPayService


多硬件适配:定义CabinetDriver接口,不同厂商实现


补货管理:增加补货任务,通过WebSocket推送缺货预警


4.3 运行测试

bash代码解读复制代码# 启动MySQL、Redis # 运行SpringBoot主类 mvn spring-boot:run  # 测试接口 curl http://localhost:8080/api/product/list 


4.4 生产环境注意事项

✅ 必须使用HTTPS + 域名备案


✅ 支付回调需要幂等处理(防止重复通知)


✅ 柜门传感器需要心跳检测(断网重连机制)


✅ 订单超时自动取消(定时任务扫描未支付订单)


✅ 接入监控系统(Prometheus + Grafana)


4.5 总结

智能售货柜的核心闭环:扫码 → 选品 → 支付 → 开门 → 取货 → 关门确认。


通过SpringBoot高效开发,结合Redis保证高并发下的订单一致性,支付模块支持扩展多种渠道。实际落地需配合硬件物联网协议(MQTT/CoAP),并将重量传感器、门磁开关的数据通过边缘网关上报云端。


作者:悟空码字

链接:https://juejin.cn/post/7628224961766850600

来源:稀土掘金

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


回到顶部