CloudBillingServiceImpl.java 17.4 KB
package com.zteits.bill.biz.cloudbilling;

import org.apache.commons.lang3.StringUtils;
import com.alibaba.fastjson.JSONObject;
import com.clouds.common.cache.sys.SysCodeValueCacheUtil;
import com.clouds.common.constants.*;
import com.clouds.common.redis.RedisCacheUtil;
import com.clouds.common.utils.ResultUtils;
import com.xiaoleilu.hutool.date.DateUnit;
import com.xiaoleilu.hutool.date.DateUtil;
import com.xiaoleilu.hutool.date.TimeInterval;
import com.zteits.bill.biz.cloudbilling.inner.CloudBillingInnerService;
import com.zteits.bill.dao.whitelist.WhitelistDao;
import com.zteits.bill.domain.WhitelistIsFreeDO;
import com.zteits.charge.constants.FeeTypeEnum;
import com.zteits.charge.entrance.ChargeService;
import com.zteits.charge.model.dto.ChargeResponse;
import com.zteits.charge.model.dto.FreeTimeSegmentDTO;
import com.zteits.charge.model.dto.RuleDTO;
import com.zteits.charge.model.dto.RuleDetailDTO;
import com.zteits.charge.model.request.ChargeRequest;
import com.zteits.charge.model.request.CommonCircleChargeRequest;
import com.zteits.charge.model.request.NatureDayChargeRequest;
import com.zteits.charge.model.request.TimeSegmentChargeRequest;
import com.zteits.clouds.api.apibase.bean.BizResult;
import com.zteits.clouds.api.apibase.constants.ErrorType;
import com.zteits.clouds.api.apibase.exception.AppException;
import com.zteits.clouds.api.apibase.exception.BizException;
import com.zteits.clouds.api.dto.bill.billing.dto.BillingResultDTO;
import com.zteits.clouds.api.dto.bill.cloudbilling.dto.CloudBillingRuleDTO;
import com.zteits.clouds.api.dto.bill.cloudbilling.dto.CloudBillingRuleDetailDTO;
import com.zteits.clouds.api.dto.bill.cloudbilling.dto.CloudRuleCollectDTO;
import com.zteits.clouds.api.dto.bill.cloudbilling.dto.FreeSectionDTO;
import com.zteits.clouds.api.dto.bill.cloudbilling.param.CloudBillingReq;
import com.zteits.clouds.api.dto.bill.whitelist.CarNumIsFreeDTO;
import com.zteits.clouds.api.dto.bill.whitelist.param.QueryCarNumIsWhitelistRequest;
import com.zteits.clouds.api.service.bill.cloudbilling.CloudBillingService;
import com.zteits.clouds.api.service.bill.whitelist.WhitelistService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Component
public class CloudBillingServiceImpl implements CloudBillingService {
    private static final Logger log = LoggerFactory.getLogger(CloudBillingServiceImpl.class);
    @Resource
    private CloudBillingInnerService cloudBillingInnerService;
    @Resource
    private ChargeService chargeService;
    @Autowired
    private WhitelistDao whitelistDao;
    @Autowired
    private WhitelistService whitelistService;

    @Autowired
    private RedisCacheUtil redisCacheUtil;

    // 计费规则缓存的时间-单位 秒
    private final Long BILL_CACHE_LIVE_SECONDS = 24 * 60 * 60L;

