Kaynağa Gözat

提交优惠券想过代码,用于优惠券核销和退回

wangzhijun 2 gün önce
ebeveyn
işleme
27080283b7

+ 25 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/mapper/CouponReceiveMapper.java

@@ -1,11 +1,14 @@
 package com.ylx.massage.mapper;
 
+import java.time.LocalDateTime;
 import java.util.List;
+import java.util.Map;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.ylx.massage.domain.vo.CouponReceiveVo;
 import org.apache.ibatis.annotations.Param;
 import com.ylx.massage.domain.CouponReceive;
+import org.apache.ibatis.annotations.Select;
 
 /**
  * 优惠券领取表(CouponReceive)表数据库访问层
@@ -34,5 +37,27 @@ public interface CouponReceiveMapper extends BaseMapper<CouponReceive> {
 
 
     List<CouponReceiveVo> getByOpenId(String openid);
+
+    Map<String, Object> selectCouponDetailForCalc(@Param("couponId") String couponId,
+                                                  @Param("openId") String openId);
+
+    // 乐观锁核销
+    int useCouponOptimisticLock(@Param("couponId") String couponId,
+                                @Param("openId") String openId,
+                                @Param("orderId") Long orderId,
+                                @Param("orderType") Integer orderType,
+                                @Param("useTime") LocalDateTime useTime);
+
+    // 使用数量 +1
+    int incrementUsedNum(@Param("couponId") String couponId);
+
+    // 乐观锁退还
+    int returnCouponOptimisticLock(@Param("couponId") String couponId,
+                                   @Param("openId") String openId,
+                                   @Param("orderId") Long orderId);
+
+    // 使用数量 -1
+    int decrementUsedNum(@Param("couponId") String couponId);
+
 }
 

+ 11 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/service/CouponReceiveService.java

@@ -8,7 +8,9 @@ import com.ylx.massage.domain.vo.ClaimCouponRequestVO;
 import com.ylx.massage.domain.vo.CouponReceiveVo;
 import com.ylx.massage.domain.vo.CouponReceivesVO;
 
+import java.time.LocalDateTime;
 import java.util.List;
+import java.util.Map;
 
 /**
  * 优惠券领取表(CouponReceive)表服务接口
@@ -48,5 +50,14 @@ public interface CouponReceiveService extends IService<CouponReceive> {
     List<Coupon> couponWindows(CouponReceive couponReceive);
 
 
+    Map<String, Object> selectCouponDetailForCalc(String couponId, String openId);
+
+    int useCouponOptimisticLock(String couponId, String openId, Long orderId, Integer orderType, LocalDateTime now);
+
+    int incrementUsedNum(String couponId);
+
+    int returnCouponOptimisticLock(String couponId, String openId, Long orderId);
+
+    void decrementUsedNum(String couponId);
 }
 

+ 8 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/service/CouponService.java

@@ -4,6 +4,8 @@ package com.ylx.massage.service;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.ylx.massage.domain.Coupon;
 
+import java.math.BigDecimal;
+
 /**
  * 优惠券的规则信息(Coupon)表服务接口
  *
@@ -12,5 +14,11 @@ import com.ylx.massage.domain.Coupon;
  */
 public interface CouponService extends IService<Coupon> {
 
+    BigDecimal calculateDiscountAmount(String couponId, String openId,BigDecimal orderAmount);
+
+    void useCoupon(String couponId, String openId, Long orderId, Integer orderType);
+
+    void returnCoupon(String couponId, String openId, Long orderId);
+
 }
 

+ 26 - 4
nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/CouponReceiveServiceImpl.java

@@ -29,10 +29,7 @@ import javax.annotation.Resource;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
 import java.time.LocalDateTime;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Date;
-import java.util.List;
+import java.util.*;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
@@ -266,5 +263,30 @@ public class CouponReceiveServiceImpl extends ServiceImpl<CouponReceiveMapper, C
             return Collections.emptyList();
         }
     }
