diff --git a/src/main/java/club/joylink/xiannccda/ats/message/FrameHandler.java b/src/main/java/club/joylink/xiannccda/ats/message/FrameHandler.java index 479ba3d..c7a5a9c 100644 --- a/src/main/java/club/joylink/xiannccda/ats/message/FrameHandler.java +++ b/src/main/java/club/joylink/xiannccda/ats/message/FrameHandler.java @@ -15,11 +15,6 @@ public class FrameHandler extends ByteToMessageCodec { this.client = client; } - @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { - super.channelRead(ctx, msg); - } - @Override protected void encode(ChannelHandlerContext channelHandlerContext, FrameSchema frameSchema, ByteBuf byteBuf) throws Exception { diff --git a/src/main/java/club/joylink/xiannccda/ats/message/FrameSchema.java b/src/main/java/club/joylink/xiannccda/ats/message/FrameSchema.java index c22beed..fda8eec 100644 --- a/src/main/java/club/joylink/xiannccda/ats/message/FrameSchema.java +++ b/src/main/java/club/joylink/xiannccda/ats/message/FrameSchema.java @@ -1,6 +1,8 @@ package club.joylink.xiannccda.ats.message; import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import java.util.ArrayList; import java.util.List; public class FrameSchema { @@ -8,11 +10,12 @@ public class FrameSchema { /** * (System ID)占1字节,系统中每台计算机均有唯一的System ID, 此处表示发送方的System ID,为0xff。 */ - short systemId; + short systemId = 0xff; /** * (Total Length) 占2字节,表示每个信息帧中消息的总长度+1字节(Multi-flag)。 最长为1025字节。如果数据长度大于1024字节,剩余的在紧接着的几帧数据中发送。 */ int totalLength; + final static int PackageMaxLength = 1024; /** * (Multi-flag)为帧标识位,占1字节,值为0或者1,分别表示下面的意思: 1:表示在这帧数据中,一个完整的消息没有发送结束,有后续帧; * 0:表示在这帧数据中,消息完整发送,没有后续帧。同样,几个消息可合并成一帧数据发送 @@ -23,5 +26,66 @@ public class FrameSchema { */ ByteBuf packageData; - List messages; + public FrameSchema(ByteBuf buf) { + int packageLength = buf.readableBytes(); + this.packageData = buf; + this.totalLength = packageLength + 1; + if (packageLength == PackageMaxLength) { + this.multiFlag = 1; + } else { + this.multiFlag = 0; + } + } + + public static int calculateMessageLength(List messages) { + int total = 0; + for (MessageData data : messages) { + total += data.total(); + } + return total; + } + + public static List buildFrom(MessageData... messages) { + return buildFrom(List.of(messages)); + } + + public static List buildFrom(List messages) { + final int messageLength = calculateMessageLength(messages); + int size = 1; + if (messageLength > PackageMaxLength) { + size = messageLength / PackageMaxLength + 1; + } + ByteBuf buf = Unpooled.buffer(messageLength); + for (MessageData data : messages) { + data.encode(buf); + } + + List frames = new ArrayList<>(); + for (int i = 1; i <= size; i++) { + FrameSchema frame; + if (i == size) { + frame = new FrameSchema(buf.readBytes(messageLength % PackageMaxLength)); + } else { + frame = new FrameSchema(buf.readBytes(PackageMaxLength)); + } + frames.add(frame); + } + return frames; + } + + public static List decodeFrom(FrameSchema... frames) { + return decodeFrom(List.of(frames)); + } + + public static List decodeFrom(List frames) { + List messages = new ArrayList<>(); + return messages; + } + + public void encode(ByteBuf buf) { + buf.writeByte(this.systemId); + buf.writeShort(this.totalLength); + buf.writeByte(this.multiFlag); + buf.writeBytes(this.packageData); + } } diff --git a/src/main/java/club/joylink/xiannccda/ats/message/MessageData.java b/src/main/java/club/joylink/xiannccda/ats/message/MessageData.java index 32a45e0..cf53506 100644 --- a/src/main/java/club/joylink/xiannccda/ats/message/MessageData.java +++ b/src/main/java/club/joylink/xiannccda/ats/message/MessageData.java @@ -22,6 +22,13 @@ public abstract class MessageData { */ MessageId msgId; + public MessageData(MessageId msgId, int contentLength) { + this.length = 8 + contentLength; + this.time = System.currentTimeMillis(); + this.version = 0x01; + this.msgId = msgId; + } + public void decode(ByteBuf buf) throws Exception { final int headerBytes = 10; int readableBytes = buf.readableBytes(); @@ -44,24 +51,13 @@ public abstract class MessageData { */ public abstract void decode2(ByteBuf buf) throws Exception; - /** - * 消息总长度: 时间戳(Time)长度+ 版本号(Version)长度+消息ID(message _id)长度(2字节)+ 消息内容(content)长度。 - * - * @return - */ - public int messageLength() { - return 8; - } - - public ByteBuf encode() { - final int messageLength = this.messageLength(); - ByteBuf buf = Unpooled.buffer(messageLength + 2); + public void encode(ByteBuf buf) { + final int messageLength = this.length; buf.writeShort(messageLength); buf.writeInt((int) this.time); buf.writeShort(this.version); buf.writeShort(this.msgId.val); this.encode2(buf); - return buf; } /** @@ -72,4 +68,7 @@ public abstract class MessageData { protected void encode2(ByteBuf buf) { } + public int total() { + return this.length + 2; + } } diff --git a/src/main/java/club/joylink/xiannccda/ats/message/OccMessageManage.java b/src/main/java/club/joylink/xiannccda/ats/message/OccMessageManage.java index fe7cf7f..d22166f 100644 --- a/src/main/java/club/joylink/xiannccda/ats/message/OccMessageManage.java +++ b/src/main/java/club/joylink/xiannccda/ats/message/OccMessageManage.java @@ -21,5 +21,9 @@ public class OccMessageManage implements ApplicationRunner { @Override public void run(ApplicationArguments args) throws Exception { // 读取数据配置,创建客户端 + this.registerClient(new XianOccMessagingClient(3, "localhost")); + for (XianOccMessagingClient client : this.clientMap.values()) { + client.connection.connect(); + } } } diff --git a/src/main/java/club/joylink/xiannccda/ats/message/TcpClientConnection.java b/src/main/java/club/joylink/xiannccda/ats/message/TcpClientConnection.java index a575b63..24076dc 100644 --- a/src/main/java/club/joylink/xiannccda/ats/message/TcpClientConnection.java +++ b/src/main/java/club/joylink/xiannccda/ats/message/TcpClientConnection.java @@ -52,9 +52,10 @@ public class TcpClientConnection { channelFuture.channel().closeFuture().sync(); } catch (Exception e) { log.error("与OCC服务连接异常", e); - } finally { - log.info("关闭eventLoop"); - group.shutdownGracefully(); } +// finally { +// log.info("关闭eventLoop"); +// group.shutdownGracefully(); +// } } } diff --git a/src/main/java/club/joylink/xiannccda/ats/message/line3/HeartBeatMsg.java b/src/main/java/club/joylink/xiannccda/ats/message/line3/HeartBeatMsg.java index 03aa466..c0a653f 100644 --- a/src/main/java/club/joylink/xiannccda/ats/message/line3/HeartBeatMsg.java +++ b/src/main/java/club/joylink/xiannccda/ats/message/line3/HeartBeatMsg.java @@ -1,12 +1,17 @@ package club.joylink.xiannccda.ats.message.line3; import club.joylink.xiannccda.ats.message.MessageData; +import club.joylink.xiannccda.ats.message.MessageId; import io.netty.buffer.ByteBuf; public class HeartBeatMsg extends MessageData { + public HeartBeatMsg() { + super(MessageId.MESSAGE_POLLING, 0); + } + @Override public void decode2(ByteBuf buf) throws Exception { - + } } diff --git a/src/main/java/club/joylink/xiannccda/controller/DraftingController.java b/src/main/java/club/joylink/xiannccda/controller/DraftingController.java index a6e74ef..5c9e408 100644 --- a/src/main/java/club/joylink/xiannccda/controller/DraftingController.java +++ b/src/main/java/club/joylink/xiannccda/controller/DraftingController.java @@ -5,7 +5,6 @@ import club.joylink.xiannccda.entity.Drafting; import club.joylink.xiannccda.entity.Drafting.Creation; import club.joylink.xiannccda.repository.IDraftingRepository; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.security.SecurityRequirement; @@ -15,9 +14,9 @@ import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** @@ -55,6 +54,15 @@ public class DraftingController { return this.draftingRepository.create(drafting); } + @PostMapping("/{id}/saveAs") + @SecurityRequirement(name = "jwt") + @Operation(summary = "创建绘图草稿") + @ApiResponse(description = "绘图草稿信息") + public Drafting create(@PathVariable Integer id, + @RequestBody @Validated(Creation.class) Drafting drafting) { + return this.draftingRepository.saveAs(id, drafting); + } + @GetMapping("/{id}") @SecurityRequirement(name = "jwt") @Operation(summary = "获取绘图草稿数据") @@ -63,6 +71,15 @@ public class DraftingController { return this.draftingRepository.getById(id); } + @PutMapping("/{id}") + @SecurityRequirement(name = "jwt") + @Operation(summary = "保存绘图草稿数据") + @ApiResponse(description = "保存成功失败标识") + public boolean updateDrawData(@PathVariable Integer id, + @RequestBody @Validated(Creation.class) Drafting drafting) { + return this.draftingRepository.updateDrawData(id, drafting.getProto()); + } + @DeleteMapping("/{id}") @SecurityRequirement(name = "jwt") @Operation(summary = "删除绘图草稿数据") diff --git a/src/main/java/club/joylink/xiannccda/entity/Drafting.java b/src/main/java/club/joylink/xiannccda/entity/Drafting.java index acd1668..ba52872 100644 --- a/src/main/java/club/joylink/xiannccda/entity/Drafting.java +++ b/src/main/java/club/joylink/xiannccda/entity/Drafting.java @@ -3,6 +3,7 @@ package club.joylink.xiannccda.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import java.time.LocalDateTime; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; @@ -28,10 +29,11 @@ public class Drafting { private Integer id; @Schema(description = "草稿图名称") - @NotBlank(message = "草稿图名称不能为空", groups = {Creation.class}) + @NotBlank(message = "草稿图名称不能为空", groups = {Creation.class, SaveAs.class}) private String name; @Schema(description = "绘图数据") + @NotNull(message = "数据不能为空", groups = {SaveData.class, SaveAs.class}) private byte[] proto; @Schema(description = "创建时间") @@ -53,4 +55,12 @@ public class Drafting { public interface Creation { } + + public interface SaveAs { + + } + + public interface SaveData { + + } } diff --git a/src/main/java/club/joylink/xiannccda/repository/IDraftingRepository.java b/src/main/java/club/joylink/xiannccda/repository/IDraftingRepository.java index 3f45f6c..ba01830 100644 --- a/src/main/java/club/joylink/xiannccda/repository/IDraftingRepository.java +++ b/src/main/java/club/joylink/xiannccda/repository/IDraftingRepository.java @@ -24,4 +24,8 @@ public interface IDraftingRepository extends IService { Page pageQuery(DraftingQueryDto query); Drafting create(Drafting drafting); + + boolean updateDrawData(Integer id, byte[] proto); + + Drafting saveAs(Integer id, Drafting drafting); } diff --git a/src/main/java/club/joylink/xiannccda/repository/impl/DraftingRepository.java b/src/main/java/club/joylink/xiannccda/repository/impl/DraftingRepository.java index 1af49cf..73550cc 100644 --- a/src/main/java/club/joylink/xiannccda/repository/impl/DraftingRepository.java +++ b/src/main/java/club/joylink/xiannccda/repository/impl/DraftingRepository.java @@ -2,6 +2,7 @@ package club.joylink.xiannccda.repository.impl; import club.joylink.xiannccda.dto.DraftingQueryDto; import club.joylink.xiannccda.entity.Drafting; +import club.joylink.xiannccda.exception.BusinessExceptionAssertEnum; import club.joylink.xiannccda.mapper.DraftingMapper; import club.joylink.xiannccda.repository.IDraftingRepository; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; @@ -28,10 +29,9 @@ public class DraftingRepository extends ServiceImpl im @Override public Page pageQuery(DraftingQueryDto query) { - log.info("分页查询参数: {},{},{}", query.getCurrent(), query.getSize(), query.getName()); LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); // 分页不查询具体proto数据 - wrapper.select(info -> !info.getColumn().equals("proto")); + wrapper.select(Drafting.class, info -> !info.getColumn().equals("proto")); if (StringUtils.hasText(query.getName())) { wrapper.like(Drafting::getName, query.getName()); } @@ -45,4 +45,32 @@ public class DraftingRepository extends ServiceImpl im this.save(drafting); return drafting; } + + @Override + public boolean updateDrawData(Integer id, byte[] proto) { + Drafting drafting = new Drafting(); + drafting.setId(id); + drafting.setProto(proto); + drafting.setUpdateAt(LocalDateTime.now()); + return this.updateById(drafting); + } + + @Override + public Drafting saveAs(Integer id, Drafting drafting) { + BusinessExceptionAssertEnum.UNIQUE_FIELD_REPEAT.assertNotTrue( + this.isNameExist(drafting.getName()), + String.format("草稿名称已存在: %s", drafting.getName())); + Drafting old = this.getById(id); + old.setId(null); + old.setName(drafting.getName()); + old.setProto(drafting.getProto()); + this.save(old); + old.setProto(null); // 另存为数据不回传 + return old; + } + + public boolean isNameExist(String name) { + return this.count(Wrappers.lambdaQuery().eq(Drafting::getName, name)) > 0; + } + } diff --git a/src/test/java/club/joylink/xiannccda/occmock/OccHandler.java b/src/test/java/club/joylink/xiannccda/occmock/OccHandler.java new file mode 100644 index 0000000..e8065fe --- /dev/null +++ b/src/test/java/club/joylink/xiannccda/occmock/OccHandler.java @@ -0,0 +1,40 @@ +package club.joylink.xiannccda.occmock; + +import club.joylink.xiannccda.ats.message.FrameSchema; +import club.joylink.xiannccda.ats.message.line3.HeartBeatMsg; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ByteToMessageCodec; +import java.util.List; +import java.util.concurrent.TimeUnit; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class OccHandler extends ByteToMessageCodec { + + + @Override + public void channelRegistered(ChannelHandlerContext ctx) throws Exception { + log.info("客户端连接: {}", ctx.channel().id()); + ctx.executor().scheduleAtFixedRate(() -> { + log.info("定时发送心跳消息"); + List frames = FrameSchema.buildFrom(new HeartBeatMsg()); + for (FrameSchema frame : frames) { + ctx.channel().write(frame); + } + ctx.channel().flush(); + }, 1, 10, TimeUnit.SECONDS); + } + + @Override + protected void encode(ChannelHandlerContext channelHandlerContext, FrameSchema frameSchema, + ByteBuf byteBuf) throws Exception { + frameSchema.encode(byteBuf); + } + + @Override + protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, + List list) throws Exception { + + } +} diff --git a/src/test/java/club/joylink/xiannccda/occmock/OccServer.java b/src/test/java/club/joylink/xiannccda/occmock/OccServer.java index 68eae8a..26ad750 100644 --- a/src/test/java/club/joylink/xiannccda/occmock/OccServer.java +++ b/src/test/java/club/joylink/xiannccda/occmock/OccServer.java @@ -1,5 +1,65 @@ package club.joylink.xiannccda.occmock; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.logging.LogLevel; +import io.netty.handler.logging.LoggingHandler; + public class OccServer { + public static void main(String[] args) { + final int PORT = 2603; + // Configure the server. + //创建两个EventLoopGroup对象 + EventLoopGroup bossGroup = new NioEventLoopGroup(1);//创建boos线程组,用于服务端接受客户端的连接 + EventLoopGroup workerGroup = new NioEventLoopGroup();//创建worker线程组,用于进行SocketChannel的数据读写,处理业务逻辑 + //创建Handler + final OccHandler serverHandler = new OccHandler(); + try { + //创建ServerBootstrap对象 + ServerBootstrap b = new ServerBootstrap(); + b.group(bossGroup, workerGroup)//设置EventLoopGroup + .channel(NioServerSocketChannel.class)//设置要被实例化的NioServerSocketChannel类 +// .option(ChannelOption.SO_BACKLOG, 100) //设置NioServerSocketChannel的可设置项 + .handler(new LoggingHandler(LogLevel.INFO))//设置NioServerSocketChannel的处理器 + .childHandler(new ChannelInitializer() {//设置处理连入的Client的SocketChannel的处理器 + @Override + public void initChannel(SocketChannel ch) throws Exception { + ChannelPipeline p = ch.pipeline(); + p.addLast(serverHandler); + } + }); + + // Start the server. + //绑定端口,并同步等待成功,即启动服务端 + ChannelFuture f = b.bind(PORT).addListener(listener -> { + if (listener.isSuccess()) { + System.out.println("OCC测试服务启动..."); + } + }).sync(); + + // Wait until the server socket is closed. + //监听服务端关闭,并阻塞等待 + //这里并不是关闭服务器,而是“监听”服务端关闭 + f.channel().closeFuture().addListener(listener -> { + System.out.println("OCC测试服务关闭"); + }).sync(); + System.out.println("all after"); + } catch (Exception e) { + System.err.println("OCC测试服务异常"); + e.printStackTrace(); + } finally { + // Shut down all event loops to terminate all threads. + bossGroup.shutdownGracefully(); + workerGroup.shutdownGracefully(); + } + + } + }