学成-选课下单
1 2
| - 免费课:选课后直接可学 - 收费课:选课 → 生成订单 → 支付成功 → 加入课程表 → 才能学习
|
一、选课
实现
1 2 3 4 5 6 7 8 9 10 11 12
| @ApiOperation("添加选课") @PostMapping("/choosecourse/{courseId}") public XcChooseCourseDto addChooseCourse(@PathVariable("courseId") Long courseId) {
SecurityUtil.XcUser user = SecurityUtil.getUser(); String userId = user.getId();
XcChooseCourseDto xcChooseCourseDto = myCourseTablesService.addChooseCourse(userId, courseId);
return xcChooseCourseDto; }
|

并且将选课id存入数据库
二、支付
支付流程

1.环境配置

配置于nacos配置文件中即可
支付宝网关更新
1
| https://openapi-sandbox.dl.alipaydev.com/gateway.do
|
需要到
1
| src/main/java/com/xuecheng/orders/config/AlipayConfig.java
|
这个类下做出修改

然后手机扫码之后返回代码,需要点击右上角浏览器打开的方式唤起客户端
2.实现
依赖
1 2 3 4 5 6 7 8 9 10 11 12 13
| <dependency> <groupId>com.alipay.sdk</groupId> <artifactId>alipay-sdk-java</artifactId> <version>3.7.73.ALL</version> </dependency>
<dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency>
|
配置类
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
| package com.xuecheng.orders.config;
public class AlipayConfig {
public static String notify_url = "http://商户网关地址/alipay.trade.wap.pay-JAVA-UTF-8/notify_url.jsp"; public static String return_url = "http://商户网关地址/alipay.trade.wap.pay-JAVA-UTF-8/return_url.jsp"; public static String URL = "https://openapi-sandbox.dl.alipaydev.com/gateway.do"; public static String CHARSET = "UTF-8"; public static String FORMAT = "json";
public static String log_path = "/log"; public static String SIGNTYPE = "RSA2"; }
|
请求接口
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
| @Controller public class PayTestController {
@Value("${pay.alipay.APP_ID}") String APP_ID; @Value("${pay.alipay.APP_PRIVATE_KEY}") String APP_PRIVATE_KEY;
@Value("${pay.alipay.ALIPAY_PUBLIC_KEY}") String ALIPAY_PUBLIC_KEY;
@RequestMapping("/alipaytest") public void doPost(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws ServletException, IOException, AlipayApiException { AlipayClient alipayClient = new DefaultAlipayClient(AlipayConfig.URL, APP_ID, APP_PRIVATE_KEY, AlipayConfig.FORMAT, AlipayConfig.CHARSET, ALIPAY_PUBLIC_KEY,AlipayConfig.SIGNTYPE); AlipayTradeWapPayRequest alipayRequest = new AlipayTradeWapPayRequest();
alipayRequest.setBizContent("{" + " \"out_trade_no\":\"202210100010101002\"," + " \"total_amount\":0.1," + " \"subject\":\"Iphone6 16G\"," + " \"product_code\":\"QUICK_WAP_WAY\"" + " }"); String form = alipayClient.pageExecute(alipayRequest).getBody(); httpResponse.setContentType("text/html;charset=" + AlipayConfig.CHARSET); httpResponse.getWriter().write(form); httpResponse.getWriter().flush(); }
}
|
3.支付结果回调
支付流程如下
结果接收一共分为俩种
3.1、被动接收
1 2 3
| @ApiOperation("接收支付结果通知") @PostMapping("/receivenotify") public void receivenotify(HttpServletRequest request) throws UnsupportedEncodingException, AlipayApiException {
|
如上,我们自定义支付宝请求接口
3.2、主动查询
1 2 3 4 5
| @Override public PayRecordDto queryPayResult(String payNo){ XcPayRecord payRecord = getPayRecordByPayno(payNo); if (payRecord == null) { XueChengPl
|

主动查询如上
三、支付成功通知
订单支付成功后会发消息,消息内容大概是:
1 2 3 4 5
| { "outBusinessId": "选课记录ID", "orderType": "60201", "status": "支付成功" }
|
其中最关键的是 outBusinessId,就是之前创建订单时传进去的选课记录ID
learning 收到消息后,靠这个 ID 找到对应的选课记录,然后开通学习资格
这个不同服务之间进行通知,通常需要保证保证一系列信息
1.MQ
项目用的是 RabbitMQ,代码里用的是 Spring AMQP(@RabbitListener)
RabbitMQ 的核心模型
消息不是直接发到队列的,必须先发给交换机,交换机再根据规则路由到队列
2.具体实现
2.1、MQ 配置
项目用的是 Nacos 配置中心,不是硬编码在代码里。
1 2 3 4 5 6
| orders 服务 → bootstrap.yml → 引用 Nacos learning 服务 → bootstrap.yml → 引用 Nacos ↓ 两边都引用了 shared-configs 里的: rabbitmq-${spring.profiles.active}.yml (比如 rabbitmq-dev.yaml)
|
- RabbitMQ 的 host、port、用户名、密码、ack模式等参数,全部在 Nacos 的 rabbitmq-dev.yaml 里统一管理
- 代码里只负责声明交换机/队列/消息逻辑,不写死连接参数
2.2、支付通知这条 MQ 拓扑结构
两服务各自有一个 PayNotifyConfig.java,但声明的是同一套东西:
1 2 3
| 交换机:paynotify_exchange_fanout 类型:Fanout 队列: paynotify_queue 消息体:payresult_notify
|
Fanout 是广播模式,消息发到交换机后,会投递给所有绑定的队列,不需要 routingKey。
好处是:如果以后还有别的服务也关心”支付成功”这个事件,直接绑一个新队列就行,订单服务的发送代码完全不用改
1 2 3 4 5
| orders 发消息 ↓ paynotify_exchange_fanout ├──→ paynotify_queue(learning 消费) └──→ 未来可能的其他队列(比如积分服务)
|
两边都声明的原因是:谁先启动,谁负责把 Exchange 和 Queue 创建好,防止对方还没起来时消息找不到地方放
2.3、生产者发消息
在 OrderServiceImpl.java 的 saveAliPayStatus 方法里:
第一步:消息先落本地库
1
| mqMessageService.addMessage("payresult_notify", orders.getOutBusinessId(), orders.getOrderType(), null)
|
消息先写入本地 mq_message 表,状态为待处理,还没发到 RabbitMQ
第二步:notifyPayResult 发消息
1 2 3 4 5 6 7 8 9 10
|
rabbitTemplate.convertAndSend( PAYNOTIFY_EXCHANGE_FANOUT, "", msgObj, correlationData )
|
第三步:Confirm 回调怎么处理
1 2 3 4 5
| 发送成功(ack) → mqMessageService.completed(messageId) 消息从待处理表移入历史表,标记完成
发送失败(nack) → 打日志,等待后续补偿 定时任务会扫描未完成的消息重新投递
|
2.4、消费者收消息
在 ReceivePayNotifyService.java:
1 2 3 4 5 6 7 8 9 10 11 12 13
| @RabbitListener(queues = PayNotifyConfig.PAYNOTIFY_QUEUE) public void receive(MqMessage message) { if (message.getMessageType().equals("payresult_notify") && message.getBusinessKey2().equals("60201")) { String choosecourseId = message.getBusinessKey1(); myCourseTablesService.saveChooseCourseSuccess(choosecourseId); } }
|
注意最后一点:业务失败就抛异常,不手动 ACK,让 RabbitMQ 重新投递这条消息
3.MQ优势
3.1、生产者可靠性
问题:订单服务发消息,但RabbitMQ挂了,或者网络抖动,消息丢了怎么办?
3.2、Broker可靠性
问题:消息发到RabbitMQ了,但RabbitMQ重启,消息没了怎么办?
3.3、消息处理得了 消费者可靠性
四、总结
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
| 用户点选课 ↓ learning 调 content,查课程是否收费 ↓ 写入选课记录(状态:待支付 701002) ↓ 前端拿选课记录ID,调 orders 创建订单 (outBusinessId = 选课记录ID,这个很关键) ↓ orders 生成:订单 + 支付记录 + 支付二维码 ↓ 用户扫码支付(调支付宝SDK) ↓ 支付宝回调 /notify ↓ orders 做两件事: 1. 验签(防伪造) 2. 验金额(防篡改) ↓ 更新支付记录(601002)+ 更新订单(600002) ↓ 发 MQ 消息通知 learning ↓ learning 消费消息: 1. 把选课记录改为成功(701001) 2. 写入我的课程表 ↓ 用户正式具备学习资格
|