    @Override
    public BizResult<BillingResultDTO> billing(CloudBillingReq req) {
        validateReq(req);
        TimeInterval timer = DateUtil.timer();
        log.warn("{}===云平台计费处理=== req:{}", req.getBaseRequest().getRequestId(), JSONObject.toJSONString(req));
        timer.start();
        //真实车型
        Integer realCarType = req.getCarType();

        if (!CarTypeEnum.LARGE_CAR.val().equals(realCarType)) {
            // 大型车以外的,都按照 小型车计费规则计算
            realCarType = CarTypeEnum.SMALL_CAR.val();
        }

        log.info("云平台计费使用的车辆类型{}", realCarType);

        // 计算停车时长,单位为秒
        Long parkDuration = DateUtil.between(req.getInTime(), req.getOutTime(), DateUnit.SECOND);

        // 白名单货车是否免费,true 的话,计费为0, false的话,正常计费
        // 逻辑是: 在停时间小于配置时间,则免费,超过配置时间,从入场时间开始正常计费
        boolean isWhiteTruckFree = this.whiteTruckHandler(req, parkDuration);
        if (isWhiteTruckFree) {
            BillingResultDTO billingResultDTO = new BillingResultDTO();
            billingResultDTO.setCarNo(req.getCarNo());
            billingResultDTO.setParkDuration(parkDuration);
            billingResultDTO.setParkFee(0L);

            log.info("白名单货车,小于配置的停车时长,免费;计费请求耗时:{}毫秒", timer.interval());
            return new BizResult<>(billingResultDTO);
        }

        // 先从缓存查询,没有再去数据库查询,然后更新缓存
        CloudRuleCollectDTO cloudRuleCollectDTO = this.getCachedRuleCollectInfoByPlNo(req.getPlNo());
        if (null == cloudRuleCollectDTO) {
            log.error("停车场未找到计费规则-{}", req.getPlNo());
            //throw new AppException(ErrorType.BIZ_ERROR, "停车场未找到计费规则-" + req.getPlNo());
            BillingResultDTO billingResultDTO = new BillingResultDTO();
            billingResultDTO.setCarNo(req.getCarNo());
            billingResultDTO.setParkDuration(parkDuration);
            billingResultDTO.setParkFee(0L);
            return new BizResult<>(billingResultDTO);
        }
        if (CloudRuleCollectDTO.CAR_TYPE_STRATEGY == cloudRuleCollectDTO.getCarTypeStrategy()) {
            // 0 不区分车辆类型 1 区分车辆类型
            realCarType = 0;
        }
        // 获取该停车场对应计费规则 -- 先从缓存查询,没有再去数据库查询,然后更新缓存
        CloudBillingRuleDTO cloudBillingRuleDTO = this.getCachedRuleInfoByCollectIdAndCarType(req.getPlNo(), cloudRuleCollectDTO.getRuleCollectId(), realCarType);
        if (cloudBillingRuleDTO == null) {
            log.error("找不到对应的计费规则{}", JSONObject.toJSONString(req));
            // throw new AppException(ErrorType.BIZ_ERROR, "找不到对应的计费规则{}", JSONObject.toJSONString(req));
            BillingResultDTO billingResultDTO = new BillingResultDTO();
            billingResultDTO.setCarNo(req.getCarNo());
            billingResultDTO.setParkDuration(parkDuration);
            billingResultDTO.setParkFee(0L);
            return new BizResult<>(billingResultDTO);
        }
        // 获取该停车场对应计费规则详情 -- 先从缓存查询,没有再去数据库查询,然后更新缓存
        List<CloudBillingRuleDetailDTO> cloudBillingRuleDetailDTOList = this.getCachedRuleDetailInfoByRuleId(req.getPlNo(), cloudBillingRuleDTO.getRuleId());

        if (cloudBillingRuleDetailDTOList == null || cloudBillingRuleDetailDTOList.size() == 0) {
            log.error("找不到对应的计费规则详情{}", JSONObject.toJSONString(req));
            //throw new AppException(ErrorType.BIZ_ERROR, "找不到对应的计费规则详情{}", JSONObject.toJSONString(req));
            BillingResultDTO billingResultDTO = new BillingResultDTO();
            billingResultDTO.setCarNo(req.getCarNo());
            billingResultDTO.setParkDuration(parkDuration);
            billingResultDTO.setParkFee(0L);
            return new BizResult<>(billingResultDTO);
        }
        // 构造免费时段list
        List<FreeTimeSegmentDTO> freeTimeSegmentDTOList = new ArrayList<>();
        List<FreeSectionDTO> freeSectionDTOList = req.getFreeSectionDTOList();
        for (FreeSectionDTO dto : freeSectionDTOList) {
            FreeTimeSegmentDTO freeTimeSegmentDTO = new FreeTimeSegmentDTO();
            BeanUtils.copyProperties(dto, freeTimeSegmentDTO);
            freeTimeSegmentDTOList.add(freeTimeSegmentDTO);
        }
        int feeType = cloudBillingRuleDTO.getFeeType();

        // 构造收费详情
        RuleDTO ruleDTO = new RuleDTO();

        BeanUtils.copyProperties(cloudBillingRuleDTO, ruleDTO);
        // 构造收费详情list
        List<RuleDetailDTO> ruleDetailDTOList = new ArrayList<>();
        for (CloudBillingRuleDetailDTO temp : cloudBillingRuleDetailDTOList) {
            RuleDetailDTO ruleDetailDTO = new RuleDetailDTO();
            BeanUtils.copyProperties(temp, ruleDetailDTO);
            ruleDetailDTOList.add(ruleDetailDTO);
        }

        // 拼接查询条件
        Date inTime = req.getInTime();
        ChargeRequest chargeRequest = new ChargeRequest();
        chargeRequest.setRuleDetailList(ruleDetailDTOList);
        chargeRequest.setRule(ruleDTO);
        chargeRequest.setCarNo(req.getCarNo());
        chargeRequest.setInTime(inTime);
        chargeRequest.setOutTime(req.getOutTime());
        chargeRequest.setPlNo(req.getPlNo());
        chargeRequest.setFreeTimeSegmentList(freeTimeSegmentDTOList);


        // 获取该停车绑定的计费规则的生效日期。
        Date effDate = cloudRuleCollectDTO.getEffDate();

        // 如果配置了停车场计费开始日期,则从这个日期以后的时间才收费
        if (effDate != null && inTime.before(effDate)) {
            chargeRequest.setInTime(effDate);
        }

        ChargeResponse chargeResponse = null;

        // 开始调用计费接口
        if (FeeTypeEnum.FEE_TYPE_24_HOURS.getValue() == feeType) {
            // 普通循环计费
            CommonCircleChargeRequest commonCircleChargeRequest = new CommonCircleChargeRequest();
            BeanUtils.copyProperties(chargeRequest, commonCircleChargeRequest);
            log.info("云平台普通循环计费请求{}", commonCircleChargeRequest);
            chargeResponse = chargeService.commonCircleChargeTemp(commonCircleChargeRequest);

        } else if (FeeTypeEnum.FEE_TYPE_DAY_NIGHT.getValue() == feeType) {
            // 分时段计费
            TimeSegmentChargeRequest timeSegmentChargeRequest = new TimeSegmentChargeRequest();
            BeanUtils.copyProperties(chargeRequest, timeSegmentChargeRequest);
            log.info("云平台白夜分时段计费请求{}", timeSegmentChargeRequest);
            chargeResponse = chargeService.twoSegmentChargeService(timeSegmentChargeRequest);

        } else if (FeeTypeEnum.FEE_TYPE_NATURE_DAY.getValue() == feeType) {
            // 自然天计费
            NatureDayChargeRequest natureDayChargeRequest = new NatureDayChargeRequest();
            BeanUtils.copyProperties(chargeRequest, natureDayChargeRequest);
            log.info("云平台自然天计费请求{}", JSONObject.toJSON(natureDayChargeRequest));
            chargeResponse = chargeService.natureDayChargeService(natureDayChargeRequest);
        }
        assert chargeResponse != null;
        if (0 != chargeResponse.getCode()) {
            // 请求返回不为0,说明异常
            throw new AppException(ErrorType.BIZ_ERROR, "计费异常" + chargeResponse.getMessage() + "");
        }

        this.whiteCarChargeOnceHandler(chargeResponse, req);

        BillingResultDTO billingResultDTO = new BillingResultDTO();
        billingResultDTO.setCarNo(req.getCarNo());
        billingResultDTO.setParkDuration(chargeResponse.getDuration());
        billingResultDTO.setParkFee(chargeResponse.getFee());
        log.warn("云平台计费响应{},计费请求耗时:{}毫秒", JSONObject.toJSON(chargeResponse), timer.interval());
        return new BizResult<>(billingResultDTO);
    }

