核销优惠券(OpenFeign远程调用、微信小程序滑动分页查询后端实现、ThreadLocal存储用户信息、seata解决分布式事务问题)

目录

核销优惠券

一、我的优惠券

二、获取可用优惠券

优惠券微服务(服务提供者)

订单微服务(服务调用者)

三、核销优惠券实现

优惠券微服务(服务提供者)

订单微服务(服务调用者)

分布式事务处理

四、退回优惠券

优惠券微服务(服务提供者)

订单微服务(服务调用者)


核销优惠券

一、我的优惠券

需求分析:

小程序用户,进入"我的"-->"我的优惠券"就可以查询自己已抢到的优惠券(按抢券时间降序显示)本查询为滚动查询,向上拖屏幕查询下一屏,一屏显示10条

用户抢到优惠券有三个状态:

  • 未使用:未过有效期的优惠券

  • 已使用:已经在订单中使用的优惠券

  • 已过期:未使用且已过有效期的优惠券

接口文档:

接口地址:GET /market/consumer/coupon/my

请求、响应参数:

根据上述需求,我么可以写出如下的SQL查询语句:

sql 复制代码
-- 查询指定用户的优惠券
select * from coupon
         where user_id = 当前登录用户id
         and status = 前端传入的状态
         and id < 前端传入的最后一张优惠券id
         order by create_time desc
         limit 10

where>order>limit 查询会先过滤再排序最后进行拿取,因此不必担心优惠券乱序;优惠券id是通过雪花算法生成的主键ID,且随时间戳增大而增大,也就是说主键ID是递增的;我们通过创建时间降序排序就是主键ID降序排序,所以这里只要主键ID小于前端传入的最后一张优惠券id,即把前端已经展示的那些优惠券过滤掉

代码实现:

CouponController:

java 复制代码
@ApiOperation("查询我的优惠券列表")
@GetMapping("/my")
public List<CouponInfoResDTO> queryMyCouponForPage(Long lastId, Integer status) {
    return couponService.queryForList(lastId, UserContext.currentUserId(), status);
}

ICouponService:

java 复制代码
/**
 * 我的优惠券列表
 *
 * @param lastId 最后一个优惠券id
 * @param userId 用户id
 * @param status 状态
 * @return 优惠券列表
 */
List<CouponInfoResDTO> queryForList(Long lastId, Long userId, Integer status);

CouponServiceImpl:

java 复制代码
@Override
public List<CouponInfoResDTO> queryForList(Long lastId, Long userId, Integer status) {
    List<Coupon> list = this.lambdaQuery()
            .eq(Coupon::getStatus, status)
            .eq(Coupon::getUserId, userId)
            .lt(lastId != null, Coupon::getId, lastId)
            .orderByDesc(Coupon::getCreateTime)
            .last("limit 10")
            .list();
    return BeanUtils.copyToList(list, CouponInfoResDTO.class);
}

二、获取可用优惠券

需求分析:

在下单时,可以先获取当前订单可用的优惠券,也就是要从优惠券表中查询满足下面条件的记录:

  • 优惠券属于当前登录用户

  • 优惠券还没有过期,还没有使用

  • 满减金额<=订单金额,且订单金额>优惠金额

由于优惠券是否可以使用是由订单来决定的,所以前端请求是发给订单微服务,再由订单微服务转给优惠券微服务

优惠券微服务(服务提供者)

接口文档:

优惠券微服务负责查询当前用户当笔订单所有可用的优惠券,并按照优惠金额从大到小排序,条件是:

  • 所属用户:当前登录用户

  • 状态:未使用,且在有效使用期限内

  • 满减金额:小于等于订单总额

  • 优惠金额:小于订单金额

接口路径:GET /market/inner/coupon/getAvailable

请求、响应参数:

代码开发:

CouponController:

本接口属于内部调用接口,也就是在下单微服务通过feign进行远程调用

所以需要写在jzo2o-market 模块的com.jzo2o.market.controller.inner包下

java 复制代码
package com.jzo2o.market.controller.inner;

import com.jzo2o.api.market.dto.response.AvailableCouponsResDTO;
import com.jzo2o.market.service.ICouponService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.math.BigDecimal;
import java.util.List;

@RestController("innerCouponController")
@RequestMapping("/inner/coupon")
@Api(tags = "内部接口-优惠券相关接口")
public class CouponController{

    @Autowired
    private ICouponService couponService;

