spring boot 商品秒杀-商品活动组件

功能背景

公司的商城项目,需要添加商品秒杀功能,后续也需要扩展添加商品团购之类的功能。

开发思路

管理端:

  1. 校验
    • 校验商品规格是否有效
    • 校验商品规格是否已添加未下架或未结束的秒杀活动
    • 校验秒杀价和秒杀库存是否超过商品规格原价和库存
  2. 添加到数据库(商品秒杀的表里需要商品id、规格商品id、秒杀库存、秒杀剩余库存、秒杀开始时间、秒杀结束时间、创建时间、修改时间、创建人)
  3. 同步规格(在我们的项目中一个商品规格同一时间只能参加一种活动)
  4. 同步到redis
  5. 添加时间轮定时任务
补偿机制

管理端启动的时候查询一下所有状态为未关闭的商品秒杀,然后将超时未关闭的直接关闭、未达到开始时间的,已经开始的分别进行不同的补偿处理或者添加相应的定时任务。

APP端

  1. 提供活动商品列表页接口
  2. 订单生成
    • 获取活动商品剩余库存和商品限购数量以及用户已购商品数量用于判断是否超卖
    • 订单关联业务处理(订单优惠计算、优惠记录生成、订单商品快照生成、订单上链)
    • 减库存(调用组件接口完成减活动商品库存操作:通过redis分布式锁进行加锁,再使用redis increment方法对活动商品剩余库存进行原子性操作,再异步(我用的redis消息队列)减少数据库库存)
    • 获取活动商品的订单支付超时限制(需求设定秒杀、团购的支付超时时间不同),设置订单支付超时时间,并添加订单超时定时任务(定时任务同样是调用组件接口完成增加库存)。

并发测试

我使用的apache bench进行的并发测试

ab -n 20000 -c 2000 -p C:\params.txt -T application/json http://192.168.50.140:8181/rural-mall/app/api/orders

本地测试,未出现超卖问题(并发再高点,电脑就受不住了)

组件接口

商品活动定时任务组件

该组件主要通过netty时间轮来实现活动开始和结束的定时任务在应用重启时的补偿机制。

商品活动结束时间轮执行任务
import org.example.commodity.activity.entity.ActivityInfo;
import org.example.commodity.activity.timertask.processor.ICommodityActivityEndProcessor;
import io.netty.util.Timeout;
import io.netty.util.TimerTask;
import lombok.extern.slf4j.Slf4j;

/**
 * 商品活动结束时间轮执行任务
 */
@Slf4j
public abstract class BaseCommodityActivityEndTimerTask<T extends ActivityInfo> implements TimerTask {

    private final Long activityId;
    private final ICommodityActivityEndProcessor<T> endProcessor;

    protected BaseCommodityActivityEndTimerTask(Long activityId,
                                                ICommodityActivityEndProcessor<T> endProcessor) {
        this.activityId = activityId;
        this.endProcessor = endProcessor;
    }

    @Override
    public void run(Timeout timeout) {
        log.info("商品活动{}结束任务开始执行", activityId);
        try {
            endProcessor.endActivity(activityId);
        }catch (Exception e){
            log.error("商品活动{}结束失败,{}", activityId,e.getMessage());
        }
        log.error("商品活动{}结束成功", activityId);
    }
}

实现示例
@Slf4j
public class CommoditySecKillingOffTimerTask extends BaseCommodityActivityEndTimerTask<CommoditySecKillingActivityInfo> {


    public CommoditySecKillingOffTimerTask(Long activityId, ICommodityActivityEndProcessor<CommoditySecKillingActivityInfo> endProcessor) {
        super(activityId, endProcessor);
    }
}
商品活动开始时间轮执行任务
import org.example.commodity.activity.entity.ActivityInfo;
import org.example.commodity.activity.timertask.processor.ICommodityActivityStartProcessor;
import io.netty.util.Timeout;
import io.netty.util.TimerTask;
import lombok.extern.slf4j.Slf4j;

/**
 * 商品活动执行时间轮执行任务
 */
@Slf4j
public abstract class BaseCommodityActivityStartTimerTask<T extends ActivityInfo> implements TimerTask {

