mirror of
https://codeup.aliyun.com/67c68d4e484ca2f0a13ac3c1/ydc/jsowell-charger-web.git
synced 2026-04-20 02:55:04 +08:00
510 lines
18 KiB
Java
510 lines
18 KiB
Java
package com.jsowell.common.util;
|
||
|
||
import com.google.common.collect.ImmutableMap;
|
||
import com.google.common.collect.Lists;
|
||
import com.google.common.primitives.Bytes;
|
||
import com.jsowell.common.constant.CacheConstants;
|
||
import com.jsowell.common.constant.Constants;
|
||
import com.jsowell.common.core.domain.entity.SysDictData;
|
||
import com.jsowell.common.core.domain.ykc.YKCBaseMessage;
|
||
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
|
||
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
|
||
import com.jsowell.common.core.redis.RedisCache;
|
||
import com.jsowell.common.core.redis.StaticRedisCache;
|
||
import com.jsowell.common.enums.ykc.PileChannelEntity;
|
||
import com.jsowell.common.enums.ykc.ReturnCodeEnum;
|
||
import com.jsowell.common.exception.BusinessException;
|
||
import io.netty.channel.ChannelHandlerContext;
|
||
import lombok.extern.slf4j.Slf4j;
|
||
import org.apache.commons.collections4.CollectionUtils;
|
||
|
||
import java.math.BigDecimal;
|
||
import java.nio.charset.StandardCharsets;
|
||
import java.util.List;
|
||
import java.util.Map;
|
||
import java.util.concurrent.TimeUnit;
|
||
import java.util.stream.Stream;
|
||
|
||
@Slf4j
|
||
public class YKCUtils {
|
||
|
||
private static final BigDecimal multiple = new BigDecimal("100000");
|
||
|
||
/**
|
||
* 校验云快充请求数据格式
|
||
*
|
||
* @param msg 请求报文
|
||
* @return
|
||
*/
|
||
public static boolean checkMsg(byte[] msg) {
|
||
// log.info("checkMsg:{}", BytesUtil.binary(msg, 16));
|
||
// 起始标志
|
||
byte[] head = BytesUtil.copyBytes(msg, 0, 1);
|
||
// 数据长度
|
||
byte[] length = BytesUtil.copyBytes(msg, 1, 1);
|
||
// 序列号域
|
||
byte[] serialNumber = BytesUtil.copyBytes(msg, 2, 2);
|
||
// 加密标志
|
||
byte[] encryptFlag = BytesUtil.copyBytes(msg, 4, 1);
|
||
// 帧类型标志
|
||
byte[] frameType = BytesUtil.copyBytes(msg, 5, 1);
|
||
// 消息体
|
||
byte[] msgBody = BytesUtil.copyBytes(msg, 6, msg.length - 8);
|
||
// 帧校验域
|
||
byte[] crcByte = new byte[]{msg[msg.length - 2], msg[msg.length - 1]};
|
||
|
||
//起始位必须是0x68
|
||
if (0x68 != head[0]) {
|
||
log.error("起始位必须是0x68");
|
||
return false;
|
||
}
|
||
|
||
// 如果是0x03帧,则不进行crc校验
|
||
if (0x03 == frameType[0]) {
|
||
log.debug("0x03帧,则不进行crc校验");
|
||
return true;
|
||
}
|
||
|
||
// 序列号域+加密标志+帧类型标志+消息体
|
||
byte[] data = Bytes.concat(serialNumber, encryptFlag, frameType, msgBody);
|
||
// 校验长度
|
||
if (data.length != BytesUtil.bytesToIntLittle(length)) {
|
||
log.error("数据长度不正确, 数据长度:{}, 实际长度:{}", BytesUtil.bytesToIntLittle(length), data.length);
|
||
return false;
|
||
}
|
||
// CRC校验 source target
|
||
String sourceCRC = String.format("%04x", BytesUtil.bytesToInt(crcByte, 0));
|
||
String targetCRC = String.format("%04x", CRC16Util.calcCrc16(data));
|
||
String oldTargetCRC = String.format("%04x", CRC16Util.calcCrc16Old(data));
|
||
// 将高低位位置反转,得出新的crc
|
||
String lowString = StringUtils.substring(targetCRC, 0, 2);
|
||
String highString = StringUtils.substring(targetCRC, 2, 4);
|
||
String crc = highString + lowString;
|
||
// 若目标crc和高低位反转前/后的crc都不一致,则校验不通过
|
||
if (sourceCRC.equalsIgnoreCase(targetCRC) || sourceCRC.equalsIgnoreCase(crc)) {
|
||
return true;
|
||
}
|
||
log.error("CRC校验不通过, 源crc:{}, 计算得出crc:{}, 老crc计算:{}, 高低位反转后crc:{}, 帧类型:{}, 帧名称:{}, 报文:{}"
|
||
, sourceCRC, targetCRC, oldTargetCRC, crc, YKCUtils.frameType2Str(frameType),
|
||
YKCFrameTypeCode.getFrameTypeStr(YKCUtils.frameType2Str(frameType)), BytesUtil.binary(msg, 16));
|
||
return false;
|
||
}
|
||
|
||
public static boolean checkMsg(YKCDataProtocol ykcDataProtocol) {
|
||
// log.info("checkMsg:{}", BytesUtil.binary(msg, 16));
|
||
// 起始标志
|
||
byte[] head = ykcDataProtocol.getHead();
|
||
// 数据长度
|
||
byte[] length = ykcDataProtocol.getLength();
|
||
// 序列号域
|
||
byte[] serialNumber = ykcDataProtocol.getSerialNumber();
|
||
// 加密标志
|
||
byte[] encryptFlag = ykcDataProtocol.getEncryptFlag();
|
||
// 帧类型标志
|
||
byte[] frameType = ykcDataProtocol.getFrameType();
|
||
// 消息体
|
||
byte[] msgBody = ykcDataProtocol.getMsgBody();
|
||
// 帧校验域
|
||
byte[] crcByte = ykcDataProtocol.getCrcByte();
|
||
|
||
//起始位必须是0x68
|
||
if (0x68 != head[0]) {
|
||
log.error("起始位必须是0x68");
|
||
return false;
|
||
}
|
||
|
||
// 如果是0x03帧,则不进行crc校验
|
||
if (0x03 == frameType[0]) {
|
||
log.debug("0x03帧,则不进行crc校验");
|
||
return true;
|
||
}
|
||
|
||
// 序列号域+加密标志+帧类型标志+消息体
|
||
byte[] data = Bytes.concat(serialNumber, encryptFlag, frameType, msgBody);
|
||
// 校验长度
|
||
if (data.length != BytesUtil.bytesToIntLittle(length)) {
|
||
log.error("数据长度不正确, 数据长度:{}, 实际长度:{}", BytesUtil.bytesToIntLittle(length), data.length);
|
||
return false;
|
||
}
|
||
// CRC校验 source target
|
||
String sourceCRC = String.format("%04x", BytesUtil.bytesToInt(crcByte, 0));
|
||
String targetCRC = String.format("%04x", CRC16Util.calcCrc16(data));
|
||
String oldTargetCRC = String.format("%04x", CRC16Util.calcCrc16Old(data));
|
||
// 将高低位位置反转,得出新的crc
|
||
String lowString = StringUtils.substring(targetCRC, 0, 2);
|
||
String highString = StringUtils.substring(targetCRC, 2, 4);
|
||
String crc = highString + lowString;
|
||
// 若目标crc和高低位反转前/后的crc都不一致,则校验不通过
|
||
if (sourceCRC.equalsIgnoreCase(targetCRC) || sourceCRC.equalsIgnoreCase(crc)) {
|
||
return true;
|
||
}
|
||
log.error("CRC校验不通过, 源crc:{}, 计算得出crc:{}, 老crc计算:{}, 高低位反转后crc:{}, 帧类型:{}, 帧名称:{}, 报文:{}"
|
||
, sourceCRC, targetCRC, oldTargetCRC, crc, YKCUtils.frameType2Str(frameType),
|
||
YKCFrameTypeCode.getFrameTypeStr(YKCUtils.frameType2Str(frameType)), BytesUtil.binary(ykcDataProtocol.getBytes(), 16));
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* 获取结果报文
|
||
* @param ykcDataProtocol
|
||
* @param messageBody
|
||
* @return
|
||
*/
|
||
public static byte[] getResult(YKCDataProtocol ykcDataProtocol, byte[] messageBody) {
|
||
// 起始标志
|
||
byte[] head = ykcDataProtocol.getHead();
|
||
|
||
// 序列号域
|
||
byte[] serialNumber = ykcDataProtocol.getSerialNumber();
|
||
|
||
// 加密标志
|
||
byte[] encryptFlag = ykcDataProtocol.getEncryptFlag();
|
||
|
||
// 请求帧类型
|
||
byte[] requestFrameType = ykcDataProtocol.getFrameType();
|
||
|
||
// 应答帧类型
|
||
byte[] responseFrameType = YKCFrameTypeCode.PlatformAnswersRelation.getResponseFrameTypeBytes(requestFrameType);
|
||
|
||
// 数据域 值为“序列号域+加密标志+帧类型标志+消息体”字节数之和
|
||
byte[] dataFields = Bytes.concat(serialNumber, encryptFlag, responseFrameType, messageBody);
|
||
|
||
// 计算crc: 从序列号域到数据域的 CRC 校验
|
||
int crc16 = CRC16Util.calcCrc16(dataFields);
|
||
|
||
return Bytes.concat(head, BytesUtil.intToBytes(dataFields.length, 1), dataFields, BytesUtil.intToBytes(crc16));
|
||
}
|
||
|
||
public static byte[] getResult(YKCBaseMessage message, byte[] messageBody) {
|
||
// 起始标志
|
||
String header = message.getHeader();
|
||
byte[] headBytes = BytesUtil.stringToHexBytes(header, 1);
|
||
|
||
// 序列号域
|
||
int serialNumber = message.getSerialNumber();
|
||
byte[] serialNumberBytes = BytesUtil.intToBytesLittle(serialNumber, 2);
|
||
|
||
// 加密标志
|
||
int encryptFlag = message.getEncryptFlag();
|
||
byte[] encryptFlagBytes = BytesUtil.intToBytesLittle(encryptFlag, 1);
|
||
|
||
// 请求帧类型
|
||
String frameType = message.getFrameType();
|
||
byte[] requestFrameTypeBytes = BytesUtil.hexString2Bytes(frameType.replace("0x", ""));
|
||
|
||
// 应答帧类型
|
||
byte[] responseFrameTypeBytes = YKCFrameTypeCode.PlatformAnswersRelation.getResponseFrameTypeBytes(requestFrameTypeBytes);
|
||
|
||
// 数据域 值为“序列号域+加密标志+帧类型标志+消息体”字节数之和
|
||
byte[] dataFields = Bytes.concat(serialNumberBytes, encryptFlagBytes, responseFrameTypeBytes, messageBody);
|
||
|
||
// 计算crc: 从序列号域到数据域的 CRC 校验
|
||
int crc16 = CRC16Util.calcCrc16(dataFields);
|
||
|
||
return Bytes.concat(headBytes, BytesUtil.intToBytes(dataFields.length, 1), dataFields, BytesUtil.intToBytes(crc16));
|
||
}
|
||
|
||
/**
|
||
* 保存桩最后链接到平台的时间
|
||
* @param pileSn 桩编号
|
||
*/
|
||
public static void saveLastTimeAndCheckChannel(String pileSn, ChannelHandlerContext ctx) {
|
||
String redisKey = CacheConstants.PILE_LAST_CONNECTION + pileSn;
|
||
StaticRedisCache.staticRedisCache.setCacheObject(redisKey, DateUtils.getDateTime(), CacheConstants.cache_expire_time_30d);
|
||
|
||
// 保存桩号和channel的关系
|
||
PileChannelEntity.checkChannel(pileSn, ctx);
|
||
|
||
// PileChannelEntity.checkChannelV2(pileSn, ctx);
|
||
}
|
||
|
||
/**
|
||
* 阻止重复帧, 并返回是否重复
|
||
* 判断逻辑:相同的channelId和序列号的请求,30秒内只允许一次
|
||
* @return true 重复
|
||
*/
|
||
public static boolean verifyTheDuplicateRequest(YKCDataProtocol ykcDataProtocol, ChannelHandlerContext ctx) {
|
||
// 获取序列号域
|
||
int serialNumber = BytesUtil.bytesToIntLittle(ykcDataProtocol.getSerialNumber());
|
||
// 获取channelId
|
||
String channelId = ctx.channel().id().asShortText();
|
||
String redisKey = "Request_" + channelId + "_" + serialNumber;
|
||
Boolean result = StaticRedisCache.staticRedisCache.setnx(redisKey, ykcDataProtocol.getHEXString(), 30);
|
||
// result返回false说明没有设置成功,就是说已经有相同请求了,所以返回true重复
|
||
return !result;
|
||
}
|
||
|
||
/**
|
||
* 从消息中获取pileSn
|
||
*/
|
||
public static byte[] getPileSnBytes(String pileSn) {
|
||
// 通过pileSN转换为byte[]
|
||
return BytesUtil.hexStringToByteArray(pileSn);
|
||
}
|
||
|
||
/**
|
||
* 转换电压电流以及起始soc
|
||
* 精确到小数点后一位;待机置零
|
||
*
|
||
* @param bytes
|
||
* @return
|
||
*/
|
||
public static String convertVoltageCurrent(byte[] bytes) {
|
||
// 转换为int 最后一位是小数位
|
||
int i = BytesUtil.bytesToIntLittle(bytes);
|
||
// 使用BigDecimal
|
||
BigDecimal bigDecimal = new BigDecimal(i);
|
||
BigDecimal divide = bigDecimal.divide(new BigDecimal(10), 1, BigDecimal.ROUND_UP);
|
||
return divide.toString();
|
||
}
|
||
|
||
/**
|
||
* 转换小数点
|
||
*
|
||
* @param bytes
|
||
* @param scale 小数位
|
||
* @return
|
||
*/
|
||
public static String convertDecimalPoint(byte[] bytes, int scale) {
|
||
// 转换为int
|
||
int i = BytesUtil.bytesToIntLittle(bytes);
|
||
// 使用BigDecimal
|
||
BigDecimal bigDecimal = new BigDecimal(i);
|
||
BigDecimal divide = bigDecimal.divide(BigDecimal.valueOf(Math.pow(10d, scale)), scale, BigDecimal.ROUND_UP);
|
||
return divide.toString();
|
||
}
|
||
|
||
/**
|
||
* 获取价格byte数组
|
||
* 价格转换为byte数组
|
||
* @param price 实际价格,单位元
|
||
* @param scale 保留小数位
|
||
* @return
|
||
*/
|
||
public static byte[] getPriceByte(String price, int scale) {
|
||
// 保留小数位 例:保留5位小数,需要乘以10的5次方
|
||
BigDecimal value = BigDecimal.valueOf(Math.pow(10d, scale));
|
||
int i = new BigDecimal(price).multiply(value).intValue();
|
||
return BytesUtil.intToBytesLittle(i, 4);
|
||
}
|
||
|
||
/**
|
||
* byte转帧类型字符串 如:"01"--> "0x01"
|
||
* @param bytes
|
||
* @return
|
||
*/
|
||
public static String frameType2Str(byte[] bytes) {
|
||
String s = BytesUtil.bin2HexStr(bytes);
|
||
return frameType2Str(s);
|
||
}
|
||
|
||
/**
|
||
* 如:"01"--> "0x01"
|
||
* @param frameType 帧类型
|
||
* @return
|
||
*/
|
||
public static String frameType2Str(String frameType) {
|
||
return Constants.HEX_PREFIX + frameType;
|
||
}
|
||
|
||
public static byte[] frameTypeStr2Bytes(String frameTypeStr) {
|
||
byte[] bytes = BytesUtil.hexString2Bytes(frameTypeStr);
|
||
return bytes;
|
||
}
|
||
|
||
/**
|
||
* 转换温度
|
||
* BIN 码 1 整形,偏移量-50;待机置零
|
||
*/
|
||
public static String transitionTemperature(byte[] bytes) {
|
||
String s = BytesUtil.binary(bytes, 10);
|
||
int i = Integer.parseInt(s);
|
||
if (i > 0) {
|
||
return String.valueOf(i - 50);
|
||
}
|
||
return "一";
|
||
}
|
||
|
||
/**
|
||
* 解析vin
|
||
*/
|
||
public static String parseVin(byte[] vinCodeByteArr) {
|
||
// return BytesUtil.ascii2Str(vinCodeByteArr);
|
||
return new String(vinCodeByteArr, StandardCharsets.US_ASCII);
|
||
}
|
||
|
||
/**
|
||
* 解析充电桩连接器编号
|
||
*
|
||
* @param pileConnectorCode 充电桩连接器编号
|
||
* @param totalLength 总长度
|
||
* @param connectorOffset 连接器编号的偏移量
|
||
* @return 包含桩编号和连接器编号的映射
|
||
*/
|
||
private static Map<String, String> parsePileConnector(String pileConnectorCode, int totalLength, int connectorOffset) {
|
||
String pileSn = StringUtils.substring(pileConnectorCode, 0, totalLength - 2);
|
||
String connectorCode = StringUtils.substring(pileConnectorCode, connectorOffset);
|
||
|
||
return ImmutableMap.of("pileSn", pileSn, "connectorCode", connectorCode);
|
||
}
|
||
|
||
/**
|
||
* 解析充电桩编号
|
||
*
|
||
* @param pileConnectorCode 充电桩连接器编号
|
||
* @return 包含桩编号和连接器编号的映射
|
||
*/
|
||
public static Map<String, String> parsePileConnectorCode(String pileConnectorCode) {
|
||
int length = pileConnectorCode.length();
|
||
|
||
// 如果length不等于14, 16, 8, 10, 2, 判断为错误的长度
|
||
if (length != Constants.PILE_CONNECTOR_CODE_LENGTH_FOR_EV
|
||
&& length != Constants.PILE_SN_LENGTH_FOR_EV
|
||
&& length != Constants.PILE_CONNECTOR_CODE_LENGTH_FOR_EBIKE
|
||
&& length != Constants.PILE_SN_LENGTH_FOR_EBIKE
|
||
&& length != 2) {
|
||
throw new BusinessException(ReturnCodeEnum.CODE_DATA_LENGTH_ERROR.getValue(), "Invalid pile connector code length: " + length);
|
||
}
|
||
|
||
if (length == Constants.PILE_CONNECTOR_CODE_LENGTH_FOR_EV) {
|
||
// 汽车桩
|
||
return parsePileConnector(pileConnectorCode, Constants.PILE_CONNECTOR_CODE_LENGTH_FOR_EV, pileConnectorCode.length() - 2);
|
||
} else if (length == Constants.PILE_CONNECTOR_CODE_LENGTH_FOR_EBIKE) {
|
||
// 电单车
|
||
return parsePileConnector(pileConnectorCode, Constants.PILE_CONNECTOR_CODE_LENGTH_FOR_EBIKE, pileConnectorCode.length() - 2);
|
||
} else if (length == Constants.PILE_SN_LENGTH_FOR_EV || length == Constants.PILE_SN_LENGTH_FOR_EBIKE) {
|
||
// 充电桩编号
|
||
return ImmutableMap.of("pileSn", pileConnectorCode, "connectorCode", "");
|
||
} else {
|
||
// 枪口号
|
||
return ImmutableMap.of("pileSn", "", "connectorCode", pileConnectorCode);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取pileSn
|
||
* @param pileConnectorCode 充电桩枪口编号
|
||
* @return
|
||
*/
|
||
public static String getPileSn(String pileConnectorCode) {
|
||
Map<String, String> map = parsePileConnectorCode(pileConnectorCode);
|
||
return map.get("pileSn");
|
||
}
|
||
|
||
/**
|
||
* 获取枪口号
|
||
* @param pileConnectorCode 充电桩枪口编号
|
||
* @return
|
||
*/
|
||
public static String getConnectorCode(String pileConnectorCode) {
|
||
Map<String, String> map = parsePileConnectorCode(pileConnectorCode);
|
||
return map.get("connectorCode");
|
||
}
|
||
|
||
/**
|
||
* 保存soc
|
||
* 默认保存7天
|
||
* @param transactionCode
|
||
* @param soc
|
||
*/
|
||
public static void saveSOC(String transactionCode, String soc) {
|
||
if (StringUtils.isBlank(transactionCode) || Double.parseDouble(soc) <= 0) {
|
||
return;
|
||
}
|
||
|
||
String hashKey = CacheConstants.GET_THE_SOC + transactionCode;
|
||
RedisCache staticRedisCache = StaticRedisCache.staticRedisCache;
|
||
try {
|
||
// 检查值是否符合要求
|
||
if (!soc.matches("^-?\\d+(\\.\\d{1})?$")) {
|
||
throw new IllegalArgumentException("输入的值必须是一个最多有一位小数的数字: " + soc);
|
||
}
|
||
double doubleValue = Double.parseDouble(soc);
|
||
// 获取当前的最小值和最大值
|
||
String currentMin = (String) staticRedisCache.hget(hashKey, "min");
|
||
String currentMax = (String) staticRedisCache.hget(hashKey, "max");
|
||
|
||
double min = currentMin != null ? Double.parseDouble(currentMin) : Double.MAX_VALUE;
|
||
double max = currentMax != null ? Double.parseDouble(currentMax) : Double.MIN_VALUE;
|
||
|
||
// 更新最小值或最大值
|
||
if (doubleValue < min) {
|
||
staticRedisCache.hset(hashKey, "min", soc, 7, TimeUnit.DAYS);
|
||
}
|
||
if (doubleValue > max) {
|
||
staticRedisCache.hset(hashKey, "max", soc, 7, TimeUnit.DAYS);
|
||
}
|
||
} catch (Exception e) {
|
||
e.printStackTrace();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 根据交易流水号获取redis保存的soc信息
|
||
*/
|
||
public static Map<String, String> getSOCMap(String transactionCode) {
|
||
String hashKey = CacheConstants.GET_THE_SOC + transactionCode;
|
||
RedisCache staticRedisCache = StaticRedisCache.staticRedisCache;
|
||
return staticRedisCache.getCacheMap(hashKey);
|
||
}
|
||
|
||
/**
|
||
* 根据的交易码获取当前soc
|
||
* @param transactionCode
|
||
*/
|
||
public static String getCurrentSOC(String transactionCode) {
|
||
// String hashKey = CacheConstants.GET_THE_SOC + transactionCode;
|
||
// RedisCache staticRedisCache = StaticRedisCache.staticRedisCache;
|
||
// Map<String, Object> cacheMap = staticRedisCache.getCacheMap(hashKey);
|
||
|
||
Map<String, String> socMap = getSOCMap(transactionCode);
|
||
// 获取最小值和最大值, 两个值中最大的为当前soc
|
||
return (String) socMap.get("max");
|
||
}
|
||
|
||
/**
|
||
* 获取使用新分账方法的stationIdList
|
||
*
|
||
* new_logic_station_id_list
|
||
*/
|
||
public static List<String> getNewLogicStationIdList() {
|
||
List<String> stationIdList = Lists.newArrayList();
|
||
// 从字典中查询
|
||
List<SysDictData> new_logic_station_id_list = DictUtils.getDictCache("new_logic_station_id_list");
|
||
if (CollectionUtils.isNotEmpty(new_logic_station_id_list)) {
|
||
for (SysDictData sysDictData : new_logic_station_id_list) {
|
||
stationIdList.add(sysDictData.getDictValue());
|
||
}
|
||
}
|
||
return stationIdList;
|
||
}
|
||
|
||
/**
|
||
* 两个BigDecimal, 取出大于0的最小值
|
||
* @param settleAmount 结算金额
|
||
* @param actualReceivedAmount 实收金额
|
||
*/
|
||
public static BigDecimal getMinBigDecimal(BigDecimal settleAmount, BigDecimal actualReceivedAmount) {
|
||
BigDecimal minValue = Stream.of(settleAmount, actualReceivedAmount)
|
||
.filter(v -> v.compareTo(BigDecimal.ZERO) > 0) // 过滤出大于 0 的值
|
||
.min(BigDecimal::compareTo) // 取最小值
|
||
.orElse(settleAmount); // 如果没有大于 0 的值,则返回结算金额
|
||
return minValue;
|
||
}
|
||
|
||
/**
|
||
* 三个BigDecimal, 取出大于0的最小值
|
||
* @param settleAmount 结算金额
|
||
* @param actualReceivedAmount 实收金额
|
||
* @param orderAmount 订单金额
|
||
*/
|
||
public static BigDecimal getMinBigDecimal(BigDecimal settleAmount, BigDecimal actualReceivedAmount, BigDecimal orderAmount) {
|
||
BigDecimal minValue = Stream.of(settleAmount, actualReceivedAmount, orderAmount)
|
||
.filter(v -> v.compareTo(BigDecimal.ZERO) > 0) // 过滤出大于 0 的值
|
||
.min(BigDecimal::compareTo) // 取最小值
|
||
.orElse(orderAmount); // 如果没有大于 0 的值,则返回订单金额
|
||
return minValue;
|
||
}
|
||
|
||
}
|