每天分时间段收费订单算法简单实现

1、创建订单信息;
2、补充收费标准信息,折扣信息,分润信息(防止订单时间内,出现价格变动);
3、根据订单创建时间生成一个可逆的有识别度的序列号,防止串改订单信息(可以加一个不可逆算法,如MD5加密,把参数信息都加进去);

package com.mingshuo.chongdianzhuang.util;

import com.ruoyi.common.utils.StringUtils;

import java.util.Random;

/**
 * Description:
 * <p>
 * 订单编号
 * </p>
 *
 * @Author: leo.xiong
 * @CreateDate: 2023/2/20 16:46
 * @Email: leo.xiong@suyun360.com
 * @Since:
 */
public class OrderUtils {
    /**
     * 初始时间 2023-01-01 00:00:00
     * 4 3830 0820 0291 5360
     */
    private static final Long FIRST_DATA_TIME = 1672502400000L;
    /**
     * 最大时间偏移量11位 99999999999
     * 不足11为补充一个随机数填充11位
     */
    private static final int MAX_TIME_PEEL_SIZE = 11;
    /**
     * 最大时间偏移量有效为 1,第一位必然有效 0表示有效位为2,9表示有效位为11 (0->2 ... 9->11)
     */
    private static final int MAX_VALID_TIME = 2;
    /**
     * 设备ID最大为7位
     */
    private static final int MAX_DEVICE_SIZE = 7;
    /**
     * 手机号长度取后2位
     */
    private static final int MAX_PHONE_SIZE = 2;

    /**
     * 1、当前时间 - 初始时间的毫秒偏移量((1672502400000 + 9999999999)/365/24/3600/1000 ≈ 56年,不足11位,后面补充随机数,最后一位表示有效位数(0-10),0表示1为有效 10表示11位有效数字)
     * 2、设备ID (9999999 暂定1千万级,不足7位,前面补0)
     * 3、最后两位使用用户手机最后两位
     * 总共 12+7+2=21位
     *
     * @param systemTime 缩减位数,增加读取成本
     * @param deviceId   设备ID,设备ID不能超过7位数
     * @param phone      手机号
     * @return
     */
    public static String createOrderById(Long systemTime, Long deviceId, String phone) {
        return createOrderBySn(systemTime, StringUtils.leftPad(String.valueOf(deviceId), MAX_DEVICE_SIZE, '0'), phone);
    }

    public static String createOrderBySn(Long systemTime, String deviceSn, String phone) {
        StringBuffer snSb = new StringBuffer();
        snSb.append(systemTime - FIRST_DATA_TIME);
        int len = snSb.length();
        String maxRandomValue = StringUtils.rightPad("", MAX_TIME_PEEL_SIZE - len, '9');
        snSb.append(Double.valueOf(Math.random() * Long.valueOf(maxRandomValue)).longValue());
        snSb.append(len - MAX_VALID_TIME);
        snSb.append(deviceSn);
        snSb.append(phone.substring(phone.length() - MAX_PHONE_SIZE));
        return snSb.toString();
    }

    /**
     * 回显数据可以用于校验订单信息是否经过了人工修改(时间/设备/手机不匹配)
     *
     * @param sn
     * @return
     */
    public static String printOrder(String sn) {
        String timeLen = sn.substring(MAX_TIME_PEEL_SIZE, 1 + MAX_TIME_PEEL_SIZE);
        StringBuffer orderSb = new StringBuffer();
        Long time = Long.valueOf(sn.substring(0, Integer.valueOf(timeLen) + MAX_VALID_TIME)) + FIRST_DATA_TIME;
        orderSb.append(time);
        orderSb.append("|");
        Long deviceId = Long.valueOf(sn.substring(MAX_TIME_PEEL_SIZE + 1, sn.length() - MAX_PHONE_SIZE));
        orderSb.append(deviceId);
        orderSb.append("|");
        String phoneLast = sn.substring(sn.length() - 2);
        orderSb.append(phoneLast);
        return orderSb.toString();
    }