    /**
     * 根据ruleId 获取对应的计费规则详情
     * 先从缓存查询,没有再去数据库查询,然后更新缓存
     */
    private List<CloudBillingRuleDetailDTO> getCachedRuleDetailInfoByRuleId(String plNo, String ruleId) {
        String key = RedisKeyEnum.CLOUD_PARK_CHARGE_RULE.key();
        String hashKey = plNo + "_" + "park_rule_details_" + ruleId;
        List<CloudBillingRuleDetailDTO> cloudBillingRuleDetailDTOList = redisCacheUtil.hGet(key, hashKey);
        if (cloudBillingRuleDetailDTOList == null || cloudBillingRuleDetailDTOList.size() == 0) {
            cloudBillingRuleDetailDTOList = cloudBillingInnerService.queryRuleDetailInfoByRuleId(ruleId);
            // 不为空的时候,放入缓存
            redisCacheUtil.hPut(key, hashKey, cloudBillingRuleDetailDTOList);
        }

        return cloudBillingRuleDetailDTOList;
    }

    /**
     * 根据collect id以及车辆类型,获取对应的计费规则
     * 先从缓存查询,没有再去数据库查询,然后更新缓存
     */
    private CloudBillingRuleDTO getCachedRuleInfoByCollectIdAndCarType(String plNo, String ruleCollectId, Integer realCarType) {
        //  String key = RedisKeyEnum.CLOUD_PARK_CHARGE_RULE.key() + "park_rule:" + ruleCollectId + "_" + realCarType;
        String key = RedisKeyEnum.CLOUD_PARK_CHARGE_RULE.key();
        String hashKey =  plNo + "_" + "park_rule_" +ruleCollectId + "_" + realCarType;
        CloudBillingRuleDTO cloudBillingRuleDTO = redisCacheUtil.hGet(key, hashKey);
        if (cloudBillingRuleDTO == null) {
            cloudBillingRuleDTO = cloudBillingInnerService.queryRuleInfoByCollectIdAndCarType(ruleCollectId, realCarType);
            if (cloudBillingRuleDTO != null) {
                // 不为空的时候,放入缓存
                redisCacheUtil.hPut(key, hashKey, cloudBillingRuleDTO);
            }
        }

        return cloudBillingRuleDTO;
    }

