it_lv 1 mēnesi atpakaļ
vecāks
revīzija
0afff85f4f

+ 89 - 58
尚硅谷_尚品甄选_第7章_库存.md

@@ -438,23 +438,18 @@ Integer lock(@Param("skuId") Long skuId, @Param("num")Integer num);
 ##### SkuStockMapper.xml
 
 ```xml
-<?xml version="1.0" encoding="UTF-8" ?>
-<!DOCTYPE mapper
-PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
-"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="com.spzx.product.mapper.SkuStockMapper">
-
-    <!-- 在当前事务范围内添加全局排他锁,其他事务不能对当前数据进行操作 -->
-    <select id="check" resultType="com.spzx.product.domain.SkuStock">
-        select * from sku_stock where sku_id = #{skuId} and available_num >= #{num} for update
+    <!--悲观锁进行库存锁定检查-->
+    <select id="checkStock" resultType="com.spzx.product.domain.SkuStock">
+        SELECT * from sku_stock where sku_id = #{skuId} and available_num >= #{skuNum} and del_flag = 0 for update
     </select>
 
-    <update id="lock">
-        update sku_stock
-        set lock_num = lock_num + #{num}, available_num = available_num - #{num}
-        where sku_id = #{skuId}
+    <!--库存锁定-->
+    <update id="lockStock">
+        UPDATE sku_stock set lock_num = lock_num + #{skuNum} , available_num = available_num - #{skuNum} where sku_id = #{skuId} and  del_flag = 0;
     </update>
-
 </mapper>
 ```
 
@@ -463,6 +458,27 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 ##### SkuStockController
 
 ```java
+package com.spzx.product.controller;
+
+import com.spzx.common.core.domain.R;
+import com.spzx.common.core.web.controller.BaseController;
+import com.spzx.common.security.annotation.InnerAuth;
+import com.spzx.product.service.ISkuStockService;
+import com.spzx.product.vo.SkuLockVo;
+import io.swagger.v3.oas.annotations.Operation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 商品sku库存表 前端控制器
+ * </p>
+ *
+ * @author atguigu
+ * @since 2025-04-28
+ */
 @RestController
 @RequestMapping("/skuStock")
 public class SkuStockController extends BaseController {
@@ -471,18 +487,32 @@ public class SkuStockController extends BaseController {
     private ISkuStockService skuStockService;
 
 
+    /**
+     * 检查与锁定库存,要求不能出现库存超卖
+     * @param orderNo
+     * @param skuLockVoList
+     * @return
+     */
     @InnerAuth
     @Operation(summary = "检查与锁定库存")
     @PostMapping("/checkAndLock/{orderNo}")
     public R<String> checkAndLock(@PathVariable String orderNo, @RequestBody List<SkuLockVo> skuLockVoList) {
-        return R.ok(skuStockService.checkAndLock(orderNo, skuLockVoList));
+        String stockErrorMsg = skuStockService.checkAndLock(orderNo, skuLockVoList);
+        return R.ok(stockErrorMsg);
     }
+
 }
 ```
 
 ##### ISkuStockService
 
 ```java
+/**
+ * 检查并锁定库存,避免库存“超卖”
+ * @param orderNo
+ * @param skuLockVoList
+ * @return
+ */
 String checkAndLock(String orderNo, List<SkuLockVo> skuLockVoList);
 ```
 
@@ -492,59 +522,60 @@ String checkAndLock(String orderNo, List<SkuLockVo> skuLockVoList);
 @Autowired
 private RedisTemplate redisTemplate;
 
-@Transactional(rollbackFor = {Exception.class})
+/**
+ * 检查并锁定库存,避免库存“超卖”
+ *
+ * @param orderNo
+ * @param skuLockVoList
+ * @return
+ */
 @Override
