天天看點

優惠券系統-第三章-活動中心

優惠券系統-第三章介紹

         本文主要設計一個基于送券,送積分等的活動中心。

活動中心設計

        活動中心主要是有各種活動,比如雙十一活動,可能參加一個活動會送多個優惠券,某一個活動送大禮包還可能包含了積分(類似京豆),所有活動與優惠券(或者積分)屬于一對多的關系。領券中心與活動中心有一點差別就是,領券中心每次隻有一個券,活動中心可以是一個大禮包。如下面圖所示,某電商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的代碼,讀者可以閱讀其他資料。

      活動相關介紹完畢後,第四章将會介紹優惠券的下單使用。