    @ApiOperation("获取可用优惠券列表")
    @GetMapping("/getAvailable")
    public List<AvailableCouponsResDTO> getAvailable(@RequestParam("totalAmount") BigDecimal totalAmount) {
        return couponService.getAvailable(totalAmount);
    }
}

我们有两个请求参数但是参数列表却只有一个参数,这是因为用户Id我们可以从线程当中取:

而这个功能的实现实际上是通过拦截器拦截请求,并将请求头当中的用户信息封装保存到线程当中,并在需要时取出,结束后删除信息释放线程的存储:

ICouponService:

java 复制代码
/**
 * 获取可用优惠券列表
 *
 * @param totalAmount 订单总金额
 * @return 可用的优惠券列表
 */
List<AvailableCouponsResDTO> getAvailable(BigDecimal totalAmount);

CouponServiceImpl:

java 复制代码
@Override
public List<AvailableCouponsResDTO> getAvailable(BigDecimal totalAmount) {
    //- 优惠金额:小于订单金额
    //1. 查询优惠券
    List<Coupon> list = this.lambdaQuery()
            .eq(Coupon::getUserId, UserContext.currentUserId())//- 所属用户:当前登录用户
            .eq(Coupon::getStatus, CouponStatusEnum.NO_USE.getStatus())//- 状态:未使用
            .ge(Coupon::getValidityTime, LocalDateTime.now())//- 在有效使用期限内
            .le(Coupon::getAmountCondition, totalAmount)//- 满减金额:小于等于订单总额
            .list();

    //2. 优惠金额:小于订单金额
    List<Coupon> collect = list.stream()
            .map(e -> e.setDiscountAmount(CouponUtils.calDiscountAmount(e, totalAmount))) //获取每个优惠券对应当前订单的优惠金额
            .filter(e ->
                    e.getDiscountAmount().compareTo(new BigDecimal(0)) > 0
                            && e.getDiscountAmount().compareTo(totalAmount) < 0
            )//0 < 优惠金额:小于订单金额
            .sorted(Comparator.comparing(Coupon::getDiscountAmount).reversed())//按照优惠金额从大到小排序
            .collect(Collectors.toList());

    //3. 返回结果
    return BeanUtils.copyToList(collect, AvailableCouponsResDTO.class);
}

这里的有效期validityTime实际上就是过期时间,而这个字段在SeizeCouponSyncProcessHandler类的处理方法中是将当前时间加上活动规定好的过期时间来设置的:

所以这里就是在取当前用户下未使用、未过期、满减金额不大于订单总金额的优惠券信息;

接下来就是通过我们封装好的方法计算到底这张券能省多少钱(包括满减跟折扣【这里关于金额我们有三个字段:满减、折扣、优惠金额】):

CouponUtils.calDiscountAmount(e, totalAmount)的具体实现:

随后通过filter过滤掉那些不符合业务的值(比如优惠金额大于总金额的、优惠金额是负数的),然后通过sorted进行排序(由于comparing方法默认正序即从小到大排序,因此加上reversed()进行从大到小排序)

最后通过copyToList快速拷贝过滤掉不需要的字段并返回前端

暴露接口:

CouponApi:

由于是内部接口需要先在jzo2o-api中定义接口,api编写好需要上传到本地仓库

java 复制代码
package com.jzo2o.api.market;

import com.jzo2o.api.market.dto.response.AvailableCouponsResDTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.math.BigDecimal;
import java.util.List;

/**
 * 内部接口 - 优惠券相关接口
 *
 * @author itcast
 */
@FeignClient(contextId = "jzo2o-market", value = "jzo2o-market", path = "/market/inner/coupon")
public interface CouponApi {

    /**
     * 获取可用优惠券列表
     *
     * @param totalAmount 订单总金额
     * @return 优惠券列表
     */
    @GetMapping("/getAvailable")
    List<AvailableCouponsResDTO> getAvailable(@RequestParam("totalAmount") BigDecimal totalAmount);
}

将服务提供者提供的接口暴露给nacos,需要将接口注解上@FeignClient以及服务提供者的路径,然后我们可以选择在服务调用者上添加@EnableFeignClients(basePackages = "com.jzo2o.api")注解来扫描提供好的接口也可以选择在api模块下创建配置类添加@EnableFeignClients(basePackages = "com.jzo2o.api")注解:

java 复制代码
package com.jzo2o.config;

