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;
}
}
收费信息
计费详情