+
+    @Override
+    public Map<String, Object> selectCouponDetailForCalc(String couponId, String openId) {
+        return this.baseMapper.selectCouponDetailForCalc(couponId, openId);
+    }
+
+    @Override
+    public int useCouponOptimisticLock(String couponId, String openId, Long orderId, Integer orderType, LocalDateTime now) {
+        return this.baseMapper.useCouponOptimisticLock(couponId, openId, orderId, orderType, now);
+    }
+
+    @Override
+    public int incrementUsedNum(String couponId) {
+        return this.baseMapper.incrementUsedNum(couponId);
+    }
+
+    @Override
+    public int returnCouponOptimisticLock(String couponId, String openId, Long orderId) {
+        return this.baseMapper.returnCouponOptimisticLock(couponId,openId,orderId);
+    }
+
+    @Override
+    public void decrementUsedNum(String couponId) {
+        this.baseMapper.decrementUsedNum(couponId);
+    }
 }
 

+ 109 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/CouponServiceImpl.java

@@ -1,11 +1,26 @@
 package com.ylx.massage.service.impl;
 
 
+import cn.hutool.core.collection.CollUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ylx.common.exception.ServiceException;
 import com.ylx.massage.domain.Coupon;
+import com.ylx.massage.domain.CouponReceive;
 import com.ylx.massage.mapper.CouponMapper;
+import com.ylx.massage.service.CouponReceiveService;
 import com.ylx.massage.service.CouponService;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
 
 /**
  * 优惠券的规则信息(Coupon)表服务实现类
@@ -13,8 +28,102 @@ import org.springframework.stereotype.Service;
  * @author makejava
  * @since 2024-05-13 16:32:59
  */