    private final Long activityId;
    private final ICommodityActivityStartProcessor<T> startProcessor;

    public BaseCommodityActivityStartTimerTask(Long activityId,
                                               ICommodityActivityStartProcessor<T> onProcessor) {
        this.activityId = activityId;
        this.startProcessor = onProcessor;
    }

    @Override
    public void run(Timeout timeout) {
        log.info("商品活动{}执行任务开始执行", activityId);
        try {
            startProcessor.onActivity(activityId);
        }catch (Exception e){
            log.error("商品活动{}执行失败,{}", activityId,e.getMessage());
        }
        log.error("商品活动{}执行成功", activityId);
    }
}
实现示例
/**
 * 商品秒杀执行时间轮执行任务
 */
@Slf4j
public class CommoditySecKillingOnTimerTask extends BaseCommodityActivityStartTimerTask<CommoditySecKillingActivityInfo> {


    public CommoditySecKillingOnTimerTask(Long secKillingId,
                                          ICommodityActivityStartProcessor<CommoditySecKillingActivityInfo> onProcessor) {
        super(secKillingId, onProcessor);
    }
}
商品活动补偿配置
import org.example.commodity.activity.entity.ActivityInfo;
import org.example.commodity.activity.timertask.processor.ICommodityActivityEndProcessor;
import org.example.commodity.activity.timertask.processor.ICommodityActivityStartProcessor;
import org.example.timertask.BaseTimerTaskStartConfiguration;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;

public abstract class BaseCommodityActivityStartConfiguration<T extends ActivityInfo> extends BaseTimerTaskStartConfiguration<T> {

    private final ICommodityActivityEndProcessor<T> activityEndProcessor;
    private final ICommodityActivityStartProcessor<T> activityStartProcessor;

    public BaseCommodityActivityStartConfiguration(ICommodityActivityEndProcessor<T> offProcessor,
                                                   ICommodityActivityStartProcessor<T> startProcessor) {
        this.endProcessor = offProcessor;
        this.startProcessor = startProcessor;
        this.activityEndProcessor = offProcessor;
        this.activityStartProcessor = startProcessor;
    }

    @Override
    public void configProcessor() {
    }


    @Override
    public boolean isUnStart(LocalDateTime now, T item) {
        return item.getStartTime().isAfter(now);
    }

    @Override
    public boolean isStarting(LocalDateTime now, T item) {
        return item.getStartTime().isBefore(now) && item.getEndTime().isAfter(now);
    }

    @Override
    public boolean isEnded(LocalDateTime now, T item) {
        return item.getEndTime().isBefore(now);
    }

    @Override
    public void addOnTimerTask(LocalDateTime now, T item) {
        BaseCommodityActivityStartTimerTask<T> timerTask = getStartTimerTask(item.getId(), this.activityStartProcessor);
        long delaySeconds = Duration.between(now, item.getStartTime()).toSeconds();
        this.hashedWheelTimer.newTimeout(timerTask, delaySeconds, TimeUnit.SECONDS);
    }

    protected abstract BaseCommodityActivityStartTimerTask<T> getStartTimerTask(Long activityId, ICommodityActivityStartProcessor<T> activityStartProcessor);
    @Override
    public void addOffTomerTask(LocalDateTime now, T item) {
        this.activityStartProcessor.startActivity(item.getId());
        BaseCommodityActivityEndTimerTask<T> timerTask = getEndTimerTask(item.getId(),this.activityEndProcessor);
        long delaySeconds = Duration.between(LocalDateTime.now(), item.getEndTime()).toSeconds();
        hashedWheelTimer.newTimeout(timerTask, delaySeconds, TimeUnit.SECONDS);
    }
    protected abstract BaseCommodityActivityEndTimerTask<T> getEndTimerTask(Long activityId, ICommodityActivityEndProcessor<T> activityEndProcessor);
}
实现示例
@Component
public class CommoditySecKillingStartConfigurationBase extends BaseCommodityActivityStartConfiguration<CommoditySecKillingActivityInfo> {

    private final CommoditySecKillingService secKillingService;

