diff --git a/jsowell-admin/src/main/java/com/jsowell/web/controller/pile/OrderBasicInfoController.java b/jsowell-admin/src/main/java/com/jsowell/web/controller/pile/OrderBasicInfoController.java index f0282aa6f..bbe84d859 100644 --- a/jsowell-admin/src/main/java/com/jsowell/web/controller/pile/OrderBasicInfoController.java +++ b/jsowell-admin/src/main/java/com/jsowell/web/controller/pile/OrderBasicInfoController.java @@ -185,7 +185,7 @@ public class OrderBasicInfoController extends BaseController { } /** - * 重试订单退款接口 + * 重试订单退款接口/重试退款按钮 * http://localhost:8080/order/retryRefundOrder */ @PostMapping("/retryRefundOrder") diff --git a/jsowell-common/src/main/java/com/jsowell/common/YouDianUtils.java b/jsowell-common/src/main/java/com/jsowell/common/YouDianUtils.java index 0093e7238..a1d1d61f0 100644 --- a/jsowell-common/src/main/java/com/jsowell/common/YouDianUtils.java +++ b/jsowell-common/src/main/java/com/jsowell/common/YouDianUtils.java @@ -3,29 +3,42 @@ package com.jsowell.common; import com.jsowell.common.util.BytesUtil; import lombok.extern.slf4j.Slf4j; +import java.util.Arrays; + /** * 友电电单车充电桩协议工具类 */ @Slf4j public class YouDianUtils { + public static void main(String[] args) { + String s = "44 4e 59 0a 00 3b 37 ab 04 01 00 21 00 38 02"; + byte[] bytes = BytesUtil.hexStringToByteArray(s); + + String s2 = BytesUtil.printHexBinary(bytes); + System.out.println(s2); + byte[] bytes2 = BytesUtil.hexStringToByteArray(s2); + System.out.println(bytes2); + boolean b = validateChecksum(bytes); + + // + String s3 = "44 4e 59 0a 00 3b 37 ab 04 01 00 21 00"; + byte[] bytes3 = BytesUtil.hexStringToByteArray(s3); + int i = calculateCheckField(bytes3); + BytesUtil.intToBytesLittle(i); + } /** * 校验方法 * 整个数据包中的每个字节(不包括校验字段本身),将它们的数值累加起来。然后取累加和的低2字节(16位),作为校验字段的值 + * @param bytes 完整数据包, 包含校验字段 */ public static boolean validateChecksum(byte[] bytes) { if (bytes.length < 2) { return false; // 校验字段长度不足时返回 false } - // 计算累加和 - int sum = 0; - for (int i = 0; i < bytes.length - 2; i++) { - sum += (bytes[i] & 0xFF); // 将每个字节视为无符号值进行累加 - } - - // 取累加和的低 2 字节(16 位) - int calculatedChecksum = sum & 0xFFFF; + byte[] copyOfRange = Arrays.copyOfRange(bytes, 0, bytes.length - 2); + int calculatedChecksum = calculateCheckField(copyOfRange); // 读取校验字段的值 byte[] checksumBytes = {bytes[bytes.length - 2], bytes[bytes.length - 1]}; @@ -35,4 +48,29 @@ public class YouDianUtils { log.info("计算的校验值:{}, 接收到的校验值:{}", calculatedChecksum, receivedChecksum); return calculatedChecksum == receivedChecksum; } + + /** + * 计算校验字段 + * @param bytes 数据包不含校验字段, 包头+长度+物理ID+消息ID+命令+数据 + */ + public static int calculateCheckField(byte[] bytes) { + // 计算累加和 + int sum = 0; + for (byte aByte : bytes) { + sum += (aByte & 0xFF); // 将每个字节视为无符号值进行累加 + } + + // 取累加和的低 2 字节(16 位) + int i = sum & 0xFFFF; + log.info("计算校验字段:{}", i); + return i; + } + + /** + * 获取校验字段byte数组 小端 + */ + public static byte[] getCheckFieldBytes(byte[] bytes) { + int calculatedChecksum = calculateCheckField(bytes); + return BytesUtil.intToBytesLittle(calculatedChecksum); + } } diff --git a/jsowell-common/src/main/java/com/jsowell/common/core/domain/ebike/EBikeCommandEnum.java b/jsowell-common/src/main/java/com/jsowell/common/core/domain/ebike/EBikeCommandEnum.java deleted file mode 100644 index ac883bc28..000000000 --- a/jsowell-common/src/main/java/com/jsowell/common/core/domain/ebike/EBikeCommandEnum.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.jsowell.common.core.domain.ebike; - -import lombok.extern.slf4j.Slf4j; - -/** - * 友电电单车协议指令 - */ -@Slf4j -public enum EBikeCommandEnum { - // HEARTBEAT("01", "设备心跳包"), - REGISTRATION("20", "设备注册包"), - GET_SERVER_TIME("22", "设备获取服务器时间"), - HEARTBEAT_2("21", "设备心跳包"), - CARD_OPERATION("02", "刷卡操作"), - SETTLEMENT_UPLOAD("03", "结算消费信息上传"), - CHARGE_PORT_CONFIRMATION("04", "充电端口订单确认"), - POWER_HEARTBEAT("06", "端口充电时功率心跳包"), - CHARGER_HEARTBEAT("41", "充电柜专有心跳包"), - ALARM_PUSH("42", "报警推送指令"), - CHARGE_COMPLETE("43", "充电完成通知,但不结算"), - PORT_PUSH("44", "端口推送指令"); - - ; - - EBikeCommandEnum(String code, String desc) { - this.code = code; - this.desc = desc; - } - - private final String code; // 帧类型code - private final String desc; // 帧类型名称 - - public String getCode() { - return code; - } - - public String getDesc() { - return desc; - } - -} \ No newline at end of file diff --git a/jsowell-common/src/main/java/com/jsowell/common/core/domain/ebike/EBikeDataProtocol.java b/jsowell-common/src/main/java/com/jsowell/common/core/domain/ebike/EBikeDataProtocol.java new file mode 100644 index 000000000..43bd8e2ac --- /dev/null +++ b/jsowell-common/src/main/java/com/jsowell/common/core/domain/ebike/EBikeDataProtocol.java @@ -0,0 +1,76 @@ +package com.jsowell.common.core.domain.ebike; + +import com.google.common.primitives.Bytes; +import com.jsowell.common.util.BytesUtil; +import lombok.Data; + +/** + * 云快充数据模板 + */ +@Data +public class EBikeDataProtocol { + /** + * 包头 3字节 + */ + private byte[] head; + + /** + * 数据长度 2字节 + */ + private byte[] length; + + /** + * 物理id 4字节 + */ + private byte[] physicalId; + + /** + * 消息id 2字节 + */ + private byte[] messageId; + + /** + * 命令 1字节 + */ + private byte[] command; + + /** + * 消息体 N字节 + */ + private byte[] msgBody; + + /** + * 帧校验域 2字节 + */ + private byte[] checksum; + + public EBikeDataProtocol(byte[] msg) { + // 起始标志 + this.head = BytesUtil.copyBytes(msg, 0, 3); + // 数据长度 + this.length = BytesUtil.copyBytes(msg, 3, 2); + // 物理id + this.physicalId = BytesUtil.copyBytes(msg, 5, 4); + // 消息id + this.messageId = BytesUtil.copyBytes(msg, 9, 2); + // 命令 + this.command = BytesUtil.copyBytes(msg, 11, 1); + // 消息体 + this.msgBody = BytesUtil.copyBytes(msg, 12, msg.length - 14); + // 帧校验域 + this.checksum = new byte[]{msg[msg.length - 2], msg[msg.length - 1]}; + } + + /** + * 转换为十六进制字符串 + * + * @return 报文 + */ + public String getHEXString() { + return BytesUtil.binary(this.getBytes(), 16); + } + + public byte[] getBytes() { + return Bytes.concat(this.head, this.length, this.physicalId, this.messageId, this.command, this.msgBody, this.checksum); + } +} diff --git a/jsowell-common/src/main/java/com/jsowell/common/protocol/SyncPromise.java b/jsowell-common/src/main/java/com/jsowell/common/protocol/SyncPromise.java index 7ce5e820a..1f9517488 100644 --- a/jsowell-common/src/main/java/com/jsowell/common/protocol/SyncPromise.java +++ b/jsowell-common/src/main/java/com/jsowell/common/protocol/SyncPromise.java @@ -27,24 +27,7 @@ public class SyncPromise { this.rpcResult = rpcResult; } - /** - * 同步等待返回结果 - */ - // public RpcResponse get(long timeout, TimeUnit unit) throws InterruptedException { - // // 等待阻塞,超时时间内countDownLatch减到0,将提前唤醒,以此作为是否超时判断 - // boolean earlyWakeUp = countDownLatch.await(timeout, unit); - // - // if(earlyWakeUp) { - // // 超时时间内countDownLatch减到0,提前唤醒,说明已有结果 - // return rpcResponse; - // } else { - // // 超时时间内countDownLatch没有减到0,自动唤醒,说明超时时间内没有等到结果 - // isTimeout = true; - // return null; - // } - // } - - public byte[] get2(long timeout, TimeUnit unit) throws InterruptedException { + public byte[] get(long timeout, TimeUnit unit) throws InterruptedException { // 等待阻塞,超时时间内countDownLatch减到0,将提前唤醒,以此作为是否超时判断 boolean earlyWakeUp = countDownLatch.await(timeout, unit); @@ -61,12 +44,5 @@ public class SyncPromise { public void wake() { countDownLatch.countDown(); } - - // public RpcResponse getRpcResponse() { - // return rpcResponse; - // } - // public void setRpcResponse(RpcResponse rpcResponse) { - // this.rpcResponse = rpcResponse; - // } } \ No newline at end of file diff --git a/jsowell-common/src/main/java/com/jsowell/common/util/BytesUtil.java b/jsowell-common/src/main/java/com/jsowell/common/util/BytesUtil.java index 4f43ded4b..bfa054737 100644 --- a/jsowell-common/src/main/java/com/jsowell/common/util/BytesUtil.java +++ b/jsowell-common/src/main/java/com/jsowell/common/util/BytesUtil.java @@ -2,13 +2,12 @@ package com.jsowell.common.util; import com.google.common.primitives.Bytes; import com.jsowell.common.constant.Constants; -import org.springframework.mail.MailMessage; import javax.xml.bind.DatatypeConverter; import java.io.UnsupportedEncodingException; import java.math.BigInteger; +import java.nio.ByteBuffer; import java.time.LocalDateTime; -import java.util.Arrays; import java.util.Date; import java.util.Stack; @@ -18,12 +17,20 @@ public class BytesUtil { /** * 将int数值转换为占两个字节的byte数组,本方法适用于(高位在前,低位在后)的顺序。 + * 大端模式 */ public static byte[] intToBytes(int value) { //limit 传入2 return intToBytes(value, 2); } + /** + * 将int转换byte 大端模式 + * + * @param value int值 + * @param limit 保留长度 + * @return + */ public static byte[] intToBytes(int value, int limit) { byte[] src = new byte[limit]; for (int i = 0; i < limit; i++) { @@ -37,6 +44,52 @@ public class BytesUtil { return src; } + /** + * 将int转换为byte数组,并采用小端模式。 + * + * @param value 需要转换的整型值 + * @return 转换后的字节数组 + */ + public static byte[] intToBytesLittleEndian(int value) { + ByteBuffer buffer = ByteBuffer.allocate(2); // 分配4字节的空间 + buffer.order(java.nio.ByteOrder.LITTLE_ENDIAN); // 设置为小端模式 + buffer.putInt(value); // 将整数放入缓冲区 + return buffer.array(); // 返回字节数组 + } + + /** + * 将int转为byte 小端模式 + * + * @param value int值 + * @return + */ + public static byte[] intToBytesLittle(int value) { + //limit 传入2 + return intToBytesLittle(value, 2); + } + + /** + * 将int转换byte 小端模式 + * + * @param value int值 + * @param limit 保留长度 + * @return + */ + public static byte[] intToBytesLittle(int value, int limit) { + // 参数检查,确保limit非负且不超过int类型的字节长度 + if (limit < 0 || limit > 4) { + throw new IllegalArgumentException("Limit must be between 0 and 4 inclusive."); + } + + byte[] src = new byte[limit]; + for (int i = 0; i < limit; i++) { + int x = 8 * i; + // 直接进行位移操作,无需特殊判断x是否为0 + src[i] = (byte) ((value >> x) & 0xFF); + } + return src; + } + public static int bytesToInt(byte[] src) { return bytesToInt(src, 0); } @@ -148,6 +201,7 @@ public class BytesUtil { /** * 将byte[]转为十六进制的字符串 + * 可以与hexStringToByteArray互转 * @param bytes byte[] * @return 转换后的字符串 */ @@ -270,36 +324,7 @@ public class BytesUtil { return revert(temp); } - /** - * 将int转为byte 小端模式 - * - * @param value int值 - * @return - */ - public static byte[] intToBytesLittle(int value) { - //limit 传入2 - return intToBytes(value, 2); - } - /** - * 将int转换byte 小端模式 - * - * @param value int值 - * @param limit 保留长度 - * @return - */ - public static byte[] intToBytesLittle(int value, int limit) { - byte[] src = new byte[limit]; - for (int i = 0; i < limit; i++) { - int x = 8 * i; - if (x == 0) { - src[i] = (byte) (value & 0xFF); - } else { - src[i] = (byte) ((value >> x) & 0xFF); - } - } - return src; - } public static void main(String[] args) { byte[] length = new byte[] {0x09, 0x00}; @@ -744,16 +769,31 @@ public class BytesUtil { /** * 将String转换为byte[] - * + * 可以与printHexBinary互转 * @param s String * @return byte[] */ public static byte[] hexStringToByteArray(String s) { + if (s == null || s.length() % 2 != 0) { + throw new IllegalArgumentException("Input string must not be null and its length must be even."); + } + + s = s.replaceAll("\\s", ""); // 使用正则表达式去除所有空白字符 + int len = s.length(); byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { - data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16)); + int byteValue = Character.digit(s.charAt(i), 16) << 4; + byteValue += Character.digit(s.charAt(i + 1), 16); + + if (byteValue < 0) { + throw new IllegalArgumentException("Invalid character in the input string at index " + i); + } + + data[i / 2] = (byte) byteValue; } + return data; } } diff --git a/jsowell-netty/src/main/java/com/jsowell/netty/decoder/CustomDecoder.java b/jsowell-netty/src/main/java/com/jsowell/netty/decoder/CustomDecoder.java deleted file mode 100644 index 3ea0044c8..000000000 --- a/jsowell-netty/src/main/java/com/jsowell/netty/decoder/CustomDecoder.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.jsowell.netty.decoder; - -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.ByteToMessageDecoder; -import io.netty.handler.codec.CorruptedFrameException; - -import java.util.List; - -public class CustomDecoder extends ByteToMessageDecoder { - private static final byte START_FLAG = (byte) 0x68; - - @Override - protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { - // 检查可读数据长度是否大于等于起始标志符和数据长度字段的长度 - if (in.readableBytes() >= 3) { - // 标记当前读取位置 - in.markReaderIndex(); - // 读取起始标志符 - byte startFlag = in.readByte(); - // 检查起始标志符是否正确 - if (startFlag != START_FLAG) { - // 如果不正确,重置读取位置,并抛出异常 - in.resetReaderIndex(); - throw new CorruptedFrameException("Invalid start flag: " + startFlag); - } - // 读取数据长度 - byte length = in.readByte(); - // 检查可读数据长度是否大于等于数据长度字段的值 - if (in.readableBytes() >= length) { - // 读取完整数据包 - ByteBuf frame = in.readBytes(length + 2); // 包括校验位 - out.add(frame); - } else { - // 如果可读数据长度不够,重置读取位置,并等待下一次读取 - in.resetReaderIndex(); - } - } - } -} diff --git a/jsowell-netty/src/main/java/com/jsowell/netty/handler/electricbicycles/AbstractEBikeHandler.java b/jsowell-netty/src/main/java/com/jsowell/netty/handler/electricbicycles/AbstractEBikeHandler.java index 764c2467c..2cba58195 100644 --- a/jsowell-netty/src/main/java/com/jsowell/netty/handler/electricbicycles/AbstractEBikeHandler.java +++ b/jsowell-netty/src/main/java/com/jsowell/netty/handler/electricbicycles/AbstractEBikeHandler.java @@ -1,13 +1,13 @@ package com.jsowell.netty.handler.electricbicycles; import com.google.common.primitives.Bytes; +import com.jsowell.common.YouDianUtils; import com.jsowell.common.constant.CacheConstants; +import com.jsowell.common.core.domain.ebike.EBikeDataProtocol; 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.enums.ykc.PileChannelEntity; import com.jsowell.common.util.BytesUtil; -import com.jsowell.common.util.CRC16Util; import com.jsowell.common.util.DateUtils; import io.netty.channel.ChannelHandlerContext; import org.springframework.beans.factory.InitializingBean; @@ -25,41 +25,31 @@ public abstract class AbstractEBikeHandler implements InitializingBean { * 执行逻辑 * 有应答 */ - public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, ChannelHandlerContext ctx) { + public byte[] supplyProcess(EBikeDataProtocol dataProtocol, ChannelHandlerContext ctx) { throw new UnsupportedOperationException(); } - /** - * 执行逻辑 - * 不需要应答 - */ - // public void pushProcess() { - // throw new UnsupportedOperationException(); - // } - /** * 组装应答的结果 - * @param ykcDataProtocol 请求数据 + * @param dataProtocol 请求数据 * @param messageBody 消息体 * @return 应答结果 */ - protected byte[] getResult(YKCDataProtocol ykcDataProtocol, byte[] messageBody) { + protected byte[] getResult(EBikeDataProtocol dataProtocol, byte[] messageBody) { // 起始标志 - byte[] head = ykcDataProtocol.getHead(); - // 序列号域 - byte[] serialNumber = ykcDataProtocol.getSerialNumber(); + byte[] head = dataProtocol.getHead(); + // 长度 = 物理ID(4) + 消息ID(2) + 命令(1) + 数据(n) + 校验(2),每包最多256字节 + byte[] length = BytesUtil.intToBytesLittle(9 + messageBody.length); + // 物理id + byte[] physicalId = dataProtocol.getPhysicalId(); // 加密标志 - byte[] encryptFlag = ykcDataProtocol.getEncryptFlag(); + byte[] messageId = dataProtocol.getMessageId(); // 请求帧类型 - 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)); + byte[] command = dataProtocol.getCommand(); + // 整个数据包中的每个字节(不包括校验字段本身) + byte[] dataFields = Bytes.concat(head, length, physicalId, messageId, command, messageBody); + byte[] checkFieldBytes = YouDianUtils.getCheckFieldBytes(dataFields); + return Bytes.concat(dataFields, checkFieldBytes); } /** diff --git a/jsowell-netty/src/main/java/com/jsowell/netty/handler/electricbicycles/GetServerTimeHandler.java b/jsowell-netty/src/main/java/com/jsowell/netty/handler/electricbicycles/GetServerTimeHandler.java index ad27ed5a7..37cb97b42 100644 --- a/jsowell-netty/src/main/java/com/jsowell/netty/handler/electricbicycles/GetServerTimeHandler.java +++ b/jsowell-netty/src/main/java/com/jsowell/netty/handler/electricbicycles/GetServerTimeHandler.java @@ -1,12 +1,18 @@ package com.jsowell.netty.handler.electricbicycles; -import com.jsowell.common.core.domain.ebike.EBikeCommandEnum; +import com.alibaba.fastjson2.JSON; +import com.jsowell.common.core.domain.ebike.EBikeDataProtocol; +import com.jsowell.common.util.BytesUtil; import com.jsowell.netty.factory.EBikeOperateFactory; +import com.jsowell.pile.domain.ebike.AbsEBikeMessage; +import com.jsowell.pile.domain.ebike.EBikeCommandEnum; +import com.jsowell.pile.domain.ebike.deviceupload.EBikeMessageCmd22; +import io.netty.channel.ChannelHandlerContext; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; /** - * 设备心跳包 + * 设备获取服务器时间 */ @Slf4j @Component @@ -17,4 +23,23 @@ public class GetServerTimeHandler extends AbstractEBikeHandler { public void afterPropertiesSet() throws Exception { EBikeOperateFactory.register(type, this); } + + /** + * 执行逻辑 + * 有应答 + * + * @param dataProtocol + * @param ctx + */ + @Override + public byte[] supplyProcess(EBikeDataProtocol dataProtocol, ChannelHandlerContext ctx) { + // 解析字节数组 + EBikeMessageCmd22 message = (EBikeMessageCmd22) AbsEBikeMessage.parseMessage(dataProtocol.getBytes()); + log.info("设备获取服务器时间:{}", JSON.toJSONString(message)); + + // 获取当前服务器10位时间戳 + byte[] timeBytes = BytesUtil.getIntBytes((int) (System.currentTimeMillis() / 1000)); + // System.out.println("data: " + BytesUtil.bytesToIntLittle(timeBytes)); + return getResult(dataProtocol, timeBytes); + } } diff --git a/jsowell-netty/src/main/java/com/jsowell/netty/handler/electricbicycles/HeartbeatHandler.java b/jsowell-netty/src/main/java/com/jsowell/netty/handler/electricbicycles/HeartbeatHandler.java index 9a5dcb0d7..e7d1200b1 100644 --- a/jsowell-netty/src/main/java/com/jsowell/netty/handler/electricbicycles/HeartbeatHandler.java +++ b/jsowell-netty/src/main/java/com/jsowell/netty/handler/electricbicycles/HeartbeatHandler.java @@ -1,7 +1,13 @@ package com.jsowell.netty.handler.electricbicycles; -import com.jsowell.common.core.domain.ebike.EBikeCommandEnum; +import com.alibaba.fastjson2.JSON; +import com.jsowell.common.constant.Constants; +import com.jsowell.common.core.domain.ebike.EBikeDataProtocol; import com.jsowell.netty.factory.EBikeOperateFactory; +import com.jsowell.pile.domain.ebike.AbsEBikeMessage; +import com.jsowell.pile.domain.ebike.EBikeCommandEnum; +import com.jsowell.pile.domain.ebike.deviceupload.EBikeMessageCmd21; +import io.netty.channel.ChannelHandlerContext; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @@ -17,4 +23,20 @@ public class HeartbeatHandler extends AbstractEBikeHandler { public void afterPropertiesSet() throws Exception { EBikeOperateFactory.register(type, this); } + + /** + * 执行逻辑 + * 有应答 + * + * @param dataProtocol + * @param ctx + */ + @Override + public byte[] supplyProcess(EBikeDataProtocol dataProtocol, ChannelHandlerContext ctx) { + // 解析字节数组 + EBikeMessageCmd21 message = (EBikeMessageCmd21) AbsEBikeMessage.parseMessage(dataProtocol.getBytes()); + EBikeMessageCmd21.DeviceHeartbeat deviceHeartbeat = message.getDeviceHeartbeat(); + log.info("设备心跳包:{}", JSON.toJSONString(message)); + return getResult(dataProtocol, Constants.zeroByteArray); + } } diff --git a/jsowell-netty/src/main/java/com/jsowell/netty/handler/electricbicycles/RegistrationHandler.java b/jsowell-netty/src/main/java/com/jsowell/netty/handler/electricbicycles/RegistrationHandler.java index b4512e9e4..bbf58ea16 100644 --- a/jsowell-netty/src/main/java/com/jsowell/netty/handler/electricbicycles/RegistrationHandler.java +++ b/jsowell-netty/src/main/java/com/jsowell/netty/handler/electricbicycles/RegistrationHandler.java @@ -1,12 +1,18 @@ package com.jsowell.netty.handler.electricbicycles; -import com.jsowell.common.core.domain.ebike.EBikeCommandEnum; +import com.alibaba.fastjson2.JSON; +import com.jsowell.common.constant.Constants; +import com.jsowell.common.core.domain.ebike.EBikeDataProtocol; import com.jsowell.netty.factory.EBikeOperateFactory; +import com.jsowell.pile.domain.ebike.AbsEBikeMessage; +import com.jsowell.pile.domain.ebike.EBikeCommandEnum; +import com.jsowell.pile.domain.ebike.deviceupload.EBikeMessageCmd20; +import io.netty.channel.ChannelHandlerContext; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; /** - * 设备心跳包 + * 设备注册包 */ @Slf4j @Component @@ -17,4 +23,20 @@ public class RegistrationHandler extends AbstractEBikeHandler { public void afterPropertiesSet() throws Exception { EBikeOperateFactory.register(type, this); } + + /** + * 执行逻辑 + * 有应答 + * + * @param dataProtocol + * @param ctx + */ + @Override + public byte[] supplyProcess(EBikeDataProtocol dataProtocol, ChannelHandlerContext ctx) { + // 解析字节数组 + EBikeMessageCmd20 message = (EBikeMessageCmd20) AbsEBikeMessage.parseMessage(dataProtocol.getBytes()); + EBikeMessageCmd20.DeviceRegister deviceRegister = message.getDeviceRegister(); + log.info("设备注册包:{}", JSON.toJSONString(message)); + return getResult(dataProtocol, Constants.zeroByteArray); + } } diff --git a/jsowell-netty/src/main/java/com/jsowell/netty/handler/electricbicycles/SettlementUploadHandler.java b/jsowell-netty/src/main/java/com/jsowell/netty/handler/electricbicycles/SettlementUploadHandler.java index cbdd31de7..fd9825a8e 100644 --- a/jsowell-netty/src/main/java/com/jsowell/netty/handler/electricbicycles/SettlementUploadHandler.java +++ b/jsowell-netty/src/main/java/com/jsowell/netty/handler/electricbicycles/SettlementUploadHandler.java @@ -1,12 +1,17 @@ package com.jsowell.netty.handler.electricbicycles; -import com.jsowell.common.core.domain.ebike.EBikeCommandEnum; +import com.alibaba.fastjson2.JSON; +import com.jsowell.common.core.domain.ebike.EBikeDataProtocol; import com.jsowell.netty.factory.EBikeOperateFactory; +import com.jsowell.pile.domain.ebike.AbsEBikeMessage; +import com.jsowell.pile.domain.ebike.EBikeCommandEnum; +import com.jsowell.pile.domain.ebike.deviceupload.EBikeMessageCmd03; +import io.netty.channel.ChannelHandlerContext; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; /** - * 设备心跳包 + * 结算消费信息上传 */ @Slf4j @Component @@ -17,4 +22,21 @@ public class SettlementUploadHandler extends AbstractEBikeHandler { public void afterPropertiesSet() throws Exception { EBikeOperateFactory.register(type, this); } + + /** + * 执行逻辑 + * 有应答 + * + * @param dataProtocol + * @param ctx + */ + @Override + public byte[] supplyProcess(EBikeDataProtocol dataProtocol, ChannelHandlerContext ctx) { + // 解析字节数组 + EBikeMessageCmd03 message = (EBikeMessageCmd03) AbsEBikeMessage.parseMessage(dataProtocol.getBytes()); + EBikeMessageCmd03.SettlementInfo settlementInfo = message.getSettlementInfo(); + log.info("结算消费信息上传:{}", JSON.toJSONString(message)); + return null; + } + } diff --git a/jsowell-netty/src/main/java/com/jsowell/netty/handler/yunkuaichong/ReservationChargingStartupResultHandler.java b/jsowell-netty/src/main/java/com/jsowell/netty/handler/yunkuaichong/ReservationChargingStartupResultHandler.java index b39d9aa19..83f0a7899 100644 --- a/jsowell-netty/src/main/java/com/jsowell/netty/handler/yunkuaichong/ReservationChargingStartupResultHandler.java +++ b/jsowell-netty/src/main/java/com/jsowell/netty/handler/yunkuaichong/ReservationChargingStartupResultHandler.java @@ -34,6 +34,55 @@ public class ReservationChargingStartupResultHandler extends AbstractYkcHandler YKCOperateFactory.register(type, this); } + public static void main(String[] args) { + // 获取消息体 + String msg = "8823000000071801240823102300000088230000000718010190"; + byte[] msgBody = BytesUtil.str2Bcd(msg); + + int startIndex = 0; + int length = 16; + + // 交易流水号 + byte[] transactionCodeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length); + String transactionCode = BytesUtil.bcd2Str(transactionCodeByteArr); + + // 桩编码 + startIndex += length; + length = 7; + byte[] pileSnByteArr = BytesUtil.copyBytes(msgBody, startIndex, length); + String pileSn = BytesUtil.bcd2Str(pileSnByteArr); + + // 枪号 + startIndex += length; + length = 1; + byte[] connectorCodeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length); + String connectorCode = BytesUtil.bcd2Str(connectorCodeByteArr); + + // vin + startIndex += length; + length = 17; + byte[] vinCodeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length); + String vinCode = YKCUtils.parseVin(vinCodeByteArr); // 解析vin + + // 启动结果 0x00失败 0x01成功 + startIndex += length; + length = 1; + byte[] startupResultByteArr = BytesUtil.copyBytes(msgBody, startIndex, length); + String startupResult = BytesUtil.bcd2Str(startupResultByteArr); + String startupResultMsg = StringUtils.equals(startupResult, "00") ? "失败" : "成功"; + + // 失败原因 + startIndex += length; + length = 1; + byte[] failReasonByteArr = BytesUtil.copyBytes(msgBody, startIndex, length); + String failReason = BytesUtil.bcd2Str(failReasonByteArr); + String failReasonMsg = ChargingFailedReasonEnum.getMsgByCode(Integer.parseInt(failReason, 16)); + + log.info("[===预约充电启动结果上送===]交易流水号:{}, 桩编号:{}, 枪号:{}, vin:{}, 启动结果:{}, 失败原因:{}", + transactionCode, pileSn, connectorCode, vinCode, startupResult + "-" +startupResultMsg, failReasonMsg); + + } + @Override public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, ChannelHandlerContext channel) { log.info("[===预约充电启动结果上送===] param:{}, channel:{}", JSON.toJSONString(ykcDataProtocol), channel.toString()); @@ -95,7 +144,7 @@ public class ReservationChargingStartupResultHandler extends AbstractYkcHandler .pileSn(pileSn) .connectorCode(connectorCode) .vinCode(vinCode) - .startupResult(startupResultMsg) + .startupResult(startupResult) .failReason(failReasonMsg) .build(); pileBasicInfoService.startupResult(chargingStartupResult); diff --git a/jsowell-netty/src/main/java/com/jsowell/netty/handler/yunkuaichong/TimeCheckSettingHandler.java b/jsowell-netty/src/main/java/com/jsowell/netty/handler/yunkuaichong/TimeCheckSettingHandler.java index 1e7751c4c..854abf3df 100644 --- a/jsowell-netty/src/main/java/com/jsowell/netty/handler/yunkuaichong/TimeCheckSettingHandler.java +++ b/jsowell-netty/src/main/java/com/jsowell/netty/handler/yunkuaichong/TimeCheckSettingHandler.java @@ -12,7 +12,7 @@ import org.springframework.stereotype.Component; * 对时设置 * * 运营平台同步充电桩时钟,以保证充电桩与运营平台的时钟一致 - * + * @deprecated * @author JS-ZZA * @date 2022/9/19 15:11 */ diff --git a/jsowell-netty/src/main/java/com/jsowell/netty/server/electricbicycles/ElectricBicyclesServerChannelInitializer.java b/jsowell-netty/src/main/java/com/jsowell/netty/server/electricbicycles/ElectricBicyclesServerChannelInitializer.java index a94749fd7..4e4cb6edc 100644 --- a/jsowell-netty/src/main/java/com/jsowell/netty/server/electricbicycles/ElectricBicyclesServerChannelInitializer.java +++ b/jsowell-netty/src/main/java/com/jsowell/netty/server/electricbicycles/ElectricBicyclesServerChannelInitializer.java @@ -1,11 +1,10 @@ package com.jsowell.netty.server.electricbicycles; -import com.jsowell.netty.decoder.MessageDecode; -import com.jsowell.netty.decoder.MessageEncode; import com.jsowell.netty.decoder.StartAndLengthFieldFrameDecoder; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; +import io.netty.handler.codec.bytes.ByteArrayDecoder; import io.netty.handler.timeout.IdleStateHandler; import org.springframework.stereotype.Component; @@ -22,8 +21,10 @@ public class ElectricBicyclesServerChannelInitializer extends ChannelInitializer protected void initChannel(SocketChannel channel) throws Exception { ChannelPipeline pipeline = channel.pipeline(); pipeline.addLast("frameDecoder", new StartAndLengthFieldFrameDecoder()); - pipeline.addLast("decoder", new MessageDecode()); - pipeline.addLast("encoder", new MessageEncode()); + // pipeline.addLast("decoder", new MessageDecode()); + // pipeline.addLast("encoder", new MessageEncode()); + pipeline.addLast("decoder", new ByteArrayDecoder()); + pipeline.addLast("encoder", new ByteArrayDecoder()); //读超时时间设置为10s,0表示不监控 pipeline.addLast(new IdleStateHandler(60, 0, 0, TimeUnit.SECONDS)); pipeline.addLast("handler", chargingPileHandler); diff --git a/jsowell-netty/src/main/java/com/jsowell/netty/server/electricbicycles/ElectricBicyclesServerHandler.java b/jsowell-netty/src/main/java/com/jsowell/netty/server/electricbicycles/ElectricBicyclesServerHandler.java index 65e0aef56..0877f53b5 100644 --- a/jsowell-netty/src/main/java/com/jsowell/netty/server/electricbicycles/ElectricBicyclesServerHandler.java +++ b/jsowell-netty/src/main/java/com/jsowell/netty/server/electricbicycles/ElectricBicyclesServerHandler.java @@ -1,8 +1,10 @@ package com.jsowell.netty.server.electricbicycles; import com.alibaba.fastjson2.JSON; +import com.google.common.collect.Lists; +import com.jsowell.common.util.BytesUtil; import com.jsowell.netty.service.electricbicycles.EBikeBusinessService; -import com.jsowell.pile.domain.ebike.AbsEBikeMessage; +import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; @@ -11,7 +13,9 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -19,16 +23,39 @@ import java.util.concurrent.TimeUnit; @ChannelHandler.Sharable @Slf4j @Component -public class ElectricBicyclesServerHandler extends SimpleChannelInboundHandler { +public class ElectricBicyclesServerHandler extends SimpleChannelInboundHandler { private final Map responseFutureMap = new ConcurrentHashMap<>(); + private final List notPrintFrameTypeList = Lists.newArrayList("0x03"); + @Autowired private EBikeBusinessService eBikeService; @Override - protected void channelRead0(ChannelHandlerContext ctx, AbsEBikeMessage msg) throws Exception { - log.info("收到消息, channelId:{}, msg:{}", ctx.channel().id().toString(), JSON.toJSONString(msg)); + protected void channelRead0(ChannelHandlerContext ctx, Object message) throws Exception { + Channel channel = ctx.channel(); + log.info("收到消息, channelId:{}, msg:{}", channel.id().toString(), JSON.toJSONString(message)); + byte[] msg = (byte[]) message; + + // 处理数据 + byte[] response = eBikeService.process(msg, ctx); + if (Objects.nonNull(response)) { + log.info("响应数据:{}", BytesUtil.binary(response, 16)); + // 响应客户端 + ByteBuf buffer = ctx.alloc().buffer().writeBytes(response); + // this.channelWrite(channel.id(), buffer); + ctx.writeAndFlush(buffer); + // if (!CollectionUtils.containsAny(notPrintFrameTypeList, frameType)) { + // // 应答帧类型 + // byte[] responseFrameTypeBytes = YKCFrameTypeCode.PlatformAnswersRelation.getResponseFrameTypeBytes(frameTypeBytes); + // String responseFrameType = YKCUtils.frameType2Str(responseFrameTypeBytes); + // log.info("【>>>>>平台响应消息>>>>>】channel:{}, 响应帧类型:{}, 响应帧名称:{}, 原帧类型:{}, 原帧名称:{}, 序列号域:{}, response:{}", + // channel.id(), responseFrameType, YKCFrameTypeCode.getFrameTypeStr(responseFrameType), + // frameType, YKCFrameTypeCode.getFrameTypeStr(frameType), serialNumber, + // BytesUtil.binary(response, 16)); + // } + } } public String sendCommandAndWaitForResponse(Channel channel, String command, long timeout) throws InterruptedException { diff --git a/jsowell-netty/src/main/java/com/jsowell/netty/service/electricbicycles/impl/EBikeBusinessServiceImpl.java b/jsowell-netty/src/main/java/com/jsowell/netty/service/electricbicycles/impl/EBikeBusinessServiceImpl.java index 00a33e808..571f4ad9b 100644 --- a/jsowell-netty/src/main/java/com/jsowell/netty/service/electricbicycles/impl/EBikeBusinessServiceImpl.java +++ b/jsowell-netty/src/main/java/com/jsowell/netty/service/electricbicycles/impl/EBikeBusinessServiceImpl.java @@ -1,13 +1,13 @@ package com.jsowell.netty.service.electricbicycles.impl; -import com.jsowell.common.core.domain.ykc.YKCDataProtocol; +import com.jsowell.common.core.domain.ebike.EBikeDataProtocol; import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode; import com.jsowell.common.enums.ykc.PileChannelEntity; import com.jsowell.common.enums.ykc.PileConnectorDataBaseStatusEnum; +import com.jsowell.common.util.BytesUtil; import com.jsowell.common.util.StringUtils; -import com.jsowell.common.util.YKCUtils; -import com.jsowell.netty.factory.YKCOperateFactory; -import com.jsowell.netty.handler.yunkuaichong.AbstractYkcHandler; +import com.jsowell.netty.factory.EBikeOperateFactory; +import com.jsowell.netty.handler.electricbicycles.AbstractEBikeHandler; import com.jsowell.netty.service.electricbicycles.EBikeBusinessService; import com.jsowell.pile.service.OrderBasicInfoService; import com.jsowell.pile.service.PileConnectorInfoService; @@ -33,16 +33,15 @@ public class EBikeBusinessServiceImpl implements EBikeBusinessService { @Override public byte[] process(byte[] msg, ChannelHandlerContext ctx) { - if (!YKCUtils.checkMsg(msg)) { - // 校验不通过,丢弃消息 - return null; - } - YKCDataProtocol ykcDataProtocol = new YKCDataProtocol(msg); + EBikeDataProtocol ykcDataProtocol = new EBikeDataProtocol(msg); // 获取帧类型 - String frameType = YKCUtils.frameType2Str(ykcDataProtocol.getFrameType()); + String command = BytesUtil.bin2HexStr(ykcDataProtocol.getCommand()); // 获取业务处理handler - AbstractYkcHandler invokeStrategy = YKCOperateFactory.getInvokeStrategy(frameType); - return invokeStrategy.supplyProcess(ykcDataProtocol, ctx); + AbstractEBikeHandler invokeStrategy = EBikeOperateFactory.getInvokeStrategy(command); + if (invokeStrategy != null) { + return invokeStrategy.supplyProcess(ykcDataProtocol, ctx); + } + return null; } @Override diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/domain/ebike/AbsEBikeMessage.java b/jsowell-pile/src/main/java/com/jsowell/pile/domain/ebike/AbsEBikeMessage.java index a1cfab4a4..c5f9d992d 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/domain/ebike/AbsEBikeMessage.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/domain/ebike/AbsEBikeMessage.java @@ -101,4 +101,43 @@ public abstract class AbsEBikeMessage { } } + public static void main(String[] args) { + String s = "44 4e 59 00 0d 3b 37 ab 04 b9 00 22 54 33 cc 66 03 7b".replace(" ", ""); + byte[] messageBytes = BytesUtil.hexStringToByteArray(s); + // 读取包头 + byte[] headerBytes = Arrays.copyOfRange(messageBytes, 0, 3); + String header = new String(headerBytes, StandardCharsets.UTF_8); + System.out.println("header: " + header); + + // 读取长度 + byte[] lengthBytes = Arrays.copyOfRange(messageBytes, 3, 5); + int length = BytesUtil.bytesToIntLittle(lengthBytes); + System.out.println("length: " + length); + + // 读取物理ID + byte[] physicalIdBytes = Arrays.copyOfRange(messageBytes, 5, 9); + int physicalId = BytesUtil.bytesToIntLittle(physicalIdBytes); + System.out.println("physicalId: " + physicalId); + + // 读取消息ID + byte[] messageIdBytes = Arrays.copyOfRange(messageBytes, 9, 11); + int messageId = BytesUtil.bytesToIntLittle(messageIdBytes); + System.out.println("messageId: " + messageId); + + // 读取命令 + byte commandByte = messageBytes[11]; + String command = BytesUtil.bcd2StrLittle(new byte[]{commandByte}); + System.out.println("command: " + command); + + // 读取数据 + byte[] dataBytes = Arrays.copyOfRange(messageBytes, 12, messageBytes.length - 2); + String data = BytesUtil.bytesToIntLittle(dataBytes) + ""; + System.out.println("data: " + data); + + // 读取校验 + byte[] checksumBytes = Arrays.copyOfRange(messageBytes, messageBytes.length - 2, messageBytes.length); + int checksum = BytesUtil.bytesToIntLittle(checksumBytes); + System.out.println("checksum: " + checksum); + } + } diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/domain/ebike/EBikeCommandEnum.java b/jsowell-pile/src/main/java/com/jsowell/pile/domain/ebike/EBikeCommandEnum.java new file mode 100644 index 000000000..96ee33931 --- /dev/null +++ b/jsowell-pile/src/main/java/com/jsowell/pile/domain/ebike/EBikeCommandEnum.java @@ -0,0 +1,57 @@ +package com.jsowell.pile.domain.ebike; + +import com.jsowell.pile.domain.ebike.deviceupload.*; +import lombok.extern.slf4j.Slf4j; + +/** + * 友电电单车协议指令 + */ +@Slf4j +public enum EBikeCommandEnum { + // HEARTBEAT("01", "设备心跳包"), + REGISTRATION("20", "设备注册包", EBikeMessageCmd20.class), + GET_SERVER_TIME("22", "设备获取服务器时间", EBikeMessageCmd22.class), + HEARTBEAT_2("21", "设备心跳包", EBikeMessageCmd21.class), + CARD_OPERATION("02", "刷卡操作", EBikeMessageCmd02.class), + SETTLEMENT_UPLOAD("03", "结算消费信息上传", EBikeMessageCmd03.class), + CHARGE_PORT_CONFIRMATION("04", "充电端口订单确认", EBikeMessageCmd04.class), + POWER_HEARTBEAT("06", "端口充电时功率心跳包", EBikeMessageCmd06.class), + // CHARGER_HEARTBEAT("41", "充电柜专有心跳包", EBikeMessageCmd41.class), + // ALARM_PUSH("42", "报警推送指令", EBikeMessageCmd20.class), + // CHARGE_COMPLETE("43", "充电完成通知,但不结算", EBikeMessageCmd20.class), + // PORT_PUSH("44", "端口推送指令", EBikeMessageCmd20.class); + + ; + + EBikeCommandEnum(String code, String desc, Class msgClass) { + this.code = code; + this.desc = desc; + this.msgClass = msgClass; + } + + private final String code; // 帧类型code + private final String desc; // 帧类型名称 + private final Class msgClass; + + public String getCode() { + return code; + } + + public String getDesc() { + return desc; + } + + public Class getMsgClass() { + return msgClass; + } + + public static Class getMsgClassByCode(String code) { + for (EBikeCommandEnum e : EBikeCommandEnum.values()) { + if (e.getCode().equals(code)) { + return e.getMsgClass(); + } + } + return null; + } + +} \ No newline at end of file diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/domain/ebike/deviceupload/EBikeMessageCmd20.java b/jsowell-pile/src/main/java/com/jsowell/pile/domain/ebike/deviceupload/EBikeMessageCmd20.java index 2a40b3afc..9d6e4895d 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/domain/ebike/deviceupload/EBikeMessageCmd20.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/domain/ebike/deviceupload/EBikeMessageCmd20.java @@ -60,10 +60,10 @@ public class EBikeMessageCmd20 extends AbsEBikeMessage { private String powerBoardVersion; public DeviceRegister(byte[] dataBytes) { - this.firmwareVersion = BytesUtil.bytesToIntLittle(Arrays.copyOfRange(dataBytes, 0, 2)) + ""; + this.firmwareVersion = BytesUtil.bytesToIntLittle(Arrays.copyOfRange(dataBytes, 0, 2)) * 0.01 + ""; this.portNumber = BytesUtil.bytesToIntLittle(Arrays.copyOfRange(dataBytes, 2, 3)) + ""; this.virtualId = BytesUtil.bytesToIntLittle(Arrays.copyOfRange(dataBytes, 3, 4)) + ""; - this.deviceType = BytesUtil.bcd2StrLittle(Arrays.copyOfRange(dataBytes, 4, 5)); + this.deviceType = BytesUtil.printHexBinary(Arrays.copyOfRange(dataBytes, 4, 5)); this.workMode = BytesUtil.bcd2StrLittle(Arrays.copyOfRange(dataBytes, 5, 6)); this.powerBoardVersion = BytesUtil.bcd2StrLittle(Arrays.copyOfRange(dataBytes, 6, 8)); } diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/domain/ebike/deviceupload/EBikeMessageCmd21.java b/jsowell-pile/src/main/java/com/jsowell/pile/domain/ebike/deviceupload/EBikeMessageCmd21.java index aed485ad6..c8d676d61 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/domain/ebike/deviceupload/EBikeMessageCmd21.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/domain/ebike/deviceupload/EBikeMessageCmd21.java @@ -38,6 +38,7 @@ public class EBikeMessageCmd21 extends AbsEBikeMessage { public static class DeviceHeartbeat { /** * 电压:设备的当前电压(打包发送心跳包指令时的当前时间点的实时电压) + * 2024年8月26日15点02分 已经转为标准单位V */ private String voltage; @@ -68,10 +69,10 @@ public class EBikeMessageCmd21 extends AbsEBikeMessage { public DeviceHeartbeat(byte[] dataBytes) { - this.voltage = BytesUtil.bytesToIntLittle(Arrays.copyOfRange(dataBytes, 0, 2)) + ""; + this.voltage = BytesUtil.bytesToIntLittle(Arrays.copyOfRange(dataBytes, 0, 2)) * 0.1 + ""; this.portNumber = BytesUtil.bytesToIntLittle(Arrays.copyOfRange(dataBytes, 2, 3)); - byte[] statusBytes = Arrays.copyOfRange(dataBytes, 3, this.portNumber); + byte[] statusBytes = BytesUtil.copyBytes(dataBytes, 3, this.portNumber); List statusList = Lists.newArrayList(); for (byte statusByte : statusBytes) { int status = BytesUtil.bytesToIntLittle(new byte[]{statusByte}); diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/OrderBasicInfoServiceImpl.java b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/OrderBasicInfoServiceImpl.java index 7f108361a..3d7e580ca 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/OrderBasicInfoServiceImpl.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/OrderBasicInfoServiceImpl.java @@ -2121,7 +2121,7 @@ public class OrderBasicInfoServiceImpl implements OrderBasicInfoService { } catch (BaseAdaPayException e) { throw new RuntimeException(e); } - logger.info("根据订单号:{}, 查询到支付信息:{}", orderCode, JSON.toJSONString(paymentInfoSet)); + logger.info("根据订单号:{}, 查询到支付信息:{}, ", orderCode, JSON.toJSONString(paymentInfoSet)); if (CollectionUtils.isNotEmpty(paymentInfoSet)) { for (PaymentInfo paymentInfo : paymentInfoSet) { String paymentId = paymentInfo.getPaymentId(); @@ -3796,7 +3796,7 @@ public class OrderBasicInfoServiceImpl implements OrderBasicInfoService { String orderCode = generateNewOrderCode(); String transactionCode = chargingStartupResult.getTransactionCode(); - String status = StringUtils.equals(chargingStartupResult.getStartupResult(), "00") + String status = StringUtils.equals(chargingStartupResult.getStartupResult(), "01") ? OrderStatusEnum.IN_THE_CHARGING.getValue() : OrderStatusEnum.ORDER_CLOSE_TIMEOUT.getValue(); @@ -3826,11 +3826,17 @@ public class OrderBasicInfoServiceImpl implements OrderBasicInfoService { .reason(chargingStartupResult.getFailReason()) .build(); + OrderDetail orderDetail = OrderDetail.builder() + .orderCode(orderCode) + .delFlag(DelFlagEnum.NORMAL.getValue()) + .build(); + // 保存到数据库 OrderTransactionDTO createOrderTransactionDTO = OrderTransactionDTO.builder() .orderBasicInfo(orderBasicInfo) - .orderDetail(null) + .orderDetail(orderDetail) .build(); + logger.info("createReservationOrder, chargingStartupResult:{}, 订单信息:{}", JSON.toJSONString(chargingStartupResult), JSON.toJSONString(orderBasicInfo)); pileTransactionService.doCreateOrder(createOrderTransactionDTO); OrderPayRecord principalPayRecord = OrderPayRecord.builder() diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/PileReservationInfoServiceImpl.java b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/PileReservationInfoServiceImpl.java index 4d2ded797..b5e63cf0e 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/PileReservationInfoServiceImpl.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/PileReservationInfoServiceImpl.java @@ -412,6 +412,18 @@ public class PileReservationInfoServiceImpl implements PileReservationInfoServic return 0; } + // 是否发指令 false:不发指令 true:发指令, 默认不发指令 + boolean sendFlag = false; + if (StringUtils.equals(pileReservationInfo.getStatus(), "1")) { + sendFlag = true; + } + + // 发指令结果 false:失败 true:成功, 默认失败 + boolean sendResult = false; + + // 是否更新数据库 false:不更新 true:更新, 默认更新 + boolean updateFlag = true; + // 操作 0x01:启动 0x02:取消 0x03:修改 String operation = "03"; if (StringUtils.isNotBlank(dto.getStartTime())) { @@ -431,47 +443,55 @@ public class PileReservationInfoServiceImpl implements PileReservationInfoServic if (StringUtils.equals(dto.getStatus(), Constants.ZERO)) { // 停用 operation = "02"; + sendFlag = true; } else if (StringUtils.equals(dto.getStatus(), Constants.ONE)) { // 启用 operation = "01"; + sendFlag = true; } } pileReservationInfo.setUpdateBy(dto.getMemberId()); /* - 先发送指令, 收到回复更新数据库 + 发送指令 */ - // 查询会员的绑定vin列表 2024年7月30日11点04分 以当前请求会员的VIN为准 - List plateNumberVOList = memberPlateNumberRelationService.selectMemberPlateNumberRelation(dto.getMemberId()); - List vinCodes = Lists.newArrayList(); - int count = 0; - for (MemberPlateNumberVO vo : plateNumberVOList) { - if (count < 3 && StringUtils.isNotBlank(vo.getVinCode())) { - vinCodes.add(vo.getVinCode()); - count++; + if (sendFlag) { + // 查询会员的绑定vin列表 2024年7月30日11点04分 以当前请求会员的VIN为准 + List plateNumberVOList = memberPlateNumberRelationService.selectMemberPlateNumberRelation(dto.getMemberId()); + List vinCodes = Lists.newArrayList(); + int count = 0; + for (MemberPlateNumberVO vo : plateNumberVOList) { + if (count < 3 && StringUtils.isNotBlank(vo.getVinCode())) { + vinCodes.add(vo.getVinCode()); + count++; + } } + // 如果 vinCodes 的数量少于 3,用 "0" 补足 + while (vinCodes.size() < 3) { + vinCodes.add(""); + } + String type = StringUtils.equals(pileReservationInfo.getReservationType(), "single") ? "00" : "01"; + ReservationChargingCommand command = ReservationChargingCommand.builder() + .transactionCode(Constants.ILLEGAL_TRANSACTION_CODE) + .pileSn(pileReservationInfo.getPileSn()) + .connectorCode(pileReservationInfo.getPileConnectorCode().replace(pileReservationInfo.getPileSn(), "")) + .operation(operation) + .reservationType(type) + .verifyIdentity(pileReservationInfo.getVerifyIdentity()) + .vin1(vinCodes.get(0)) + .vin2(vinCodes.get(1)) + .vin3(vinCodes.get(2)) + .reservedStartTime(pileReservationInfo.getStartTime().toLocalTime()) + .reservedEndTime(pileReservationInfo.getEndTime().toLocalTime()) + .amount(Constants.WHITELIST_DEFAULT_AMOUNT) + .build(); + String result = pileRemoteService.reservationCharging(command); + sendResult = StringUtils.equals(result, Constants.ONE); } - // 如果 vinCodes 的数量少于 3,用 "0" 补足 - while (vinCodes.size() < 3) { - vinCodes.add(""); - } - String type = StringUtils.equals(pileReservationInfo.getReservationType(), "single") ? "00" : "01"; - ReservationChargingCommand command = ReservationChargingCommand.builder() - .transactionCode(Constants.ILLEGAL_TRANSACTION_CODE) - .pileSn(pileReservationInfo.getPileSn()) - .connectorCode(pileReservationInfo.getPileConnectorCode().replace(pileReservationInfo.getPileSn(), "")) - .operation(operation) - .reservationType(type) - .verifyIdentity(pileReservationInfo.getVerifyIdentity()) - .vin1(vinCodes.get(0)) - .vin2(vinCodes.get(1)) - .vin3(vinCodes.get(2)) - .reservedStartTime(pileReservationInfo.getStartTime().toLocalTime()) - .reservedEndTime(pileReservationInfo.getEndTime().toLocalTime()) - .amount(Constants.WHITELIST_DEFAULT_AMOUNT) - .build(); - String result = pileRemoteService.reservationCharging(command); - if (StringUtils.equals(result, Constants.ONE)) { + + log.info("修改预约充电信息, updateFlag:{}, sendFlag:{}, sendResult:{}", updateFlag, sendFlag, sendResult); + // 更新数据库, 一般情况下是都更新的, 只有在发送指令并发送失败的情况下才不更新 + if (updateFlag && (sendFlag == sendResult)) { return this.insertOrUpdateSelective(pileReservationInfo); } return 0; diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/YKCPushCommandServiceImpl.java b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/YKCPushCommandServiceImpl.java index aeaf85b3a..9243e936c 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/YKCPushCommandServiceImpl.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/YKCPushCommandServiceImpl.java @@ -207,7 +207,7 @@ public class YKCPushCommandServiceImpl implements YKCPushCommandService { }); // 等待获取结果 - byte[] rpcResponse = syncPromise.get2(timeout, unit); + byte[] rpcResponse = syncPromise.get(timeout, unit); if(rpcResponse == null) { if(syncPromise.isTimeout()) { diff --git a/jsowell-pile/src/main/resources/mapper/pile/OrderBasicInfoMapper.xml b/jsowell-pile/src/main/resources/mapper/pile/OrderBasicInfoMapper.xml index df913b945..c802de69f 100644 --- a/jsowell-pile/src/main/resources/mapper/pile/OrderBasicInfoMapper.xml +++ b/jsowell-pile/src/main/resources/mapper/pile/OrderBasicInfoMapper.xml @@ -2549,6 +2549,7 @@ AND t1.create_time #{endTime,jdbcType=VARCHAR} AND t1.order_status = '6' AND t1.del_flag = '0' + order by t1.create_time desc