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 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 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 freeTimeSegmentDTOList = new ArrayList<>(); List 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 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 getCachedRuleDetailInfoByRuleId(String plNo, String ruleId) { String key = RedisKeyEnum.CLOUD_PARK_CHARGE_RULE.key(); String hashKey = plNo + "_" + "park_rule_details_" + ruleId; List 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 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, "车辆类型"); } } }