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 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 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 map = parsePileConnectorCode(pileConnectorCode); return map.get("pileSn"); } /** * 获取枪口号 * @param pileConnectorCode 充电桩枪口编号 * @return */ public static String getConnectorCode(String pileConnectorCode) { Map 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 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 cacheMap = staticRedisCache.getCacheMap(hashKey); Map socMap = getSOCMap(transactionCode); // 获取最小值和最大值, 两个值中最大的为当前soc return (String) socMap.get("max"); } /** * 获取使用新分账方法的stationIdList * * new_logic_station_id_list */ public static List getNewLogicStationIdList() { List stationIdList = Lists.newArrayList(); // 从字典中查询 List 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; } }