记录充值订单、支付结果处理和 XXL-JOB 的部署与接入,并复盘订单模块涉及的异步处理。
本篇要点
- 梳理充值订单与支付记录
- 学习 XXL-JOB 调度中心和执行器
- 复盘订单异步处理
**学习提示:**课程中的支付、延迟方案与调度配置应按实际代码和环境验证;不要把示例配置视为生产部署结果。
内容回顾
1、设计模式优化
你熟悉设计模式?
你项目中是否使用过设计模式?
你项目做过哪些代码级别优化?
– 使用策略模式优化添加购买记录
– 使用aop + 注解方式实现专辑缓存
– 第一步 创建接口,定义添加购买记录方法
– 第二步 根据不同购买类型比较多个实现类,创建vip、声音和专辑实现类,分别实现方法
– 第三步 创建环境类,根据不同购买类型返回不同实现类对象
2、微信支付
项目中,对于微信支付如何判断成功或者失败?
1 发起微信支付请求之后,前端每隔5s查询一次查询支付状态接口看微信是否成功,如果支付中状态等待,如果支付成功更新状态
2 等待微信支付成功回调判断是否支付成功
3 如果微信支付失败,微信端抛出异常,捕获微信返回异常返回给用户
今天内容
1、充值
需求

实现
提交充值订单数据接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| @Tag(name = "充值管理") @RestController @RequestMapping("api/account/rechargeInfo") @SuppressWarnings({"all"}) public class RechargeInfoApiController {
@Autowired private RechargeInfoService rechargeInfoService;
@GuiguLogin @Operation(summary = "充值") @PostMapping("submitRecharge") public Result submitRecharge(@RequestBody RechargeInfoVo rechargeInfoVo){ Long userId = AuthContextHolder.getUserId(); String orderNo = this.rechargeInfoService.submitRecharge(rechargeInfoVo,userId); HashMap<String, Object> map = new HashMap<>(); map.put("orderNo",orderNo); return Result.ok(map); }
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| @Service @SuppressWarnings({"all"}) public class RechargeInfoServiceImpl extends ServiceImpl<RechargeInfoMapper, RechargeInfo> implements RechargeInfoService {
@Autowired private RechargeInfoMapper rechargeInfoMapper;
@Override public String submitRecharge(RechargeInfoVo rechargeInfoVo, Long userId) { RechargeInfo rechargeInfo = new RechargeInfo(); rechargeInfo.setUserId(userId); rechargeInfo.setRechargeStatus(SystemConstant.ORDER_STATUS_UNPAID); rechargeInfo.setRechargeAmount(rechargeInfoVo.getAmount()); rechargeInfo.setPayWay(rechargeInfoVo.getPayWay()); rechargeInfo.setOrderNo(UUID.randomUUID().toString().replaceAll("-","")); rechargeInfoMapper.insert(rechargeInfo); return rechargeInfo.getOrderNo(); } }
|
保存充值支付记录
1 2 3 4 5 6 7 8 9 10 11
| else { Result<RechargeInfo> rechargeInfoResult = rechargeInfoFeignClient.getRechargeInfo(orderNo); Assert.notNull(rechargeInfoResult,"返回充值对象不能不为空"); RechargeInfo rechargeInfo = rechargeInfoResult.getData(); Assert.notNull(rechargeInfo,"返回充值对象不能不为空"); paymentInfo.setUserId(rechargeInfo.getUserId()); paymentInfo.setContent("充值"); paymentInfo.setAmount(rechargeInfo.getRechargeAmount()); }
|
支付成功更新
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| @Override public void rechargePaySuccess(String orderNo) { RechargeInfo rechargeInfo = this.getRechargeInfoByOrderNo(orderNo); if (SystemConstant.ORDER_STATUS_PAID .equals(rechargeInfo.getRechargeStatus())) return; rechargeInfo.setRechargeStatus(SystemConstant.ORDER_STATUS_PAID); this.updateById(rechargeInfo);
BigDecimal rechargeAmount = rechargeInfo.getRechargeAmount(); Long userId = rechargeInfo.getUserId(); LambdaQueryWrapper<UserAccount> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(UserAccount::getUserId, userId);
UserAccount userAccount = userAccountService.getOne(wrapper); userAccount.setTotalAmount(userAccount.getTotalAmount() .add(rechargeAmount)); userAccount.setAvailableAmount(userAccount.getAvailableAmount() .add(rechargeAmount));
LambdaQueryWrapper<UserAccount> wrapper1 = new LambdaQueryWrapper<>(); wrapper1.eq(UserAccount::getUserId, userId); userAccountService.update(userAccount,wrapper1); }
|
2、任务调度框架XXL-JOB
概述
注解+cron表达式实现
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Component @EnableScheduling public class ScheduledTask {
@Autowired private RabbitService rabbitService;
@Scheduled(cron = "0/5 * * * * ?") public void task1() { System.out.println(new Date().toLocaleString()); } }
|
xxl-job: 是大众点评员工徐雪里于2015年发布的分布式任务调度平台,是一个轻量级分布式任务调度框架,其核心设计目标是开发迅速、学习简单、轻量级、易扩展
XXL-JOB使用
第一步 部署XXL-JOB调度中心
– 调度中心springboot工程使用jdk1.8环境

– 创建数据库

– 修改配置文件


http://localhost:8080/xxl-job-admin admin/123456


第二步 创建执行器项目,注册到调度中心
首先,修改执行器项目配置文件,添加调度中心地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| server.port=8281
logging.config=classpath:logback.xml
xxl.job.admin.addresses=http://127.0.0.1:8080/xxl-job-admin
xxl.job.accessToken=
xxl.job.executor.appname=xxl-job-executor-sample
xxl.job.executor.address=
xxl.job.executor.ip=
xxl.job.executor.port=9999
xxl.job.executor.logpath=C:\\tingshu
xxl.job.executor.logretentiondays=30
|
其次,创建配置类,XxlJobSpringExecutor对象初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Bean public XxlJobSpringExecutor xxlJobExecutor() { logger.info(">>>>>>>>>>> xxl-job config init."); XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor(); xxlJobSpringExecutor.setAdminAddresses(adminAddresses); xxlJobSpringExecutor.setAppname(appname); xxlJobSpringExecutor.setAddress(address); xxlJobSpringExecutor.setIp(ip); xxlJobSpringExecutor.setPort(port); xxlJobSpringExecutor.setAccessToken(accessToken); xxlJobSpringExecutor.setLogPath(logPath); xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);
return xxlJobSpringExecutor; }
|
第三步 编写执行器项目具体业务
- 编写具体业务代码
- 给业务代码方法起名字 ,使用注解 @XxlJob(“demoJobHandler”)
1 2 3 4 5 6 7 8 9 10 11
| @XxlJob("demoJobHandler") public void demoJobHandler() throws Exception { XxlJobHelper.log("XXL-JOB, Hello World.");
for (int i = 0; i < 5; i++) { XxlJobHelper.log("beat at:" + i); TimeUnit.SECONDS.sleep(2); System.out.println(Thread.currentThread().getName()+"atguigu...."); } }
|
第四步 在调度中心配置执行器任务