    public CommoditySecKillingStartConfigurationBase(CommoditySecKillingService secKillingService,
                                                     CommoditySecKillingEndProcessor offProcessor,
                                                     CommoditySecKillingOnProcessor startProcessor) {
        super(offProcessor,startProcessor);
        this.secKillingService = secKillingService;
        this.offProcessor = offProcessor;
        this.startProcessor = startProcessor;
    }

    @Override
    public List<CommoditySecKillingActivityInfo> listUnStartInfo() {
        List<CommoditySecKillingDto> list = secKillingService.listInfo(CommoditySecKilling.STATUS_UN_START);
        return MapperUtil.mapAsList(list,CommoditySecKillingActivityInfo::getInstance);
    }

    @Override
    public List<CommoditySecKillingActivityInfo> listUnEndInfo() {
        List<CommoditySecKillingDto> list = secKillingService.listInfo(CommoditySecKilling.STATUS_STARTING);
        return MapperUtil.mapAsList(list,CommoditySecKillingActivityInfo::getInstance);
    }

    @Override
    protected BaseCommodityActivityStartTimerTask<CommoditySecKillingActivityInfo> getStartTimerTask(Long activityId, ICommodityActivityStartProcessor<CommoditySecKillingActivityInfo> activityStartProcessor) {
        return new CommoditySecKillingOnTimerTask(activityId,activityStartProcessor);
    }

    @Override
    protected BaseCommodityActivityEndTimerTask<CommoditySecKillingActivityInfo> getEndTimerTask(Long activityId, ICommodityActivityEndProcessor<CommoditySecKillingActivityInfo> activityEndProcessor) {
        return new CommoditySecKillingOffTimerTask(activityId,activityEndProcessor);
    }
}

核心组件

活动商品的库存、用户限购等数据的处理