    public static void main(String[] args) {
        Long systemTime = System.currentTimeMillis();
        System.out.println(systemTime + "|" + 1536 + "|" + 31);
        String sn = OrderUtils.createOrderById(systemTime, 1536L, "15989160331");
        System.out.println(OrderUtils.printOrder(sn));
    }
}
package com.mingshuo.chongdianzhuang.util;

import com.google.common.collect.Lists;
import com.mingshuo.chongdianzhuang.manage.domain.BizDeviceInstanceFee;
import com.mingshuo.chongdianzhuang.manage.domain.BizOrder;
import com.mingshuo.chongdianzhuang.manage.domain.BizOrderFeeDetail;
import com.ruoyi.framework.enums.EntityStatus;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Description:
 * <p>
 * 按时间分段收费算费
 * </p>
 *
 * @Author: leo.xiong
 * @CreateDate: 2023/2/21 9:30
 * @Email: leo.xiong@suyun360.com
 * @Since:
 */
public class TimePhasedChargesUtils {
    /**
     * 去除毫秒影响
     */
    private static final long MILLI_SECONDS = 1000;

    /**
     * 计算订单金额信息
     *
     * @param bizOrder                 订单ID/订单开始时间/订单结束时间/订单结束人
     * @param bizDeviceInstanceFeeList 分段收费标准 (订单时间段设备收费单价/服务单价/折扣信息)
     * @param shareBenefitRate         平台分润比例
     * @return (收费时间段 / 收费开始结束时间 / 设备费用 / 服务费用 / 打折费用 / 平台分润金额)
     */
    public static final List<BizOrderFeeDetail> orderFeeCalculation(BizOrder bizOrder, List<BizDeviceInstanceFee> bizDeviceInstanceFeeList, BigDecimal shareBenefitRate) {
        Date beginTime = bizOrder.getOrderBeginTime();
        Date endTime = bizOrder.getOrderEndTime();
        //如果开始时间>=结束时间,设置结束时间为开始时间+1s
        if (beginTime.getTime() / MILLI_SECONDS >= endTime.getTime() / MILLI_SECONDS) {
            endTime = new Date(beginTime.getTime() + MILLI_SECONDS);
        }
        List<Date[]> datesList = getBetweenDataList(beginTime, endTime);
        List<BizOrderFeeDetail> bizOrderFeeDetailList = Lists.newArrayList();
        supplementHourMinute(bizDeviceInstanceFeeList);
        for (Date[] dates : datesList) {
            Date beginDate = dates[0];
            Date endDate = dates[1];
            Integer[] times = getTimeIntByType(beginDate);
            recursionDetail(bizDeviceInstanceFeeList, bizOrderFeeDetailList, bizOrder, times, beginDate, endDate);
        }
        return organizeDataList(bizOrderFeeDetailList, shareBenefitRate);
    }

    /**
     * 整理数据信息
     *
     * @param bizOrderFeeDetailList
     * @return
     */
    private static List<BizOrderFeeDetail> organizeDataList(List<BizOrderFeeDetail> bizOrderFeeDetailList, BigDecimal shareBenefitRate) {
        if (CollectionUtils.isEmpty(bizOrderFeeDetailList)) {
            return Lists.newArrayList();
        }
        if (bizOrderFeeDetailList.size() <= 1) {
            calculateAmount(shareBenefitRate, bizOrderFeeDetailList.get(0));
            return bizOrderFeeDetailList;
        }
        List<BizOrderFeeDetail> bizOrderFeeDetails = Lists.newArrayListWithExpectedSize(bizOrderFeeDetailList.size());
        bizOrderFeeDetailList = bizOrderFeeDetailList.stream()
                .sorted(Comparator.comparing(BizOrderFeeDetail::getBeginTime))
                .collect(Collectors.toList());
        for (BizOrderFeeDetail bizOrderFeeDetail : bizOrderFeeDetailList) {
            if (CollectionUtils.isEmpty(bizOrderFeeDetails)) {
                bizOrderFeeDetails.add(bizOrderFeeDetail);
                continue;
            }
            //最后的时间-1秒+收费类型 == 上一条记录的初始时间+类型,则合并两条数据信息
            Long currentKey = (bizOrderFeeDetail.getEndTime().getTime() - MILLI_SECONDS) + bizOrderFeeDetail.getFeeId();
            BizOrderFeeDetail lastBizOrderFeeDetail = bizOrderFeeDetails.get(bizOrderFeeDetails.size() - 1);
            Long lastKey = lastBizOrderFeeDetail.getBeginTime().getTime() + lastBizOrderFeeDetail.getFeeId();
            if (!currentKey.equals(lastKey)) {
                bizOrderFeeDetails.add(bizOrderFeeDetail);
                continue;
            }
            lastBizOrderFeeDetail.setDuration(lastBizOrderFeeDetail.getDuration() + bizOrderFeeDetail.getDuration());
        }
        return bizOrderFeeDetails.parallelStream()
                .map(bizOrderFeeDetail -> calculateAmount(shareBenefitRate, bizOrderFeeDetail))
                .collect(Collectors.toList());
    }

