天天看点

优惠券系统-第三章-活动中心

优惠券系统-第三章介绍

         本文主要设计一个基于送券,送积分等的活动中心。

活动中心设计

        活动中心主要是有各种活动,比如双十一活动,可能参加一个活动会送多个优惠券,某一个活动送大礼包还可能包含了积分(类似京豆),所有活动与优惠券(或者积分)属于一对多的关系。领券中心与活动中心有一点区别就是,领券中心每次只有一个券,活动中心可以是一个大礼包。如下面图所示,某电商618的h5活动页,一次送618礼包(多张券)。

优惠券系统-第三章-活动中心
优惠券系统-第三章-活动中心

    在这里我们只讲活动心中的处理,领券中心的单个券处理比领活动礼包类似。我们需要两个表,一个是活动表activity_info,还需要一个活动与优惠券关联表activity_coupon,表示参与活动送哪一类券,需要参与活动送其他(京豆),直接加关联表即可。activity_code为活动的唯一标识code。

CREATE TABLE `activity` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号',
  `activity_code` varchar(50) NOT NULL COMMENT '活动码',
  `activity_name` varchar(255) DEFAULT NULL COMMENT '活动名称',
  `activity_desc` varchar(255) DEFAULT NULL COMMENT '活动描述',
  `activity_status` int(11) NOT NULL DEFAULT '1' COMMENT '活动状态(上下架)1可用,0禁用',
  `start_time` datetime DEFAULT NULL COMMENT '活动开始时间',
  `end_time` datetime DEFAULT NULL COMMENT '活动结束时间',
  `image_url` varchar(255) DEFAULT NULL COMMENT '列表图片',
  `h5_url` varchar(255) DEFAULT NULL COMMENT 'h5跳转链接',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_activity_code` (`activity_code`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='活动表';
           
CREATE TABLE `activity_coupon` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号',
  `activity_code` varchar(50) NOT NULL COMMENT '活动code',
  `coupon_act_sn` varchar(50) NOT NULL COMMENT '优惠券模板码',
  `coupon_num` int(11) NOT NULL DEFAULT '1' COMMENT '优惠券数量',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `idx_sendSn_couponSn` (`activity_code`,`coupon_act_sn`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='活动赠券配置表';
           

       这里注意activity_coupon里面的coupon_act_sn字段是第一章里面优惠券主表coupon_act的act_sn,为某一类券的标识,coupon_num为这一类券在该活动中送多少张。

流程设计

       当某一个活动发布时,会有一波领券高峰,领券需要通过MQ来异步处理,并且某一个用户参加活动的标志也要放在缓存中,同个用户的请求就会被缓存拦截下,缓存的失效时间,就可以设置为活动的结束时间。需要注意的是,出现了用户参与活动,但是没领到券的情况需要补救,这种情况是没有及时追加优惠券,导致券不够用,或者其他系统错误导致。

优惠券系统-第三章-活动中心

编码实现

     有一些细节在流程图中未给出,比如第一步的参加活动需要用分布式锁控制一个用户的请求,不可以同时进入领券代码中,防止由于同一个用户并发参加两次活动。代码如下

//参加618活动APi接口
@RequestMapping(value = "/joinAct", method = RequestMethod.POST)
    @ResponseBody
    public ResponseDto joinAct(HttpServletRequest request) {
        //验证是否登陆
        Header header = getHeader(request);
        if (header.getUserId() == null) {
            return new ResponseDto("用户信息异常");
        }
        //取出用户id,用户验证登陆属于非通用代码,根据你的业务来
        String userId = header.getUserId();

        //验证是否参加过活动的key
        String actKey = "618" + userId;
        //防止同一用户并发的缓存key
        String cacheKey = "CACHE_KEY" + userId;

        try {
            //判断同一个用户的并发,如果已经有请求进入,直接返回,同时设置超时60秒,防止死锁
            long cacheResult = redisOperations.setnx(cacheKey,"1");
            if (cacheResult != 1) {
                return new ResponseDto("领券中,请稍后重试");
            }
            redisOperations.expire(cacheKey,60);

            //判断是否参加过活动,参加过活动直接返回,并设置指定错误码
            String result = redisOperations.get(actKey);
            if (!Check.NuNStr(result)) {
                ResponseDto responseDto = new ResponseDto("您已领取过优惠券");
                responseDto.getMsg().setCode(PromotionErrorCode.ALREADY_CODE);
                return responseDto;
            }

            //送券,performSendAct方法中发送MQ,MQ中包含用户id与活动code
            sendActRequest.setPatientId(userId);
            DataTransferObject<Void> sendDto = sendActivityService.performSendAct(sendActRequest);
            if (sendDto.checkSuccess()) {
                //送券成功,设置活动标志,这里的ACT_TIME是根据活动结束时间计算出来的,代码未给出
                redisOperations.set(actKey,"1");
                redisOperations.expire(actKey,ACT_TIME);
                //送券成功,写入用户参加某活动的记录
                UserActivityRecordEntity userActivityRecordEntity = new UserActivityRecordEntity();
                userActivityRecordEntity.setBizCode("618"));
                userActivityRecordEntity.setUserId(userId);
                userActivityRecordService.saveUserActivityRecord(userActivityRecordEntity);
            }
            return new ResponseDto();
        } catch (Exception e) {
            LogUtil.error(LOGGER,"用户{},参与618领券异常,{}",userId,e);
            return new ResponseDto("领券异常");
        } finally {
            //请求结束,删除防止并发的key
            redisOperations.del(cacheKey);
        }
    }
           

       以上代码中的这个方法 sendActivityService.performSendAct(sendActRequest) 是发送MQ的方法,具体代码未给出,读者能够理解整个流程的逻辑即可,MQ的代码,读者可以阅读其他资料。

      活动相关介绍完毕后,第四章将会介绍优惠券的下单使用。