尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

SpringBoot 定时任务统一处理微信提现、订单状态同步(无人饺子机后台实战)

SpringBoot 定时任务统一处理微信提现、订单状态同步(无人饺子机后台实战)
📅 发布时间:2026/6/24 8:27:11

文章标题

SpringBoot @Scheduled 5 秒轮询定时任务实战:微信企业付款提现异步对账 + 自助机订单状态自动同步(含 Redis 队列、分表、微信转账回调处理)

文章标签

#SpringBoot #定时任务 Scheduled #微信企业付款对账 #Redis 队列 #分表 #自助售卖机后端 #工控饺子机

文章目录

  1. 业务场景说明
  2. 核心定时任务完整源码
  3. 代码分层逻辑拆解 3.1 Redis 提现队列异步对账(微信企业付款查询、超时自动撤销) 3.2 年度分表订单状态同步(Redis 订单缓存落库)
  4. 代码存在的风险与隐患
  5. 生产环境优化改造方案
  6. 适用项目场景

一、业务场景说明

本代码是无人值守 AI 饺子机自助项目后端定时调度核心类,使用@Scheduled(fixedRate = 5000)每 5 秒执行一次,承载两大核心异步业务:

  1. 微信企业付款提现对账(Redis 缓冲队列)用户发起提现后存入 Rediswithdraw数组队列,定时轮询逐个调用微信转账查询接口:
    • 转账成功:更新年度分表tb_user_withdraw_yyyy提现记录状态,移除队列
    • 转账失败 / 已撤销:直接移出队列
    • 用户待确认超过 300 秒:自动调用撤销转账接口,冲减平台收益台账
  2. 自助机订单状态同步落库设备端取餐操作写入 RedisOrderStatus_订单ID临时缓存,定时读取批量更新年度分表tb_pay_form_yyyy订单核销状态、取餐数量,清理过期 Redis 缓存。
  3. 配套能力:自动创建年度分表、读取系统全局超时配置、JdbcTemplate 批量更新 SQL。

技术栈:SpringBoot + Spring Scheduled + RedisTemplate + JdbcTemplate + 微信企业付款 API + 年度分表设计。


二、完整核心定时任务源码