- 在任务管理配置业务方法执行规则,比如每隔10s执行一次

遇到问题
**解决一:**检查调度中心配置文件数据库路径,用户名和密码
检查执行器项目调度中心地址是否正确
**解决二:**在调度中心界面操作(bug)

项目整合XXL-JOB
xxl-job项目应用场景
整合XXL-JOB
1 2 3 4 5 6 7
| <dependencies> <dependency> <groupId>com.xuxueli</groupId> <artifactId>xxl-job-core</artifactId> <version>2.4.0</version> </dependency> </dependencies>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| xxl: job: admin: addresses: http://localhost:8080/xxl-job-admin accessToken:
executor: appname: xxl-job-executor-sample address: ip: port: 9999 logpath: C:\\tingshu logretentiondays: 30
|
- 创建配置类,初始化XxlJobSpringExecutor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| package com.atguigu.tingshu.dispatch.config;
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;
@Configuration public class XxlJobConfig { private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class);
@Value("${xxl.job.admin.addresses}") private String adminAddresses;
@Value("${xxl.job.accessToken}") private String accessToken;
@Value("${xxl.job.executor.appname}") private String appname;
@Value("${xxl.job.executor.address}") private String address;
@Value("${xxl.job.executor.ip}") private String ip;
@Value("${xxl.job.executor.port}") private int port;
@Value("${xxl.job.executor.logpath}") private String logPath;
@Value("${xxl.job.executor.logretentiondays}") private int logRetentionDays; @Bean public XxlJobSpringExecutor xxlJobExecutor() { logger.info(">>>>>>>>>>> xxl-job config init."); XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor(); xxlJobSpringExecutor.setAdminAddresses(adminAddresses); xxlJobSpringExecutor.setAppname(appname); xxlJobSpringExecutor.setAddress(address); xxlJobSpringExecutor.setIp(ip); xxlJobSpringExecutor.setPort(port); xxlJobSpringExecutor.setAccessToken(accessToken); xxlJobSpringExecutor.setLogPath(logPath); xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);
return xxlJobSpringExecutor; } }
|
1 2 3 4 5 6 7 8 9 10
| @Slf4j @Component public class DispatchJobHandler {
@XxlJob("firstJobHandler") public void firstJobHandler() { log.info("xxl-job项目集成测试"); }
}
|
功能一:定期更新排行榜
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| @Slf4j @Component public class DispatchJobHandler {
@Autowired private SearchFeignClient searchFeignClient; @Autowired private XxlJobLogMapper xxlJobLogMapper;
@XxlJob("firstJobHandler") public void firstJobHandler() { log.info("xxl-job项目集成测试"); }
@XxlJob("updateLatelyAlbumRanking") public void updateLatelyAlbumRanking() { XxlJobLog xxlJobLog = new XxlJobLog(); xxlJobLog.setJobId(XxlJobHelper.getJobId()); long startTime = System.currentTimeMillis(); try { searchFeignClient.updateLatelyAlbumRanking(); } catch (Exception e) { xxlJobLog.setStatus(0); xxlJobLog.setError(ExceptionUtil.getErrorMessage(e)); log.error("定时任务执行失败,任务id为:{}", XxlJobHelper.getJobId()); e.printStackTrace(); } finally { int times = (int) (System.currentTimeMillis() - startTime); xxlJobLog.setTimes(times); xxlJobLogMapper.insert(xxlJobLog); } } }
|

