This commit is contained in:
tiger_zhou 2023-06-08 11:09:15 +08:00
commit 50129e0e87
29 changed files with 779 additions and 164 deletions

View File

@ -1,10 +1,10 @@
FROM openjdk:17 FROM openjdk:17
ADD target/xian-ncc-da-0.1.jar app.jar ADD target/xian-ncc-da-0.1.jar xian-ncc-da.jar
EXPOSE 9000 19000/tcp EXPOSE 9081
ENV TZ=Asia/Shanghai ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
CMD java -jar -Dfile.encoding=UTF-8 -Dspring.profiles.active=dev /app.jar CMD java -jar -Dfile.encoding=UTF-8 -Dspring.profiles.active=dev /xian-ncc-da.jar

View File

@ -46,112 +46,115 @@ public enum MessageId {
DEPOT_PLAN(0x0007, () -> new DepotPlanResponse()), DEPOT_PLAN(0x0007, () -> new DepotPlanResponse()),
/** /**
* 列车报点 * 2.7.8 列车报点消息
*/ */
TRAIN_RECORD(0x0008, () -> new TrainRecordResponse()), TRAIN_RECORD(0x0008, () -> new TrainRecordResponse()),
/** /**
* 列车信息全体 * 2.7.9 列车信息全体消息
*/ */
TRAIN_INDICATION_INIT(0x0009, () -> new TrainIndicationInitResponse()), TRAIN_INDICATION_INIT(0x0009, () -> new TrainIndicationInitResponse()),
/** /**
* 列车信息更新 * 2.7.10 列车信息更新消息[增加/更新]
*/ */
TRAIN_INDICATION_UPDATE(0x0010, () -> new TrainIndicationUpdateResponse()), TRAIN_INDICATION_UPDATE(0x0010, () -> new TrainIndicationUpdateResponse()),
/** /**
* 列车信息删除 * 2.7.11 列车信息删除消息
*/ */
TRAIN_INDICATION_REMOVE(0x0011, () -> new TrainIndicationRemoveResponse()), TRAIN_INDICATION_REMOVE(0x0011, () -> new TrainIndicationRemoveResponse()),
/** /**
* 统计信息查询 * 2.8.1 统计信息查询消息
*/ */
REPORT_ASK(0x0012, null), REPORT_ASK(0x0012, null),
/** /**
* 车组运行里程报告 * 2.8.4 车组运行里程报告消息
*/ */
GROUP_RUNNING_REPORT(0x0013, () -> new GroupRunningReportResponse()), GROUP_RUNNING_REPORT(0x0013, () -> new GroupRunningReportResponse()),
/** /**
* 司机驾驶里程报告 *2.8.5 司机驾驶里程报告消息
*/ */
DRIVER_DISTANCE_REPORT(0x0014, () -> new DriverDistanceReportResponse()), DRIVER_DISTANCE_REPORT(0x0014, () -> new DriverDistanceReportResponse()),
/** /**
* 调度日志报告 * 2.8.6 调度日志报告消息
*/ */
DISPATCHER_REPORT(0x0015, null), DISPATCHER_REPORT(0x0015, ()->new DispatcherReportResponse()),
/** /**
* 存备车报告 * 2.8.7 存备车报告消息
*/ */
GROUP_BAK_REPORT(0x0016, null), GROUP_BAK_REPORT(0x0016, ()->new GroupBakReportResponse()),
/** /**
* 列车整备状态报告 * 2.8.8 列车整备状态报告消息
*/ */
GROUP_STATUS_REPORT(0x0017, null), GROUP_STATUS_REPORT(0x0017, ()->new GroupStatusReportResponse()),
/** /**
* 事件及告警信息请求 * 2.8.9 事件及告警信息请求消息
*/ */
ALARM_ASK(0x0018, () -> new AlarmAckRequest()), ALARM_ASK(0x0018, () -> new AlarmAckRequest()),
/** /**
* 操作命令 * 2.8.10 操作命令消息
*/ */
ACTION_REPORT(0x0019, () -> new ActionReportResponse()), ACTION_REPORT(0x0019, () -> new ActionReportResponse()),
/** /**
* 列车信息系统事件 * 2.8.11 列车信息系统事件消息
*/ */
ALARM_REPORT(0x0020, () -> new AlarmReportResponse()), ALARM_REPORT(0x0020, () -> new AlarmReportResponse()),
/** /**
* 历史运行图申请 * 2.8.13 历史运行图申请消息
*/ */
LOAD_HISTORY_TG_DATA(0x0021, () -> new LoadHistoryTGDataRequest()), LOAD_HISTORY_TG_DATA(0x0021, () -> new LoadHistoryTGDataRequest()),
/** /**
* 计划列车运行图消息 * 2.8.14 计划列车运行图消息
*/ */
INUSED_SCHEDULE(0x0022, () -> new InusedScheduleResponse()), INUSED_SCHEDULE(0x0022, () -> new InusedScheduleResponse()),
/** /**
* 实际列车运行图消息 * 2.8.15 实际列车运行图消息
*/ */
HISTORY_SCHEDULE(0x0023, () -> new HistoryScheduleResponse()), HISTORY_SCHEDULE(0x0023, () -> new HistoryScheduleResponse()),
/** /**
* 断电续传申请 * 2.9.3.1 断点续传申请消息
*/ */
Resume_ASK(0x0024, null), Resume_ASK(0x0024, null),
/** /**
* 断点续传开始 * 2.9.3.2 断点续传开始消息
*/ */
Resume_Begin_ACK(0x0025, null), Resume_Begin_ACK(0x0025, ()->new ResumeBeginAckResponse()),
/** /**
* 断点续传结束 * 2.9.3.4 断点续传结束消息
*/ */
Resume_END_ACK(0x0026, null), Resume_END_ACK(0x0026, ()->new ResumeEndAckResponse()),
/** /**
* 查询无结果消息 * 2.8.12 查询无结果消息
*/ */
REPORT_NACK(0x0027, null), REPORT_NACK(0x0027, null),
/** /**
* 查询结果开始消息 * 2.8.2 查询结果开始消息
*/ */
REPORT_BEGIN(0x0028, () -> new ReportBeginResponse()), REPORT_BEGIN(0x0028, () -> new ReportBeginResponse()),
/** /**
* 查询结果结束消息 * 2.8.3 查询结果结束消息
*/ */
REPORT_END(0x0029, () -> new ReportEndResponse()), REPORT_END(0x0029, () -> new ReportEndResponse()),
/** /**
* 断点续传数据消息 * 2.9.3.3 断点续传数据消息
*/ */
Resume_DATA(0x0030, null), Resume_DATA(0x0030, ()->new ResumeDataResponse()),
/** /**
* 实时报警事件消息 * 2.7.12 实时报警事件消息
*/ */
MESSAGE_ALARM(0x0031, () -> new MessageAlarmResponse()), MESSAGE_ALARM(0x0031, () -> new MessageAlarmResponse()),
/** /**
* 列车阻塞消息 * 2.7.13 列车阻塞消息
*/ */
TRAIN_BLOCK_INFO(0x0032, () -> new TrainBlockInfoResponse()), TRAIN_BLOCK_INFO(0x0032, () -> new TrainBlockInfoResponse()),
/** /**
* 当天计划运行图参数消息 * 2.7.14 当天计划运行图参数消息
*/ */
INUSED_SCHEDULE_PARAMETER(0x0033, () -> new InUsedScheduleParameterResponse()), INUSED_SCHEDULE_PARAMETER(0x0033, () -> new InUsedScheduleParameterResponse()),
; ;
int val; int val;
public int idValue(){
return this.val;
}
/** /**
* 消息对象创建接口 * 消息对象创建接口
*/ */
@ -167,6 +170,15 @@ public enum MessageId {
this.omc = omc; this.omc = omc;
} }
public MessageData create() {
return null != this.omc ? this.omc.create() : null;
}
public MessageResponse createResponse() {
final MessageData messageData = create();
if(null==messageData) return null;
return messageData instanceof MessageResponse ? (MessageResponse) messageData : null;
}
public static MessageId of(int val) { public static MessageId of(int val) {
for (MessageId messageId : MessageId.values()) { for (MessageId messageId : MessageId.values()) {
if (messageId.val == val) { if (messageId.val == val) {

View File

@ -9,9 +9,9 @@ import java.util.List;
public class OccMessageDecoder extends ByteToMessageDecoder { public class OccMessageDecoder extends ByteToMessageDecoder {
final TcpClientConnection connection; final OccTcpClientConnection connection;
public OccMessageDecoder(TcpClientConnection connection) { public OccMessageDecoder(OccTcpClientConnection connection) {
this.connection = connection; this.connection = connection;
} }

View File

@ -9,9 +9,9 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j @Slf4j
public class OccMessageEncoder extends MessageToByteEncoder<List<MessageData>> { public class OccMessageEncoder extends MessageToByteEncoder<List<MessageData>> {
final TcpClientConnection connection; final OccTcpClientConnection connection;
public OccMessageEncoder(TcpClientConnection connection) { public OccMessageEncoder(OccTcpClientConnection connection) {
this.connection = connection; this.connection = connection;
} }

View File

@ -23,7 +23,7 @@ public class OccMessageManage implements ApplicationRunner {
// 读取数据配置创建客户端 // 读取数据配置创建客户端
this.registerClient(new XianOccMessagingClient(3, "localhost")); this.registerClient(new XianOccMessagingClient(3, "localhost"));
for (XianOccMessagingClient client : this.clientMap.values()) { for (XianOccMessagingClient client : this.clientMap.values()) {
client.connection.connect(); client.connect();
} }
} }
} }

View File

@ -7,14 +7,13 @@ import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup; import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.channel.socket.nio.NioSocketChannel;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@Slf4j @Slf4j
public class TcpClientConnection { public class OccTcpClientConnection {
XianOccMessagingClient client; XianOccMessagingClient client;
@ -24,11 +23,14 @@ public class TcpClientConnection {
final EventLoopGroup group; final EventLoopGroup group;
final Bootstrap bootstrap; final Bootstrap bootstrap;
volatile boolean connected;
/** /**
* 连接管道 * 连接状态
*/ */
volatile boolean connected;
Channel channel; Channel channel;
/**
* 心跳超时重连处理器
*/
HeartBeatTimeoutHandler timeoutHandler; HeartBeatTimeoutHandler timeoutHandler;
/** /**
@ -36,13 +38,13 @@ public class TcpClientConnection {
*/ */
volatile long lastReceiveMessageTime; volatile long lastReceiveMessageTime;
public TcpClientConnection(XianOccMessagingClient client, String host, int port) { public OccTcpClientConnection(XianOccMessagingClient client, String host, int port) {
this.client = client; this.client = client;
this.host = host; this.host = host;
this.port = port; this.port = port;
this.group = new NioEventLoopGroup(); this.group = new NioEventLoopGroup(1);
this.bootstrap = new Bootstrap(); this.bootstrap = new Bootstrap();
TcpClientConnection self = this; OccTcpClientConnection self = this;
bootstrap.group(group) bootstrap.group(group)
.channel(NioSocketChannel.class) .channel(NioSocketChannel.class)
.handler(new ChannelInitializer<>() { .handler(new ChannelInitializer<>() {
@ -65,30 +67,28 @@ public class TcpClientConnection {
} }
public synchronized void connect() { public synchronized void connect() {
try {
ChannelFuture channelFuture = bootstrap.connect(this.host, this.port); ChannelFuture channelFuture = bootstrap.connect(this.host, this.port);
channelFuture.addListener(future1 -> { channelFuture.addListener(future1 -> {
if (future1.isSuccess()) { if (future1.isSuccess()) {
log.info("连接到OCC服务: host={}, port={}", this.host, this.port); log.info("连接到OCC服务: {}", this.hostPortInfo());
this.connected = true; this.lastReceiveMessageTime = System.currentTimeMillis();
this.channel = channelFuture.channel(); this.channel = channelFuture.channel();
// 启动心跳超时监测 this.connected = true;
this.timeoutHandler.start();
} }
}); });
channelFuture.channel().closeFuture().addListener(listener -> { channelFuture.channel().closeFuture().addListener(listener -> {
log.info("与服务断连,尝试重连"); if (listener.isSuccess()) {
this.timeoutHandler.stop(); log.info("与服务断连,尝试重连: {}", this.hostPortInfo());
this.connected = false; this.connected = false;
this.channel = null; this.channel = null;
Thread.sleep(3000); Thread.sleep(3000);
connect(); connect();
});
} catch (Exception e) {
log.error("与OCC服务连接异常尝试重连", e);
connect();
} }
});
}
String hostPortInfo() {
return String.format("host=%s, port=%s", this.host, this.port);
} }
static class HeartBeatTimeoutHandler { static class HeartBeatTimeoutHandler {
@ -99,34 +99,22 @@ public class TcpClientConnection {
final int HeartBeatTimeout = 15 * 1000; final int HeartBeatTimeout = 15 * 1000;
static final int Period = 2; static final int Period = 2;
TcpClientConnection connection; OccTcpClientConnection connection;
boolean running;
static final ScheduledExecutorService Executor = Executors.newSingleThreadScheduledExecutor(); static final ScheduledExecutorService Executor = Executors.newSingleThreadScheduledExecutor();
public HeartBeatTimeoutHandler(TcpClientConnection connection) { public HeartBeatTimeoutHandler(OccTcpClientConnection connection) {
this.connection = connection; this.connection = connection;
Executor.scheduleAtFixedRate(() -> { Executor.scheduleAtFixedRate(() -> {
if (running) { if (connection.connected) {
long ctm = System.currentTimeMillis(); long ctm = System.currentTimeMillis();
if (connection.lastReceiveMessageTime + HeartBeatTimeout < ctm) { if (connection.lastReceiveMessageTime + HeartBeatTimeout < ctm) {
log.info("超时未收到OCC消息尝试重连: host={}, port={}", connection.host, log.info("超时未收到OCC消息尝试断开重连");
connection.port);
connection.reconnect(); connection.reconnect();
} }
} }
}, Period, Period, TimeUnit.SECONDS); }, Period, Period, TimeUnit.SECONDS);
} }
void start() {
// log.info("心跳超时监控启动");
this.running = true;
connection.lastReceiveMessageTime = System.currentTimeMillis();
}
void stop() {
// log.info("心跳超时监控stop");
this.running = false;
}
} }
} }

View File

@ -13,51 +13,34 @@ public class XianOccMessagingClient {
*/ */
final int lineId; final int lineId;
final String host; final String host;
/**
* 实时端口号
*/
final int realTimePort;
/**
* 非实时端口号
*/
final int nonRealTimePort;
/**
* 版本号
*/
final int Version = 0x01;
/**
* 心跳发送间隔
*/
final int heartbeatInterval = 10;
/**
* 心跳超时
*/
final int heartbeatTimeout = 15;
final TcpClientConnection connection; /**
* 实时消息的连接
*/
private final OccTcpClientConnection rtConnection;
/**
* 非实时消息的连接
*/
private final OccTcpClientConnection nrtConnection;
public XianOccMessagingClient(int lineId, String host) { public XianOccMessagingClient(int lineId, String host) {
this.host = host; this.host = host;
this.lineId = lineId; this.lineId = lineId;
this.realTimePort = realTimePortBase + lineId; final int realTimePort = realTimePortBase + lineId;
this.nonRealTimePort = nonRealTimePortBase + lineId; final int nonRealTimePort = nonRealTimePortBase + lineId;
// 创建实时消息连接 // 创建实时和非实时消息连接
this.connection = new TcpClientConnection(this, host, this.realTimePort); this.rtConnection = new OccTcpClientConnection(this, host, realTimePort);
this.nrtConnection = new OccTcpClientConnection(this, host, nonRealTimePort);
} }
/** /**
* 连接实时消息服务 * 连接OCC服务
*/ */
public void rtMessageConnect() { public void connect() {
// 实时消息连接
this.rtConnection.connect();
// 非实时消息连接
this.nrtConnection.connect();
} }
/**
* 连接非实时消息服务
*/
public void nrtMessageConnect() {
}
} }

View File

@ -24,4 +24,25 @@ public class DateTimeUtil {
buf.writeByte(from.getMinute()); buf.writeByte(from.getMinute());
buf.writeByte(from.getSecond()); buf.writeByte(from.getSecond());
} }
/**
* 例如传送2001年9月21日15时29分30秒则年秒各单元的值分别为2001921152930
*/
public static byte[]convert(final LocalDateTime from){
final byte[] to = new byte[7];
convert(from,to);
return to;
}
/**
* 例如传送2001年9月21日15时29分30秒则01年2月3日4时5分6秒各单元的值分别为2001921152930
*/
public static LocalDateTime convert(final byte[]from){
if (null == from || from.length != 7) throw new RuntimeException("数组from的长度须为7");
final int year = Integer.valueOf(String.format("%s%s",String.format("%d",from[0]),String.format("%02d",from[1])));
final int month = from[2];
final int day = from[3];
final int hour = from[4];
final int minute = from[5];
final int second = from[6];
return LocalDateTime.of(year,month,day,hour,minute,second);
}
} }

View File

@ -0,0 +1,75 @@
package club.joylink.xiannccda.ats.message.line3;
import club.joylink.xiannccda.ats.message.MessageResponse;
import io.netty.buffer.ByteBuf;
import java.util.ArrayList;
import java.util.List;
/**
* 2.8.6 调度日志报告消息
*/
public class DispatcherReportResponse extends MessageResponse {
/**
* 线路号(2)
*/
private Short lineId;
/**
* 查询标识号(2)<br>
* 同一时间多个查询的report_id不允许重复答复消息中的report_id值跟查询消息中的相同
*/
private Short reportId;
/**
* 消息总数(2)
*/
private Short totalMessage;
/**
* 本消息的顺序号(2)
*/
private Short messageSequence;
/**
* 记录条数(2)
*/
private Short count;
/**
* 调度日志报告列表
*/
private List<LogCell> logs;
@Override
public void decode2(ByteBuf buf) throws Exception {
this.lineId=buf.readShort();
this.reportId=buf.readShort();
this.totalMessage=buf.readShort();
this.messageSequence=buf.readShort();
this.count=buf.readShort();
this.logs=new ArrayList<>(this.count);
for(int i=0;i<this.count;i++){
this.logs.add(new LogCell().decode(buf));
}
}
public static class LogCell {
/**
* 日期(7)
*/
private byte[] reportTime = new byte[7];
/**
* 调度员(32)
*/
private byte[] userName = new byte[32];
/**
* 记录内容(256)
*/
private byte[] logItem = new byte[256];
public LogCell decode(final ByteBuf buf) {
buf.readBytes(this.reportTime);
buf.readBytes(this.userName);
buf.readBytes(this.logItem);
return this;
}
}
}

View File

@ -0,0 +1,85 @@
package club.joylink.xiannccda.ats.message.line3;
import club.joylink.xiannccda.ats.message.MessageResponse;
import io.netty.buffer.ByteBuf;
import java.util.ArrayList;
import java.util.List;
/**
* 2.8.7 存备车报告消息
*/
public class GroupBakReportResponse extends MessageResponse {
/**
* 线路号(2)
*/
private Short lineId;
/**
* 查询标识号(2)<br>
* 同一时间多个查询的report_id不允许重复答复消息中的report_id值跟查询消息中的相同
*/
private Short reportId;
/**
* 消息总数(2)
*/
private Short totalMessage;
/**
* 本消息的顺序号(2)
*/
private Short messageSequence;
/**
* 记录条数(2)
*/
private Short count;
/**
* 存备车报告列表
*/
private List<GroupBakCell> groups;
@Override
public void decode2(ByteBuf buf) throws Exception {
this.lineId=buf.readShort();
this.reportId=buf.readShort();
this.totalMessage=buf.readShort();
this.messageSequence=buf.readShort();
this.count=buf.readShort();
this.groups=new ArrayList<>(this.count);
for(int i=0;i<this.count;i++){
this.groups.add(new GroupBakCell().decode(buf));
}
}
public static class GroupBakCell{
/**
* 车组号(9)
*/
private byte[] groupId = new byte[9];
/**
* 存备车状态(1)<br>
* 0x01:上线运营<br>
* 0x02:备车<br>
* 0x03:维修 <br>
*/
private byte status;
/**
* 所处位置(1)<br>
* 0x01:车辆段1<br>
* 0x02:停车场1<br>
* 0x04:车辆段2<br>
* 0x08:停车场2<br>
*/
private byte depot;
/**
* 所在轨道名称(20)
*/
private byte[] trackName = new byte[20];
public GroupBakCell decode(final ByteBuf buf){
buf.readBytes(this.groupId);
this.status=buf.readByte();
this.depot=buf.readByte();
buf.readBytes(this.trackName);
return this;
}
}
}

View File

@ -0,0 +1,77 @@
package club.joylink.xiannccda.ats.message.line3;
import club.joylink.xiannccda.ats.message.MessageResponse;
import io.netty.buffer.ByteBuf;
import java.util.ArrayList;
import java.util.List;
/**
* 2.8.8 列车整备状态报告消息
*/
public class GroupStatusReportResponse extends MessageResponse {
/**
* 线路号(2)
*/
private Short lineId;
/**
* 查询标识号(2)<br>
* 同一时间多个查询的report_id不允许重复答复消息中的report_id值跟查询消息中的相同
*/
private Short reportId;
/**
* 消息总数(2)
*/
private Short totalMessage;
/**
* 本消息的顺序号(2)
*/
private Short messageSequence;
/**
* 记录条数(2)
*/
private Short count;
/**
* 列车整备状态列表
*/
private List<GroupStatusCell> groups;
@Override
public void decode2(ByteBuf buf) throws Exception {
this.lineId = buf.readShort();
this.reportId = buf.readShort();
this.totalMessage = buf.readShort();
this.messageSequence = buf.readShort();
this.count = buf.readShort();
this.groups = new ArrayList<>(this.count);
for (int i = 0; i < this.count; i++) {
this.groups.add(new GroupStatusCell().decode(buf));
}
}
public static class GroupStatusCell {
/**
* 车组号(9)
*/
private byte[] groupId = new byte[9];
/**
* 列车位置(1)<br>
* 0x01:车辆段/停车场<br>
* 0x02: 正线<br>
*/
private byte depot;
/**
* 整备状态(42)
*/
private byte[] status = new byte[42];
public GroupStatusCell decode(final ByteBuf buf) {
buf.readBytes(this.groupId);
this.depot = buf.readByte();
buf.readBytes(this.status);
return this;
}
}
}

View File

@ -0,0 +1,47 @@
package club.joylink.xiannccda.ats.message.line3;
import club.joylink.xiannccda.ats.message.MessageId;
import club.joylink.xiannccda.ats.message.MessageRequest;
import io.netty.buffer.ByteBuf;
import java.time.LocalDateTime;
import java.util.Arrays;
/**
* 2.9.3.1 断点续传申请消息<br>
* 时间范围说明<br>
* 申请消息的时间范围结束时间与开始时间之差不超过1小时<br>
*/
public class ResumeAskRequest extends MessageRequest {
/**
* 线路号(2)
*/
private Short lineId;
/**
* 开始时间(7)
*/
private byte[] beginTime;
/**
* 结束时间(7)
*/
private byte[] endTime;
public ResumeAskRequest(Short lineId, LocalDateTime begin, LocalDateTime end) {
super(MessageId.Resume_ASK, 2 + 7 + 7);
this.lineId = lineId;
this.beginTime = new byte[7];
Arrays.fill(this.beginTime, (byte) 0);
DateTimeUtil.convert(begin, this.beginTime);
this.endTime = new byte[7];
Arrays.fill(this.endTime, (byte) 0);
DateTimeUtil.convert(end, this.endTime);
}
@Override
public void encode2(ByteBuf buf) {
buf.writeShort(this.lineId);
buf.writeBytes(this.beginTime);
buf.writeBytes(this.endTime);
}
}

View File

@ -0,0 +1,30 @@
package club.joylink.xiannccda.ats.message.line3;
import club.joylink.xiannccda.ats.message.MessageResponse;
import io.netty.buffer.ByteBuf;
/**
* 2.9.3.2 断点续传开始消息
*/
public class ResumeBeginAckResponse extends MessageResponse {
/**
* 线路号(2)
*/
private Short lineId;
/**
* 开始时间(7)
*/
private byte[] beginTime = new byte[7];
/**
* 结束时间(7)
*/
private byte[] endTime = new byte[7];
@Override
public void decode2(ByteBuf buf) throws Exception {
this.lineId = buf.readShort();
buf.readBytes(this.beginTime);
buf.readBytes(this.endTime);
}
}

View File

@ -0,0 +1,54 @@
package club.joylink.xiannccda.ats.message.line3;
import club.joylink.xiannccda.ats.message.MessageId;
import club.joylink.xiannccda.ats.message.MessageResponse;
import io.netty.buffer.ByteBuf;
import java.util.ArrayList;
import java.util.List;
/**
* 2.9.3.3 断点续传数据消息
*/
public class ResumeDataResponse extends MessageResponse {
/**
* 线路号(2)
*/
private Short lineId;
/**
* 消息总数(2)
*/
private Short totalMessage;
/**
* 本消息的顺序号(2)
*/
private Short messageSequence;
/**
* 消息message_data的数量(2)
*/
private Short msgCnt;
/**
* 补传的消息(全部为响应类消息)消息格式内容和本接口协议定义的消息格式一致时间戳保持与实时发送时的一致
*/
private List<MessageResponse> messageData;
@Override
public void decode2(ByteBuf buf) throws Exception {
this.lineId = buf.readShort();
this.totalMessage = buf.readShort();
this.messageSequence = buf.readShort();
this.msgCnt = buf.readShort();
this.messageData = new ArrayList<>(this.msgCnt);
//
for (int i = 0; i < this.msgCnt; i++) {
final int _readIndex = buf.readerIndex();
buf.skipBytes(8);
final MessageId messageId = MessageId.of(buf.readShort());
buf.readerIndex(_readIndex);
final MessageResponse messageResponse = messageId.createResponse();
messageResponse.decode(buf);
this.messageData.add(messageResponse);
}
}
}

View File

@ -0,0 +1,28 @@
package club.joylink.xiannccda.ats.message.line3;
import club.joylink.xiannccda.ats.message.MessageResponse;
import io.netty.buffer.ByteBuf;
/**
* 2.9.3.4 断点续传结束消息
*/
public class ResumeEndAckResponse extends MessageResponse {
/**
* 线路号(2)
*/
private Short lineId;
/**
* 开始时间(7)
*/
private byte[] beginTime = new byte[7];
/**
* 结束时间(7)
*/
private byte[] endTime = new byte[7];
@Override
public void decode2(ByteBuf buf) throws Exception {
this.lineId = buf.readShort();
buf.readBytes(this.beginTime);
buf.readBytes(this.endTime);
}
}

View File

@ -4,6 +4,8 @@ import club.joylink.xiannccda.exception.BusinessException;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail; import org.springframework.http.ProblemDetail;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.bind.annotation.RestControllerAdvice;
@ -20,6 +22,15 @@ public class ResponseExceptionHandler {
return problemDetail; return problemDetail;
} }
@ExceptionHandler({HttpMessageNotReadableException.class, MethodArgumentNotValidException.class})
public ProblemDetail messageReadExceptionHandler(Exception e) {
log.error("客户端参数异常", e);
ProblemDetail problemDetail = ProblemDetail.forStatus(HttpStatus.PAYMENT_REQUIRED);
problemDetail.setProperty("code", HttpStatus.PAYMENT_REQUIRED);
problemDetail.setTitle(e.getMessage());
return problemDetail;
}
/** /**
* 系统未处理异常捕获 * 系统未处理异常捕获
* *

View File

@ -5,14 +5,14 @@ import org.springframework.web.bind.annotation.RestController;
/** /**
* <p> * <p>
* 前端控制器 * 线路信息 前端控制器
* </p> * </p>
* *
* @author walker-sheng * @author walker-sheng
* @since 2023-06-06 * @since 2023-06-06
*/ */
@RestController @RestController
@RequestMapping("/lineInfo") @RequestMapping("/api/lineInfo")
public class LineInfoController { public class LineInfoController {
} }

View File

@ -0,0 +1,18 @@
package club.joylink.xiannccda.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
* 发布图形界面 前端控制器
* </p>
*
* @author walker-sheng
* @since 2023-06-08
*/
@RestController
@RequestMapping("/api/publishedGi")
public class PublishedGiController {
}

View File

@ -21,7 +21,7 @@ import lombok.experimental.Accessors;
@Getter @Getter
@Setter @Setter
@Accessors(chain = true) @Accessors(chain = true)
@Schema(name = "Drafting", description = "$!{table.comment}") @Schema(name = "Drafting", description = "图形界面草稿数据")
public class Drafting { public class Drafting {
@Schema(description = "id") @Schema(description = "id")
@ -32,6 +32,10 @@ public class Drafting {
@NotBlank(message = "草稿图名称不能为空", groups = {Creation.class, SaveAs.class}) @NotBlank(message = "草稿图名称不能为空", groups = {Creation.class, SaveAs.class})
private String name; private String name;
@Schema(description = "草稿图类型", example = "Line/LineNetwork")
@NotBlank(message = "草稿图类型不能为空", groups = {Creation.class})
private String type;
@Schema(description = "绘图数据") @Schema(description = "绘图数据")
@NotNull(message = "数据不能为空", groups = {SaveData.class, SaveAs.class}) @NotNull(message = "数据不能为空", groups = {SaveData.class, SaveAs.class})
private byte[] proto; private byte[] proto;

View File

@ -21,7 +21,7 @@ import lombok.experimental.Accessors;
@Setter @Setter
@Accessors(chain = true) @Accessors(chain = true)
@TableName("line_info") @TableName("line_info")
@Schema(name = "LineInfo", description = "$!{table.comment}") @Schema(name = "LineInfo", description = "线路信息数据")
public class LineInfo { public class LineInfo {
@Schema(description = "id") @Schema(description = "id")

View File

@ -0,0 +1,59 @@
package club.joylink.xiannccda.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import java.time.LocalDateTime;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
/**
* <p>
*
* </p>
*
* @author walker-sheng
* @since 2023-06-08
*/
@Getter
@Setter
@Accessors(chain = true)
@TableName("published_gi")
@Schema(name = "PublishedGi", description = "发布的图形界面数据")
public class PublishedGi {
@Schema(description = "id")
private Integer id;
@Schema(description = "发布图形界面名称")
private String name;
@Schema(description = "图形界面类型")
private String type;
@Schema(description = "关联的线路号line_info表中line_id字段")
private Integer lineId;
@Schema(description = "图形界面数据")
private byte[] proto;
@Schema(description = "发布用户id")
private Integer userId;
@Schema(description = "发布时间")
private LocalDateTime publishAt;
public static final String ID = "id";
public static final String NAME = "name";
public static final String TYPE = "type";
public static final String LINE_ID = "line_id";
public static final String PROTO = "proto";
public static final String USER_ID = "user_id";
public static final String PUBLISH_AT = "publish_at";
}

View File

@ -0,0 +1,18 @@
package club.joylink.xiannccda.mapper;
import club.joylink.xiannccda.entity.PublishedGi;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* <p>
* Mapper 接口
* </p>
*
* @author walker-sheng
* @since 2023-06-08
*/
@Mapper
public interface PublishedGiMapper extends BaseMapper<PublishedGi> {
}

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="club.joylink.xiannccda.mapper.PublishedGiMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="club.joylink.xiannccda.entity.PublishedGi">
<id column="id" property="id" />
<result column="name" property="name" />
<result column="type" property="type" />
<result column="line_id" property="lineId" />
<result column="proto" property="proto" />
<result column="user_id" property="userId" />
<result column="publish_at" property="publishAt" />
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, name, type, line_id, proto, user_id, publish_at
</sql>
</mapper>

View File

@ -0,0 +1,16 @@
package club.joylink.xiannccda.repository;
import club.joylink.xiannccda.entity.PublishedGi;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 服务类
* </p>
*
* @author walker-sheng
* @since 2023-06-08
*/
public interface IPublishedGiRepository extends IService<PublishedGi> {
}

View File

@ -0,0 +1,20 @@
package club.joylink.xiannccda.repository.impl;
import club.joylink.xiannccda.entity.PublishedGi;
import club.joylink.xiannccda.mapper.PublishedGiMapper;
import club.joylink.xiannccda.repository.IPublishedGiRepository;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
* 服务实现类
* </p>
*
* @author walker-sheng
* @since 2023-06-08
*/
@Service
public class PublishedGiRepository extends ServiceImpl<PublishedGiMapper, PublishedGi> implements IPublishedGiRepository {
}

View File

@ -8,4 +8,5 @@ spring:
logging: logging:
level: level:
root: "info" root: "info"
file:
path: /usr/local/joylink/logs/xianncc

View File

@ -14,13 +14,12 @@ import io.netty.handler.logging.LoggingHandler;
public class OccServer { public class OccServer {
public static void main(String[] args) { public static void main(String[] args) {
final int PORT = 2603; final int RtPort = 2603;
final int NrtPort = 2703;
// Configure the server. // Configure the server.
//创建两个EventLoopGroup对象 //创建两个EventLoopGroup对象
EventLoopGroup bossGroup = new NioEventLoopGroup(1);//创建boos线程组用于服务端接受客户端的连接 EventLoopGroup bossGroup = new NioEventLoopGroup(1);//创建boos线程组用于服务端接受客户端的连接
EventLoopGroup workerGroup = new NioEventLoopGroup();//创建worker线程组用于进行SocketChannel的数据读写处理业务逻辑 EventLoopGroup workerGroup = new NioEventLoopGroup();//创建worker线程组用于进行SocketChannel的数据读写处理业务逻辑
//创建Handler
try {
//创建ServerBootstrap对象 //创建ServerBootstrap对象
ServerBootstrap b = new ServerBootstrap(); ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)//设置EventLoopGroup b.group(bossGroup, workerGroup)//设置EventLoopGroup
@ -34,20 +33,30 @@ public class OccServer {
p.addLast(new OccHandler()); p.addLast(new OccHandler());
} }
}); });
//创建Handler
try {
// Start the server. // Start the server.
//绑定端口并同步等待成功即启动服务端 //绑定端口并同步等待成功即启动服务端
ChannelFuture f = b.bind(PORT).addListener(listener -> { ChannelFuture f1 = b.bind(RtPort).addListener(listener -> {
if (listener.isSuccess()) { if (listener.isSuccess()) {
System.out.println("OCC测试服务启动..."); System.out.println("OCC测试实时服务启动...");
}
});
//绑定端口并同步等待成功即启动服务端
ChannelFuture f2 = b.bind(NrtPort).addListener(listener -> {
if (listener.isSuccess()) {
System.out.println("OCC测试非实时服务启动...");
} }
}); });
// Wait until the server socket is closed. // Wait until the server socket is closed.
//监听服务端关闭并阻塞等待 //监听服务端关闭并阻塞等待
//这里并不是关闭服务器而是监听服务端关闭 //这里并不是关闭服务器而是监听服务端关闭
f.channel().closeFuture().addListener(listener -> { f1.channel().closeFuture().addListener(listener -> {
System.out.println("OCC测试服务关闭"); System.out.println("OCC测试实时服务关闭");
});
f2.channel().closeFuture().addListener(listener -> {
System.out.println("OCC测试非实时服务关闭");
}).sync(); }).sync();
} catch (Exception e) { } catch (Exception e) {
System.err.println("OCC测试服务异常"); System.err.println("OCC测试服务异常");

View File

@ -0,0 +1,38 @@
package club.joylink.xiannccda.protocal.x;
import club.joylink.xiannccda.ats.message.MessageId;
import club.joylink.xiannccda.ats.message.line3.DateTimeUtil;
import club.joylink.xiannccda.ats.message.line3.DispatcherReportResponse;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.Unpooled;
import io.netty.buffer.UnpooledByteBufAllocator;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.Arrays;
public class TestDispatcherReportResponse {
public static void main(String[]args){
final DispatcherReportResponse response = new DispatcherReportResponse();
final ByteBufAllocator allocator = UnpooledByteBufAllocator.DEFAULT;
final ByteBuf body = allocator.buffer(1024);
body.writeInt((int) (System.currentTimeMillis() / 1000));
body.writeShort(0xff);
body.writeShort(MessageId.DISPATCHER_REPORT.idValue());
body.writeShort(3);
body.writeShort(110);
body.writeShort(2);
body.writeShort(1);
body.writeShort(2);//Count
body.readBytes(DateTimeUtil.convert(LocalDateTime.now()));
//
final byte[]userName = new byte[32];
Arrays.fill(userName, (byte) '\0');
final ByteBuf userNameBuf = Unpooled.wrappedBuffer(userName);
userNameBuf.readBytes("调度员1".getBytes(StandardCharsets.UTF_8));
//
}
}

@ -1 +1 @@
Subproject commit 86de0cff9ee560f0c7e670184d5baccfdb28ee44 Subproject commit 2e001eddeeeaea3aa9c607df72d2196572a717da