    /**
     * 获取停车场关联的计费规则合集
     * 先从缓存查询,没有再去数据库查询,然后更新缓存
     */
    private CloudRuleCollectDTO getCachedRuleCollectInfoByPlNo(String plNo) {
        String key = RedisKeyEnum.CLOUD_PARK_CHARGE_RULE.key();
        String hashKey = plNo + "_park_rule_collection";

        CloudRuleCollectDTO dto = redisCacheUtil.hGet(key, hashKey);
        if (dto == null) {
            dto = cloudBillingInnerService.queryRuleCollectInfoByPlNo(plNo, 0L);
            if (dto != null) {
                // 不为空的时候,放入缓存
                redisCacheUtil.hPut(key, hashKey, dto);
            }
        }

        return dto;
    }

    // 白名单收费一次逻辑判断
    private void whiteCarChargeOnceHandler(ChargeResponse chargeResponse, CloudBillingReq req) {

        if (chargeResponse.getFee() > 0) {
            // 计费大于0
            QueryCarNumIsWhitelistRequest request;
            request = new QueryCarNumIsWhitelistRequest();
            request.setCarNumber(req.getCarNo());
            request.setCarType(req.getCarType());
            request.setPlNo(req.getPlNo());
            request.setSysCode(req.getSysCode());
            request.setCurrentDate(req.getOutTime());
            request.setChargerType(WhitelistEnum.CHARGE_TYPE_2.getKey());

            BizResult<CarNumIsFreeDTO> bizResult = whitelistService.queryCarNumIsOnlyOneFeeEveryDay(request);
            CarNumIsFreeDTO dto = ResultUtils.getBizResultData(bizResult);
            if (dto.getIsFree() == CarNumIsFreeDTO.FREE) {
                //  白名单免费
                chargeResponse.setFee(0L);
            }
            if (dto.getIsFree() == CarNumIsFreeDTO.NO_FREE && dto.getMaxCharge() != null) {
                // 白名单按照固定费率收费
                chargeResponse.setFee(dto.getMaxCharge());
            }
        }
    }