+@Transactional(rollbackFor = Exception.class)
 public String checkAndLock(String orderNo, List<SkuLockVo> skuLockVoList) {
-   
-    // 遍历所有商品,验库存并锁库存
-    skuLockVoList.forEach(skuLockVo -> {
-        // 验库存:查询,返回是当前sku的库存信息
-        SkuStock skuStock = baseMapper.check(skuLockVo.getSkuId(), skuLockVo.getSkuNum());
-        // 如果是null,这里就验库存失败
-        if (null == skuStock) {
-            skuLockVo.setHaveStock(false);//没有足够库存
+    //1.遍历商品扣减VO列表,检查是否满足锁定库存条件,为VO中属性是否有库存赋值
+    for (SkuLockVo skuLockVo : skuLockVoList) {
+        //1.1 判断是否满足锁定库存,采用MySQL的悲观锁来进行锁定:select语句where条件+for update
+        SkuStock skuStock = baseMapper.checkStock(skuLockVo.getSkuId(), skuLockVo.getSkuNum());
+        //1.2 根据select悲观锁结果来判断是否有库存
+        if (skuStock != null) {
+            //有库存就将VO的属性设置为true
+            skuLockVo.setHaveStock(true);
         } else {
-            skuLockVo.setHaveStock(true);//有足够库存
+            //没有库存,将VO的属性设置为false
+            skuLockVo.setHaveStock(false);
         }
-    });
-
-    // 只要有一个商品库存不足
-    if (skuLockVoList.stream().anyMatch(skuLockVo -> !skuLockVo.isHaveStock())) {
-
-        // 过滤出所有库存不足的商品
-        List<SkuLockVo> noStockList = skuLockVoList
-            .stream()
-            .filter(item -> !item.isHaveStock())
-            .collect(Collectors.toList());
-
-        //组装错误提示
-        StringBuffer errorMsg = new StringBuffer();
-        for (SkuLockVo skuLockVo : noStockList) {
-
-			//查询现有库存
-            Integer num = baseMapper.selectById(skuLockVo.getSkuId()).getAvailableNum();
-            errorMsg
-                .append(skuLockVo.getSkuName())
-                .append("库存不足,剩余库存:")
-                .append(num)
-                .append(";");
-		}
-
-		//返回错误信息
-		return errorMsg.toString();
-
-    } else {//所有商品库存充足
-
+    }
+    //2.如果有任意一件商品无法锁定,则锁定失败,封装锁定库存商品提示信息
+    StringBuilder stockErrorMsg = new StringBuilder("");
+    boolean flag = skuLockVoList.stream().anyMatch(skuLockVo -> !skuLockVo.isHaveStock());
+    if (flag) {
+        //2.1遍历商品库存锁定VO列表 找出所有没有锁定成功的商品
         for (SkuLockVo skuLockVo : skuLockVoList) {
-            //锁定库存
-            baseMapper.lock(skuLockVo.getSkuId(), skuLockVo.getSkuNum());
+            if (!skuLockVo.isHaveStock()) {
+                //2.2 查询当前库存不足商品实时可用库存数量
+                SkuStock skuStock = baseMapper.selectOne(
+                        new LambdaQueryWrapper<SkuStock>().eq(SkuStock::getSkuId, skuLockVo.getSkuId())
+                );
+                //2.3 拼接错误提示信息
+                stockErrorMsg.append(skuLockVo.getSkuName())
+                        .append("库存不足,当前剩余:")
+                        .append(skuStock.getAvailableNum())
+                        .append(";");
+            }
         }
     }
+    if (StringUtils.isNotBlank(stockErrorMsg.toString())) {
+        return stockErrorMsg.toString();
+    }
+    //3.如果全部商品可以锁定成功,则进行商品库存锁定
+    for (SkuLockVo skuLockVo : skuLockVoList) {
+        baseMapper.lockStock(skuLockVo.getSkuId(), skuLockVo.getSkuNum());
+    }
 
-    // 如果所有商品都锁定成功的情况下,需要缓存锁定信息到redis。以方便将来解锁库存 或者 减库存
+    //4.将商品锁定库存信息存在Redis-用于取消订单解锁库存/订单支付成功最终库存扣减
     String dataKey = "sku:lock:data:" + orderNo;
-    this.redisTemplate.opsForValue().set(dataKey, skuLockVoList);
-    return "";
+    redisTemplate.opsForValue().set(dataKey, skuLockVoList);
+    return null;
 }
 ```
 

+ 307 - 247
尚硅谷_尚品甄选_第8章_支付.md

@@ -303,6 +303,23 @@ spring:
 
 #### 1.3.5 spzx-payment-dev.yml
 
+在共享配置文件`application-dev.yml`中新增rabbitMQ信息
+
+```yaml
+spring:
+    rabbitmq:
+        host: 192.168.200.10
+        port: 5672
+        username: guest
+        password: guest
+        publisher-confirm-type: CORRELATED #开启生产者确认机制
+        publisher-returns: true #开启生产者退回机制 
+        listener:
+          simple:
+            acknowledge-mode: manual #默认情况下消息消费者是自动确认消息的,如果要手动确认消息则需要修改确认模式为manual
+            prefetch: 1 # 消费者每次从队列获取的消息数量。此属性当不设置时为:轮询分发,设置为1为:公平分发
+```
+
 在nacos上添加商品服务配置文件
 
 ```yaml
@@ -315,24 +332,13 @@ mybatis-plus:
 spring:
   data:
     redis:
-      host: localhost
+      host: 192.168.200.10
       port: 6379
-      password:
-  rabbitmq:
-    host: localhost
-    port: 5672
-    username: guest
-    password: guest
-    publisher-confirm-type: CORRELATED
-    publisher-returns: true
-    listener:
-      simple:
-        cknowledge-mode: manual #默认情况下消息消费者是自动确认消息的,如果要手动确认消息则需要修改确认模式为manual
-        prefetch: 1 # 消费者每次从队列获取的消息数量。此属性当不设置时为:轮询分发,设置为1为:公平分发
+      password: 
   datasource:
     type: com.zaxxer.hikari.HikariDataSource
     driver-class-name: com.mysql.cj.jdbc.Driver
-    url: jdbc:mysql://127.0.0.1:3306/spzx-payment?characterEncoding=utf-8&useSSL=false
+    url: jdbc:mysql://192.168.200.6:3306/spzx-payment?characterEncoding=utf-8&useSSL=false
     username: root
     password: root
     hikari:
@@ -664,12 +670,12 @@ com.spzx.order.api.factory.RemoteOrderInfoFallbackFactory
 
 ```yaml
 alipay:
-  alipay_url: https://openapi.alipay.com/gateway.do
-  app_id: 2021001163617452
-  app_private_key: MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC8Z7EZmanxyFGsK4LrIUeKKrrGxWAHIgPmUV8TtZDs+jeplJSw1ckSY63QhEU444D5qd6xruJHBuB33HG+ik4n8N8nRWi3AtMgpC061oq2DcgtIKMmQHO7/poYDwbpDZrOWXIyiNshFfUOSTUpnrS8UvEks6n6xR/G72r2FG07oZzO7g3XsPMr73wpYajMYC/bhTm6CJGEWZikONNDFkQpVHa+zgitwsqlBuvBvVwGwOHA9B8aRfokwAMl6BDXKoH8BNnSEMpWSTRSwbssayXAQWNU7XKDKGozbn4U2dEbl8GCFzikI/T7ybTNm5gs46ZZBGlq/YB4+v4D3t74Vl6nAgMBAAECggEAOidzhehliYkAlLk1huhV0bMQxewEkQ8RzxTM2SORIWS2q7R+FPtYPkHgU92QFFg85lNltsi5dZ0MylKUFXFRYIi8CL4m7V6E1q12fJPeawVkBXHuig8Y6i1TWRvCUUtuvkTjt++AW/0QECHOtBMVzI95eY+vZwVToq8h/+UcNmxKyVt66Qpo4+r+cUvlvGX5mXgQVC5Ftf/MtHA1i+kjtzBITC0xAvmSXKzjN1YhtcS9rXyMHXBiFhXLdmvOXjkn0Okosr2+tmesXfSwDGhH3ZlOdHzit4D602RNl0nTA1dOUWHuCncs1TrWbriax86P/EYvmzMiHWCVTmmNJC0bMQKBgQD0HAXKNsYsdjCQOV4t3SMqOKaul67x/KA20PmMZVfQ2sQkyjyFgWpL8C16Rzf3zI7df+zF5SkvhFY4+LRZVwX5okEFYTzAZ/NYouj1/DABYOPq0E0sY18/xtq7FJ/CIk8qmCqcczqoyaoxoaC1zAt9E4CYE89iEOnO+GhcI3H3LwKBgQDFlQzvbXhWRyRFkeft/a52XLnyj6t9iP7wNGbGCSeoMDrAu3ZgoqacUPWj5MgSFZdT48H9rF4pPixXoe3jfUNsWBUHqD1F2drDz7lpL0PbpSsgy6ei+D4RwTADsuyXwrkvrWrGro+h6pNJFyly3nea/gloDtJTzfhFFwtNfmqyCQKBgBXzMx4UwMscsY82aV6MZO4V+/71CrkdszZaoiXaswPHuB1qxfhnQ6yiYyR8pO62SR5ns120Fnj8WFh1HJpv9cyVp20ZakIO1tXgiDweOh7VnIjvxBC6usTcV6y81QS62w2Ec0hwIBUvVQtzciUGvP25NDX4igxSYwPGWHP4h/XnAoGAcQN2aKTnBgKfPqPcU4ac+drECXggESgBGof+mRu3cT5U/NS9Oz0Nq6+rMVm1DpMHAdbuqRikq1aCqoVWup51qE0hikWy9ndL6GCynvWIDOSGrLWQZ2kyp5kmy5bWOWAJ6Ll6r7Y9NdIk+NOkw614IFFaNAj2STUw4uPxdRvwD3ECgYEArwOZxR3zl/FZfsvVCXfK8/fhuZXMOp6Huwqky4tNpVLvOyihpOJOcIFj6ZJhoVdmiL8p1/1S+Sm/75gx1tpFurKMNcmYZbisEC7Ukx7RQohZhZTqMPgizlVBTu5nR3xkheaJC9odvyjrWQJ569efXo30gkW04aBp7A15VNG5Z/U=
-  alipay_public_key: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkWs+3gXMosiWG+EbfRyotWB0waqU3t7qMQSBxU0r3JZoND53jvWQfzrGZ8W+obMc+OgwupODDVxhG/DEKVBIptuUQYdvAjCSH98m2hclFcksspuCy9xS7PyflPE47pVzS6vA3Slvw5OFQ2qUcku4paWnBxguLUGPjEncij5NcyFyk+/k57MmrVJwCZaI+lFOS3Eq2IXc07tWXO4s/2SWr3EJiwJutOGBdA1ddvv1Urrl0pWpEFg30pJB6J7YteuxdEL90kuO5ed/vnTK5qgQRvEelROkUW44xONk1784v28OJXmGICmNL1+KyM/SFbFOSgJZSV1tEXUzvL/xvzFpLwIDAQAB
-  return_payment_url: http://ry-spzx.atguigu.cn/#/pages/money/paySuccess
-  notify_payment_url: http://ry-spzx-api.atguigu.cn/payment/alipay/callback/notify
+  alipay_url: https://openapi-sandbox.dl.alipaydev.com/gateway.do
+  app_id: 9021000123601169
+  app_private_key: MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCW770BcTA49wE/YTTyCRpms01xycGmaH/LrCulrn7BeEJdS6xhUDVmpYF3kSdaivZTfPE3vbA/K7BBcWCp3/ouVlIRp73h+pujwNQBx6OYxgoZcxmGLDuu4vZcdQxTq1bwmPmwiDaajA6Qwy4bYVrsJead6Oup/VcrvQ7PxZXYNsCkEe2rCh01RxPErvcic1CNuCPHtJBtEQOIatTjyC9avuywkH6PwDwTSuK+lt0MOlVMYYdfeWwndke4WqDpZzNYrsubrpOi+cVxpSZT+UNZgOBW9n/8QmUb0MmFtgGe295YzbV358j0/gaYbvBl2nmEFTACoO8x6IlFFfJ6v7GPAgMBAAECggEACncU48S1Uz3NGFflE+MNsZE9DWavxpn4Qmbtn885erCb6VwV16l4CmXvVBr8X4rDCYbgMr00Oy0MS31Q9nZRImVZy1+WCtUVdYUpLfZpoW4ZxSn12xe2C6ow6rgy4po+rO/uYA8RuiV73Q2rZLqbU0RNeSVtMZwfLyksnbtVaGhIMNxvbPWf46NFRiq0zWFeW4MtGd+EdsvTyUg5pW+kvcnbd/6vPICpcrgYERxXg4PEVFm/F3MgDvmD8VhPGMERT7ojWIwLSNaYrlfALQFOOTsftg8mzf7iBB39BT3wXjJ0hwPQa/fa8L7Gf8ZDXotZX9/cphmAJZh5IwKSzWOAAQKBgQDEoCuhR8s7oMXG+yjUZmH4zd27UQFbgfi/ROMR1BM9hC9Md9VrpimgfSq4sTKdaMUs+Kx2HZ+qexvDINuR/kBjssFqNopP6ocr1NqpIkNoJvdapfPDwNvr5owCV11jGMDHtPYstCjR9nAQjkvlB+UBmkuPIP9XTSlxxEqjtrOZAQKBgQDEg6OT+JhMU2z3hetgeerShEYXgM5Xt7K2dG5bh/jg2yWjuQ9mHPltbJLv1/XKdqcxyP+wvTf7Pr5o8AunMlqGltwUn8R/1v1U08CoMaB9wjxmE1QJ6fP+cpBuqTjA/rxJfJyGEpqE7q7nsuH3Q+PlQLQwxVdTb/qn+6CdMMM6jwKBgQCiZvDiaxwPigEREo1ssYumJtNHTebjH/zGbi1molWp763paOjb2whyyC8rESCPJuQE+vfnLxybgMczacYxVWD1d8WM5rNiwoEW0N4x6Fzof5a6RmuKeZarx2ZI4MP/f4hq6qqRzdQRZ4yz//PrnZOmuTxDYavE/bSHhOtpQRQiAQKBgQCEexKOMSvLWPq/LENweQC8B3yRX768GBWdN4Umo9Y/EL6hT6N3/dSR1n6AvyDu62rv/0eRSR8yfCw15o6Umze4QIACT2tTnAaRZ2gce8hsv8OkNSeUv2pEgcupLuAaDCG3kRCcunH9aEP14/y+i8BZz3Mq5f9nOrjLArJXBoa3NQKBgETSQyLlceqC9m0qZYQBBycS7KGldOV+vTILOLQafWp3OPNc2kuLd7yQspj8WjAJBkhrcqmw6Pf7tt5E6XRTi6+Z47Zc/PIDSixhYRotdkaELxCY5P3gmAtRNUmP/hv9WZJyGygxAUO9mC/CZkAjWg0JM7yOl3f7+n8utIahKHAM
+  alipay_public_key: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwoLuiTrn/cTUqv147Bzu9jl0heV3P+dlISB4UulkR8NSQ66h+WGROn/Y6FxUNBiz5++mS9wOpYA2fFikueTeaC5s2kNtDnO9Gb6eduX9sOi/cDbu5kaedwzld4Zg4kLYm304EVILAah8U83Go7VngbeV+55RG700mAm+DSd3GVCRXnlUsDilxK+IfzOhvLbMGWSOsHy0K9IwDI+HfHKW6iN5jW5NE+mQMbyUKquPg12EnBkXtnj7i8q4FY1o3+jbUt906tXMKTWPBOG3JwqRv2ndaB71mQB9Ae4wcCdREco5RnnRbg8kMJzkGApDicsr9+TmjB16vJ5+CB+hce7LFwIDAQAB
+  return_payment_url: http://localhost:81/#/pages/money/paySuccess
+  notify_payment_url: http://zf896e63.natappfree.cc/payment/alipay/callback/notify
 ```
 
 沙箱版本
@@ -761,13 +767,18 @@ public class AlipayController extends BaseController {
     @Autowired
     private IAlipayService alipayService;
 
-    @Operation(summary = "支付宝下单")
-    @RequiresLogin
-    @RequestMapping("/submitAlipay/{orderNo}")
-    @ResponseBody
-    public AjaxResult submitAlipay(@PathVariable(value = "orderNo") String orderNo) {
-        String form = alipayService.submitAlipay(orderNo);
-        return success(form);
+       /**
+     * 对接支付宝返回支付表单用于H5端支付
+     * @param orderNo
+     * @return
+     */
+    @Operation(summary = "对接支付宝返回支付表单用于H5端支付")
+    @GetMapping("/submitAlipay/{orderNo}")
+    public AjaxResult submitAlipay(@PathVariable String orderNo){
+        //1.获取支付宝支付表单
+        String payForm = alipayService.submitAlipay(orderNo);
+        //2.响应支付宝支付表单,用于H5端支付
+        return success(payForm);
     }
 
 }
@@ -802,36 +813,51 @@ public class AlipayServiceImpl implements IAlipayService {
     @Autowired
     private IPaymentInfoService paymentInfoService;
 
-    @SneakyThrows
+       /**
+     * 对接支付宝返回支付表单用于H5端支付
+     *
+     * @param orderNo
+     * @return
+     */
     @Override
     public String submitAlipay(String orderNo) {
-        //保存支付记录
-        PaymentInfo paymentInfo = paymentInfoService.savePaymentInfo(orderNo);
-        // 创建请求对象
-        AlipayTradeWapPayRequest alipayRequest = new AlipayTradeWapPayRequest();//创建API对应的request
-        // 同步回调
-        alipayRequest.setReturnUrl(AlipayConfig.return_payment_url);
-        // 异步回调
-        alipayRequest.setNotifyUrl(AlipayConfig.notify_payment_url);
-
-        /******必传参数******/
-        JSONObject bizContent = new JSONObject();
-        //商户订单号,商家自定义,保持唯一性
-        bizContent.put("out_trade_no", paymentInfo.getOrderNo());
-        //支付金额,最小值0.01元
-        //bizContent.put("total_amount", paymentInfo.getAmount());
-        bizContent.put("total_amount", 0.01); 
-        
-        //订单标题,不可使用特殊符号
-        bizContent.put("subject", paymentInfo.getContent());
-        /******可选参数******/
-        //手机网站支付默认传值QUICK_WAP_WAY
-        bizContent.put("product_code", "QUICK_WAP_WAY");
-
-       alipayRequest.setBizContent(bizContent.toString());
-        AlipayTradeWapPayResponse response = alipayClient.pageExecute(alipayRequest,"POST");
-
-        return response.getBody(); //调用SDK生成表单;
+        try {
+            //1.保存本地交易记录
+            PaymentInfo paymentInfo = paymentInfoService.savePaymentInfo(orderNo);
+            if (!"0".equals(paymentInfo.getPaymentStatus())) {
+                throw new ServiceException("本地交易记录支付状态有误");
+            }
+            //2.对接支付宝服务端,产生一笔支付宝交易,获取支付宝支付表单
+            // 创建请求对象
+            AlipayTradeWapPayRequest alipayRequest = new AlipayTradeWapPayRequest();//创建API对应的request
+            // 同步回调 应用引导用户去到成功页面
+            alipayRequest.setReturnUrl(AlipayConfig.return_payment_url);
+            // 异步回调 用户付款成功后,支付宝异步通知商户系统
+            alipayRequest.setNotifyUrl(AlipayConfig.notify_payment_url);
+
+            /******必传参数******/
+            JSONObject bizContent = new JSONObject();
+            //商户订单号,商家自定义,保持唯一性
+            bizContent.put("out_trade_no", paymentInfo.getOrderNo());
+            //支付金额,最小值0.01元
+            //bizContent.put("total_amount", paymentInfo.getAmount());
+            bizContent.put("total_amount", paymentInfo.getAmount());
+
+            //订单标题,不可使用特殊符号
+            bizContent.put("subject", paymentInfo.getContent());
+            /******可选参数******/
+            //手机网站支付默认传值QUICK_WAP_WAY
+            bizContent.put("product_code", "QUICK_WAP_WAY");
+
+            alipayRequest.setBizContent(bizContent.toString());
+            AlipayTradeWapPayResponse response =
+                    alipayClient.pageExecute(alipayRequest, "POST");
+
+            return response.getBody(); //调用SDK生成表单;
+        } catch (AlipayApiException e) {
+            log.error("支付宝支付异常:{}", e);
+            throw new RuntimeException(e);
+        }
     }
 }
 ```
@@ -848,40 +874,51 @@ PaymentInfo savePaymentInfo(String orderNo);
 @Autowired
 private RemoteOrderInfoService remoteOrderInfoService;
 
+/**
+ * 保存本地交易记录信息
+ *
+ * @param orderNo
+ * @return
+ */
 @Override
+@Transactional(rollbackFor = Exception.class)
 public PaymentInfo savePaymentInfo(String orderNo) {
-
-    //防止重复提交:如果支付日志已存在则直接返回
-    PaymentInfo paymentInfo = baseMapper.selectOne(
-        new LambdaQueryWrapper<PaymentInfo>().eq(PaymentInfo::getOrderNo, orderNo)
+    //1.根据订单编号查询本地记录记录,存在则返回
+    PaymentInfo paymentInfo = this.getOne(
+            new LambdaQueryWrapper<PaymentInfo>()
+                    .eq(PaymentInfo::getOrderNo, orderNo)
     );
-
-    if(paymentInfo != null) {
+    if (paymentInfo != null) {
         return paymentInfo;
     }
-
-    //根据订单号获取订单信息
-    R<OrderInfo> orderInfoResult = remoteOrderInfoService.getByOrderNo(orderNo, SecurityConstants.INNER);
-    if (R.FAIL == orderInfoResult.getCode()) {
-        throw new ServiceException(orderInfoResult.getMsg());
+    //2.构建本地交易记录对象 paymentInfo
+    PaymentInfo paymentInfo1 = new PaymentInfo();
+    paymentInfo1.setCreateBy(SecurityContextHolder.getUserName());
+    paymentInfo1.setUserId(SecurityContextHolder.getUserId());
+    paymentInfo1.setOrderNo(orderNo);
+    paymentInfo1.setPayType(2);
+    //3.根据订单编号远程调用订单服务获取订单及订单明细信息
+    R<OrderInfo> r = remoteOrderInfoService.getByOrderNo(orderNo, SecurityConstants.INNER);
+    if (R.FAIL == r.getCode()) {
+        throw new RuntimeException("远程调用订单服务失败,原因:" + r.getMsg());
+    }
+    OrderInfo orderInfo = r.getData();
+    //4. 判断订单状态,如果是已支付或者已关闭,则直接返回
+    if (orderInfo.getOrderStatus() != 0) {
+        throw new RuntimeException("订单状态异常,不能进行支付");
     }
-    OrderInfo orderInfo = orderInfoResult.getData();
-
-    paymentInfo = new PaymentInfo();
-    paymentInfo.setUserId(orderInfo.getUserId());
-    String content = orderInfo.getOrderItemList()
-        .stream()
-        // 将每个 OrderItem 变换为其 SKU 名称
-        .map(OrderItem::getSkuName)
-        // 使用空格连接所有 SKU 名称
-        .collect(Collectors.joining(" ")); 
-
-    paymentInfo.setContent(content);
-    paymentInfo.setAmount(orderInfo.getTotalAmount());
-    paymentInfo.setOrderNo(orderNo);
-    paymentInfo.setPaymentStatus(0);
-    baseMapper.insert(paymentInfo);
-    return paymentInfo;
+    paymentInfo1.setAmount(orderInfo.getTotalAmount());
+    paymentInfo1.setContent(orderInfo.getOrderItemList().get(0).getSkuName());
+    paymentInfo1.setPaymentStatus("0");
+
+    //TODO 支付宝端交易编号、回调时间、回调内容 等 用户付款成功,支付宝会回调商户系统再更新三个字段
+    //paymentInfo1.setTradeNo();
+    //paymentInfo1.setCallbackTime();
+    //paymentInfo1.setCallbackContent();
+
+    //4.保存本地交易记录
+    this.save(paymentInfo1);
+    return paymentInfo1;
 }
 ```
 
@@ -894,11 +931,16 @@ public PaymentInfo savePaymentInfo(String orderNo) {
 #### 1.8.1 AlipayController
 
 ```java
-@RequestMapping("/callback/notify")
-@ResponseBody
-public String alipayNotify(@RequestParam Map<String, String> paramMap, HttpServletRequest request) {
-    log.info("AlipayController...alipayNotify方法执行了...");
-    return "success" ;
+/**
+ * 支付宝回调:用户付款成功后,支付宝会通知商户系统支付结果
+ * @param paramMap
+ * @return
+ */
+@Operation(summary = "用户付款成功后,支付宝会通知商户系统支付结果")
+@PostMapping("/callback/notify")
+public String paySuccessNotify(@RequestParam Map<String, String> paramMap){
+    String result = alipayService.paySuccessNotify(paramMap);
+    return result;
 }
 ```
 
@@ -989,94 +1031,76 @@ authtoken信息获取:
 ##### AlipayController
 
 ```java
-@Autowired
-private IPaymentInfoService paymentInfoService;
-
-@Autowired
-private RemoteOrderInfoService remoteOrderInfoService;
-
-@Autowired
-private RabbitService rabbitService;
-
+/**
+ * 支付宝回调:用户付款成功后,支付宝会通知商户系统支付结果
+ * @param paramMap
+ * @return
+ */
+@Operation(summary = "用户付款成功后,支付宝会通知商户系统支付结果")
 @PostMapping("/callback/notify")
-@ResponseBody
-public String alipayNotify(@RequestParam Map<String, String> paramMap) {
+public String paySuccessNotify(@RequestParam Map<String, String> paramMap){
+    String result = alipayService.paySuccessNotify(paramMap);
+    return result;
+}
+```
+
+##### AlipayService
 
-    log.info("alipayNotify方法执行了...");
+```java
+/**
+ * 支付宝回调:用户付款成功后,支付宝会通知商户系统支付结果
+ * @param paramMap
+ * @return
+ */
+String paySuccessNotify(Map<String, String> paramMap);
+```
 
-    String result = "failure";
+##### AlipayServiceImpl
 
+```java
+/**
+ * 支付宝回调:用户付款成功后,支付宝会通知商户系统支付结果
+ *
+ * @param paramMap
+ * @return
+ */
+@Override
+public String paySuccessNotify(Map<String, String> paramMap) {
     try {
-        //调用SDK验证签名
-        boolean signVerified = AlipaySignature.rsaCheckV1(
-            paramMap,
-            AlipayConfig.alipay_public_key,
-            AlipayConfig.charset,
-            AlipayConfig.sign_type
+        //1.验证签名 避免出现虚假通知 ,确保该接口调用方是支付宝/防止数据被篡改
+        log.info("支付宝支付成功回调参数:{}", paramMap);
+        boolean flag = AlipaySignature.rsaCheckV1(
+                paramMap,
+                AlipayConfig.alipay_public_key,
+                AlipayConfig.charset,
+                AlipayConfig.sign_type
         );
-
-
-        //校验验签是否成功
-        String outTradeNo = paramMap.get("out_trade_no");
-        if(!signVerified){
-            log.error("订单 {} 验签失败", outTradeNo);
-            return result;
-        }
-        log.error("验签成功!");
-
-        //商家需要验证该通知数据中的 out_trade_no 是否为商家系统中创建的订单号。
-        R<OrderInfo> orderInfoResult = remoteOrderInfoService.getByOrderNo(outTradeNo, SecurityConstants.INNER);
-        if(R.FAIL == orderInfoResult.getCode()){
-            log.error("远程获取订单 {} 失败", outTradeNo);
-            return result;
-        }
-        if(orderInfoResult.getData() == null){
-            log.error("订单 {} 不存在", outTradeNo);
-            return result;
-        }
-        OrderInfo orderInfo = orderInfoResult.getData();
-
-        // 判断 total_amount 是否确实为该订单的实际金额(即商户订单创建时的金额)。
-        String totalAmount = paramMap.get("total_amount");
-        if(orderInfo.getTotalAmount().compareTo(new BigDecimal(totalAmount)) != 0){
-            log.error("订单 {} 金额不一致", outTradeNo);
-            return result;
-        }
-
-        // 校验通知中的 seller_id(或者 seller_email) 是否为 out_trade_no 这笔单据的对应的操作方(有的时候,一个商家可能有多个 seller_id/seller_email)。
-        // "2088721032347805" 沙箱中的 商户PID
-        String sellerId = paramMap.get("seller_id");
-        if(!"2088721032347805".equals(sellerId)){
-            log.error("订单 {} 商家不一致", outTradeNo);
-            return result;
+        if (!flag) {
+            log.error("支付宝支付成功回调:签名验证失败");
+            return "failure";
         }
-
-        //验证 app_id 是否为该商家本身。
-        String appId = paramMap.get("app_id");
-        if(!AlipayConfig.app_id.equals(appId)){
-            log.error("订单 {} appid不一致", outTradeNo);
-            return result;
-        }
-
-        // 交易状态
-        String tradeStatus = paramMap.get("trade_status");
-        if ("TRADE_SUCCESS".equals(tradeStatus) || "TRADE_FINISHED".equals(tradeStatus)) {
-            // 正常的支付成功,我们应该更新交易记录状态
-            paymentInfoService.updatePaymentStatus(paramMap, 2);
-            
-           //基于MQ通知订单系统,修改订单状态
-           rabbitService.sendMessage(MqConst.EXCHANGE_PAYMENT_PAY, MqConst.ROUTING_PAYMENT_PAY, paymentInfo.getOrderNo());
-           //基于MQ通知商品系统,扣减库存
-           rabbitService.sendMessage(MqConst.EXCHANGE_PRODUCT, MqConst.ROUTING_MINUS, outTradeNo);
-                
-            return "success";
+        //2.基于通知ID进行幂等性处理
+        String notifyId = paramMap.get("notify_id");
+        String key = "pay:alipay:notifyId:" + notifyId;
+        Boolean onece = redisTemplate.opsForValue().setIfAbsent(key, notifyId, 25, TimeUnit.HOURS);
+        if (onece) {
+            try {
+                //3.验证金额、支付状态 -- 确保用户付款成功
+                String tradeStatus = paramMap.get("trade_status");
+                if ("TRADE_SUCCESS".equals(tradeStatus) || "TRADE_FINISHED".equals(tradeStatus)) {
+                    //4.更新本地交易记录状态:已支付
+                    paymentInfoService.updatePaymentStatus(paramMap);
+                    return "success";
+                }
+            } catch (Exception e) {
+                redisTemplate.delete(key);
+                throw new RuntimeException(e);
+            }
         }
-
     } catch (AlipayApiException e) {
-        e.printStackTrace();
+        throw new RuntimeException(e);
     }
-
-    return result;
+    return null;
 }
 ```
 
@@ -1085,31 +1109,45 @@ public String alipayNotify(@RequestParam Map<String, String> paramMap) {
 更新支付信息
 
 ```java
-void updatePaymentStatus(Map<String, String> map, Integer payType);
+/**
+     * 用户付款成功后,更新本地交易记录
+     * @param paramMap
+     */
+void updatePaymentStatus(Map<String, String> paramMap);
 ```
 
 ##### PaymentInfoServiceImpl
 
 ```java
-@Transactional(rollbackFor = Exception.class)
+/**
+ * 用户付款成功后,更新本地交易记录
+ *
+ * @param paramMap
+ */
 @Override
-public void updatePaymentStatus(Map<String, String> map, Integer payType) {
-   PaymentInfo paymentInfo = baseMapper.selectOne(
-       new LambdaQueryWrapper<PaymentInfo>()
-       .eq(PaymentInfo::getOrderNo, map.get("out_trade_no"))
-   );
-    //已支付,直接返回
-   if (paymentInfo.getPaymentStatus() == 1) {
-      return;
-   }
-
-   //更新支付信息
-   paymentInfo.setPayType(payType);
-   paymentInfo.setPaymentStatus(1);
-   paymentInfo.setTradeNo(map.get("trade_no"));
-   paymentInfo.setCallbackTime(new Date());
-   paymentInfo.setCallbackContent(JSON.toJSONString(map));
-   baseMapper.updateById(paymentInfo);
+public void updatePaymentStatus(Map<String, String> paramMap) {
+    //1.根据订单编号查询本地交易记录 验证:用户实付金额跟商户侧应付金额是否一致
+    String orderNo = paramMap.get("out_trade_no");
+    PaymentInfo paymentInfo = this.getOne(
+            new LambdaQueryWrapper<PaymentInfo>()
+                    .eq(PaymentInfo::getOrderNo, orderNo)
+    );
+    String buyerPayAmount = paramMap.get("buyer_pay_amount");
+    if (paymentInfo.getAmount().compareTo(new BigDecimal(buyerPayAmount)) != 0) {
+        log.error("该笔订单实付金额与交易记录金额不一致,订单编号:{}", orderNo);
+        throw new RuntimeException("该笔订单实付金额与交易记录金额不一致,订单编号:" + orderNo);
+    }
+    //2.更新本地交易记录:状态:已支付、支付宝订单号、回调时间、回调内容
+    paymentInfo.setPaymentStatus("1");
+    paymentInfo.setTradeNo(paramMap.get("trade_no"));
+    paymentInfo.setCallbackTime(new Date());
+    paymentInfo.setCallbackContent(paramMap.toString());
+    this.updateById(paymentInfo);
+
+    //3.发送MQ消息通知订单服务修改订单状态
+    rabbitService.sendMessage(MqConst.EXCHANGE_PAYMENT_PAY, MqConst.ROUTING_PAYMENT_PAY, orderNo);
+    //4.发送MQ消息通知商品服务库存扣减
+    rabbitService.sendMessage(MqConst.EXCHANGE_PRODUCT, MqConst.ROUTING_MINUS, orderNo);
 
 }
 ```
@@ -1129,34 +1167,51 @@ public void updatePaymentStatus(Map<String, String> map, Integer payType) {
 ```java
 package com.spzx.order.receiver;
 
+import com.rabbitmq.client.Channel;
+import com.spzx.common.core.utils.StringUtils;
+import com.spzx.common.rabbit.constant.MqConst;
+import com.spzx.order.service.OrderInfoService;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.amqp.core.Message;
+import org.springframework.amqp.rabbit.annotation.Exchange;
+import org.springframework.amqp.rabbit.annotation.Queue;
+import org.springframework.amqp.rabbit.annotation.QueueBinding;
+import org.springframework.amqp.rabbit.annotation.RabbitListener;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+
+/**
+ * @author: atguigu
+ * @create: 2025-05-10 16:34
+ */
 @Slf4j
 @Component
 public class OrderReceiver {
 
     @Autowired
-    private IOrderInfoService orderInfoService;
+    private OrderInfoService orderInfoService;
 
     /**
-     * 监听订单支付成功消息;更新订单状态
+     * 监听订单支付成功消息,修改订单状态
      *
      * @param orderNo
      * @param message
      * @param channel
      */
-    @SneakyThrows
     @RabbitListener(bindings = @QueueBinding(
             exchange = @Exchange(value = MqConst.EXCHANGE_PAYMENT_PAY, durable = "true"),
-            value = @Queue(value = MqConst.QUEUE_PAYMENT_PAY, durable = "true"),
+            value = @Queue(MqConst.QUEUE_PAYMENT_PAY),
             key = MqConst.ROUTING_PAYMENT_PAY
     ))
-    public void processPaySucess(String orderNo, Message message, Channel channel) {
-        //业务处理
-        if (StringUtils.isNotEmpty(orderNo)) {
-            log.info("[订单服务]监听订单支付成功消息:{}", orderNo);
-            //更改订单支付状态
-            orderInfoService.processPaySuccess(orderNo);
+    @SneakyThrows
+    public void paymentSuccess(String orderNo, Message message, Channel channel) {
+        if (StringUtils.isNotBlank(orderNo)) {
+            log.info("[订单服务]监听到订单支付成功消息:{}", orderNo);
+            orderInfoService.paymentSuccess(orderNo);
         }
-        //手动应答
         channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
     }
 }
@@ -1165,25 +1220,23 @@ public class OrderReceiver {
 #### 2.1.2 IOrderInfoService
 
 ```java
-void processPaySuccess(String orderNo);
+void paymentSuccess(String orderNo);
 ```
 
 #### 2.1.3 OrderInfoServiceImpl
 
 ```java
+/**
+ * 订单支付成功消息,修改订单状态
+ *
+ * @param orderNo
+ */
 @Override
-public void processPaySuccess(String orderNo) {
-    //获取订单信息
-    OrderInfo orderInfo = orderInfoMapper.selectOne(
-        new LambdaQueryWrapper<OrderInfo>()
-        .eq(OrderInfo::getOrderNo, orderNo)
-        .select(OrderInfo::getId, OrderInfo::getOrderStatus)
-    );
-  
-    if(orderInfo.getOrderStatus().intValue() == 0) {
-        orderInfo.setOrderStatus(1);//已支付
-        orderInfo.setPaymentTime(new Date());
-        orderInfoMapper.updateById(orderInfo);
+public void paymentSuccess(String orderNo) {
+    OrderInfo orderInfo = this.getByOrderNo(orderNo);
+    if (orderInfo.getOrderStatus().intValue()==0) {
+        orderInfo.setOrderStatus(1);
+        this.updateById(orderInfo);
     }
 }
 ```
@@ -1195,78 +1248,85 @@ public void processPaySuccess(String orderNo) {
 #### 2.2.1 ProductReceiver
 
 ```java
-/**
- * 扣减库存
- * @param orderNo  订单号
+/***
+ * 监听订单支付成功后扣减订单中商品库存数量
+ * @param orderNo
+ * @param channel
+ * @param message
+ * @throws IOException
  */
 @SneakyThrows
 @RabbitListener(bindings = @QueueBinding(
         exchange = @Exchange(value = MqConst.EXCHANGE_PRODUCT, durable = "true"),
-        value = @Queue(value = MqConst.QUEUE_MINUS, durable = "true"),
-        key = {MqConst.ROUTING_MINUS}
+        value = @Queue(MqConst.QUEUE_MINUS),
+        key = MqConst.ROUTING_MINUS
 ))
 public void minus(String orderNo, Channel channel, Message message) {
-    //业务处理
-    if (StringUtils.isNotEmpty(orderNo)){
-        log.info("[商品服务]监听减库存消息:{}", orderNo);
-        //扣减库存
-        productService.minus(orderNo);
+    if (StringUtils.isNotBlank(orderNo)) {
+        log.info("[订单服务]监听到订单支付成功,减库存消息:{}", orderNo);
+        //1.幂等性处理
+        String key = "order:minus:orderNo:" + orderNo;
+        Boolean once = redisTemplate.opsForValue().setIfAbsent(key, orderNo, 5, TimeUnit.MINUTES);
+        if (!once) {
+            return;
+        }
+        try {
+            //2.业务逻辑处理
+            skuStockService.minusStock(orderNo);
+        } catch (Exception e) {
+            redisTemplate.delete(key);
+            throw new RuntimeException(e);
+        }
+        //3.手动应答
+        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
     }
-
-    //手动应答
-    channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
 }
 ```
 
 #### 2.3.2 IProductService
 
 ```java
-void minus(String orderNo);
+/**
+ * 从下单时存入Redis缓存锁定记录,进行库存真正扣减
+ * @param orderNo
+ */
+void minusStock(String orderNo);
 ```
 
 #### 2.2.3 ProductServiceImpl
 
 ```java
-@Transactional(rollbackFor = {Exception.class})
+/**
+ * 从下单时存入Redis缓存锁定记录,进行库存真正扣减
+ *
+ * @param orderNo
+ */
 @Override
-public void minus(String orderNo) {
-
-    //幂等性处理
-    String key = "sku:minus:" + orderNo;
-    //业务去重,防止重复消费
-    Boolean isExist = redisTemplate.opsForValue().setIfAbsent(key, orderNo, 1, TimeUnit.HOURS);
-    if(!isExist) return;
-
-    // 获取锁定库存的缓存信息
+public void minusStock(String orderNo) {
     String dataKey = "sku:lock:data:" + orderNo;
-    List<SkuLockVo> skuLockVoList = (List<SkuLockVo>)this.redisTemplate.opsForValue().get(dataKey);
-    if (CollectionUtils.isEmpty(skuLockVoList)){
-        return ;
+    List<SkuLockVo> skuLockVoList = (List<SkuLockVo>) redisTemplate.opsForValue().get(dataKey);
+    if (CollectionUtils.isEmpty(skuLockVoList)) {
+        return;
     }
-
-    // 减库存
+    //遍历锁定记录列表,进行真正扣减库存
     skuLockVoList.forEach(skuLockVo -> {
-        int row = skuStockMapper.minus(skuLockVo.getSkuId(), skuLockVo.getSkuNum());
+        baseMapper.minusStock(skuLockVo.getSkuId(), skuLockVo.getSkuNum());
     });
-
-    // 扣减库存之后,删除锁定库存的缓存。
-    this.redisTemplate.delete(dataKey);
+    redisTemplate.delete(dataKey);
 }
 ```
 
 #### 2.2.4 SkuStockMapper
 
 ```java
-Integer minus(@Param("skuId") Long skuId, @Param("num")Integer num);
+void minusStock(@Param("skuId") Long skuId,@Param("skuNum") Integer skuNum);
 ```
 
 #### 2.2.5 SkuStockMapper.xml
 
 ```xml
-<update id="minus">
-    update sku_stock
-    set lock_num = lock_num - #{num}, total_num = total_num - #{num}, sale_num = sale_num + #{num}
-    where sku_id = #{skuId}
+<update id="minusStock">
+    UPDATE sku_stock set lock_num = lock_num - #{skuNum} , total_num = total_num - #{skuNum} where sku_id = #{skuId} and  del_flag = 0;
 </update>
 ```