package com.jbossjf.bootproject.service; import com.jbossjf.bootproject.common.weixin.WXPayUtility; import com.jbossjf.bootproject.common.CommonHelp; import com.jbossjf.bootproject.common.JsonGenericUtil; import com.jbossjf.bootproject.model.*; import com.jbossjf.bootproject.service.weixin.CancelTransferService; import com.jbossjf.bootproject.service.weixin.GetTransferBillByOutNoService; import com.jbossjf.bootproject.service.weixin.QueryByOutTradeNoService; import org.json.JSONObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Map; @Component public class ScheduledTasks { @Autowired private JdbcTemplate jdbcTemplate; @Autowired private PayFormNaticeService payFormNaticeService; @Autowired private CancelTransferService cancelTransferService; @Autowired QueryByOutTradeNoService queryByOutTradeNoService; @Autowired GetTransferBillByOutNoService getTransferBillByOutNoService; @Autowired UserWithdrawNaticeService userWithdrawNaticeService; @Autowired private RedisTemplate redisTemplate; @Autowired private DeviceService deviceService; @Autowired private SystemParametersService systemParametersService; /** * 每5秒执行一次定时调度 * 1. 处理Redis提现队列,微信企业付款对账、超时撤销 * 2. 读取系统配置取餐超时时间 * 3. 年度分表自动建表,同步Redis订单取餐状态至数据库 */ @Scheduled(fixedRate = 1000 * 5) public void performTask() { try { // ====================== 第一块:处理Redis提现队列 withdraw ====================== if(redisTemplate.hasKey("withdraw")) { Map<String, String> map = null; String json = redisTemplate.opsForValue().get("withdraw").toString(); if (!json.trim().equals("[]")) { List<WithdrawalNumberModel> withdrawalNumberModelList = JsonGenericUtil.jsonToList(json, WithdrawalNumberModel.class); SimpleDateFormat formatter1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ENGLISH); // 倒序遍历,避免remove导致数组下标错乱 for (int i = withdrawalNumberModelList.size() - 1; i >= 0; i--) try{ Thread.sleep(10); map = (Map<String, String>) withdrawalNumberModelList.get(i); // 调用微信查询转账单接口 GetTransferBillByOutNoService.GetTransferBillByOutNoRequest request = new GetTransferBillByOutNoService.GetTransferBillByOutNoRequest(); request.outBillNo = map.get("out_trade_no"); GetTransferBillByOutNoService.TransferBillEntity response = getTransferBillByOutNoService.run(request); // 场景1:转账成功 SUCCESS if (response.state.name().equals("SUCCESS")) { SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy"); Date date = new Date(); String tableName = "tb_user_withdraw_" + yearFormat.format(date); // 不存在分表则自动创建 if (userWithdrawNaticeService.tableExists(tableName) == false) { userWithdrawNaticeService.createTable(tableName); } List<UserWithdraw> userWithdrawList = userWithdrawNaticeService.getUserWithdrawByIDTarget(tableName, response.outBillNo); // 更新提现记录状态与转账单号 String sql = "update " + tableName + " as tpl set tpl.transfer_bill_no = '" + response.transferBillNo + "' , tpl.status = '启用' where " + " tpl.id = '" + userWithdrawList.get(0).getId() + "';"; int u = jdbcTemplate.update(sql); if (u > 0) { // 处理完成移出队列,刷新Redis withdrawalNumberModelList.remove(i); String jsonStr = JsonGenericUtil.toJson(withdrawalNumberModelList); redisTemplate.opsForValue().set("withdraw", jsonStr); } } // 场景2:转账失败,直接移除队列 if (response.state.name().equals("FAIL")) { withdrawalNumberModelList.remove(i); String jsonStr = JsonGenericUtil.toJson(withdrawalNumberModelList); redisTemplate.opsForValue().set("withdraw", jsonStr); } // 场景3:转账已撤销,直接移除队列 if (response.state.name().equals("CANCELLED")) { withdrawalNumberModelList.remove(i); String jsonStr = JsonGenericUtil.toJson(withdrawalNumberModelList); redisTemplate.opsForValue().set("withdraw", jsonStr); } // 场景4:待用户确认超过300秒,自动撤销转账 if (response.state.name().equals("WAIT_USER_CONFIRM")) { Date temp_date = formatter1.parse(response.createTime); int def = CommonHelp.getDistanceDateTime(temp_date, new Date()); if (def >= (300)) { // 调用微信撤销转账接口 CancelTransferService.CancelTransferRequest request1 = new CancelTransferService.CancelTransferRequest(); request1.outBillNo = response.outBillNo; CancelTransferService.CancelTransferResponse response1 = cancelTransferService.run(request1); if (response1.state.equals("CANCELING")) { SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy"); Date date = new Date(); String tableName = "tb_user_withdraw_" + yearFormat.format(date); if (userWithdrawNaticeService.tableExists(tableName) == false) { userWithdrawNaticeService.createTable(tableName); } List<UserWithdraw> userWithdrawList = userWithdrawNaticeService.getUserWithdrawByIDTarget(tableName, response.outBillNo); // 撤销后冲减平台收益台账 String sql = " INSERT INTO tb_tota_userl_profit (id,name,user_id,in_amount,out_amount) " + " VALUES ('" + userWithdrawList.get(0).getUser_id() + "', '用户提现', '" + userWithdrawList.get(0).getUser_id() + "',0," + userWithdrawList.get(0).getAmount() + ") " + " ON DUPLICATE KEY UPDATE out_amount = out_amount - VALUES(out_amount); "; int u = jdbcTemplate.update(sql); if (u > 0) { withdrawalNumberModelList.remove(i); String jsonStr = JsonGenericUtil.toJson(withdrawalNumberModelList); redisTemplate.opsForValue().set("withdraw", jsonStr); } } } } }catch (Exception ex){ // 单条转账处理异常不阻断整体队列循环 ex.printStackTrace(); } } } // ====================== 第二块:读取系统配置,获取取餐超时阈值 ====================== List<SystemParameters> systemParametersList = systemParametersService.GetList(); int client_expired_time = 15000; for(int i = 0;i<systemParametersList.size();i++) { if(systemParametersList.get(i).getParam_name().equals("client_expired_time")) { client_expired_time = Integer.parseInt(systemParametersList.get(i).getParam_value()); } } // ====================== 第三块:订单年度分表自动创建 ====================== SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy"); Date date = new Date(); String tableName = "tb_pay_form_" + yearFormat.format(date); if (payFormNaticeService.tableExists(tableName) == false) { payFormNaticeService.createPayFormTable(tableName); } String tableItemName = "tb_pay_form_item_" + yearFormat.format(date); if (payFormNaticeService.tableExists(tableItemName) == false) { payFormNaticeService.createPayFormItemTable(tableItemName); } // ====================== 第四块:同步Redis订单取餐状态到数据库 ====================== List<PayForm> payFormList = payFormNaticeService.getPayFormUncollectedMealStatusDataTarget(tableName,tableItemName,client_expired_time); StringBuilder sqlSb = new StringBuilder(); String status = ""; int pick_up_quantity = 0; for(int i =0;i<payFormList.size();i++) { String redisKey = "OrderStatus" + payFormList.get(i).getId(); if(redisTemplate.hasKey(redisKey)) { try { String raw = redisTemplate.opsForValue().get(redisKey).toString(); JSONObject rootJson = new JSONObject(raw); pick_up_quantity = Integer.parseInt(rootJson.get("pick_up_quantity").toString()); String orderStatus = rootJson.get("status").toString(); String orderId = rootJson.get("id").toString(); if ("1".equals(orderStatus)) { status = "取餐"; sqlSb.append("UPDATE ").append(tableName) .append(" SET write_off_status = '").append(status).append("' ") .append(" WHERE id = '").append(orderId).append("';"); } else if ("0".equals(orderStatus)) { status = "未取餐"; sqlSb.append("UPDATE ").append(tableName) .append(" SET write_off_status = '").append(status).append("',pick_up_food_quantity = pick_up_food_quantity+ ").append(pick_up_quantity) .append(" WHERE id = '").append(orderId).append("';"); } // 更新完成删除Redis临时缓存 redisTemplate.delete(redisKey); }catch (Exception ex) { System.out.println("订单同步异常:" + ex.getMessage()); } } } // 批量执行所有更新SQL String batchSql = sqlSb.toString(); if(!batchSql.equals("")) { jdbcTemplate.update(batchSql); } }catch (Exception ex) { // 顶层捕获所有异常,防止定时任务直接终止 ex.printStackTrace(); } } }

三、代码分层逻辑拆解

3.1 Redis 提现队列(withdraw)处理逻辑

  1. 存储结构:Redis String 存储 JSON 数组,保存待对账提现单据
  2. 遍历优化:倒序for (i = size -1; i >=0),避免list.remove(i)导致下标错位、漏处理数据
  3. 四种微信转账状态分支

    表格

    转账状态业务处理逻辑
    SUCCESS 转账成功更新年度提现分表状态,移出 Redis 队列
    FAIL 转账失败直接移除队列,放弃重试
    CANCELLED 已撤销直接移除队列
    WAIT_USER_CONFIRM 待用户确认超过 300 秒自动调用撤销转账接口,冲减平台收益台账
  4. 分表兼容:按年份动态拼接tb_user_withdraw_yyyy,不存在则自动建表
  5. 异常隔离:单条提现单据捕获 Exception,单条失败不阻塞整条队列循环

3.2 自助机订单状态同步逻辑

  1. 缓存设计:设备安卓端取餐操作写入临时 RedisOrderStatus_订单ID,定时任务统一落库,减少频繁直接操作数据库
  2. 年度分表:订单主表tb_pay_form_yyyy、订单明细表tb_pay_form_item_yyyy,启动时自动检查表,不存在自动创建
  3. 批量 SQL 优化:循环拼接 UPDATE 语句,单次jdbcTemplate.update()批量执行,减少数据库 IO
  4. 缓存清理:同步完成后立即删除 Redis 订单状态 key,避免重复更新
  5. 动态配置:从system_parameters读取全局client_expired_time取餐超时阈值,无需硬编码

3.3 定时任务基础配置说明

java

运行

@Scheduled(fixedRate = 1000 * 5)
  • fixedRate:固定 5 秒间隔执行,从上一次任务开始计时,如果任务执行耗时超过 5 秒会出现任务重叠并发执行;
  • 区别于fixedDelay:从上一次任务结束后再等待 5 秒,无并发风险。

四、现有代码存在的风险与隐患

4.1 定时任务并发重叠风险

使用fixedRate=5000,如果微信接口、数据库查询阻塞超过 5 秒,会同时启动多个定时线程:

  • 重复操作 Redis 提现队列,同一单据多次调用微信转账查询接口
  • 重复更新订单数据库,引发数据脏写、重复扣减收益

4.2 Redis 无分布式锁,集群环境数据错乱

多实例部署项目时,多台服务器同时读取withdraw队列,重复处理同一条提现记录,造成重复更新数据库、重复调用微信撤销接口。

4.3 SQL 拼接字符串,存在 SQL 注入漏洞

直接拼接userWithdrawList.get(0).getId()、订单 ID 等参数到 SQL 中,恶意字符可实现注入攻击。

4.4 Thread.sleep 阻塞定时线程

循环内手动Thread.sleep(10),拉长单次任务执行时长,加剧任务重叠问题,无业务意义。

4.5 异常捕获粒度不合理

  1. 微信 API 异常仅打印堆栈,无日志入库、无告警通知,线上故障无法及时发现;
  2. 顶层大 try-catch 吞掉全部异常,无法定位任务完全不执行的问题。

4.6 Redis 序列化 / 并发修改风险

直接读取 JSON 字符串转 List,循环中修改 List 后全量覆盖 Redis value;高并发下会出现数据丢失(多线程同时读取数组,后写入覆盖先写入的处理结果)。

4.7 硬编码魔法值过多

300 秒超时、5 秒定时、表前缀、状态文本启用/取餐/未取餐全部硬编码,后期维护修改成本极高。

4.8 批量 SQL 无事务

批量 UPDATE 订单时,部分 SQL 执行成功、部分失败,会出现订单状态不一致,无事务回滚机制。


五、生产环境优化改造方案

5.1 替换 fixedRate 为 fixedDelay,杜绝任务并发

java

运行

// 任务执行完成后,等待5秒再执行下一次,不会并发重叠 @Scheduled(fixedDelay = 5000)

5.2 增加分布式锁(Redis Lock),支持多实例部署

使用 Redisson 锁,定时任务执行前抢占锁,未抢到直接退出,避免多实例重复处理:

java

运行

RLock lock = redissonClient.getLock("scheduled_task_lock"); try { boolean acquire = lock.tryLock(0, 30, TimeUnit.SECONDS); if (!acquire) return; // 原有业务逻辑 } finally { if(lock.isHeldByCurrentThread()) lock.unlock(); }

5.3 预编译 SQL,杜绝 SQL 注入

使用JdbcTemplate.update(String sql, Object[] args)传参,禁止字符串拼接 ID、金额、状态等变量。

5.4 移除无用 Thread.sleep,增加日志分级打印

删除循环内Thread.sleep(10),替换 SLF4J 日志,区分 info/warn/error,异常打印完整堆栈并接入告警(邮件 / 钉钉)。

5.5 Redis 队列优化:改用 List LPOP/RPOP 原子出队

当前方案是全量读取数组、内存修改后覆盖写入,并发极易丢数据;优化为 Redis List 队列,每条提现记录单独一条数据,原子弹出,避免覆盖:

java

运行

// 弹出队尾一条单据,原子操作,多实例安全 String itemJson = redisTemplate.opsForList().rightPop("withdraw_queue");

5.6 批量更新增加事务控制

批量更新订单、提现记录时,开启事务,任意一条更新失败全部回滚,保证数据一致性。

5.7 抽取常量类统一管理魔法值

新建ScheduleConstant.java,统一管理定时周期、超时秒数、Redis Key 前缀、数据库表前缀、状态码文本。

5.8 拆分超大定时任务

当前一个方法承载提现对账 + 订单同步两大重业务,建议拆分为两个独立@Scheduled方法,职责单一,故障互不影响。


六、适用项目场景

  1. 校园 / 社区无人 AI 饺子机、自助售货机、自助取餐柜后端服务
  2. 微信企业付款(商家转账到零钱)批量对账、异步提现系统
  3. 分表架构下定时同步缓存数据落库的后台调度服务
  4. 工控 Android 设备配套后端,设备端状态缓存统一持久化场景
  5. 中小型支付、提现类后台定时对账系统

文末结语

这套定时任务是自助餐饮设备后端真实落地代码,兼顾了 Redis 异步缓冲、微信支付对账、年度分表三大核心需求,但原生代码存在并发、安全、稳定性隐患。上线生产环境建议按照第五部分优化方案改造,增加分布式锁、预编译 SQL、事务、日志告警,保证多实例集群部署下的数据一致性与服务稳定性。

相关新闻

  • 1.5 容器相关面试题
  • 吐血整理:开发者为什么都在用应用托管?看完这篇你就懂了
  • 服务网格:Istio 是什么?有什么用?

最新新闻

  • 3个理由告诉你为什么HTML转Figma工具正在改变设计工作流
  • 终极音乐格式转换工具:5分钟解锁所有加密音频文件
  • macOS完整安装包下载终极指南:告别复杂命令行的简单解决方案
  • 从实习生到AI架构师只需2.8年?AISMM加速路径全曝光:含7个关键里程碑、5次能力跃迁触发点及官方验证时间戳
  • 高校域名邮箱失效?用这4种替代方案成功激活AI学生权益(教育部备案院校专属通道)
  • 免费招聘神器实测|HR实测!零成本高效招人神器

日新闻

  • 终极指南:如何用shadPS4在电脑上免费畅玩PS4游戏
  • 打造个性化Instagram Clone:主题定制与用户体验优化技巧
  • 未来展望:RoseTTAFold-All-Atom的发展路线图与社区支持资源汇总

周新闻

  • Visual C++运行库修复终极指南:5分钟快速解决Windows软件启动错误
  • 手把手教你构建统计局地区经济数据爬虫:从环境搭建到数据持久化全指南
  • 2026多Agent深度解析:用AI团队替代单一模型,四种架构实战落地

月新闻

  • 【总结】入门篇:50句话让你记住架构核心概念
  • WeChatMsg技术方案解析:实现Mac微信数据自主管理的完整解决方案
  • WeChatMsg:革新性微信数据备份方案,打造你的专属数字记忆库

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号