!43 离线卡数据清除(0x46) 离线卡数据清除应答(0x45)

* merge master
* merge master
* Merge branch 'master' into Feat_离线卡数据清除、离线卡数据查询
* merge master
* Merge branch 'master' into Feat_离线卡数据清除、离线卡数据查询
* 离线卡数据清除(0x46) 离线卡数据清除应答(0x45)
* 离线卡数据清除(0x46) 离线卡数据清除应答(0x45)
* 离线卡数据清除(0x46) 离线卡数据清除应答(0x45)
* 离线卡数据清除(0x46) 离线卡数据清除应答(0x45)
This commit is contained in:
八万
2025-09-13 08:40:51 +00:00
committed by 三丙
parent eff49bb3c0
commit 9451d28894
18 changed files with 521 additions and 15 deletions

View File

@@ -49,6 +49,8 @@ public class AbstractYunKuaiChongCmdExe {
private static final DecimalFormat PRICING_ID_DECIMAL_FORMAT = new DecimalFormat("0000");
protected static final String SUCCESS = "成功";
protected static final String UNKNOWN_MSG = "未知的异常";
@@ -82,7 +84,7 @@ public class AbstractYunKuaiChongCmdExe {
}
protected static byte[] encodePileCode(String pileCode) {
if (StringUtils.length(pileCode) > 32) {
if (StringUtils.length(pileCode) > 14) {
throw new IllegalArgumentException("云快充可接受最大桩编号为14位");
}
@@ -113,14 +115,14 @@ public class AbstractYunKuaiChongCmdExe {
/**
* 编码卡号为BCD格式
* 云快充协议卡号为6字节12位BCD码需要做长度校验和补0操作
* 云快充协议卡号为8字节16位BCD码需要做长度校验和补0操作
*/
protected static byte[] encodeCardNo(String cardNo) {
if (StringUtils.length(cardNo) > 12) {
throw new IllegalArgumentException("云快充可接受最大卡号为12");
if (StringUtils.length(cardNo) > 16) {
throw new IllegalArgumentException("云快充可接受最大卡号为16");
}
String cardNoStr = StringUtils.leftPad(cardNo, 12, '0');
String cardNoStr = StringUtils.leftPad(cardNo, 16, '0');
return BCDUtil.toBytes(cardNoStr);
}

View File

@@ -52,6 +52,10 @@ public class YunKuaiChongDownlinkCmdConverter implements DownlinkCmdConverter {
COMMAND_MAP.put(DownlinkCmdEnum.OFFLINE_CARD_BALANCE_UPDATE_REQUEST, 0x42);
COMMAND_MAP.put(DownlinkCmdEnum.OFFLINE_CARD_SYNC_REQUEST, 0x44);
COMMAND_MAP.put(DownlinkCmdEnum.SYNC_TIME_REQUEST, 0x56);
COMMAND_MAP.put(DownlinkCmdEnum.OFFLINE_CARD_CLEAR_REQUEST, 0x46);
COMMAND_MAP.put(DownlinkCmdEnum.OFFLINE_CARD_QUERY_REQUEST, 0x48);
}
/**

View File

@@ -45,7 +45,7 @@ public class YunKuaiChongV150OfflineCardBalanceUpdateRequestDLCmd extends YunKua
OfflineCardBalanceUpdateRequest request = message.getMsg().getOfflineCardBalanceUpdateRequest();
msgBody.writeBytes(encodePileCode(request.getPileCode()));
msgBody.writeBytes(encodeGunCode(request.getGunCode()));
msgBody.writeBytes(BCDUtil.toBytes(request.getCardNo()));
msgBody.writeBytes(encodeCardNo(request.getCardNo()));
msgBody.writeIntLE(new BigDecimal(request.getLimitYuan()).movePointRight(2).intValue());
super.encodeAndWriteFlush(OFFLINE_CARD_BALANCE_UPDATE_REQUEST, msgBody, tcpSession);

View File

@@ -0,0 +1,73 @@
/**
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
* 微信mohan_88888
* 抖音:程序员三丙
* 付费课程知识星球https://t.zsxq.com/aKtXo
*/
package sanbing.jcpp.protocol.yunkuaichong.v150.cmd;
import static sanbing.jcpp.protocol.domain.DownlinkCmdEnum.OFFLINE_CARD_CLEAR_REQUEST;
import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolConstants.ProtocolNames.V150;
import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolConstants.ProtocolNames.V160;
import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolConstants.ProtocolNames.V170;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import lombok.extern.slf4j.Slf4j;
import sanbing.jcpp.proto.gen.DownlinkProto;
import sanbing.jcpp.protocol.ProtocolContext;
import sanbing.jcpp.protocol.annotation.ProtocolCmd;
import sanbing.jcpp.protocol.listener.tcp.TcpSession;
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongDownlinkCmdExe;
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongDwonlinkMessage;
/**
* 云快充1.5.0 离线卡数据清除
*
* @author bawan
*/
@Slf4j
@ProtocolCmd(value = 0x46, protocolNames = {V150, V160, V170})
public class YunKuaiChongV150OfflineCardClearRequestDLCmd extends YunKuaiChongDownlinkCmdExe {
@Override
public void execute(TcpSession tcpSession, YunKuaiChongDwonlinkMessage message, ProtocolContext ctx) {
log.info("{} 云快充1.5.0 离线卡数据清除", tcpSession);
if (!message.getMsg().hasOfflineCardClearRequest()) {
log.error("云快充1.5.0 离线卡数据清除消息体为空");
return;
}
DownlinkProto.OfflineCardClearRequest request = message.getMsg().getOfflineCardClearRequest();
if (request.getTotal()>24 || request.getCardNoCount()>24) {
log.error("云快充1.5.0 离线卡数据清除 下发卡个数最大支持: 24个当前: {}个", request.getTotal());
return;
}
// 初始化 buf
ByteBuf msgBody = Unpooled.buffer(bufferInitialCapacity(request));
msgBody.writeBytes(encodePileCode(request.getPileCode()));
msgBody.writeIntLE(request.getTotal());
request.getCardNoList().forEach(cardNo -> msgBody.writeBytes(encodeCardNo(cardNo)));
super.encodeAndWriteFlush(OFFLINE_CARD_CLEAR_REQUEST, msgBody, tcpSession);
}
/**
* 桩编号 BCD 码 7
* 下发卡个数 BIN 码 1 最大 15 个
* ........ ........ ........ ........
* n卡物理卡号 BIN 码 8 离线卡物理卡号
* @param request request
* @return bufferInitialCapacity
*/
private int bufferInitialCapacity(DownlinkProto.OfflineCardClearRequest request) {
return 7 + 1 + (8 * request.getCardNoCount());
}
}

View File

@@ -0,0 +1,105 @@
/**
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
* 微信mohan_88888
* 抖音:程序员三丙
* 付费课程知识星球https://t.zsxq.com/aKtXo
*/
package sanbing.jcpp.protocol.yunkuaichong.v150.cmd;
import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolConstants.ProtocolNames.V150;
import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolConstants.ProtocolNames.V160;
import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolConstants.ProtocolNames.V170;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Lists;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import lombok.extern.slf4j.Slf4j;
import sanbing.jcpp.infrastructure.util.codec.BCDUtil;
import sanbing.jcpp.proto.gen.UplinkProto;
import sanbing.jcpp.protocol.ProtocolContext;
import sanbing.jcpp.protocol.annotation.ProtocolCmd;
import sanbing.jcpp.protocol.listener.tcp.TcpSession;
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongUplinkCmdExe;
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongUplinkMessage;
/**
* 云快充1.5.0 离线卡数据清除应答
*
* @author bawan
*/
@Slf4j
@ProtocolCmd(value = 0x45, protocolNames = {V150, V160, V170})
public class YunKuaiChongV150OfflineCardClearResponseULCmd extends YunKuaiChongUplinkCmdExe {
private static final Map<Byte, Map<Byte, String>> CLEAR_RESULT;
static {
CLEAR_RESULT = Map.of(
(byte) 0x00,Map.of((byte)0x01,"卡号格式错误"),
(byte) 0x01,Map.of((byte)0x02,SUCCESS)
);
}
@Override
public void execute(TcpSession tcpSession, YunKuaiChongUplinkMessage message, ProtocolContext ctx) {
log.info("{} 云快充1.5.0 离线卡数据清除应答", tcpSession);
ByteBuf byteBuf = Unpooled.wrappedBuffer(message.getMsgBody());
// 桩编号
byte[] pileCodeBytes = new byte[7];
byteBuf.readBytes(pileCodeBytes);
String pileCode = BCDUtil.toString(pileCodeBytes);
// 清除结果集合
List<UplinkProto.ClearResult> clearResultList = Lists.newArrayList();
while (byteBuf.readableBytes() >= 10) {
byte[] cardNoBytes = new byte[8];
// 离线卡物理卡号
byteBuf.readBytes(cardNoBytes);
String cardNo = BCDUtil.toString(cardNoBytes);
// 清除标记 0x00 清除失败 0x01 清除成功
byte clearFlag = byteBuf.readByte();
// 失败原因 0x01 卡号格式错误 0x02 清除成功
byte failureReason = byteBuf.readByte();
// clearResult
UplinkProto.ClearResult clearResult = UplinkProto.ClearResult.newBuilder()
.setCardNo(cardNo)
.setSuccess(clearFlag == 0x01)
.setErrorMsg(errorMsg(clearFlag,failureReason))
.build();
// add
clearResultList.add(clearResult);
}
UplinkProto.UplinkQueueMessage queueMessage = uplinkMessageBuilder(pileCode, tcpSession, message)
.setOfflineCardClearResponse(UplinkProto.OfflineCardClearResponse.newBuilder()
.setPileCode(pileCode)
.addAllClearResult(clearResultList)
.build())
.build();
// 转发到后端
tcpSession.getForwarder().sendMessage(queueMessage);
}
private String errorMsg(byte clearResult, byte failureReason) {
if(clearResult == 0x01) {
return SUCCESS;
}
Map<Byte, String> clearResultMap = CLEAR_RESULT.get(clearResult);
if(null == clearResultMap) {
return UNKNOWN_MSG;
}
return clearResultMap.getOrDefault(failureReason,UNKNOWN_MSG);
}
}

View File

@@ -0,0 +1,72 @@
/**
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
* 微信mohan_88888
* 抖音:程序员三丙
* 付费课程知识星球https://t.zsxq.com/aKtXo
*/
package sanbing.jcpp.protocol.yunkuaichong.v150.cmd;
import static sanbing.jcpp.protocol.domain.DownlinkCmdEnum.OFFLINE_CARD_QUERY_REQUEST;
import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolConstants.ProtocolNames.V150;
import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolConstants.ProtocolNames.V160;
import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolConstants.ProtocolNames.V170;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import lombok.extern.slf4j.Slf4j;
import sanbing.jcpp.proto.gen.DownlinkProto;
import sanbing.jcpp.protocol.ProtocolContext;
import sanbing.jcpp.protocol.annotation.ProtocolCmd;
import sanbing.jcpp.protocol.listener.tcp.TcpSession;
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongDownlinkCmdExe;
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongDwonlinkMessage;
/**
* 云快充1.5.0 离线卡数据查询
*
* @author bawan
*/
@Slf4j
@ProtocolCmd(value = 0x48, protocolNames = {V150, V160, V170})
public class YunKuaiChongV150OfflineCardQueryRequestDLCmd extends YunKuaiChongDownlinkCmdExe {
@Override
public void execute(TcpSession tcpSession, YunKuaiChongDwonlinkMessage message, ProtocolContext ctx) {
log.info("{} 云快充1.5.0 离线卡数据查询", tcpSession);
if (!message.getMsg().hasOfflineCardQueryRequest()) {
log.error("云快充1.5.0 离线卡数据查询消息体为空");
return;
}
DownlinkProto.OfflineCardQueryRequest request = message.getMsg().getOfflineCardQueryRequest();
if (request.getTotal()>26 || request.getCardNoCount()>26) {
log.error("云快充1.5.0 离线卡数据查询 下发卡个数最大支持: 26个当前: {}个", request.getTotal());
return;
}
// 初始化 buf
ByteBuf msgBody = Unpooled.buffer(bufferInitialCapacity(request));
msgBody.writeIntLE(request.getTotal());
msgBody.writeBytes(encodePileCode(request.getPileCode()));
request.getCardNoList().forEach(cardNo -> msgBody.writeBytes(encodeCardNo(cardNo)));
super.encodeAndWriteFlush(OFFLINE_CARD_QUERY_REQUEST, msgBody, tcpSession);
}
/**
* 桩编号 BCD 码 7
* 下发卡个数 BIN 码 1 最大 15 个
* ........ ........ ........ ........
* n卡物理卡号 BIN 码 8 离线卡物理卡号
* @param request request
* @return bufferInitialCapacity
*/
private int bufferInitialCapacity(DownlinkProto.OfflineCardQueryRequest request) {
return 7 + 1 + ( 8 * request.getCardNoCount());
}
}

View File

@@ -0,0 +1,80 @@
/**
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
* 微信mohan_88888
* 抖音:程序员三丙
* 付费课程知识星球https://t.zsxq.com/aKtXo
*/
package sanbing.jcpp.protocol.yunkuaichong.v150.cmd;
import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolConstants.ProtocolNames.V150;
import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolConstants.ProtocolNames.V160;
import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolConstants.ProtocolNames.V170;
import java.util.List;
import com.google.common.collect.Lists;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import lombok.extern.slf4j.Slf4j;
import sanbing.jcpp.infrastructure.util.codec.BCDUtil;
import sanbing.jcpp.proto.gen.UplinkProto;
import sanbing.jcpp.protocol.ProtocolContext;
import sanbing.jcpp.protocol.annotation.ProtocolCmd;
import sanbing.jcpp.protocol.listener.tcp.TcpSession;
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongUplinkCmdExe;
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongUplinkMessage;
/**
* 云快充1.5.0 离线卡数据查询应答
*
* @author bawan
*/
@Slf4j
@ProtocolCmd(value = 0x47, protocolNames = {V150, V160, V170})
public class YunKuaiChongV150OfflineCardQueryResponseULCmd extends YunKuaiChongUplinkCmdExe {
@Override
public void execute(TcpSession tcpSession, YunKuaiChongUplinkMessage message, ProtocolContext ctx) {
log.info("{} 云快充1.5.0 离线卡数据查询应答", tcpSession);
ByteBuf byteBuf = Unpooled.wrappedBuffer(message.getMsgBody());
// 桩编号
byte[] pileCodeBytes = new byte[7];
byteBuf.readBytes(pileCodeBytes);
String pileCode = BCDUtil.toString(pileCodeBytes);
// 清除结果集合
List<UplinkProto.QueryResult> queryResultList = Lists.newArrayList();
while (byteBuf.readableBytes() >= 9) {
byte[] cardNoBytes = new byte[8];
// 离线卡物理卡号
byteBuf.readBytes(cardNoBytes);
String cardNo = BCDUtil.toString(cardNoBytes);
// 查询结果 0x00 不存在 0x01 存在
byte queryResultByte = byteBuf.readByte();
// clearResult
UplinkProto.QueryResult queryResult = UplinkProto.QueryResult.newBuilder()
.setCardNo(cardNo)
.setExist(queryResultByte == 0x01)
.build();
// add
queryResultList.add(queryResult);
}
UplinkProto.UplinkQueueMessage queueMessage = uplinkMessageBuilder(pileCode, tcpSession, message)
.setOfflineCardQueryResponse(UplinkProto.OfflineCardQueryResponse.newBuilder()
.setPileCode(pileCode)
.addAllQueryResult(queryResultList)
.build())
.build();
// 转发到后端
tcpSession.getForwarder().sendMessage(queueMessage);
}
}

View File

@@ -9,7 +9,6 @@ package sanbing.jcpp.protocol.yunkuaichong.v150.cmd;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import lombok.extern.slf4j.Slf4j;
import sanbing.jcpp.infrastructure.util.codec.BCDUtil;
import sanbing.jcpp.proto.gen.DownlinkProto.OfflineCardSyncRequest;
import sanbing.jcpp.protocol.ProtocolContext;
import sanbing.jcpp.protocol.annotation.ProtocolCmd;
@@ -41,7 +40,7 @@ public class YunKuaiChongV150OfflineCardSyncRequestDLCmd extends YunKuaiChongDow
OfflineCardSyncRequest request = message.getMsg().getOfflineCardSyncRequest();
if (request.getTotal() > 15) {
if (request.getTotal()>15 || request.getCardInfoCount()>15) {
log.error("云快充1.5.0 离线卡数据同步 下发卡个数最大支持: 15个当前: {}个", request.getTotal());
return;
}
@@ -50,8 +49,8 @@ public class YunKuaiChongV150OfflineCardSyncRequestDLCmd extends YunKuaiChongDow
msgBody.writeBytes(encodePileCode(request.getPileCode()));
msgBody.writeIntLE(request.getTotal());
request.getCardInfoList().forEach(cardInfo -> {
msgBody.writeBytes(BCDUtil.toBytes(cardInfo.getLogicCardNo()));
msgBody.writeBytes(BCDUtil.toBytes(cardInfo.getCardNo()));
msgBody.writeBytes(encodeCardNo(cardInfo.getLogicCardNo()));
msgBody.writeBytes(encodeCardNo(cardInfo.getCardNo()));
});
super.encodeAndWriteFlush(OFFLINE_CARD_SYNC_REQUEST, msgBody, tcpSession);

View File

@@ -35,10 +35,6 @@ public class YunKuaiChongV150OfflineCardSyncResponseULCmd extends YunKuaiChongUp
private static final Map<Byte, Map<Byte, String>> FAILURE_REASON;
private static final String SUCCESS = "成功";
static {
FAILURE_REASON = Map.of(
(byte) 0x00,Map.of((byte)0x01,"卡号格式错误",(byte)0x02,"储存空间不足"),