import com.jzo2o.common.handler.RequestIdHandler;
import com.jzo2o.common.handler.UserInfoHandler;
import com.jzo2o.interceptor.FeignInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Slf4j
@Configuration
@EnableFeignClients(basePackages = "com.jzo2o.api")
@Import({com.jzo2o.utils.MyQueryMapEncoder.class})
@ConditionalOnProperty(prefix = "feign", name = "enable", havingValue = "true")
public class ClientScanConfiguration {

    @Bean
    public FeignInterceptor feignInterceptor(UserInfoHandler userInfoHandler, RequestIdHandler requestIdHandler){
        return new FeignInterceptor(userInfoHandler, requestIdHandler);
    }


}

订单微服务(服务调用者)

接口文档:

接口功能:用户下单,小程序请求订单管理服务接口查询可用的优惠券

接口路径:GET /orders-manager/consumer/orders/getAvailableCoupons

请求、响应参数:

代码开发:

ConsumerOrdersController:

java 复制代码
@ApiOperation("获取可用优惠券")
@GetMapping("/getAvailableCoupons")
public List<AvailableCouponsResDTO> getCoupons(Long serveId,Integer purNum) {
    return ordersCreateService.getAvailableCoupons(serveId, purNum);
}

IOrdersCreateService:

java 复制代码
/**
 * 获取可用优惠券
 *
 * @param serveId 服务项目id
 * @param purNum  购买数量
 * @return 可用优惠券
 */
List<AvailableCouponsResDTO> getAvailableCoupons(Long serveId, Integer purNum);

OrdersCreateServiceImpl:

java 复制代码
    @Autowired
    private CouponApi couponApi;
    
    @Override
    public List<AvailableCouponsResDTO> getAvailableCoupons(Long serveId, Integer purNum) {
        // 1.获取服务
        ServeAggregationResDTO serveResDTO = serveApi.findById(serveId);
        if (serveResDTO == null || serveResDTO.getSaleStatus() != 2) {
            throw new ForbiddenOperationException("服务不可用");
        }

        // 2.计算订单总金额
        BigDecimal totalAmount = serveResDTO.getPrice().multiply(new BigDecimal(purNum));

        // 3.获取可用优惠券,并返回优惠券列表
        List<AvailableCouponsResDTO> available = couponApi.getAvailable(totalAmount);
        return available;
    }

通过远程调用获取服务信息,再用服务信息当中的价格乘以前端过来的购买数量计算出总金额,再通过远程调用获取可用优惠券的信息并返回前端。

效果展示:

三、核销优惠券实现

需求分析:

优惠券核销就是在用户下单的时候,勾选合适的优惠券进行扣减

这个功能,前端请求依旧是发给订单微服务,再由订单微服务转给优惠券微服务的

优惠券微服务(服务提供者)

优惠券微服务的职责:

  1. 校验优惠券信息: 只有订单金额大于等于满减金额,并且优惠券在有效状态方可使用

  2. 修改优惠券表中该优惠券的使用状态(已使用)、使用时间(当前时间)、订单id(订单微服务传入)

  3. 向优惠券核销表添加一条记录

  4. 核销成功返回最终优惠的金额

接口文档:

接口路径:POST /market/inner/coupon/use

请求、响应参数:

代码开发:

CouponController:

java 复制代码
@ApiOperation("使用优惠券,并返回优惠金额")
@PostMapping("/use")
public CouponUseResDTO use(@RequestBody CouponUseReqDTO couponUseReqDTO) {
    return couponService.use(couponUseReqDTO);
}

ICouponService:

java 复制代码
/**
 * 核销优惠券
 *
 * @param couponUseReqDTO 优惠券对象
 * @return 实际使用金额
 */
CouponUseResDTO use(CouponUseReqDTO couponUseReqDTO);

CouponServiceImpl:

