控制系统复杂度-模块化编程

        什么是系统复杂度?类似于算法复杂度吧,但算法复杂度是标识运行代码所需的时间和空间资源,而这里所说的复杂度是系统的复杂度,是研发人员对系统的认知难度。

       为什么要思考系统复杂度?不刻意去控制系统复杂度,一方面系统不够健壮,缺乏弹性,另一方面,研发会写出一堆控制逻辑和业务逻辑揉在一起的烂代码,前人挖坑,后人背锅!

      怎么控制系统复杂度?合理利用分治,模块化编程,控制好问题规模,将问题化为子问题,迭代地解决各个子问题,当所有子问题都被解决了,最初的问题就解决了。控制逻辑和业务逻辑一定要分开,这是最基本的要求!

下边是一个例子:一个请求过来,要给新用户充话费,给老用户发券,,发的券有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){
//略
}

 

你可能感兴趣的:(系统设计)