    /**
     * 计费数据
     *
     * @param bizDeviceInstanceFeeList
     * @param bizOrderFeeDetailList
     * @param bizOrder
     * @param times
     * @param beginDate
     * @param endDate
     */
    private static void recursionDetail(List<BizDeviceInstanceFee> bizDeviceInstanceFeeList, List<BizOrderFeeDetail> bizOrderFeeDetailList, BizOrder bizOrder, Integer[] times, Date beginDate, Date endDate) {
        Long beginTime = beginDate.getTime(), endTime = endDate.getTime();
        for (BizDeviceInstanceFee bizDeviceInstanceFee : bizDeviceInstanceFeeList) {
            Long bizStartTime = getDate(times, bizDeviceInstanceFee.getStartHour(), bizDeviceInstanceFee.getStartMinute(), 0);
            Long bizEndTime = getDate(times, bizDeviceInstanceFee.getEndHour(), bizDeviceInstanceFee.getEndMinute(), -1);
            //如果收费结束时间 < 消费开始时间,则收费段不匹配
            if (bizEndTime < beginTime) {
                continue;
            }
            //如果收费开始时间 > 消费结束时间,则收费段不匹配
            if (bizStartTime > endTime) {
                continue;
            }
            //如果收费开始时间 <= 消费开始时间 && 收费结束时间 >= 消费结束时间,则完全匹配,添加消费记录,跳出循环
            if (bizStartTime <= beginTime && bizEndTime >= endTime) {
                bizOrderFeeDetailList.add(buildBizOrderFeeDetail(bizOrder, bizDeviceInstanceFee, beginDate, endDate));
                break;
            }
            //如果收费结束时间 < 消费结束时间,需要拆分时间段 消费开始时间 - 收费结束时间|收费结束时间+1s作为消费开始时间-消费结束时间
            if (bizEndTime < endTime) {
                bizOrderFeeDetailList.add(buildBizOrderFeeDetail(bizOrder, bizDeviceInstanceFee, beginDate, new Date(bizEndTime)));
                //存在一份 收费结束时间+1s - 消费结束时间 没有计费,需要递归调用
                recursionDetail(bizDeviceInstanceFeeList, bizOrderFeeDetailList, bizOrder, times, new Date(bizEndTime + 1000), endDate);
                break;
            }
            //如果消费开始时间 < 收费开始时间,需要拆分时间段 消费开始时间 - 收费开始时间-1s|收费开始时间作为消费开始时间-消费结束时间
            if (beginTime < bizStartTime) {
                //存在一份 消费开始时间 - 收费开始时间-1s 没有计费,需要递归调用
                recursionDetail(bizDeviceInstanceFeeList, bizOrderFeeDetailList, bizOrder, times, beginDate, new Date(bizStartTime - 1000));
                bizOrderFeeDetailList.add(buildBizOrderFeeDetail(bizOrder, bizDeviceInstanceFee, new Date(bizStartTime), endDate));
                break;
            }
        }
    }