java 复制代码
@Override
@Transactional(rollbackFor = Exception.class)
public CouponUseResDTO use(CouponUseReqDTO couponUseReqDTO) {
    //1. 校验优惠券信息: 只有订单金额大于等于满减金额,并且优惠券在有效状态方可使用
    Coupon coupon = this.lambdaQuery()
            .eq(Coupon::getUserId, UserContext.currentUserId())//- 所属用户:当前登录用户
            .eq(Coupon::getStatus, CouponStatusEnum.NO_USE.getStatus())//- 状态:未使用
            .ge(Coupon::getValidityTime, LocalDateTime.now())//- 在有效使用期限内
            .le(Coupon::getAmountCondition, couponUseReqDTO.getTotalAmount())//- 满减金额:小于等于订单总额
            .eq(Coupon::getId, couponUseReqDTO.getId())//优惠券id
            .one();
    if (ObjectUtil.isNull(coupon)) {
        throw new ForbiddenOperationException("优惠券核销失败");
    }

    //2. 修改优惠券表中该优惠券的使用状态(已使用)、使用时间(当前时间)、订单id(订单微服务传入)
    coupon.setStatus(CouponStatusEnum.USED.getStatus());//使用状态(已使用)
    coupon.setUseTime(LocalDateTime.now());//使用时间(当前时间)
    coupon.setOrdersId(couponUseReqDTO.getOrdersId().toString());//订单id(订单微服务传入)
    this.updateById(coupon);

    //3. 向优惠券核销表添加一条记录
    CouponWriteOff couponWriteOff = new CouponWriteOff();
    couponWriteOff.setCouponId(couponUseReqDTO.getId());
    couponWriteOff.setUserId(UserContext.currentUserId());
    couponWriteOff.setOrdersId(couponUseReqDTO.getOrdersId());
    couponWriteOff.setActivityId(coupon.getActivityId());
    couponWriteOff.setWriteOffTime(LocalDateTime.now());
    couponWriteOff.setWriteOffManPhone(coupon.getUserPhone());
    couponWriteOff.setWriteOffManName(coupon.getUserName());
    couponWriteOffService.save(couponWriteOff);

    //4. 核销成功返回最终优惠的金额
    BigDecimal discountAmount = CouponUtils.calDiscountAmount(coupon, couponUseReqDTO.getTotalAmount());
    CouponUseResDTO couponUseResDTO = new CouponUseResDTO();
    couponUseResDTO.setDiscountAmount(discountAmount);
    return couponUseResDTO;
}

这里进行重复地优惠券信息校验是因为,当我们点击立即预约之后会生成该订单,但支付的这段时间内万一优惠券过期或者优惠券活动过期的话就不应该让优惠券生效,所以这里反复地check满足条件的优惠券并用唯一的id取出我们前端选的那一张优惠券;

而后修改状态以及使用时间、优惠券所用的订单id;再向核销表添加记录:这里除了主键id自动生成,其余字段都得手动填入,最终用save方法插入保存,随后将优惠金额返回;

由于操作了多张表,所以需要添加@Transactional(rollbackFor = Exception.class)注解

接口暴露:

CouponApi:

java 复制代码
/**
 * 使用优惠券,并返回优惠金额
 *
 * @param couponUseReqDTO 优惠券信息对象
 * @return 优惠金额
 */
@PostMapping("/use")
CouponUseResDTO use(@RequestBody CouponUseReqDTO couponUseReqDTO);

订单微服务(服务调用者)

订单微服务的职责:

  1. 创建订单(前面已经做了)

  2. 调用优惠券微服务核销优惠券

  3. 修改订单的优惠金额和实付金额

OrdersCreateServiceImpl:

修改OrdersCreateServiceImpl原有下单方法,根据订单有无优惠券划分不同的逻辑

IOrdersCreateService:

在com.jzo2o.orders.manager.service.IOrdersCreateService中添加优惠券下单的方法

java 复制代码
/**
 * 保存订单(带优惠券)
 *
 * @param orders 订单信息
 * @param couponId 优惠券id
 */
void saveOrdersWithCoupon(Orders orders, Long couponId);

OrdersCreateServiceImpl:

在OrdersCreateServiceImpl中添加优惠券下单的方法

java 复制代码
@Transactional
public void saveOrdersWithCoupon(Orders orders,Long couponId) {
    //1. 调用优惠券微服务核销优惠券
    CouponUseReqDTO couponUseReqDTO = new CouponUseReqDTO();
    couponUseReqDTO.setId(couponId);//优惠券id
    couponUseReqDTO.setOrdersId(orders.getId());//订单id
    couponUseReqDTO.setTotalAmount(orders.getTotalAmount());//总金额
    CouponUseResDTO couponUseResDTO = couponApi.use(couponUseReqDTO);

    //2. 修改订单的优惠金额和实付金额
    BigDecimal discountAmount = couponUseResDTO.getDiscountAmount();
    orders.setDiscountAmount(discountAmount);//优惠金额
    orders.setRealPayAmount(orders.getTotalAmount().subtract(discountAmount));//实付金额

    //3. 创建订单
    this.save(orders);
}