    /*
       白名单货车的处理逻辑:
       如果时白名单货车,停车时长小于配置的时间,则免费出场
     */
    private boolean whiteTruckHandler(CloudBillingReq req, Long parkDuration) {
        String trunkFreeTimeStr = SysCodeValueCacheUtil.queryCodeNameByCodeValue(
                CodeValKindEnum.TRUCK_CAR_WHITE_LIST,
                CodeValueEnum.TRUCK_CAR_WHITE_LIST_FREE_TIME);
        Integer trunkFreeTime = 0;
        if (StringUtils.isNotEmpty(trunkFreeTimeStr)) {
            trunkFreeTime = Integer.valueOf(trunkFreeTimeStr);
        }
        log.info("{}===计费处理: 车牌号从缓存中获取货车白名单免费时长{}", req.getBaseRequest().getRequestId(), trunkFreeTimeStr);
        if (trunkFreeTime > 0) {
            QueryCarNumIsWhitelistRequest request = new QueryCarNumIsWhitelistRequest();
            request.setCarNumber(req.getCarNo());
            request.setPlNo(req.getPlNo());
            request.setCurrentDate(req.getOutTime());
            request.setChargerType(WhitelistEnum.CHARGE_TYPE_4.getKey());
            // 根据车牌以及停车场编号 获取白名单类型
            WhitelistIsFreeDO whitelistIsFreeDO = whitelistDao.queryWhitelistByCarNumAndPlNo(request);
            boolean truckFlag = (null != whitelistIsFreeDO
                    && WhitelistEnum.CHARGE_TYPE_4.getKey().equals(whitelistIsFreeDO.getChargeType())
            );
            if (truckFlag) {
                log.info("{}===计费处理: 车牌号 {} 属于白名单货车,免费时长为:{}", req.getBaseRequest().getRequestId(), req.getCarNo(),
                        trunkFreeTime);
                return parkDuration <= trunkFreeTime;

            }
        }
        log.warn("缓存中获取货车白名单免费时长=[{}]", trunkFreeTime);
        return false;
    }

    /**
     * 请求参数验证
     *
     * @param req 请求参数
     */
    private void validateReq(CloudBillingReq req) {
        if (StringUtils.isBlank(req.getCarNo())) {
            throw new BizException(ErrorType.PARAMM_NULL, "车牌号");
        }
        if (StringUtils.isBlank(req.getPlNo())) {
            throw new BizException(ErrorType.PARAMM_NULL, "停车场编号");
        }
        if (null == req.getInTime()) {
            throw new BizException(ErrorType.PARAMM_NULL, "进场时间");
        }
        if (null == req.getOutTime()) {
            throw new BizException(ErrorType.PARAMM_NULL, "出场时间");
        }
        if (null == req.getCarType()) {
            throw new BizException(ErrorType.PARAMM_NULL, "车辆类型");
        }

    }
}