    /**
     * 按照时间切割
     * 暂不考虑跨天,存在跨天定义多个时间片段
     *
     * @param bizDeviceInstanceFeeList
     */
    private static void supplementHourMinute(List<BizDeviceInstanceFee> bizDeviceInstanceFeeList) {
        for (BizDeviceInstanceFee bizDeviceInstanceFee : bizDeviceInstanceFeeList) {
            String[] startHourMinute = hourMinute(bizDeviceInstanceFee.getStartTime());
            String[] endHourMinute = hourMinute(bizDeviceInstanceFee.getEndTime());
            bizDeviceInstanceFee.setStartHour(Integer.valueOf(startHourMinute[0]));
            bizDeviceInstanceFee.setStartMinute(Integer.valueOf(startHourMinute[1]));
            bizDeviceInstanceFee.setEndHour(Integer.valueOf(endHourMinute[0]));
            bizDeviceInstanceFee.setEndMinute(Integer.valueOf(endHourMinute[1]));
            //跨天
            if (bizDeviceInstanceFee.getStartHour() > bizDeviceInstanceFee.getEndHour()) {
                bizDeviceInstanceFee.setAcrossDay(true);
            }
        }
    }

    /**
     * 按分钟收费,不足一分钟按一分钟收费
     *
     * @param bizOrder
     * @param bizDeviceInstanceFee
     * @param beginTime
     * @param endTime
     * @return
     */
    private static BizOrderFeeDetail buildBizOrderFeeDetail(BizOrder bizOrder, BizDeviceInstanceFee bizDeviceInstanceFee, Date beginTime, Date endTime) {
        BizOrderFeeDetail bizOrderFeeDetail = new BizOrderFeeDetail();
        bizOrderFeeDetail.setOrderId(bizOrder.getId());
        bizOrderFeeDetail.setFeeId(bizDeviceInstanceFee.getId());
        bizOrderFeeDetail.setCreateTime(bizOrder.getOrderEndTime());
        bizOrderFeeDetail.setCreateBy(bizOrder.getUserId().toString());
        bizOrderFeeDetail.setUpdateTime(bizOrder.getOrderEndTime());
        bizOrderFeeDetail.setUpdateBy(bizOrder.getUserId().toString());
        bizOrderFeeDetail.setDelFlag(EntityStatus.NOMAL.getCode());
        //去毫秒处理
        bizOrderFeeDetail.setBeginTime(new Date(beginTime.getTime() / 1000 * 1000));
        bizOrderFeeDetail.setEndTime(new Date(endTime.getTime() / 1000 * 1000));
        BigDecimal duration = new BigDecimal(bizOrderFeeDetail.getEndTime().getTime() - bizOrderFeeDetail.getBeginTime().getTime())
                .divide(new BigDecimal("1000"), 6, RoundingMode.HALF_UP)
                .divide(new BigDecimal("60"), 0, RoundingMode.HALF_UP);
        bizOrderFeeDetail.setDuration(duration.longValue());
        bizOrderFeeDetail.setBizDeviceInstanceFee(bizDeviceInstanceFee);
        return bizOrderFeeDetail;
    }