这里远程调用优惠券微服务的接口是为了得到优惠金额从而方便直接扣减得到实付金额并插入到订单表当中,这又涉及到了分布式事务的问题,我们接下来讲解分布式事务处理:

分布式事务处理

下单时核销优惠券,创建订单和核销优惠券需要保证事务一致性,这就需要分布式事务的控制

  1. 在订单基础工程jzo2o-orders-base和优惠券工程jzo2o-market中添加seata依赖

    XML 复制代码
    <dependency>
        <groupId>com.jzo2o</groupId>
        <artifactId>jzo2o-seata</artifactId>
    </dependency>
  2. 修改订单管理服务和优惠券服务的bootstrap.yml,添加seata配置文件

  3. 由于使用的是AT模式,需要保证order和market两个数据库中都有undo_log的数据表

    sql 复制代码
    create table undo_log(
        id            bigint auto_increment primary key,
        branch_id     bigint       not null,
        xid           varchar(100) not null,
        context       varchar(128) not null,
        rollback_info longblob     not null,
        log_status    int          not null,
        log_created   datetime     not null,
        log_modified  datetime     not null,
        ext           varchar(100) null,
        constraint ux_undo_log unique (xid, branch_id)
    ) charset = utf8 row_format = DYNAMIC;
  4. 修改下单方法,在核销优惠券方法中开启全局事务

这里要保证事务,所以我们导入了依赖、添加seata配置文件、新增AT模式需要的快照表最后将普通的事务注解改成@GlobalTransactional注解

效果展示:


四、退回优惠券

需求分析:

用户通过小程序取消订单,订单服务执行取消订单逻辑,如果该订单使用了优惠券则请求优惠券服务退回优惠券

优惠券微服务(服务提供者)

优惠券微服务的职责:

  1. 检查优惠券是否有核销记录,没有则不需要退回

  2. 在优惠券退回表中添加记录

  3. 更新优惠券表中的状态字段,并清空订单id及使用时间字段

    如果优惠券已过期则标记为已失效,如果未过期,则标记为未使用

    如果优惠券对应的活动已作废则标记为已作废

  4. 删除优惠券核销表中的相关记录

接口文档:

接口功能:取消订单调用此接口退回优惠券

接口路径:POST /market/inner/coupon/useBack

代码开发:

CouponController:

java 复制代码
@PostMapping("/useBack")
@ApiOperation("退回优惠券")
public void useBack(@RequestBody CouponUseBackReqDTO couponUseBackReqDTO) {
    couponService.useBack(couponUseBackReqDTO);
}

ICouponService:

java 复制代码
/**
 * 退回优惠券
 *
 * @param couponUseBackReqDTO 优惠券
 */
void useBack(CouponUseBackReqDTO couponUseBackReqDTO);

CouponServiceImpl:

java 复制代码
   @Override
    @Transactional(rollbackFor = Exception.class)
    public void useBack(CouponUseBackReqDTO couponUseBackReqDTO) {
        //1. 检查优惠券是否有核销记录,没有则不需要退回
        CouponWriteOff couponWriteOff = couponWriteOffService.lambdaQuery()
                .eq(CouponWriteOff::getOrdersId, couponUseBackReqDTO.getOrdersId())
                .eq(CouponWriteOff::getUserId, couponUseBackReqDTO.getUserId())
                .one();
        if (ObjectUtil.isNull(couponWriteOff)) {
            throw new ForbiddenOperationException("优惠券退回失败,原因: 没有对应的核销记录");
        }

        //2. 在优惠券退回表中添加记录
        CouponUseBack couponUseBack = new CouponUseBack();
        couponUseBack.setCouponId(couponWriteOff.getCouponId());//优惠券id 千万不要从couponUseBackReqDTO对象中获取
        couponUseBack.setUserId(couponUseBackReqDTO.getUserId());
        couponUseBack.setUseBackTime(LocalDateTime.now());
        couponUseBack.setWriteOffTime(couponWriteOff.getWriteOffTime());
        couponUseBackService.save(couponUseBack);

        //3. 更新优惠券表中的状态字段,并清空订单id及使用时间字段
        //3-1 根据优惠券id查询信息
        Coupon coupon = this.getById(couponWriteOff.getCouponId());
        if (ObjectUtil.isNull(coupon)) {
            throw new ForbiddenOperationException("优惠券退回失败,原因: 没有对应的优惠券信息");
        }

        //3-2 根据活动id查询信息
        Activity activity = activityService.getById(coupon.getActivityId());
        if (ObjectUtil.isNull(activity)) {
            throw new ForbiddenOperationException("优惠券退回失败,原因: 没有对应的优惠券活动信息");
        }

        //3-3 如果优惠券已过期则标记为已失效,如果未过期,则标记为未使用
        CouponStatusEnum couponStatusEnum = coupon.getValidityTime().isAfter(LocalDateTime.now())
                ? CouponStatusEnum.NO_USE : CouponStatusEnum.INVALID;

        //3-4 如果优惠券对应的活动已作废则标记为已作废
        if (activity.getStatus().equals(ActivityStatusEnum.VOIDED.getStatus())){
            couponStatusEnum = CouponStatusEnum.VOIDED;
        }

        //3-5 执行优惠券的更新
//下面的写法无法对数据表字段进行空值更新,要改为使用lambdaUpdate来处理. 此问题在测试视频中专门有讲解
//        coupon.setStatus(couponStatusEnum.getStatus());
//        coupon.setOrdersId(null);
//        coupon.setUseTime(null);
//        boolean b = this.updateById(coupon);

        boolean b = this.lambdaUpdate()
                .set(Coupon::getStatus, couponStatusEnum.getStatus())
                .set(Coupon::getOrdersId, null)
                .set(Coupon::getUseTime, null)
                .eq(Coupon::getId, coupon.getId())
                .update();
        if (!b) {
            throw new ForbiddenOperationException("优惠券退回失败,原因: 更新优惠券失败");
        }

        //4. 删除优惠券核销表中的相关记录
        boolean b1 = couponWriteOffService.removeById(couponWriteOff.getId());
        if (!b1) {
            throw new ForbiddenOperationException("优惠券退回失败,原因: 删除优惠券核销记录失败");
        }
    }

为什么这里说优惠券id 千万不要从couponUseBackReqDTO对象中获取呢?这是因为我们这是一个远程调用接口,对于调用者来说,它不一定会提供这个优惠券id,而我们从数据库拿到的信息肯定是能取到的,因此不允许从参数中获取而是从数据库中获取;

接下来就是往退回表当中插入数据并查询优惠券、优惠券活动的信息来判断我们应该要修改成什么状态(已失效?已作废?);

由于MP当中updateById方法赋值为null的字段会被Mybatis忽略(即不修改),所以不能直接set然后抛给updateById方法,而是使用lambdaUpdate()方法完成空值的设置;

最后再将核销记录删掉完成服务提供

接口暴露:

CouponApi:

java 复制代码
/**
 * 退回优惠券
 *
 * @param couponUseBackReqDTO 优惠券对象
 */
@PostMapping("/useBack")
@ApiOperation("退回优惠券")
void useBack(@RequestBody CouponUseBackReqDTO couponUseBackReqDTO);

订单微服务(服务调用者)

订单微服务的职责:

在原有的订单取消逻辑之前,先判断是否使用了优惠券,如果是,则先退回优惠券

分布式事务处理:

由于退回优惠卷业务涉及到了订单和优惠券两个微服务,需要使用分布式事务控制

修改OrderCancelStrategyManager原有取消订单方法,添加退回优惠券的逻辑

这里可以看到,我们调用远程方法的时候确实没有设置优惠券id的相关值,所以微服务接口那里不能直接从参数里取

效果展示:

相关推荐
h***936643 分钟前
记录 idea 启动 tomcat 控制台输出乱码问题解决
java·tomcat·intellij-idea
点点@44 分钟前
Java idea运行项目包名过长
java·ide·intellij-idea
Highcharts.js1 小时前
Highcharts 金融图表之“点线图 ”讲解
java·开发语言·highcharts·金融点线图·点线图·模块安装
她说..1 小时前
Spring AOP 操作日志框架(CV可用)
java·jvm·spring·springboot·springcloud
Linux运维技术栈1 小时前
生产环境资源占用过高排查实战:从Heap Dump到全链路优化
java·服务器·网络·数据库·程序
带刺的坐椅1 小时前
Solon v3.7 黑科技: 消灭空指针异常!
java·ai·solon·jspecify
VX:Fegn08951 小时前
计算机毕业设计|基于springboot+vue的健康饮食管理系统
java·vue.js·spring boot·后端·课程设计
l***46681 小时前
Spring之DataSource配置
java·后端·spring
Hubert-hui1 小时前
技术文章推荐
java·开发语言