功能二:更新过期vip状态
1 2 3 4 5 6
| @Operation(summary = "更新Vip到期失效状态") @GetMapping("updateVipExpireStatus") public Result updateVipExpireStatus() { userInfoService.updateVipExpireStatus(); return Result.ok(); }
|
1 2 3 4 5 6 7 8 9 10
| <update id="updateVipExpireStatus"> update user_info set is_vip = 0 where is_vip = 1
and vip_expire_time < now()
and is_deleted = 0 </update>
|
1 2 3 4
| < 转义 < <= 转义 ≤ > 转义 > >= 转义 ≤
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @XxlJob("updateVipExpireStatus") public void updateVipExpireStatus() { XxlJobLog xxlJobLog = new XxlJobLog(); xxlJobLog.setJobId(XxlJobHelper.getJobId()); long startTime = System.currentTimeMillis(); try { userInfoFeignClient.updateVipExpireStatus();
} catch (Exception e) { xxlJobLog.setStatus(0); xxlJobLog.setError(ExceptionUtil.getErrorMessage(e)); log.error("定时任务执行失败,任务id为:{}", XxlJobHelper.getJobId()); e.printStackTrace(); } finally { int times = (int) (System.currentTimeMillis() - startTime); xxlJobLog.setTimes(times); xxlJobLogMapper.insert(xxlJobLog); } }
|
3、总结订单
订单模块回顾

订单模块话术
==订单========
订单流程
根据购买不同类型:vip、专辑、声音,基于优惠折扣计算结算价格
提交订单,首先考虑数据安全,进行签名校验,并且判断是否重复提交,提交订单添加订单数据,包含订单基本信息,详情信息和优惠信息
订单技术:
- redis判断是否重复提交
- 策略模式优化添加购买记录(vip、声音、专辑),根据不同的购买类型做不同的操作
- 延迟消息实现订单超时不支付自动取消,延迟消息使用RabbitMQ+TTL或者延迟插件或者Redisson
==订单========
订单模块我们主要使用了Redis用来防止订单的重复提交,就是在我们生成订单时会生成一个流水号,并把它放入到Redis中,其中key是userId,Value是这个流水号,然后在提交订单时会根据这个key去查询,如果查到了就说明没有重复提交,并把这个值删除,如果没有查询到就说名重复提交了。以及订单支付的延迟消息投递,我们使用的RabbitMq的死信队列+ttl或者Rabbitmq的延迟插件。最后我们还通过策略模式对购买功能进行了优化。
策略模式就是在同一个行为在不同场景的不同操作,项目中实现为:购买的物品(vip,声音、专辑)不同来修改不同的数据库。
可能涉及问题
1、Redis用来防止订单的重复提交
– 结算接口时候向redis里面放数据
– 点击立即结算只是调用提交订单接口,不会调用结算页面接口,redis不会变化

– 使用bitmap计算播放量,同一个用户对于同一个声音24小时计算一次播放量
– 使用redis的hash类型存储排行榜数据,使用任务调度框架实现定期更新排行榜
– 详情添加redis缓存,解决缓存四个问题
…………
2、RabbitMq相关问题
– 确认模式:到达交换机
– 返回模式:到达队列
– 消费端确认
– 失败重试,使用redis对消息持久化
使用Redisson实现延迟消息
Redisson实现分布式锁
– 两个机制:重试机制 和 看门狗机制
– 使用n个哈希函数计算数据在数组位置,把位置值1
– 获取:使用n个哈希函数计算数据在数组位置,如果有任何一个位置值是0肯定不存在,如果都是1可能存在,因为hash冲突问题
– 存在误判率
3、策略模式
- 策略模式就是在同一个行为在不同场景的不同操作,面向对象多态机制
- 添加购买记录使用策略模式优化
4、延迟消息