商品活动服务
import cn.hutool.core.util.ObjectUtil;
import org.example.commodity.activity.entity.ActivityInfo;
import org.example.commodity.activity.entity.CommodityActivitySpecSku;
import org.example.commodity.activity.entity.CommodityActivityVirtualCard;
import org.example.commodity.activity.entity.CommoditySkuActivityAttrDto;
import org.example.commodity.activity.exception.CommodityActivityException;
import org.example.commodity.activity.queue.CommodityActivityMessagePushService;
import org.example.commodity.activity.queue.CommodityActivityPurchaseParam;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class CommodityActivityService {

    private final Map<String, BaseCommodityActivityProcessor<? extends ActivityInfo>> processorMap;
    private final CommodityActivityMessagePushService pushServiceMessage;

    /**
     * 默认超时时间
     */
    private static final Integer DEFAULT_TIME_OUT = 10 * 24 * 60 * 60;

    public CommodityActivityService(List<BaseCommodityActivityProcessor<? extends ActivityInfo>> processors,
                                    CommodityActivityMessagePushService pushServiceMessage) {

        this.processorMap = processors.stream().collect(Collectors.toMap(BaseCommodityActivityProcessor::getActivityType, item -> item));
        this.pushServiceMessage = pushServiceMessage;
    }

    public List<CommodityActivitySpecSku> listCommoditySku(Long commodityId, String activityType) {
        BaseCommodityActivityProcessor<? extends ActivityInfo> activityProcessor = processorMap.get(activityType);
        if (ObjectUtil.isNull(activityProcessor)) {
            throw new CommodityActivityException("未注册的活动类型!");
        }
        List<CommodityActivitySpecSku> skuList = activityProcessor.listActivityCommoditySku(commodityId);
        skuList.forEach(item -> {
            CommoditySkuActivityAttrDto activityAttr = activityProcessor.getActivityAttr(item.getId());
            assert activityAttr != null;
            item.setActivityAttr(activityAttr)
                    .setPrice(activityAttr.getActivityPrice())
                    .setStocks(activityAttr.getRemainingStocks());
        });

        return skuList;
    }

    public List<CommodityActivityVirtualCard> listCommodityVirtualCard(Long commodityId, String activityType) {
        BaseCommodityActivityProcessor<? extends ActivityInfo> activityProcessor = processorMap.get(activityType);
        if (ObjectUtil.isNull(activityProcessor)) {
            throw new CommodityActivityException("未注册的活动类型!");
        }
        List<CommodityActivityVirtualCard> virtualCardList = activityProcessor.listActivityCommodityVirtualCard(commodityId);
        virtualCardList.forEach(item -> {
            CommoditySkuActivityAttrDto activityAttr = activityProcessor.getActivityAttr(item.getId());
            assert activityAttr != null;
            item.setActivityAttr(activityAttr)
                    .setPrice(activityAttr.getActivityPrice())
                    .setStocks(activityAttr.getRemainingStocks());
        });
        return virtualCardList;
    }

    public CommoditySkuActivityAttrDto getActivityAttr(Long commoditySkuId, String activityType) {
        BaseCommodityActivityProcessor<? extends ActivityInfo> activityProcessor = processorMap.get(activityType);
        if (ObjectUtil.isNull(activityProcessor)) {
            throw new CommodityActivityException("未注册的活动类型!");
        }
        return activityProcessor.getActivityAttr(commoditySkuId);
    }

    /**
     * 根据活动类型获取最小的超时时间
     * @param activityTypes 活动类型集合
     * @return Integer
     */
    public Integer getMinActivityTimeOut(List<String> activityTypes) {
        return activityTypes.stream().map(activityType -> {
            BaseCommodityActivityProcessor<? extends ActivityInfo> activityProcessor = processorMap.get(activityType);
            return activityProcessor.getActivityTimeOut();
        }).min(Integer::compareTo).orElse(DEFAULT_TIME_OUT);
    }

    /**
     * 减少活动缓存库存
     * 然后使用消息队列异步处理同步数据库数据
     */
    public void decreaseActivityRedisStocks(Long activityId, Long commoditySkuId, String activityType, Integer quantity, Long customerId) {
        BaseCommodityActivityProcessor<? extends ActivityInfo> activityProcessor = processorMap.get(activityType);
        if (ObjectUtil.isNull(activityProcessor)) {
            throw new CommodityActivityException("未注册的活动类型!");
        }
        activityProcessor.decreaseActivityRedisStocks(activityId,commoditySkuId,quantity);
        activityProcessor.addCustomerPurchaseNumber(commoditySkuId,customerId,quantity);
        pushServiceMessage.publish(new CommodityActivityPurchaseParam(activityType,activityId,quantity));
    }
    /**
     * 恢复活动缓存库存
     * 然后使用消息队列异步处理同步数据库数据
     */
    public void increaseActivityRedisStocks(Long activityId, Long commoditySkuId, String activityType, Integer quantity, Long customerId) {
        BaseCommodityActivityProcessor<? extends ActivityInfo> activityProcessor = processorMap.get(activityType);
        if (ObjectUtil.isNull(activityProcessor)) {
            throw new CommodityActivityException("未注册的活动类型!");
        }
        activityProcessor.increaseActivityRedisStocks(activityId,commoditySkuId,quantity);
        activityProcessor.addCustomerPurchaseNumber(commoditySkuId,customerId,-1 * quantity);
        pushServiceMessage.publish(new CommodityActivityPurchaseParam(activityType, activityId,-1 *quantity));
    }
    /**
     * 减少活动数据库库存
     */
    public void decreaseActivityStocks( String activityType, CommodityActivityPurchaseParam purchaseParam) {
        BaseCommodityActivityProcessor<? extends ActivityInfo> activityProcessor = processorMap.get(activityType);
        if (ObjectUtil.isNull(activityProcessor)) {
            throw new CommodityActivityException("未注册的活动类型!");
        }
        activityProcessor.decreaseActivityStocks(purchaseParam.getActivityId(), purchaseParam.getQuantity());
    }
    /**
     * 获取用户购买数量
     */
    public Long getCustomerPurchaseNumber( String activityType, Long commoditySkuId, Long customerId) {
        BaseCommodityActivityProcessor<? extends ActivityInfo> activityProcessor = processorMap.get(activityType);
        if (ObjectUtil.isNull(activityProcessor)) {
            throw new CommodityActivityException("未注册的活动类型!");
        }
        return activityProcessor.getCustomerPurchaseNumber(commoditySkuId,customerId);
    }
}
商品活动缓存服务
import cn.hutool.core.util.ObjectUtil;
import org.example.commodity.activity.entity.ActivityInfo;
import org.example.commodity.activity.exception.CommodityActivityException;
import org.example.commodity.activity.util.CommodityActivityRedisUtil;
import lombok.extern.slf4j.Slf4j;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;

