mirror of
https://gitee.com/san-bing/JChargePointProtocol
synced 2026-05-05 02:19:56 +08:00
云快充1.5.0 初始化
This commit is contained in:
33
jcpp-protocol-bootstrap/src/layers.xml
Normal file
33
jcpp-protocol-bootstrap/src/layers.xml
Normal file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
抖音关注:程序员三丙
|
||||
知识星球:https://t.zsxq.com/j9b21
|
||||
|
||||
-->
|
||||
<layers xmlns="http://www.springframework.org/schema/boot/layers"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/boot/layers
|
||||
https://www.springframework.org/schema/boot/layers/layers-2.7.xsd">
|
||||
<application>
|
||||
<into layer="spring-boot-loader">
|
||||
<include>org/springframework/boot/loader/**</include>
|
||||
</into>
|
||||
<into layer="application" />
|
||||
</application>
|
||||
<dependencies>
|
||||
<into layer="application">
|
||||
<includeModuleDependencies />
|
||||
</into>
|
||||
<into layer="snapshot-dependencies">
|
||||
<include>*:*:*SNAPSHOT</include>
|
||||
</into>
|
||||
<into layer="dependencies" />
|
||||
</dependencies>
|
||||
<layerOrder>
|
||||
<layer>dependencies</layer>
|
||||
<layer>spring-boot-loader</layer>
|
||||
<layer>snapshot-dependencies</layer>
|
||||
<layer>application</layer>
|
||||
</layerOrder>
|
||||
</layers>
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.protocol;
|
||||
|
||||
import org.springframework.boot.Banner;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
*/
|
||||
@SpringBootApplication(scanBasePackages = {"sanbing.jcpp.protocol",
|
||||
"sanbing.jcpp.infrastructure.stats",
|
||||
"sanbing.jcpp.infrastructure.queue",
|
||||
"sanbing.jcpp.infrastructure.util"})
|
||||
@EnableAsync
|
||||
@EnableScheduling
|
||||
public class JCPPProtocolServiceApplication {
|
||||
|
||||
private static final String SPRING_CONFIG_NAME_KEY = "--spring.config.name";
|
||||
private static final String DEFAULT_SPRING_CONFIG_PARAM = SPRING_CONFIG_NAME_KEY + "=" + "protocol-service";
|
||||
|
||||
public static void main(String[] args) {
|
||||
new SpringApplicationBuilder(JCPPProtocolServiceApplication.class).bannerMode(Banner.Mode.LOG).run(updateArguments(args));
|
||||
}
|
||||
|
||||
private static String[] updateArguments(String[] args) {
|
||||
if (Arrays.stream(args).noneMatch(arg -> arg.startsWith(SPRING_CONFIG_NAME_KEY))) {
|
||||
String[] modifiedArgs = new String[args.length + 1];
|
||||
System.arraycopy(args, 0, modifiedArgs, 0, args.length);
|
||||
modifiedArgs[args.length] = DEFAULT_SPRING_CONFIG_PARAM;
|
||||
return modifiedArgs;
|
||||
}
|
||||
return args;
|
||||
}
|
||||
}
|
||||
12
jcpp-protocol-bootstrap/src/main/resources/banner.txt
Normal file
12
jcpp-protocol-bootstrap/src/main/resources/banner.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
___ ________ ________ ________
|
||||
|\ \|\ ____\|\ __ \|\ __ \
|
||||
\ \ \ \ \___|\ \ \|\ \ \ \|\ \
|
||||
__ \ \ \ \ \ \ \ ____\ \ ____\
|
||||
|\ \\_\ \ \ \____\ \ \___|\ \ \___|
|
||||
\ \________\ \_______\ \__\ \ \__\
|
||||
\|________|\|_______|\|__| \|__|
|
||||
|
||||
===================================================
|
||||
:: ${application.title} :: ${application.formatted-version}
|
||||
===================================================
|
||||
56
jcpp-protocol-bootstrap/src/main/resources/log4j2.xml
Normal file
56
jcpp-protocol-bootstrap/src/main/resources/log4j2.xml
Normal file
@@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration status="INFO" monitorInterval="30">
|
||||
|
||||
<properties>
|
||||
<Property name="LOG_DIR">/var/log/sanbing/jcpp</Property>
|
||||
<Property name="LOG_PATTERN">%d{yyyy-MM-dd HH:mm:ss:SSS} [%X{TRACE_ID}] [%t] %p %c{1} %m%n%throwable</Property>
|
||||
</properties>
|
||||
|
||||
<Appenders>
|
||||
|
||||
<Console name="CONSOLE" target="SYSTEM_OUT" follow="true">
|
||||
<PatternLayout pattern="${LOG_PATTERN}"/>
|
||||
</Console>
|
||||
|
||||
<RollingFile name="ROLLING_FILE" fileName="${LOG_DIR}/jcpp-protocol.log"
|
||||
filePattern="${LOG_DIR}/jcpp-protocol.%d{yyyy-MM-dd}-%i.log"
|
||||
immediateFlush="false">
|
||||
<PatternLayout pattern="${LOG_PATTERN}"/>
|
||||
|
||||
<Policies>
|
||||
<TimeBasedTriggeringPolicy modulate="true" interval="1"/>
|
||||
</Policies>
|
||||
|
||||
<DefaultRolloverStrategy>
|
||||
<Delete basePath="${LOG_DIR}" maxDepth="1">
|
||||
<IfFileName glob="*.log"/>
|
||||
<IfAccumulatedFileSize exceeds="10GB"/>
|
||||
</Delete>
|
||||
</DefaultRolloverStrategy>
|
||||
</RollingFile>
|
||||
|
||||
</Appenders>
|
||||
|
||||
<Loggers>
|
||||
|
||||
<logger name="org.springframework" level="INFO" />
|
||||
|
||||
<AsyncRoot level="INFO" includeLocation="true">
|
||||
<AppenderRef ref="CONSOLE"/>
|
||||
<AppenderRef ref="ROLLING_FILE"/>
|
||||
</AsyncRoot>
|
||||
|
||||
<AsyncLogger name="sanbing.jcpp" level="INFO" additivity="false" includeLocation="false">
|
||||
<AppenderRef ref="CONSOLE"/>
|
||||
<AppenderRef ref="ROLLING_FILE"/>
|
||||
</AsyncLogger>
|
||||
|
||||
<AsyncLogger name="sanbing.jcpp.protocol" level="${env:PROTOCOLS_LOG_LEVEL:-TRACE}"
|
||||
additivity="false" includeLocation="false">
|
||||
<AppenderRef ref="CONSOLE"/>
|
||||
<AppenderRef ref="ROLLING_FILE"/>
|
||||
</AsyncLogger>
|
||||
|
||||
</Loggers>
|
||||
|
||||
</configuration>
|
||||
152
jcpp-protocol-bootstrap/src/main/resources/protocol-service.yml
Normal file
152
jcpp-protocol-bootstrap/src/main/resources/protocol-service.yml
Normal file
@@ -0,0 +1,152 @@
|
||||
server:
|
||||
address: "${HTTP_BIND_ADDRESS:0.0.0.0}"
|
||||
port: "${HTTP_BIND_PORT:8081}"
|
||||
undertow:
|
||||
buffer-size: "${SERVER_UNDERTOW_BUFFER_SIZE:16384}"
|
||||
directBuffers: "${SERVER_UNDERTOW_DIRECT_BUFFERS:true}"
|
||||
threads:
|
||||
io: "${SERVER_UNDERTOW_THREADS_IO:4}"
|
||||
worker: "${SERVER_UNDERTOW_THREADS_WORKER:128}"
|
||||
max-http-post-size: "${SERVER_UNDERTOW_MAX_HTTP_POST_SIZE:10MB}"
|
||||
no-request-timeout: "${SERVER_UNDERTOW_NO_REQUEST_TIMEOUT:10000}"
|
||||
accesslog:
|
||||
enabled: true
|
||||
pattern: "%t %a %r %s (%D ms)"
|
||||
dir: /var/log/sanbing/accesslog
|
||||
options:
|
||||
server:
|
||||
record-request-start-time: true
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: "${SPRING_APPLICATION_NAME:java-charge-point-protocol-server}"
|
||||
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: '${METRICS_ENDPOINTS_EXPOSE:prometheus,health}'
|
||||
endpoint:
|
||||
health:
|
||||
show-details: always
|
||||
|
||||
metrics:
|
||||
enabled: "${METRICS_ENABLED:true}"
|
||||
timer:
|
||||
percentiles: "${METRICS_TIMER_PERCENTILES:0.5}"
|
||||
|
||||
service:
|
||||
# 服务类型:纯协议解析前置 - protocol,纯应用后端 - app,单体服务(包含protocol和app) - monolith
|
||||
type: "${SERVICE_TYPE:protocol}"
|
||||
# 可自定义的服务ID,如果不指定,则默认为HOSTNAME
|
||||
id: "${SERVICE_ID:}"
|
||||
protocols:
|
||||
sessions:
|
||||
default-inactivity-timeout-in-sec: "${PROTOCOLS_SESSIONS_DEFAULT_INACTIVITY_TIMEOUT_IN_SEC:600}"
|
||||
default-state-check-interval-in-sec: "${PROTOCOLS_SESSIONS_DEFAULT_STATE_CHECK_INTERVAL_IN_SEC:60}"
|
||||
yunkuaichongV150:
|
||||
enabled: "${PROTOCOLS_YUNKUAICHONGV150_ENABLED:true}"
|
||||
listener:
|
||||
tcp:
|
||||
bind-address: "${PROTOCOLS_YUNKUAICHONGV150_LISTENER_TCP_BIND_ADDRESS:0.0.0.0}"
|
||||
bind-port: "${PROTOCOLS_YUNKUAICHONGV150_LISTENER_TCP_BIND_PORT:38001}"
|
||||
boss-group-thread_count: "${PROTOCOLS_YUNKUAICHONGV150_LISTENER_TCP_BOSS_GROUP_THREADS:4}"
|
||||
worker-group-thread-count: "${PROTOCOLS_YUNKUAICHONGV150_LISTENER_TCP_WORKER_GROUP_THREADS:16}"
|
||||
so-keep-alive: "${PROTOCOLS_YUNKUAICHONGV150_LISTENER_TCP_SO_KEEPALIVE:true}"
|
||||
so-backlog: "${PROTOCOLS_YUNKUAICHONGV150_LISTENER_TCP_SO_BACKLOG:128}"
|
||||
so-rcvbuf: "${PROTOCOLS_YUNKUAICHONGV150_LISTENER_TCP_SO_RCVBUF:65536}"
|
||||
so-sndbuf: "${PROTOCOLS_YUNKUAICHONGV150_LISTENER_TCP_SO_SNDBUF:65536}"
|
||||
nodelay: "${PROTOCOLS_YUNKUAICHONGV150_LISTENER_TCP_NODELAY:true}"
|
||||
handler:
|
||||
idle-timeout-seconds: "${PROTOCOLS_YUNKUAICHONGV150_LISTENER_TCP_HANDLER_IDLE_TIMEOUT_SECONDS:600}"
|
||||
max_connections: "${PROTOCOLS_YUNKUAICHONGV150_LISTENER_TCP_HANDLER_MAX_CONNECTIONS:100000}"
|
||||
# 默认为二进制类型的拆包器
|
||||
# 可选JSON类型的拆包器 "${PROTOCOLS_YUNKUAICHONGV150_NETTY_HANDLER_BINARY_CONFIGURATION:type:JSON}"
|
||||
# 可选纯文本类型的拆包器 "${PROTOCOLS_YUNKUAICHONGV150_NETTY_HANDLER_BINARY_CONFIGURATION:type:TEXT;maxFrameLength:128;stripDelimiter:true;messageSeparator:null;charsetName:UTF-8}"
|
||||
configuration: "${PROTOCOLS_YUNKUAICHONGV150_NETTY_HANDLER_BINARY_CONFIGURATION:type:BINARY;decoder:sanbing.jcpp.protocol.listener.tcp.decoder.JCPPLengthFieldBasedFrameDecoder;byteOrder:LITTLE_ENDIAN;head:68;lengthFieldOffset:1;lengthFieldLength:1;lengthAdjustment:2;initialBytesToStrip:0}"
|
||||
forwarder:
|
||||
# 作为前置服务单独启时可选:kafka、kafka-sharding,未来计划扩展RocketMQ, GRpc、REST
|
||||
type: "${PROTOCOLS_YUNKUAICHONGV150_FORWARD_TYPE:kafka}"
|
||||
kafka:
|
||||
topic: "${PROTOCOLS_YUNKUAICHONGV150_FORWARD_KAFKA_TOPIC:protocol_uplink}"
|
||||
jcpp-partition: "${PROTOCOLS_YUNKUAICHONGV150_FORWARD_KAFKA_JCPP_PARTITION:true}" # 是否利用JCPP的分片框架
|
||||
# 以下配置只有在service.type为protocol时且jcpp-partition为false时才生效
|
||||
bootstrap-servers: "${PROTOCOLS_YUNKUAICHONGV150_FORWARD_KAFKA_SERVERS:10.102.12.102:9092}"
|
||||
acks: "${PROTOCOLS_YUNKUAICHONGV150_FORWARD_KAFKA_ACKS:1}"
|
||||
# # 可选 protobuf(推荐)、json
|
||||
encoder: "${PROTOCOLS_YUNKUAICHONGV150_FORWARD_KAFKA_ENCODER:protobuf}"
|
||||
retries: "${PROTOCOLS_YUNKUAICHONGV150_FORWARD_KAFKA_RETRIES:1}"
|
||||
compression-type: "${PROTOCOLS_YUNKUAICHONGV150_FORWARD_KAFKA_COMPRESSION_TYPE:lz4}" # none, gzip, snappy, lz4, zstd
|
||||
batch-size: "${PROTOCOLS_YUNKUAICHONGV150_FORWARD_KAFKA_BATCH_SIZE:16384}"
|
||||
linger-ms: "${PROTOCOLS_YUNKUAICHONGV150_FORWARD_KAFKA_LINGER_MS:0}"
|
||||
buffer-memory: "${PROTOCOLS_YUNKUAICHONGV150_FORWARD_BUFFER_MEMORY:33554432}"
|
||||
other-properties: "${PROTOCOLS_YUNKUAICHONGV150_FORWARD_QUEUE_KAFKA_OTHER_PROPERTIES:}"
|
||||
|
||||
# 应用程序服务注册中心配置
|
||||
zk:
|
||||
enabled: "${ZOOKEEPER_ENABLED:true}"
|
||||
url: "${ZOOKEEPER_URL:zookeeper:2181}"
|
||||
retry-interval-ms: "${ZOOKEEPER_RETRY_INTERVAL_MS:3000}"
|
||||
connection-timeout-ms: "${ZOOKEEPER_CONNECTION_TIMEOUT_MS:3000}"
|
||||
session-timeout-ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}"
|
||||
zk-dir: "${ZOOKEEPER_NODES_DIR:/jcpp}"
|
||||
recalculate-delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:0}"
|
||||
|
||||
# 队列配置
|
||||
queue:
|
||||
# 在protocol服务中只能选择 kafka
|
||||
type: "${QUEUE_TYPE:kafka}"
|
||||
partitions:
|
||||
hash_function_name: "${QUEUE_PARTITIONS_HASH_FUNCTION_NAME:murmur3_128}" # murmur3_32, murmur3_128 or sha256
|
||||
in_memory:
|
||||
stats:
|
||||
print-interval-ms: "${QUEUE_IN_MEMORY_STATS_PRINT_INTERVAL_MS:60000}"
|
||||
kafka:
|
||||
bootstrap-servers: "${KAFKA_SERVERS:kafka:9092}"
|
||||
ssl:
|
||||
enabled: "${KAFKA_SSL_ENABLED:false}"
|
||||
truststore-location: "${KAFKA_SSL_TRUSTSTORE_LOCATION:}"
|
||||
truststore-password: "${KAFKA_SSL_TRUSTSTORE_PASSWORD:}"
|
||||
keystore-location: "${KAFKA_SSL_KEYSTORE_LOCATION:}"
|
||||
keystore-password: "${KAFKA_SSL_KEYSTORE_PASSWORD:}"
|
||||
key-password: "${KAFKA_SSL_KEY_PASSWORD:}"
|
||||
acks: "${KAFKA_ACKS:1}"
|
||||
retries: "${KAFKA_RETRIES:1}"
|
||||
compression-type: "${KAFKA_COMPRESSION_TYPE:lz4}" # none, gzip, snappy, lz4, zstd
|
||||
batch-size: "${KAFKA_BATCH_SIZE:1048576}"
|
||||
linger-ms: "${KAFKA_LINGER_MS:1}"
|
||||
max-request-size: "${KAFKA_MAX_REQUEST_SIZE:1048576}"
|
||||
max-in-flight-requests-per-connection: "${KAFKA_MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION:5}"
|
||||
buffer-memory: "${BUFFER_MEMORY:33554432}"
|
||||
replication-factor: "${QUEUE_KAFKA_REPLICATION_FACTOR:1}"
|
||||
max-poll-interval-ms: "${QUEUE_KAFKA_MAX_POLL_INTERVAL_MS:300000}"
|
||||
max-poll-records: "${QUEUE_KAFKA_MAX_POLL_RECORDS:10240}"
|
||||
max-partition-fetch-bytes: "${QUEUE_KAFKA_MAX_PARTITION_FETCH_BYTES:16777216}"
|
||||
fetch-max-bytes: "${QUEUE_KAFKA_FETCH_MAX_BYTES:134217728}"
|
||||
request-timeout-ms: "${QUEUE_KAFKA_REQUEST_TIMEOUT_MS:30000}"
|
||||
session-timeout-ms: "${QUEUE_KAFKA_SESSION_TIMEOUT_MS:10000}"
|
||||
auto-offset-reset: "${QUEUE_KAFKA_AUTO_OFFSET_RESET:earliest}"
|
||||
other-inline: "${QUEUE_KAFKA_OTHER_PROPERTIES:}"
|
||||
topic-properties:
|
||||
app: "${QUEUE_KAFKA_APP_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:52428800;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}"
|
||||
consumer-stats:
|
||||
enabled: "${QUEUE_KAFKA_CONSUMER_STATS_ENABLED:true}"
|
||||
print-interval-ms: "${QUEUE_KAFKA_CONSUMER_STATS_MIN_PRINT_INTERVAL_MS:60000}"
|
||||
kafka-response-timeout-ms: "${QUEUE_KAFKA_CONSUMER_STATS_RESPONSE_TIMEOUT_MS:1000}"
|
||||
app:
|
||||
topic: "${QUEUE_APP_TOPIC:protocol_uplink}"
|
||||
poll-interval: "${QUEUE_APP_POLL_INTERVAL_MS:5}"
|
||||
pack-processing-timeout: "${QUEUE_APP_PACK_PROCESSING_TIMEOUT_MS:2000}"
|
||||
consumer-per-partition: "${QUEUE_APP_CONSUMER_PER_PARTITION:true}"
|
||||
partitions: "${QUEUE_APP_PARTITIONS:10}"
|
||||
# 可选 protobuf(推荐)、json,需要跟..forwarder.kafka.encoder保持一致
|
||||
decoder: "${QUEUE_APP_DECODER:protobuf}"
|
||||
stats:
|
||||
enabled: "${QUEUE_APP_STATS_ENABLED:true}"
|
||||
print-interval-ms: "${QUEUE_APP_STATS_PRINT_INTERVAL_MS:60000}"
|
||||
|
||||
thread-pool:
|
||||
sharding:
|
||||
hash_function_name: "${THREAD_POOL_SHARDING_HASH_FUNCTION_NAME:murmur3_128}" # murmur3_32, murmur3_128 or sha256
|
||||
parallelism: "${THREAD_POOL_SHARDING_PARALLELISM:128}"
|
||||
stats-print-interval-ms: "${THREAD_POOL_SHARDING_STATS_PRINT_INTERVAL_MS:10000}"
|
||||
@@ -0,0 +1,38 @@
|
||||
package sanbing.jcpp.protocol; /**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
*/
|
||||
@ActiveProfiles("test")
|
||||
@SpringBootTest(classes = JCPPProtocolServiceApplication.class, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
@AutoConfigureMockMvc
|
||||
public class AbstractProtocolTestBase {
|
||||
|
||||
static {
|
||||
System.setProperty("spring.config.name", "protocol-service");
|
||||
}
|
||||
|
||||
protected final Logger log = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
|
||||
@Autowired
|
||||
protected MockMvc mockMvc;
|
||||
|
||||
@Autowired
|
||||
protected Environment environment;
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.protocol.adapter;
|
||||
|
||||
import cn.hutool.core.util.HexUtil;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.After;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil;
|
||||
import sanbing.jcpp.infrastructure.util.property.PropertyUtils;
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto;
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto.DownlinkRestMessage;
|
||||
import sanbing.jcpp.protocol.AbstractProtocolTestBase;
|
||||
import sanbing.jcpp.protocol.domain.DownlinkCmdEnum;
|
||||
import sanbing.jcpp.protocol.domain.ProtocolSession;
|
||||
import sanbing.jcpp.protocol.listener.tcp.configs.BinaryHandlerConfiguration;
|
||||
import sanbing.jcpp.protocol.listener.tcp.decoder.JCPPLengthFieldBasedFrameDecoder;
|
||||
import sanbing.jcpp.protocol.listener.tcp.decoder.TcpMsgDecoder;
|
||||
import sanbing.jcpp.protocol.provider.impl.DefaultProtocolSessionRegistryProvider;
|
||||
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
import static sanbing.jcpp.protocol.listener.tcp.configs.BinaryHandlerConfiguration.LITTLE_ENDIAN_BYTE_ORDER;
|
||||
|
||||
class DownlinkControllerTest extends AbstractProtocolTestBase {
|
||||
final String PROTOCOL_NAME = "yunkuaichongV150";
|
||||
|
||||
@Value("${service.protocols.yunkuaichongV150.listener.tcp.handler.configuration}")
|
||||
private String yunkuaichongV150TcpHandler;
|
||||
|
||||
@Value("${service.protocols.yunkuaichongV150.listener.tcp.bind-port}")
|
||||
private int yunkuaichongV150TcpPort;
|
||||
|
||||
@Resource
|
||||
DefaultProtocolSessionRegistryProvider sessionRegistryProvider;
|
||||
|
||||
private EventLoopGroup group;
|
||||
private Channel channel;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws InterruptedException {
|
||||
final JsonNode cfgJson = JacksonUtil.valueToTree(PropertyUtils.getProps(yunkuaichongV150TcpHandler));
|
||||
|
||||
BinaryHandlerConfiguration binaryHandlerConfig = JacksonUtil.treeToValue(cfgJson, BinaryHandlerConfiguration.class);
|
||||
|
||||
ByteOrder byteOrder = LITTLE_ENDIAN_BYTE_ORDER.equalsIgnoreCase(binaryHandlerConfig.getByteOrder())
|
||||
? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN;
|
||||
|
||||
JCPPLengthFieldBasedFrameDecoder framer = new JCPPLengthFieldBasedFrameDecoder(binaryHandlerConfig.getHead(), byteOrder,
|
||||
binaryHandlerConfig.getLengthFieldOffset(), binaryHandlerConfig.getLengthFieldLength(),
|
||||
binaryHandlerConfig.getLengthAdjustment(), binaryHandlerConfig.getInitialBytesToStrip());
|
||||
|
||||
|
||||
group = new NioEventLoopGroup();
|
||||
Bootstrap b = new Bootstrap();
|
||||
b.group(group)
|
||||
.channel(NioSocketChannel.class)
|
||||
.handler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(SocketChannel ch) throws Exception {
|
||||
ch.pipeline()
|
||||
.addLast(framer)
|
||||
.addLast("tcpByteDecoderOverride", new TcpMsgDecoder<>(PROTOCOL_NAME, TcpMsgDecoder::toByteArray))
|
||||
.addLast(new SimpleChannelInboundHandler<>() {
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
|
||||
log.info("接收到字节码:{}", msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 连接到服务器
|
||||
ChannelFuture f = b.connect("127.0.0.1", yunkuaichongV150TcpPort).sync();
|
||||
channel = f.channel();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
if (channel != null) {
|
||||
channel.close();
|
||||
}
|
||||
group.shutdownGracefully();
|
||||
}
|
||||
|
||||
@Test
|
||||
void remoteStartCharging() throws Exception {
|
||||
// 先发送一段登录
|
||||
channel.writeAndFlush(Unpooled.wrappedBuffer(HexUtil.decodeHex("6822001900012023121200001001011047562e393572313300898604d11722d0348606024E87"))).sync();
|
||||
|
||||
// 停一会等注册完成 todo 也可以读下行消息判断是否登录成功
|
||||
Thread.sleep(1000);
|
||||
|
||||
UUID messageId = UUID.randomUUID();
|
||||
ProtocolSession protocolSession = sessionRegistryProvider.getSESSION_CACHE().asMap().values().stream().findFirst().get().get();
|
||||
UUID sessionId = protocolSession.getId();
|
||||
UUID requestId = UUID.randomUUID();
|
||||
|
||||
// 创建 DownlinkRestMessage 实例
|
||||
String pileCode = "20231212000010";
|
||||
DownlinkRestMessage downlinkMsg = DownlinkRestMessage.newBuilder()
|
||||
.setMessageIdMSB(messageId.getMostSignificantBits())
|
||||
.setMessageIdLSB(messageId.getLeastSignificantBits())
|
||||
.setSessionIdMSB(sessionId.getMostSignificantBits())
|
||||
.setSessionIdLSB(sessionId.getLeastSignificantBits())
|
||||
.setProtocolName(PROTOCOL_NAME)
|
||||
.setPileCode(pileCode)
|
||||
.setRequestIdMSB(requestId.getMostSignificantBits())
|
||||
.setRequestIdLSB(requestId.getLeastSignificantBits())
|
||||
.setDownlinkCmd(DownlinkCmdEnum.REMOTE_START_CHARGING.name())
|
||||
.setRemoteStartChargingRequest(ProtocolProto.RemoteStartChargingRequest.newBuilder()
|
||||
.setPileCode(pileCode)
|
||||
.setGunCode("01")
|
||||
.setLimitCent(10000)
|
||||
.setTradeNo("12345678901234567890")
|
||||
.build())
|
||||
.build();
|
||||
|
||||
// 序列化为 Protobuf 字节流
|
||||
byte[] protobufContent = downlinkMsg.toByteArray();
|
||||
|
||||
// 调用 POST 接口
|
||||
mockMvc.perform(post("/api/onDownlink")
|
||||
.contentType("application/x-protobuf")
|
||||
.content(protobufContent))
|
||||
.andDo(print()) // 打印请求和响应信息
|
||||
.andExpect(status().is(HttpStatus.OK.value()));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user