1. 支付
订单搞定之后就是支付了,首先搭建支付工程。
1.1. 搭建环境


pom.xml
| 12
 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
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 
 | <?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <parent>
 <groupId>com.atguigu</groupId>
 <artifactId>gmall-1010</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 </parent>
 <groupId>com.atguigu</groupId>
 <artifactId>gmall-payment</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 <name>gmall-payment</name>
 <description>谷粒商城支付系统</description>
 
 <properties>
 <java.version>1.8</java.version>
 </properties>
 
 <dependencies>
 <dependency>
 <groupId>com.atguigu</groupId>
 <artifactId>gmall-common</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 </dependency>
 <dependency>
 <groupId>com.atguigu</groupId>
 <artifactId>gmall-oms-interface</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 </dependency>
 <dependency>
 <groupId>mysql</groupId>
 <artifactId>mysql-connector-java</artifactId>
 </dependency>
 <dependency>
 <groupId>com.baomidou</groupId>
 <artifactId>mybatis-plus-boot-starter</artifactId>
 </dependency>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-amqp</artifactId>
 </dependency>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-data-redis</artifactId>
 </dependency>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-thymeleaf</artifactId>
 </dependency>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
 </dependency>
 <dependency>
 <groupId>com.alibaba.cloud</groupId>
 <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
 </dependency>
 <dependency>
 <groupId>com.alibaba.cloud</groupId>
 <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
 </dependency>
 <dependency>
 <groupId>com.alibaba.cloud</groupId>
 <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
 </dependency>
 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-openfeign</artifactId>
 </dependency>
 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-zipkin</artifactId>
 </dependency>
 
 <dependency>
 <groupId>com.alipay.sdk</groupId>
 <artifactId>alipay-sdk-java</artifactId>
 <version>4.10.0.ALL</version>
 </dependency>
 
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-test</artifactId>
 <scope>test</scope>
 <exclusions>
 <exclusion>
 <groupId>org.junit.vintage</groupId>
 <artifactId>junit-vintage-engine</artifactId>
 </exclusion>
 </exclusions>
 </dependency>
 <dependency>
 <groupId>org.springframework.amqp</groupId>
 <artifactId>spring-rabbit-test</artifactId>
 <scope>test</scope>
 </dependency>
 </dependencies>
 
 <build>
 <plugins>
 <plugin>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-maven-plugin</artifactId>
 </plugin>
 </plugins>
 </build>
 
 </project>
 
 | 
bootstrap.yml:
| 12
 3
 4
 5
 6
 7
 
 | spring:application:
 name: payment-service
 cloud:
 nacos:
 config:
 server-addr: 127.0.0.1:8848
 
 | 
application.yml:
| 12
 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
 
 | server:port: 18092
 spring:
 cloud:
 nacos:
 discovery:
 server-addr: localhost:8848
 sentinel:
 transport:
 dashboard: localhost:8080
 port: 8179
 zipkin:
 base-url: http://localhost:9411/
 sender:
 type: web
 discovery-client-enabled: false
 sleuth:
 sampler:
 probability: 1
 redis:
 host: 172.16.116.100
 rabbitmq:
 host: 172.16.116.100
 virtual-host: /fengge
 username: fengge
 password: fengge
 listener:
 simple:
 acknowledge-mode: manual
 prefetch: 1
 thymeleaf:
 cache: false
 datasource:
 driver-class-name: com.mysql.jdbc.Driver
 url: jdbc:mysql://172.16.116.100:3306/guli_payment
 username: root
 password: root
 feign:
 sentinel:
 enabled: true
 mybatis-plus:
 global-config:
 db-config:
 id-type: auto
 
 | 
启动类:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | @SpringBootApplication@EnableFeignClients
 @MapperScan("com.atguigu.gmall.payment.mapper")
 public class GmallPaymentApplication {
 
 public static void main(String[] args) {
 SpringApplication.run(GmallPaymentApplication.class, args);
 }
 
 }
 
 | 
网关配置:

nginx配置:加入payment.gmall.com
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | server {listen       80;
 server_name  api.gmall.com search.gmall.com www.gmall.com item.gmall.com sso.gmall.com cart.gmall.com order.gmall.com payment.gmall.com;
 
 proxy_set_header Host $host;
 
 location / {
 proxy_pass   http://192.168.221.1:8888;
 }
 }
 
 | 
重新加载nginx配置:nginx -s reload
在hosts中添加payment.gmall.com:

1.2. 支付流程
支付流程如下:
- 点击- 提交订单按钮,完成订单创建后,跳转到支付选择页
  
 
- 选择支付渠道,点击立即支付。跳转到具体的支付页  
 
- 用户扫码支付跳转到支付成功页  
 
1.3. 选择支付方式
下单成功后,请求路径:http://payment.gmall.com/pay.html?orderToken=202006041844036401268493714385424386
已知条件是订单编号,而支付可能需要订单金额等一些订单信息。所以订单工程应该提供一个根据订单编号查询订单的数据接口。
1.3.1. 根据订单编号查询订单
在gmall-oms工程中的OrderController中添加根据订单编号查询订单的接口方法:
| 12
 3
 4
 5
 
 | @GetMapping("token/{orderSn}")public ResponseVo<OrderEntity> queryOrderByOrderSn(@PathVariable("orderSn")String orderSn){
 OrderEntity orderEntity = this.orderService.getOne(new QueryWrapper<OrderEntity>().eq("order_sn", orderSn));
 return ResponseVo.ok(orderEntity);
 }
 
 | 
在gmall-oms-interface工程中的GmallOmsApi添加接口方法:
| 12
 
 | @GetMapping("oms/order/token/{orderSn}")public ResponseVo<OrderEntity> queryOrderByOrderSn(@PathVariable("orderSn")String orderSn);
 
 | 
1.3.2. 跳转到支付渠道选择页
在gmall-payment工程中实现页面跳转。
 
PaymentController:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 
 | @Controllerpublic class PaymentController {
 
 @Autowired
 private PaymentService paymentService;
 
 @GetMapping("pay.html")
 public String toPay(@RequestParam("orderToken") String orderToken, Model model){
 
 OrderEntity orderEntity = this.paymentService.queryOrderByOrderToken(orderToken);
 model.addAttribute("orderEntity", orderEntity);
 return "pay";
 }
 }
 
 | 
PaymentService:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | @Servicepublic class PaymentService {
 
 @Autowired
 private GmallOmsClient omsClient;
 
 public OrderEntity queryOrderByOrderToken(String orderToken) {
 
 ResponseVo<OrderEntity> orderEntityResponseVo = this.omsClient.queryOrderByOrderSn(orderToken);
 return orderEntityResponseVo.getData();
 }
 }
 
 | 
GmallOmsClient:
| 12
 3
 
 | @FeignClient("oms-service")public interface GmallOmsClient extends GmallOmsApi {
 }
 
 | 
1.3.3. 页面渲染

1.4. 完成支付功能
这里支付已支付宝为例,支付宝的支付流程如下:

调用顺序如下:
- 商户系统请求支付宝接口 alipay.trade.page.pay,支付宝对商户请求参数进行校验,而后重新定向至用户登录页面。
- 用户确认支付后,支付宝通过 get 请求 returnUrl(商户入参传入),返回同步返回参数。
- 交易成功后,支付宝通过 post 请求 notifyUrl(商户入参传入),返回异步通知参数。
- 若由于网络等问题异步通知没有到达,商户可自行调用交易查询接口 alipay.trade.query 进行查询,根据查询接口获取交易以及支付信息(商户也可以直接调用查询接口,不需要依赖异步通知)。
注意: 
- 由于同步返回的不可靠性,支付结果必须以异步通知或查询接口返回为准,不能依赖同步跳转。
- 商户系统接收到异步通知以后,必须通过验签(验证通知中的 sign 参数)来确保支付通知是由支付宝发送的。详细验签规则参考异步通知验签。
- 接收到异步通知并验签通过后,一定要检查通知内容,包括通知中的 app_id、out_trade_no、total_amount 是否与请求中的一致,并根据 trade_status 进行后续业务处理。
- 在支付宝端,partnerId 与 out_trade_no 唯一对应一笔单据,商户端保证不同次支付 out_trade_no 不可重复;若重复,支付宝会关联到原单据,基本信息一致的情况下会以原单据为准进行支付。
1.4.1. 内网穿透
支付异步通知需要独立ip使阿里支付成功后可以回调我们的接口,所以前提条件就是内网穿透。
哲西云:https://cloud.zhexi.tech
哲西云浏览器客户端配置隧道,映射网关的8888端口:

具体配置如下:
 
测试内网穿透:访问品牌列表

使用内网穿透后,外网无法通过payment.gmall.com访问支付系统了。只能通过内网穿透提供的地址访问,那么我们的网关也就无法通过域名转发请求,只能通过路径转发,于是在网关中配置路径路由:

1.4.2. 表、实体类及Mapper接口
将支付数据保存到数据库,以便跟支付宝进行对账。
创建guli_payment数据库,导入一下sql:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | CREATE TABLE `payment_info` (`id` bigint(20) NOT NULL COMMENT '商户订单号',
 `out_trade_no` varchar(64) DEFAULT NULL,
 `payment_type` tinyint(4) DEFAULT NULL COMMENT '支付类型(微信与支付宝)',
 `trade_no` varchar(64) DEFAULT NULL COMMENT '支付宝交易凭证号',
 `total_amount` decimal(18,4) DEFAULT NULL COMMENT '订单金额。订单中获取',
 `subject` varchar(100) DEFAULT NULL COMMENT '交易内容。利用商品名称拼接。',
 `payment_status` tinyint(4) DEFAULT NULL COMMENT '支付状态,默认值0-未支付,1-已支付。',
 `create_time` datetime DEFAULT NULL COMMENT '创建时间',
 `callback_time` datetime DEFAULT NULL COMMENT '回调时间,初始为空,支付宝异步回调时记录',
 `callback_content` text COMMENT '回调信息,初始为空,支付宝异步回调时记录',
 PRIMARY KEY (`id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='支付对账表';
 
 | 
对应的实体类如下:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 
 | @Data@TableName("payment_info")
 public class PaymentInfoEntity {
 
 @Id
 private Long id;
 
 private String outTradeNo;
 
 private Integer paymentType;
 
 private String tradeNo;
 
 private BigDecimal totalAmount;
 
 private String subject;
 
 private Integer paymentStatus;
 
 private Date createTime;
 
 private Date callbackTime;
 
 private String callbackContent;
 }
 
 | 
mapper接口:
| 12
 
 | public interface PaymentInfoMapper extends BaseMapper<PaymentInfoEntity> {}
 
 | 
项目结构:
 
1.4.3. 整合阿里支付
继续改造gmall-order工程
在pom.xml中,引入阿里支付的依赖:
| 12
 3
 4
 5
 
 | <dependency><groupId>com.alipay.sdk</groupId>
 <artifactId>alipay-sdk-java</artifactId>
 <version>4.10.0.ALL</version>
 </dependency>
 
 | 
在application.yml中添加阿里支付的配置:
| 12
 3
 4
 5
 6
 7
 
 | alipay:app_id: 2021001163617452
 gatewayUrl: https://openapi.alipay.com/gateway.do
 merchant_private_key: MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQ
 alipay_public_key: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkWs
 notify_url: http://9glldacce2.52http.net/pay/success
 return_url: http://9glldacce2.52http.net/pay/ok
 
 | 
app_id、私钥、公钥参照资料中的《支付宝秘钥.txt》
把课前资料中封装的阿里支付工具类及PayVo对象copy到工程中:
 
1.4.4. 跳转到支付
修改提交订单的gmall-order中OrderController方法,如下:
| 12
 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
 
 | @GetMapping("alipay.html")@ResponseBody
 public String alipay(@RequestParam("orderToken") String orderToken){
 try {
 
 OrderEntity orderEntity = this.paymentService.queryOrderByOrderToken(orderToken);
 if (orderEntity.getStatus() != 0){
 throw new OrderException("此订单无法支付,可能已经过期!");
 }
 
 
 PayVo payVo = new PayVo();
 payVo.setOut_trade_no(orderEntity.getOrderSn());
 
 payVo.setTotal_amount("0.01");
 payVo.setSubject("谷粒商城支付平台");
 
 Long payId = this.paymentService.save(orderEntity, 1);
 payVo.setPassback_params(payId.toString());
 String form = alipayTemplate.pay(payVo);
 
 
 return form;
 } catch (AlipayApiException e) {
 e.printStackTrace();
 throw new OrderException("支付出错,请刷新后重试!");
 }
 }
 
 | 
PaymentService:
| 12
 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
 
 | @Servicepublic class PaymentService {
 
 @Autowired
 private GmallOmsClient omsClient;
 
 @Autowired
 private PaymentInfoMapper paymentInfoMapper;
 
 public OrderEntity queryOrderByOrderToken(String orderToken) {
 
 ResponseVo<OrderEntity> orderEntityResponseVo = this.omsClient.queryOrderByOrderSn(orderToken);
 return orderEntityResponseVo.getData();
 }
 
 public Long save(OrderEntity orderEntity, Integer payType){
 
 PaymentInfoEntity paymentInfoEntity = this.paymentInfoMapper.selectOne(new QueryWrapper<PaymentInfoEntity>().eq("out_trade_no", orderEntity.getOrderSn()));
 
 if (paymentInfoEntity != null) {
 return paymentInfoEntity.getId();
 }
 
 paymentInfoEntity = new PaymentInfoEntity();
 paymentInfoEntity.setOutTradeNo(orderEntity.getOrderSn());
 paymentInfoEntity.setPaymentType(payType);
 paymentInfoEntity.setSubject("谷粒商城支付平台");
 
 paymentInfoEntity.setTotalAmount(new BigDecimal(0.01));
 paymentInfoEntity.setPaymentStatus(0);
 paymentInfoEntity.setCreateTime(new Date());
 this.paymentInfoMapper.insert(paymentInfoEntity);
 return paymentInfoEntity.getId();
 }
 }
 
 | 
测试效果:

1.4.5. 异步回调
由于同步返回的不可靠性,支付结果必须以异步通知或查询接口返回为准,不能依赖同步跳转。
接收到回调要做的事情:
- 验签:验证回调信息的真伪
- 检查通知内容:通知中的 app_id、out_trade_no、total_amount 是否与请求中的一致
- 验证用户付款的成功与否:根据 trade_status 进行后续业务处理(TRADE_SUCCESS)
- 把支付状态写入支付信息表payment_info中。
- 更新订单状态并减库存。
- 给支付宝返回回执。成功:success  失败:failure

1.4.5.1. 实现异步回调方法
请求方式:Post请求
请求路径:/pay/success
请求参数:PayAsyncVo
返回值:success/failure
给PaymentController新增支付成功后的回调方法:
| 12
 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
 
 | @PostMapping("pay/success")@ResponseBody
 public String paySuccess(PayAsyncVo payAsyncVo){
 
 
 Boolean flag = this.alipayTemplate.verifySignature(payAsyncVo);
 if (!flag) {
 
 return "failure";
 }
 
 
 String payId = payAsyncVo.getPassback_params();
 if (StringUtils.isBlank(payId)){
 return "failure";
 }
 PaymentInfoEntity paymentInfoEntity = this.paymentService.queryPayMentById(Long.valueOf(payId));
 if (paymentInfoEntity == null
 || !StringUtils.equals(payAsyncVo.getApp_id(), this.alipayTemplate.getApp_id())
 || !StringUtils.equals(payAsyncVo.getOut_trade_no(), paymentInfoEntity.getOutTradeNo())
 || paymentInfoEntity.getTotalAmount().compareTo(new BigDecimal(payAsyncVo.getBuyer_pay_amount())) != 0){
 return "failure";
 }
 
 
 if (!StringUtils.equals("TRADE_SUCCESS", payAsyncVo.getTrade_status())) {
 return "failure";
 }
 
 
 paymentService.paySuccess(payAsyncVo);
 
 
 this.rabbitTemplate.convertAndSend("order-exchange", "order.pay", payAsyncVo.getOut_trade_no());
 
 
 return "success";
 }
 
 | 
给PaymentService添加方法:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 
 | 
 
 
 
 public PaymentInfoEntity queryPayMentById(Long id) {
 return this.paymentInfoMapper.selectById(id);
 }
 
 
 
 
 
 public void paySuccess(PayAsyncVo payAsyncVo){
 PaymentInfoEntity paymentInfoEntity = new PaymentInfoEntity();
 paymentInfoEntity.setCallbackTime(new Date());
 paymentInfoEntity.setPaymentStatus(1);
 paymentInfoEntity.setCallbackContent(JSON.toJSONString(payAsyncVo));
 this.paymentInfoMapper.update(paymentInfoEntity, new UpdateWrapper<PaymentInfoEntity>().eq("out_trade_no", payAsyncVo.getOut_trade_no()));
 }
 
 | 
1.4.5.2. oms更新订单状态
给gmall-oms中的OrderListener添加修改订单状态为支付成功(待发货)的消息监听方法:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 
 | @RabbitListener(bindings = @QueueBinding(value = @Queue(value = "ORDER-PAY-QUEUE", durable = "true"),
 exchange = @Exchange(value = "ORDER-EXCHANGE", ignoreDeclarationExceptions = "true"),
 key = {"order.pay"}
 ))
 public void successOrder(String orderToken, Channel channel, Message message) throws IOException {
 
 if (this.orderMapper.successOrder(orderToken) == 1){
 
 this.rabbitTemplate.convertAndSend("ORDER-EXCHANGE", "stock.minus", orderToken);
 
 OrderEntity orderEntity = this.orderService.getOne(new QueryWrapper<OrderEntity>().eq("order_sn", orderToken));
 UserBoundVO userBoundVO = new UserBoundVO();
 userBoundVO.setUserId(orderEntity.getUserId());
 userBoundVO.setIntegration(orderEntity.getIntegration());
 userBoundVO.setGrowth(orderEntity.getGrowth());
 this.rabbitTemplate.convertAndSend("ORDER-EXCHANGE", "bound.plus", userBoundVO);
 
 channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
 }
 }
 
 | 
给gmall-oms工程的OrderMapper接口及实现类添加successOrder方法:
| 1
 | int successOrder(String orderToken);
 | 
| 12
 3
 
 | <update id="successOrder">update oms_order set `status`=1 where order_sn=#{orderToken} and `status`=0
 </update>
 
 | 
给gmall-oms-interface工程添加UserBoundVO
 
内容:
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | @Datapublic class UserBoundVO {
 
 private Long userId;
 
 private Integer integration;
 
 private Integer growth;
 }
 
 | 
1.4.5.3. wms减库存
给gmall-wms的StockListener添加减库存的监听器方法:
| 12
 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
 
 | @RabbitListener(bindings = @QueueBinding(value = @Queue(value = "STOCK-MINUS-QUEUE", durable = "true"),
 exchange = @Exchange(value = "ORDER-EXCHANGE", ignoreDeclarationExceptions = "true", type = ExchangeTypes.TOPIC),
 key = {"stock.minus"}
 ))
 public void minusStock(String orderToken, Channel channel, Message message) throws IOException {
 
 try {
 
 String json = this.redisTemplate.opsForValue().get(KEY_PREFIX + orderToken);
 if (StringUtils.isNotBlank(json)){
 
 List<SkuLockVo> skuLockVos = JSON.parseArray(json, SkuLockVo.class);
 
 skuLockVos.forEach(skuLockVo -> {
 this.wareSkuMapper.minus(skuLockVo.getWareSkuId(), skuLockVo.getCount());
 });
 
 this.redisTemplate.delete(KEY_PREFIX + orderToken);
 }
 channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
 } catch (Exception e) {
 e.printStackTrace();
 if (message.getMessageProperties().getRedelivered()){
 channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
 } else {
 channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
 }
 }
 }
 
 | 
给gmall-wms的WareSkuMapper添加方法:
| 1
 | void minus(@Param("id") Long wareSkuId, @Param("count") Integer count);
 | 
给gmall-wms的WareSkuMapper.xml添加映射
| 12
 3
 
 | <update id="minus">update wms_ware_sku set stock_locked = stock_locked - #{count}, stock = stock - #{count}, sales = sales + #{count} where id = #{id}
 </update>
 
 | 
gmall-ums中加积分的监听器略。。。。。。
1.4.6. 同步回调
用户扫描支付成功后,我们可以通过同步回调,跳转到商户的支付成功页。
请求方式:GET
请求路径:/pay/ok
请求参数:参照异步请求(比异步请求略少)
返回视图名称
| 12
 3
 4
 5
 6
 7
 
 | @GetMapping("pay/ok")public String payOk(PayAsyncVo payAsyncVo){
 
 
 
 return "paysuccess";
 }
 
 | 
2. 秒杀
秒杀具有瞬间高并发的特点,针对这一特点,必须要做限流 + 异步 + 缓存 (+ 页面静态化)。
限流方式:
- 前端限流,一些高并发的网站直接在前端页面开始限流,例如:小米的验证码设计
- nginx限流,直接负载部分请求到错误的静态页面:令牌算法 漏斗算法
- 网关限流,限流的过滤器。或者使用专业的限流组件sentinel
- 代码中使用分布式信号量
- rabbitmq限流(能者多劳:chanel.basicQos(1)),保证发挥所有服务器的性能。
| 12
 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
 58
 59
 60
 61
 62
 
 | @Autowiredprivate StringRedisTemplate redisTemplate;
 
 @Autowired
 private RabbitTemplate rabbitTemplate;
 
 @Autowired
 private RedissonClient redissonClient;
 
 
 
 
 
 
 @GetMapping("/miaosha/{skuId}")
 public ResponseVo<Object> kill(@PathVariable("skuId") Long skuId){
 Long userId = LoginInterceptor.getUserInfo().getUserId();
 if(userId!=null){
 
 String stock = this.redisTemplate.opsForValue().get("sec:stock:" + skuId);
 if (StringUtils.isEmpty(stock)){
 return ResponseVo.fail("秒杀结束!");
 }
 
 
 RSemaphore semaphore = this.redissonClient.getSemaphore("sec:semaphore:" + skuId);
 semaphore.trySetPermits(Integer.valueOf(stock));
 
 boolean b = semaphore.tryAcquire();
 if(b){
 
 String orderSn = IdWorker.getTimeId();
 
 SkuLockVO lockVO = new SkuLockVO();
 lockVO.setOrderToken(orderSn);
 lockVO.setCount(1);
 lockVO.setSkuId(skuId);
 
 
 RCountDownLatch latch = this.redissonClient.getCountDownLatch("sec:countdown:" + orderSn);
 latch.trySetCount(1);
 
 this.rabbitTemplate.convertAndSend("ORDER-EXCHANGE", "sec.kill", lockVO);
 return ResponseVo.ok("秒杀成功,订单号:" + orderSn);
 }else {
 return ResponseVo.fail("秒杀失败,欢迎再次秒杀!");
 }
 }
 return ResponseVo.fail("请登录后再试!");
 }
 
 @GetMapping("/miaosha/pay")
 public String payKillOrder(String orderSn) throws InterruptedException {
 
 RCountDownLatch latch = this.redissonClient.getCountDownLatch("sec:countdown:" + orderSn);
 
 latch.await();
 
 
 
 return "";
 }
 
 |