Files
jsowell-charger-web/jsowell-common/src/main/java/com/jsowell/common/util/YKCUtils.java
2025-12-11 18:15:40 +08:00

510 lines
18 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}
}