@Slf4j
public abstract class BaseCommodityActivityRedisService<T extends ActivityInfo> {

    private final CommodityActivityRedisUtil commodityActivityRedisUtil;
    public static final String ACTIVITY_INFO_KEY_FORMAT = "%s_%s";
    public static final String CUSTOMER_PURCHASE_kEY_FORMAT = "customer_purchase_%s_%s_%s";
    public static final String ACTIVITY_STOCKS_kEY_FORMAT = "%s_%s_%s";

    public BaseCommodityActivityRedisService(CommodityActivityRedisUtil commodityActivityRedisUtil) {
        this.commodityActivityRedisUtil = commodityActivityRedisUtil;
    }


    /**
     * 缓存活动信息
     */
    public void cacheActivityInfo(T activityInfo) {
        if (LocalDateTime.now().isAfter(activityInfo.getEndTime())) {
            log.info("{}秒杀已结束!", activityInfo.getId());
        }
        long seconds = Duration.between(LocalDateTime.now(), activityInfo.getEndTime()).toSeconds();
        commodityActivityRedisUtil.set(formatActivityKey(activityInfo.getCommoditySkuId()), activityInfo, seconds, TimeUnit.SECONDS);
        setActivityStocks(activityInfo.getId(), activityInfo.getCommoditySkuId(), activityInfo.getRemainingStocks(), seconds);
    }

    /**
     * 缓存活动库存
     */
    public void setActivityStocks(Long activityId, Long commoditySkuId, Integer stocks, long seconds) {
        String key = formatActivityStocksKey(activityId, commoditySkuId);
        commodityActivityRedisUtil.set(key, stocks, seconds, TimeUnit.SECONDS);
    }

    /**
     * 增加或减少活动库存
     */
    public void addActivityStocks(Long activityId, Long commoditySkuId, Integer quantity) {
        commodityActivityRedisUtil.increment(formatActivityStocksKey(activityId, commoditySkuId), -1L * quantity);
    }

    /**
     * 获取活动库存
     */
    public Integer getCommodityStocks(Long activityId, Long commoditySkuId) {
        return Integer.valueOf(commodityActivityRedisUtil.get(formatActivityStocksKey(activityId, commoditySkuId)));
    }

    /**
     * 移除活动相关信息
     */
    public void removeActivityInfo(Long commoditySkuId) {
        commodityActivityRedisUtil.delete(formatActivityKey(commoditySkuId));
        commodityActivityRedisUtil.deleteKeys(String.format(ACTIVITY_STOCKS_kEY_FORMAT, getActivityType(), commoditySkuId, "*"));
        commodityActivityRedisUtil.deleteKeys(String.format(CUSTOMER_PURCHASE_kEY_FORMAT, getActivityType(), commoditySkuId, "*"));
    }

    /**
     * 获取活动信息
     */
    public ActivityInfo getActivityInfo(Long commoditySkuId) {
        String key = formatActivityKey(commoditySkuId);
        return commodityActivityRedisUtil.getObject(key, ActivityInfo.class);
    }

    /**
     * 设置活动类型
     */
    protected abstract String getActivityType();


    private String formatActivityKey(Long commoditySkuId) {
        return String.format(ACTIVITY_INFO_KEY_FORMAT, getActivityType(), commoditySkuId);
    }


    private String formatActivityStocksKey(Long activityId, Long commoditySkuId) {
        return String.format(ACTIVITY_STOCKS_kEY_FORMAT, getActivityType(), commoditySkuId, activityId);
    }

    /**
     * 增加用户购买
     */
    public Long customerPurchase(Long customerId, Long commoditySkuId, Integer quantity) {
        String key = formatCustomerPurchaseKey(customerId, commoditySkuId);
        Long increment = commodityActivityRedisUtil.increment(key, quantity);
        ActivityInfo activity = getActivityInfo(commoditySkuId);
        if (ObjectUtil.isNull(activity)) {
            throw new CommodityActivityException("商品关联秒杀活动不存在或已结束");
        }
        long seconds = Duration.between(LocalDateTime.now(), activity.getEndTime()).toSeconds();
        commodityActivityRedisUtil.keepAlive(key, (int) seconds, TimeUnit.SECONDS);
        return increment;
    }