+@Slf4j
 @Service("couponService")
 public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> implements CouponService {
 
+    @Resource
+    private CouponReceiveService couponReceiveService;
+
+    @Override
+    public BigDecimal calculateDiscountAmount(String couponId, String openId, BigDecimal orderAmount) {
+
+        // 1. 查询优惠券领取详情
+        Map<String, Object> detail = couponReceiveService.selectCouponDetailForCalc(couponId, openId);
+
+        if (CollUtil.isEmpty(detail)) {
+            throw new ServiceException("未查询到该用户的优惠券信息");
+        }
+
+        // 3. 校验优惠券状态(必须是 0:待使用)
+        Integer couponStatus = (Integer) detail.get("coupon_status");
+        if (couponStatus == null || couponStatus != 0) {
+            throw new ServiceException("优惠券状态异常,不可使用");
+        }
+
+        // 4. 校验有效期
+        LocalDate validStart = ((java.sql.Date) detail.get("valid_start_time")).toLocalDate();
+        LocalDate expiration = ((java.sql.Date) detail.get("expiration_time")).toLocalDate();
+        LocalDate today = LocalDate.now();
+        if (today.isBefore(validStart) || today.isAfter(expiration)) {
+            throw new ServiceException("优惠券不在有效期内");
+        }
+
+        // 5. 提取优惠规则字段
+        Integer discountType = (Integer) detail.get("discount_type");
+        BigDecimal discountValue = (BigDecimal) detail.get("discount_value");
+        BigDecimal rebValue = (BigDecimal) detail.get("reb_value");
+        BigDecimal thresholdAmount = (BigDecimal) detail.get("threshold_amount");
+
+        // 6. 根据类型计算抵扣金额
+        BigDecimal discountAmount = BigDecimal.ZERO;
+        switch (discountType) {
+            case 1: // 无门槛
+                discountAmount = discountValue;
+                break;
+            case 2: // 折扣 (reb_value 例如 0.8 代表8折)
+                // 抵扣金额 = 订单金额 * (1 - 折扣值)
+                discountAmount = orderAmount.multiply(BigDecimal.ONE.subtract(rebValue));
+                break;
+            case 3: // 满减
+                if (orderAmount.compareTo(thresholdAmount) < 0) {
+                    throw new ServiceException("订单金额未达到满减门槛");
+                }
+                discountAmount = discountValue;
+                break;
+            default:
+                throw new ServiceException("未知的优惠券类型: " + discountType);
+        }
+
+        // 7. 兜底逻辑:抵扣金额不能大于订单实际金额
+        if (discountAmount.compareTo(orderAmount) > 0) {
+            discountAmount = orderAmount;
+        }
+
+        // 8. 保留两位小数,四舍五入
+        return discountAmount.setScale(2, RoundingMode.HALF_UP);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void useCoupon(String couponId, String openId, Long orderId, Integer orderType) {
+        // 1. 乐观锁核销用户领取记录
+        int updatedRows = couponReceiveService.useCouponOptimisticLock(couponId, openId, orderId, orderType, LocalDateTime.now());
+        if (updatedRows == 0) {
+            throw new ServiceException("优惠券核销失败:状态异常或已被使用");
+        }
+
+        // 2. 规则表使用数量 +1
+        int ruleUpdatedRows = couponReceiveService.incrementUsedNum(couponId);
+        if (ruleUpdatedRows == 0) {
+            throw new ServiceException("优惠券规则状态异常,核销中止");
+        }
+        log.info("优惠券核销成功: couponId={}, openId={}, orderId={}", couponId, openId, orderId);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void returnCoupon(String couponId, String openId, Long orderId) {
+        // 1. 将已使用的券退回到待使用状态
+        int rows = couponReceiveService.returnCouponOptimisticLock(couponId, openId, orderId);
+        if (rows == 0) {
+            log.warn("退还优惠券失败,可能券已过期或不属于该订单: couponId={}, orderId={}", couponId, orderId);
+            return;
+        }
+
+        // 2. 规则表使用数量 -1
+        this.couponReceiveService.decrementUsedNum(couponId);
+        log.info("优惠券退还成功: couponId={}, orderId={}", couponId, orderId);
+    }
 }
 

+ 74 - 0
nightFragrance-massage/src/main/resources/mapper/massage/CouponReceiveMapper.xml

@@ -80,5 +80,79 @@
         and a.receive_open_id = #{openid}
     </select>
 
+    <!-- 1. 核销优惠券 (乐观锁) -->
+    <update id="useCouponOptimisticLock">
+        UPDATE coupon_receive
+        SET coupon_status = 2,
+        use_time = #{useTime},
+        order_id = #{orderId},
+        order_type = #{orderType},
+        update_time = NOW()
+        WHERE coupon_id = #{couponId}
+        AND receive_open_id = #{openId}
+        AND coupon_status = 0  <!-- 核心:只有状态为 0(待使用) 才能被更新 -->
+        AND is_delete = 0
+    </update>
+
+    <!-- 2. 优惠券规则表使用数量 +1 -->
+    <update id="incrementUsedNum">
+        UPDATE coupon
+        SET used_num = used_num + 1,
+            update_time = NOW()
+        WHERE id = #{couponId}
+          AND is_delete = 0
+    </update>
+
+    <!-- 3. 退还优惠券 (乐观锁) -->
+    <update id="returnCouponOptimisticLock">
+        UPDATE coupon_receive
+        SET coupon_status = 0,
+        use_time = NULL,
+        order_id = NULL,
+        order_type = NULL,
+        update_time = NOW()
+        WHERE coupon_id = #{couponId}
+        AND receive_open_id = #{openId}
+        AND order_id = #{orderId} <!-- 核心:必须匹配原订单,防止退错 -->
+        AND coupon_status = 2     <!-- 核心:只有状态为 2(已使用) 才能退回 -->
+        AND is_delete = 0
+    </update>
+
+    <!-- 4. 优惠券规则表使用数量 -1 -->
+    <update id="decrementUsedNum">
+        UPDATE coupon
+        SET used_num = used_num - 1,
+        update_time = NOW()
+        WHERE id = #{couponId}
+        AND used_num > 0  <!-- 核心防御:防止减为负数 -->
+        AND is_delete = 0
+    </update>
+
+    <!-- 查询优惠券领取详情 -->
+    <select id="selectCouponDetailForCalc" resultType="java.util.Map">
+        SELECT
+        cr.id,
+        cr.coupon_status,
+        cr.valid_start_time,
+        cr.expiration_time,
+        c.discount_type,
+        c.discount_value,
+        c.reb_value,
+        c.threshold_amount
+        FROM coupon_receive cr
+        JOIN coupon c ON cr.coupon_id = c.id
+        <where>
+            cr.is_delete = 0
+            AND c.is_delete = 0
+            <if test="couponId != null and couponId != ''">
+                AND cr.coupon_id = #{couponId}
+            </if>
+            <if test="openId != null and openId != ''">
+                AND cr.receive_open_id = #{openId}
+            </if>
+        </where>
+        LIMIT 1
+    </select>
+
 </mapper>