    /**
     * 计算金额
     * 1、计算电费;
     * 2、计算服务费;
     * 3、存在打折,计算打折费用,服务费分别减去打折的电费和服务费,只对服务费打折(电费是基本费用)
     * 4、计算分润的费用,只分润服务费用(电费为基本费用,打折后的费用算分润费用)
     * 分润和打折费用是否只对服务费做处理,电费是基础费用,并非实际的收益费用,这里需要考虑下
     * 不足一分钟按1分钟计费
     *
     * @param shareBenefitRate  分润信息
     * @param bizOrderFeeDetail 收费详情
     */
    private static BizOrderFeeDetail calculateAmount(BigDecimal shareBenefitRate, BizOrderFeeDetail bizOrderFeeDetail) {
        BizDeviceInstanceFee bizDeviceInstanceFee = bizOrderFeeDetail.getBizDeviceInstanceFee();
        BigDecimal duration = new BigDecimal(bizOrderFeeDetail.getDuration());
        //如果结束时间-开始时间小于1分钟,则按一分钟计费
        if (duration.longValue() < 1) {
            duration = new BigDecimal("1");
        }
        if (bizDeviceInstanceFee.getEnergyFee() != null) {
            BigDecimal energyFee = new BigDecimal(bizDeviceInstanceFee.getEnergyFee());
            bizOrderFeeDetail.setElectricityFee(energyFee.multiply(duration));
        } else {
            bizOrderFeeDetail.setElectricityFee(new BigDecimal("0"));
        }
        if (bizDeviceInstanceFee.getServiceFee() != null) {
            BigDecimal serviceFee = new BigDecimal(bizDeviceInstanceFee.getServiceFee());
            bizOrderFeeDetail.setServiceFee(serviceFee.multiply(duration));
        } else {
            bizOrderFeeDetail.setServiceFee(new BigDecimal("0"));
        }
        if (bizDeviceInstanceFee.getDiscountRate() != null) {
            //需要抹零计费,存储的是整数分
            BigDecimal discountRate = new BigDecimal(bizDeviceInstanceFee.getDiscountRate());
            BigDecimal serviceFeeDiscount = bizOrderFeeDetail.getServiceFee().multiply(discountRate).divide(new BigDecimal("10"), 0, RoundingMode.HALF_UP);
            bizOrderFeeDetail.setDiscountFee(serviceFeeDiscount);
            bizOrderFeeDetail.setServiceFee(bizOrderFeeDetail.getServiceFee().subtract(serviceFeeDiscount));
        }
        if (shareBenefitRate != null) {
            //需要抹零计费,存储的是整数分
            BigDecimal serviceFeeShareBenefitRate = bizOrderFeeDetail.getServiceFee().multiply(shareBenefitRate).setScale(0, RoundingMode.HALF_UP);
            bizOrderFeeDetail.setShareBenefitFee(serviceFeeShareBenefitRate);
            bizOrderFeeDetail.setServiceFee(bizOrderFeeDetail.getServiceFee().subtract(serviceFeeShareBenefitRate));
        }
        return bizOrderFeeDetail;
    }

    private static String[] hourMinute(String startTime) {
        String[] hourMinute = null;
        if (startTime.contains(":")) {
            hourMinute = startTime.split(":");
        } else if (startTime.contains(":")) {
            hourMinute = startTime.split(":");
        } else if (startTime.contains(": ")) {
            hourMinute = startTime.split(": ");
        }
        return hourMinute;
    }

    private static Integer[] getTimeIntByType(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        Integer[] times = new Integer[6];
        times[0] = calendar.get(Calendar.YEAR);
        times[1] = calendar.get(Calendar.MONTH);
        times[2] = calendar.get(Calendar.DAY_OF_MONTH);
        times[3] = calendar.get(Calendar.HOUR);
        times[4] = calendar.get(Calendar.MINUTE);
        times[5] = calendar.get(Calendar.MINUTE);
        return times;
    }

    /**
     * 0:00-3:00 -> 00:00:00-> 2:59:59
     * 1:15-4:15 -> 01:15:00-> 04:14:59
     * 开始时间包括,结束时间不包括,减一秒
     *
     * @param times
     * @param hour
     * @param minute
     * @param second
     * @return
     */
    private static Long getDate(Integer[] times, int hour, int minute, int second) {
        Calendar calendar = Calendar.getInstance();
        calendar.set(times[0], times[1], times[2], hour, minute, 0);
        Date date = calendar.getTime();
        return date.getTime() + second * 1000;
    }

    private static List<Date[]> getBetweenDataList(Date beginTime, Date endTime) {
        List<Date[]> datesList = Lists.newArrayList();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        Date newDate = null;
        try {
            //开始时间的当天初始时间
            newDate = simpleDateFormat.parse(simpleDateFormat.format(beginTime));
        } catch (ParseException e) {
            e.printStackTrace();
        }
        while (true) {
            Date newEndDate = new Date(newDate.getTime() + 24 * 3600 * 1000 - 1);
            if (newEndDate.after(endTime)) {
                datesList.add(new Date[]{newDate, endTime});
                break;
            } else {
                datesList.add(new Date[]{beginTime, newEndDate});
                newDate = new Date(newEndDate.getTime() + 1);
                beginTime = newDate;
            }
        }
        return datesList;
    }
}

收费信息
在这里插入图片描述

计费详情
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值