    private String formatCustomerPurchaseKey(Long customerId, Long commoditySkuId) {

        return String.format(CUSTOMER_PURCHASE_kEY_FORMAT, getActivityType(), commoditySkuId, customerId);
    }


    /**
     * 获取当前用户已购数量,用于限购使用
     */
    public Long getCustomerPurchaseNumber(Long customerId, Long commoditySkuId) {
        return customerPurchase(customerId, commoditySkuId, 0);
    }

    /**
     * 获取redis分布式锁
     */
    public boolean getLock() {
        String activityType = getActivityType();
        return commodityActivityRedisUtil.getLock(activityType,activityType);
    }

    /**
     * 释放锁
     */
    public void unLock() {
        commodityActivityRedisUtil.unLock(getActivityType());
    }
}
实现示例
@Component
@Slf4j
public class CommoditySecKillingRedisService extends BaseCommodityActivityRedisService<AppCommoditySecKillingActivityInfo> {

    public CommoditySecKillingRedisService(CommodityActivityRedisUtilredisUtil) {
        super(redisUtil);
    }

    @Override
    protected String getActivityType() {
        return  CommoditySecKilling.ACTIVITY_TYPE;
    }
}

商品活动处理器
import cn.hutool.core.util.ObjectUtil;
import org.example.commodity.activity.entity.ActivityInfo;
import org.example.commodity.activity.entity.CommodityActivitySpecSku;
import org.example.commodity.activity.entity.CommodityActivityVirtualCard;
import org.example.commodity.activity.entity.CommoditySkuActivityAttrDto;
import org.example.commodity.activity.exception.CommodityActivityException;
import lombok.extern.slf4j.Slf4j;

import java.util.List;

@Slf4j
public abstract class BaseCommodityActivityProcessor<T extends ActivityInfo> {

    protected final BaseCommodityActivityRedisService<T> redisService;

    protected BaseCommodityActivityProcessor(BaseCommodityActivityRedisService<T> redisService) {
        this.redisService = redisService;
    }

    protected abstract String getActivityType();
    protected abstract  List<CommodityActivitySpecSku> listActivityCommoditySku(Long commodityId);
    protected abstract List<CommodityActivityVirtualCard> listActivityCommodityVirtualCard(Long commodityId);
    protected CommoditySkuActivityAttrDto getActivityAttr(Long commoditySkuId) {
        ActivityInfo activity = this.redisService.getActivityInfo(commoditySkuId);
        if (ObjectUtil.isNull(activity)) {
            return null;
        }
        return new CommoditySkuActivityAttrDto()
                .setActivityId(activity.getId())
                .setStartTime(activity.getStartTime())
                .setEndTime(activity.getEndTime())
                .setActivityPrice(activity.getActivityPrice())
                .setSalesRestriction(activity.getSalesRestriction())
                .setActivityStocks(activity.getActivityStocks())
                .setRemainingStocks(redisService.getCommodityStocks(activity.getId(),commoditySkuId))
                .setVirtualSalesVolume(activity.getVirtualSalesVolume());
    }

    protected abstract Integer getActivityTimeOut();

    public final void decreaseActivityRedisStocks(Long activityId, Long commoditySkuId, Integer quantity) {
        if (redisService.getLock()){
            try {
                Integer commodityStocks = redisService.getCommodityStocks(activityId, commoditySkuId);
                if (ObjectUtil.isNull(commodityStocks)) {
                    throw new CommodityActivityException("商品关联秒杀活动不存在或已结束");
                } else {
                    if (commodityStocks - quantity < 0) {
                        throw new CommodityActivityException("商品库存不足");
                    }
                    redisService.addActivityStocks(activityId, commoditySkuId, quantity);
                }
            }finally {
                redisService.unLock();
            }
        }else{
            decreaseActivityRedisStocks(activityId,commoditySkuId,quantity);
        }

    }
    public final void increaseActivityRedisStocks(Long activityId, Long commoditySkuId, Integer quantity) {
        if (redisService.getLock()){
            try{
                Integer commodityStocks = redisService.getCommodityStocks(activityId,commoditySkuId);
                if (ObjectUtil.isNull(commodityStocks)) {
                    log.info("商品{}暂无秒杀活动", commoditySkuId);
                } else {
                    redisService.addActivityStocks(activityId, commoditySkuId, -1 * quantity);
                }
            }finally {
                redisService.unLock();
            }
        }else{
            increaseActivityRedisStocks(activityId,commoditySkuId,quantity);
        }
    }


