什么是系统复杂度?类似于算法复杂度吧,但算法复杂度是标识运行代码所需的时间和空间资源,而这里所说的复杂度是系统的复杂度,是研发人员对系统的认知难度。
为什么要思考系统复杂度?不刻意去控制系统复杂度,一方面系统不够健壮,缺乏弹性,另一方面,研发会写出一堆控制逻辑和业务逻辑揉在一起的烂代码,前人挖坑,后人背锅!
怎么控制系统复杂度?合理利用分治,模块化编程,控制好问题规模,将问题化为子问题,迭代地解决各个子问题,当所有子问题都被解决了,最初的问题就解决了。控制逻辑和业务逻辑一定要分开,这是最基本的要求!
下边是一个例子:一个请求过来,要给新用户充话费,给老用户发券,,发的券有2种,大额券只能领一次且领券的时机不同,花了一下午时间写完了这个需求,代码一共600多行,看起来逻辑还是很清晰的。
/**
* 老带新业务实现:记录新老关系,发奖励
* 操作失败,要将kafka消息落地,老带新的bizType设置为2
* @param inputDto
* @return
*/
public ResponseVo notifyLDX(ActiveTaskInputDto inputDto) {
//参数检验
SubMissionResultDto checkResult = checkLDXParam(inputDto);
if(!checkResult.getResult()) {
//记录失败的卡夫卡消息
qqSportService.recordFaultKafka(inputDto.getMemberId(),inputDto.getPhoneNo(),2,1, checkResult.getFailReason());
log.error("notifyLDX 校验入参失败");
return new ResponseVo(false, BizzExEnum.ACTIVE_PARAM_ERROR.getErrorCode(), BizzExEnum.ACTIVE_PARAM_ERROR.getErrorMsg(), null);
}
//幂等性校验
if(!checkLDXIdmpotent(inputDto.getOrderId())) {
//记录失败的卡夫卡消息
qqSportService.recordFaultKafka(inputDto.getMemberId(),inputDto.getPhoneNo(),2,1, "校验幂等性失败, memberId:" + inputDto.getMemberId() + ", orderId:" + inputDto.getOrderId());
log.warn("notifyLDX 该用户的orderId已经领过奖励,{}", JsonUtil.toString(inputDto));
return new ResponseVo(false, BizzExEnum.CHECK_IDMPOTENT_ERROR.getErrorCode(), BizzExEnum.CHECK_IDMPOTENT_ERROR.getErrorMsg(), null);
}
//大额券互斥逻辑,已经使用过或未使用未过期互斥的大额券就不给用户发券
if(receivedAnyMutexCoupon(inputDto.getMemberId())) {
//记录失败的卡夫卡消息
qqSportService.recordFaultKafka(inputDto.getMemberId(),inputDto.getPhoneNo(),2,1, "存在大额互斥券, memberId:" + inputDto.getMemberId() + ", orderId:" + inputDto.getOrderId());
log.warn("notifyLDX 用户已经领过互斥的大额现金红包,{}", JsonUtil.toString(inputDto));
return new ResponseVo(false, BizzExEnum.CHECK_MUTEX_ERROR.getErrorCode(), BizzExEnum.CHECK_MUTEX_ERROR.getErrorMsg(), null);
}
//维护新老关系:1-往memberInfo表里记分享关系信息;2-往taskLog表记发券金额、活动类型
SubMissionResultDto recordInfo = recordLDXInfo(inputDto);
if(!recordInfo.getResult()) {
//记录失败的卡夫卡消息
qqSportService.recordFaultKafka(inputDto.getMemberId(),inputDto.getPhoneNo(),2,1, recordInfo.getFailReason());
log.error("notifyLDX 记录新老关系失败");
return new ResponseVo(false, BizzExEnum.RECORD_LDX_INFO_ERROR.getErrorCode(), BizzExEnum.RECORD_LDX_INFO_ERROR.getErrorMsg(), null);
}
//发放奖励:1-给老人发券&push发券消息&更新taskLog表的发券金额;2-给新人冲话费&发送短信
receiveReward(inputDto, (Map)recordInfo.getOtherInfo());
return new ResponseVo(true, BizzExEnum.OPERATE_SUCCESS.getErrorCode(), BizzExEnum.OPERATE_SUCCESS.getErrorMsg(), null);
}
/**
* 因为给新用户充话费和给老用户发券可能都失败,所以,要分别记录失败消息,都加上orderId
* @param inputDto
* @param oldMemberInfo
*/
private void receiveReward(ActiveTaskInputDto inputDto, Map oldMemberInfo){
/**
* 给新人充话费
*/
receiveNewMemberReward(inputDto);
/**
* 给老人发券,连续邀请3次以上且没有领过199现金券,给老用户发放199现金券
*/
receiveOldMemberReward(inputDto, oldMemberInfo);
}
private void receiveNewMemberReward(ActiveTaskInputDto inputDto){
}
private void receiveOldMemberReward(ActiveTaskInputDto inputDto, Map oldMemberInfo) {
}
/**
* 给老人发券
* 1.查询active_task_conf,得到发券的rpCode
* 2.判断是否可用发放199券,满足条件发放199券,不满足条件发放99券
* 3.发券,若发券失败且第2步满足发放199券的条件,需要删除redis中的记录
* @param oldMemberInfo
* @return
*/
private SubMissionResultDto receiveCoupon(Map oldMemberInfo) {
//查询卡券配置信息,可以做个Redis缓存
//解析rpCode
String memberId = (String)oldMemberInfo.get("memberId");
String phoneNo = (String)oldMemberInfo.get("phoneNo");
if(canGetBigAmountCoupon(memberId)) {
//发199券
JSONArray rpCode = getRpCode(taskConfValueDtoList, 199);
if(rpCode == null) {
log.error("receiveCoupon 查询活动配置信息没有找到199的rpCode, 入参param:{}", JsonUtil.toString(oldMemberInfo));
//发券失败,删除199的redis Key
jedisTemplate.del(RedisConstants.LDX_BIG_AMOUNT_COUPON_KEY + memberId);
return new SubMissionResultDto(false, "查询活动配置信息没有找到199的rpCode, oldMemberId" + oldMemberInfo.get("memberId"));
}
if(!giveReward(memberId, phoneNo, rpCode)) {
//发券失败,删除199的redis Key
jedisTemplate.del(RedisConstants.LDX_BIG_AMOUNT_COUPON_KEY + memberId);
log.error("receiveCoupon 发放老带新199券失败, 入参param:{}", JsonUtil.toString(oldMemberInfo));
return new SubMissionResultDto(false, "发放老带新199券失败, oldMemberId" + oldMemberInfo.get("memberId"));
}
log.info("receiveCoupon 发放老带新199券成功, 入参param:{}", JsonUtil.toString(oldMemberInfo));
return new SubMissionResultDto(true, null, "199");
} else {
//发99券
JSONArray rpCode = getRpCode(taskConfValueDtoList, 99);
if(rpCode == null) {
log.error("receiveCoupon 查询活动配置信息没有找到99的rpCode, 入参param:{}", JsonUtil.toString(oldMemberInfo));
return new SubMissionResultDto(false, "查询活动配置信息没有找到99的rpCode, oldMemberId" + oldMemberInfo.get("memberId"));
}
if(!giveReward(memberId, phoneNo, rpCode)) {
log.error("receiveCoupon 发放老带新99券失败, 入参param:{}", JsonUtil.toString(oldMemberInfo));
return new SubMissionResultDto(false, "发放老带新99券失败, oldMemberId" + oldMemberInfo.get("memberId"));
}
log.info("receiveCoupon 发放老带新99券成功, 入参param:{}", JsonUtil.toString(oldMemberInfo));
return new SubMissionResultDto(true, null, "99");
}
}
private Boolean giveReward(String memberId, String phoneNo, JSONArray rpCode) {
/**
* 略去业务逻辑
*/
}
private JSONArray getRpCode(List taskConfValueDtoList, int weight) {
//略去细节
}
/**
* 1.redis get成功,返回失败
* 2.获取用户累计邀请会员数量count:小于3个-返回失败,大于等于5个-setnx,如果成功,返回成功,否则返回失败
* 3.count为3或者4,取memberId的最后一位x,x % 2的结果:0-若setnx成功,返回成功,否则返回失败;1-返回失败
* @param memberId
* @return
*/
private Boolean canGetBigAmountCoupon(String memberId) {
if(!StringUtil.isEmpty(jedisTemplate.get(RedisConstants.LDX_BIG_AMOUNT_COUPON_KEY + memberId))) {
log.info("canGetBigAmountCoupon 查询到用户已领199券, memberId:{}", memberId);
return false;
}
Map params = new HashMap<>();
params.put("memberId", memberId);
int count = iActiveventLogDao.queryLDXMemberCount(params);
if(count < 3){
log.info("canGetBigAmountCoupon 查询到count值为{}, memberId:{}", count, memberId);
return false;
}
if(count >= 5) {
if(jedisTemplate.setnx(RedisConstants.LDX_BIG_AMOUNT_COUPON_KEY + memberId, memberId)) {
log.info("canGetBigAmountCoupon 查询到count值为{}, memberId:{}", count, memberId);
return true;
}
log.info("canGetBigAmountCoupon 查询到count值为{}, memberId:{}, setnx失败", count, memberId);
return false;
}
Integer lastNumber = getLastNumber(memberId);
if(lastNumber <0 || lastNumber > 9){
log.info("canGetBigAmountCoupon 查询到count值为{}, memberId:{},取memberId最后一位出错,lastNumber:{}", count, memberId,lastNumber);
return false;
}
if(getModResult(lastNumber)){
if(jedisTemplate.setnx(RedisConstants.LDX_BIG_AMOUNT_COUPON_KEY + memberId, memberId)) {
log.info("canGetBigAmountCoupon 查询到count值为{}, memberId:{},对2取模返回true", count, memberId);
return true;
}
log.info("canGetBigAmountCoupon 查询到count值为{}, memberId:{},对2取模返回true,setnx失败", count, memberId);
return false;
}
log.info("canGetBigAmountCoupon 查询到count值为{}, memberId:{},对2取模返回false", count, memberId);
return false;
}
private Boolean getModResult(Integer lastNumber) {
Integer modResult = lastNumber % 2;
return modResult == 0 ? true : false;
}
private Integer getLastNumber(String memberId){
String lastStr = memberId.substring(memberId.length()-1);
return Integer.parseInt(lastStr);
}
/**
* 记录老带新上报信息
* @param inputDto
* @return
*/
private SubMissionResultDto recordLDXInfo(ActiveTaskInputDto inputDto) {
/**
* 记分享关系信息
*/
/**
* 记发券金额、活动类型
*/
}
//校验参数
private SubMissionResultDto checkLDXParam(ActiveTaskInputDto inputDto){
}
private Boolean checkLDXIdmpotent(String orderId){
//略
}