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