    public final Long getCustomerPurchaseNumber(Long commoditySkuId, Long customerId) {
        return redisService.getCustomerPurchaseNumber(customerId, commoditySkuId);
    }

    protected abstract int decreaseActivityStocks(Long activityId, Integer quantity);

    public final void addCustomerPurchaseNumber(Long commoditySkuId, Long customerId, Integer quantity) {
        redisService.customerPurchase(customerId, commoditySkuId, quantity);
    }
}
实现示例
@Component
@Slf4j
public class AppCommoditySecKillingProcessorImpl  extends BaseCommodityActivityProcessor<AppCommoditySecKillingActivityInfo> {

    private final CommoditySecKillingMapper mapper;

    protected AppCommoditySecKillingProcessorImpl(BaseCommodityActivityRedisService<AppCommoditySecKillingActivityInfo> redisService,
                                                  CommoditySecKillingMapper mapper) {
        super(redisService);
        this.mapper = mapper;
    }

    @Override
    public String getActivityType() {
        return CommoditySecKilling.ACTIVITY_TYPE;
    }

    @Override
    protected List<CommodityActivitySpecSku> listActivityCommoditySku(Long commodityId) {
        List<CommoditySpecSku> skuList = mapper.listActivityCommoditySku(commodityId);

        return MapperUtil.mapAsList(skuList,item->{
            CommodityActivitySpecSku target = new CommodityActivitySpecSku();
            BeanUtils.copyProperties(item,target);
            return target;
        });
    }

    @Override
    protected List<CommodityActivityVirtualCard> listActivityCommodityVirtualCard(Long commodityId) {
        List<CommodityVirtualCard> virtualCards = mapper.listActivityCommodityVirtualCard(commodityId);
        return MapperUtil.mapAsList(virtualCards,item->{
            CommodityActivityVirtualCard target = new CommodityActivityVirtualCard();
            BeanUtils.copyProperties(item,target);
            return target;
        });
    }
    @Override
    public Integer getActivityTimeOut() {
        return 15*60;
    }



    @Override
    public int decreaseActivityStocks(Long activityId, Integer quantity) {
        log.info("商品数量:{}", quantity);
        return mapper.decreaseActivityStocks(activityId, quantity);
    }

}
消息推送接口
public interface CommodityActivityMessagePushService {

    void publish(CommodityActivityPurchaseParam commodityActivityPurchaseParam);
}

消息接收处理器
@Component
@Slf4j
public class CommodityActivityListener extends BaseMessageListener {

    private final String topicName;
    private final CommodityActivityService activityService;

    public CommodityActivityListener(StringRedisTemplate redisTemplate,
                                        @Value("${message.topic.commodity-activity:default-commodity-activity}") String topicName,
                                        CommodityActivityService activityService) {
        super(redisTemplate);
        this.topicName = topicName;
        this.activityService = activityService;
    }

    @Override
    public Topic getTopic() {
        return new ChannelTopic(this.topicName);
    }

    @Override
    public void handleMessage(MessageDto message) {
        try{
            CommodityActivityPurchaseParam purchaseParam = ((JSONObject) message.getMessageData()).toJavaObject(CommodityActivityPurchaseParam.class);
            activityService.decreaseActivityStocks(message.getMessageType(),purchaseParam);
        }catch (Exception e){
            log.error("商品活动消息队列处理失败:{}", JSONObject.toJSONString(message));
        }
    }
}

因为组件加了自动配置所以只要实现相关的接口即可使用(消息接收处理器要单独处理,要在消息队列消费者中使用handleMessage方法处理数据)

代码地址:https://gitee.com/fenglifei/commodity-activity

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值