Browse Source

feat: websocket 기본 구성

feature/socket
지대한 7 months ago
parent
commit
80836e34ea
  1. 9
      app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/codec/DronEncoder.java
  2. 14
      app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/command/impl/AdsbDronCommandImpl.java
  3. 14
      app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/command/impl/AntosDronCommandImpl.java
  4. 12
      app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/command/impl/SandboxDronCommandImpl.java
  5. 4
      app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/config/AppReadyEvent.java
  6. 20
      app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/config/NettyConfig.java
  7. 8
      app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/handler/DronHandler.java
  8. 6
      app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/socket/TcpChannelInitializer.java
  9. 2
      app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/socket/TcpSocketServer.java
  10. 11
      app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/storage/AuthKeyStorage.java
  11. 12
      app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/storage/ControlStorage.java
  12. 15
      app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/storage/DronStorage.java
  13. 13
      app/kac-socket-app/src/main/resources/application.yml
  14. 8
      app/kac-websocket-app/build.gradle
  15. 14
      app/kac-websocket-app/src/main/java/kr/co/palnet/kac/websocket/KacWebSocketApplication.java
  16. 37
      app/kac-websocket-app/src/main/java/kr/co/palnet/kac/websocket/controller/SocketReceiverController.java
  17. 25
      app/kac-websocket-app/src/main/java/kr/co/palnet/kac/websocket/core/codec/Decoder.java
  18. 26
      app/kac-websocket-app/src/main/java/kr/co/palnet/kac/websocket/core/codec/Encoder.java
  19. 22
      app/kac-websocket-app/src/main/java/kr/co/palnet/kac/websocket/core/config/AppReadyEvent.java
  20. 80
      app/kac-websocket-app/src/main/java/kr/co/palnet/kac/websocket/core/config/NettyConfig.java
  21. 42
      app/kac-websocket-app/src/main/java/kr/co/palnet/kac/websocket/core/handler/WebSocketHandler.java
  22. 86
      app/kac-websocket-app/src/main/java/kr/co/palnet/kac/websocket/core/model/ControlDTO.java
  23. 96
      app/kac-websocket-app/src/main/java/kr/co/palnet/kac/websocket/core/model/DronDTO.java
  24. 20
      app/kac-websocket-app/src/main/java/kr/co/palnet/kac/websocket/core/model/DronHistoryDTO.java
  25. 41
      app/kac-websocket-app/src/main/java/kr/co/palnet/kac/websocket/core/socket/DronChannelInitializer.java
  26. 45
      app/kac-websocket-app/src/main/java/kr/co/palnet/kac/websocket/core/socket/WebSocketServer.java
  27. 41
      app/kac-websocket-app/src/main/java/kr/co/palnet/kac/websocket/core/storage/ChannelStorage.java
  28. 83
      app/kac-websocket-app/src/main/java/kr/co/palnet/kac/websocket/core/storage/ControlStorage.java
  29. 10
      app/kac-websocket-app/src/main/java/kr/co/palnet/kac/websocket/core/util/DronUtil.java
  30. 113
      app/kac-websocket-app/src/main/java/kr/co/palnet/kac/websocket/service/ControlService.java
  31. 57
      app/kac-websocket-app/src/main/java/kr/co/palnet/kac/websocket/service/ScheduledService.java
  32. 30
      app/kac-websocket-app/src/main/resources/application.yml

9
app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/codec/DronEncoder.java

@ -3,7 +3,6 @@ package kr.co.palnet.kac.socket.core.codec;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageEncoder;
import kr.co.palnet.kac.socket.core.model.DronRS;
import kr.co.palnet.kac.util.ObjectMapperUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -12,16 +11,16 @@ import java.util.List;
@Slf4j
@RequiredArgsConstructor
public class DronEncoder extends MessageToMessageEncoder<DronRS> {
public class DronEncoder extends MessageToMessageEncoder<Object> {
private final ObjectMapper objectMapper = ObjectMapperUtils.getObjectMapper();
@Override
protected void encode(ChannelHandlerContext ctx, DronRS msg, List<Object> out) throws Exception {
protected void encode(ChannelHandlerContext ctx, Object msg, List<Object> out) throws Exception {
log.info(">>>>> encode <<<");
try {
String json = objectMapper.writeValueAsString(msg);
out.add(json);
// String json = objectMapper.writeValueAsString(msg);
// out.add(json);
} catch (Exception e) {
log.warn("json parsing error : {} :: {}", e.getMessage(), msg);
}

14
app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/command/impl/AdsbDronCommandImpl.java

@ -5,8 +5,8 @@ import kr.co.palnet.kac.socket.core.model.DronDTO;
import kr.co.palnet.kac.socket.core.model.DronHistoryDTO;
import kr.co.palnet.kac.socket.core.model.DronRQ;
import kr.co.palnet.kac.socket.core.command.DronCommand;
import kr.co.palnet.kac.socket.core.util.ControlCacheUtil;
import kr.co.palnet.kac.socket.core.util.DronCacheUtil;
import kr.co.palnet.kac.socket.core.storage.ControlStorage;
import kr.co.palnet.kac.socket.core.storage.DronStorage;
import kr.co.palnet.kac.socket.core.util.DronUtil;
import kr.co.palnet.kac.socket.service.KacAppService;
import kr.co.palnet.kac.socket.service.WebSocketService;
@ -83,7 +83,7 @@ public class AdsbDronCommandImpl implements DronCommand {
// STEP 1. 전에 내부 메모리에서 controlId 조회
ControlCacheUtil controlCacheUtil = ControlCacheUtil.getInstance();
ControlStorage controlCacheUtil = ControlStorage.getInstance();
ControlDto dronCacheDTO = controlCacheUtil.get(dron.getObjectId());
if (dronCacheDTO == null) {
@ -119,7 +119,7 @@ public class AdsbDronCommandImpl implements DronCommand {
controlDto.setRegTime(System.currentTimeMillis());
ControlDto newDronCacheDTO = ControlDto.builder().build();
controlCacheUtil.set(dron.getObjectId(), newDronCacheDTO);
controlCacheUtil.put(dron.getObjectId(), newDronCacheDTO);
} catch (IOException e) {
log.error("요청한 URL 정보가 잘못되었습니다.", e.getMessage());
@ -134,13 +134,13 @@ public class AdsbDronCommandImpl implements DronCommand {
dron.setControlWarnCd(dronCacheDTO.isControlWarnCd());
dronCacheDTO.setRegTime(System.currentTimeMillis());
controlCacheUtil.set(dron.getObjectId(), dronCacheDTO);
controlCacheUtil.put(dron.getObjectId(), dronCacheDTO);
}
// STEP 2. 이력 생성할 전문 전달 -> DRON의 대한 식별정보만 이력 관리
try {
// 저장 해 놓았다가 한거번에 전송
DronCacheUtil dronCacheUtil = DronCacheUtil.getInstance();
dronCacheUtil.set(dron);
DronStorage dronCacheUtil = DronStorage.getInstance();
dronCacheUtil.add(dron);
} catch (Exception e) {
log.error("ERROR : {}\n{}", e.getMessage(), e.getStackTrace());
}

14
app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/command/impl/AntosDronCommandImpl.java

@ -5,8 +5,8 @@ import kr.co.palnet.kac.socket.core.model.DronDTO;
import kr.co.palnet.kac.socket.core.model.DronHistoryDTO;
import kr.co.palnet.kac.socket.core.model.DronRQ;
import kr.co.palnet.kac.socket.core.command.DronCommand;
import kr.co.palnet.kac.socket.core.util.ControlCacheUtil;
import kr.co.palnet.kac.socket.core.util.DronCacheUtil;
import kr.co.palnet.kac.socket.core.storage.ControlStorage;
import kr.co.palnet.kac.socket.core.storage.DronStorage;
import kr.co.palnet.kac.socket.core.util.DronUtil;
import kr.co.palnet.kac.socket.service.KacAppService;
import kr.co.palnet.kac.socket.service.WebSocketService;
@ -82,7 +82,7 @@ public class AntosDronCommandImpl implements DronCommand {
// STEP 1. 전에 내부 메모리에서 controlId 조회
ControlCacheUtil controlCacheUtil = ControlCacheUtil.getInstance();
ControlStorage controlCacheUtil = ControlStorage.getInstance();
ControlDto dronCacheDTO = controlCacheUtil.get(dron.getObjectId());
if (dronCacheDTO == null) {
@ -117,7 +117,7 @@ public class AntosDronCommandImpl implements DronCommand {
controlDto.setRegTime(System.currentTimeMillis());
ControlDto newDronCacheDTO = ControlDto.builder().build();
controlCacheUtil.set(dron.getObjectId(), newDronCacheDTO);
controlCacheUtil.put(dron.getObjectId(), newDronCacheDTO);
} catch (IOException e) {
log.error("요청한 URL 정보가 잘못되었습니다.", e.getMessage());
@ -132,13 +132,13 @@ public class AntosDronCommandImpl implements DronCommand {
dron.setControlWarnCd(dronCacheDTO.isControlWarnCd());
dronCacheDTO.setRegTime(System.currentTimeMillis());
controlCacheUtil.set(dron.getObjectId(), dronCacheDTO);
controlCacheUtil.put(dron.getObjectId(), dronCacheDTO);
}
// STEP 2. 이력 생성할 전문 전달 -> DRON의 대한 식별정보만 이력 관리
try {
// 저장 해 놓았다가 한거번에 전송
DronCacheUtil dronCacheUtil = DronCacheUtil.getInstance();
dronCacheUtil.set(dron);
DronStorage dronCacheUtil = DronStorage.getInstance();
dronCacheUtil.add(dron);
} catch (Exception e) {
log.error("ERROR : {}\n{}", e.getMessage(), e.getStackTrace());
}

12
app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/command/impl/SandboxDronCommandImpl.java

@ -5,8 +5,8 @@ import kr.co.palnet.kac.socket.core.model.ControlDto;
import kr.co.palnet.kac.socket.core.model.DronDTO;
import kr.co.palnet.kac.socket.core.model.DronHistoryDTO;
import kr.co.palnet.kac.socket.core.model.DronRQ;
import kr.co.palnet.kac.socket.core.util.ControlCacheUtil;
import kr.co.palnet.kac.socket.core.util.DronCacheUtil;
import kr.co.palnet.kac.socket.core.storage.ControlStorage;
import kr.co.palnet.kac.socket.core.storage.DronStorage;
import kr.co.palnet.kac.socket.core.util.DronUtil;
import kr.co.palnet.kac.socket.service.KacAppService;
import kr.co.palnet.kac.socket.service.WebSocketService;
@ -85,7 +85,7 @@ public class SandboxDronCommandImpl implements DronCommand {
Long start = System.currentTimeMillis();
// STEP 1. 전에 내부 메모리에서 controlId 조회
ControlCacheUtil controlCacheUtil = ControlCacheUtil.getInstance();
ControlStorage controlCacheUtil = ControlStorage.getInstance();
ControlDto dronCacheDTO = controlCacheUtil.get(dron.getObjectId());
if (dronCacheDTO == null) {
@ -121,7 +121,7 @@ public class SandboxDronCommandImpl implements DronCommand {
controlDto.setRegTime(System.currentTimeMillis());
ControlDto newDronCacheDTO = ControlDto.builder().build();
controlCacheUtil.set(dron.getObjectId(), newDronCacheDTO);
controlCacheUtil.put(dron.getObjectId(), newDronCacheDTO);
} catch (IOException e) {
log.error("요청한 URL 정보가 잘못되었습니다.", e.getMessage());
@ -136,7 +136,7 @@ public class SandboxDronCommandImpl implements DronCommand {
dron.setControlWarnCd(dronCacheDTO.isControlWarnCd());
dronCacheDTO.setRegTime(System.currentTimeMillis());
controlCacheUtil.set(dron.getObjectId(), dronCacheDTO);
controlCacheUtil.put(dron.getObjectId(), dronCacheDTO);
}
@ -144,7 +144,7 @@ public class SandboxDronCommandImpl implements DronCommand {
// STEP 2. 이력 생성할 전문 전달 -> DRON의 대한 식별정보만 이력 관리
try {
// 저장 해 놓았다가 한거번에 전송
DronCacheUtil dronCacheUtil = DronCacheUtil.getInstance();
DronStorage dronCacheUtil = DronStorage.getInstance();
// dronCacheUtil.set(dron);
} catch (Exception e) {
log.error("ERROR : {}\n{}", e.getMessage(), e.getStackTrace());

4
app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/config/AppReadyEvent.java

@ -1,6 +1,6 @@
package kr.co.palnet.kac.socket.core.config;
import kr.co.palnet.kac.socket.core.socket.DronServerSocket;
import kr.co.palnet.kac.socket.core.socket.TcpSocketServer;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent;
@ -12,7 +12,7 @@ import org.springframework.stereotype.Component;
@Component
public class AppReadyEvent implements ApplicationListener<ApplicationReadyEvent> {
private final DronServerSocket socketServer;
private final TcpSocketServer socketServer;
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {

20
app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/config/NettyConfig.java

@ -6,7 +6,7 @@ import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import kr.co.palnet.kac.socket.core.socket.DronChannelInitializer;
import kr.co.palnet.kac.socket.core.socket.TcpChannelInitializer;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
@ -23,15 +23,15 @@ public class NettyConfig {
private int port;
@Value("${netty.socket.boss-count}")
private int bossCount;
@Value("${netty.socket.worker-count}")
private int workerCount;
@Value("${netty.socket.keep-alive}")
private boolean keepAlive;
@Value("${netty.socket.tcp-nodelay}")
private boolean tcpNodelay;
@Value("${netty.socket.backlog}")
private int backlog;
@Bean
public ServerBootstrap serverBootstrap(DronChannelInitializer nettyChannelInitializer) {
public ServerBootstrap serverBootstrap(TcpChannelInitializer channelInitializer) {
log.info(">>>>> serverBootstrap <<<<<");
// ServerBootstrap: 서버 설정을 도와주는 class
ServerBootstrap b = new ServerBootstrap();
@ -40,12 +40,18 @@ public class NettyConfig {
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.DEBUG))
// ChannelInitializer: 새로운 Channel을 구성할 때 사용되는 특별한 handler. 주로 ChannelPipeline으로 구성
.childHandler(nettyChannelInitializer);
.childHandler(channelInitializer);
// ServerBootstarp에 다양한 Option 추가 가능
// SO_BACKLOG: 동시에 수용 가능한 최대 incoming connections 개수
// 이 외에도 SO_KEEPALIVE, TCP_NODELAY 등 옵션 제공
b.option(ChannelOption.SO_BACKLOG, backlog);
b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000);
b.option(ChannelOption.SO_BACKLOG, backlog); // 동시에 수용할 수 있는 소켓 연결 요청 수입니다.
b.childOption(ChannelOption.TCP_NODELAY, tcpNodelay); // 반응속도를 높이기 위해 Nagle 알고리즘을 비활성화 합니다
b.childOption(ChannelOption.SO_LINGER, 0); // 소켓이 close될 때 신뢰성있는 종료를 위해 4way-handshake가 발생하고 이때 TIME_WAIT로 리소스가 낭비됩니다. 이를 방지하기 위해 0으로 설정합니다.
b.childOption(ChannelOption.SO_KEEPALIVE, keepAlive); // Keep-alive를 켭니다.
b.childOption(ChannelOption.SO_REUSEADDR, false); // SO_LINGER설정이 있으면 안해도 되나 혹시나병(!)으로 TIME_WAIT걸린 포트를 재사용할 수 있도록 설정합니다.
return b;
}
@ -61,7 +67,7 @@ public class NettyConfig {
@Bean(destroyMethod = "shutdownGracefully")
public NioEventLoopGroup workerGroup() {
log.info(">>>>> workerGroup <<<<<");
return new NioEventLoopGroup(workerCount);
return new NioEventLoopGroup();
}
// IP 소켓 주소(IP 주소, Port 번호)를 구현

8
app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/handler/DronSocketHandler.java → app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/handler/DronHandler.java

@ -6,7 +6,7 @@ import io.netty.channel.SimpleChannelInboundHandler;
import kr.co.palnet.kac.socket.core.command.DronCommand;
import kr.co.palnet.kac.socket.core.model.DronRQ;
import kr.co.palnet.kac.socket.core.model.DronRS;
import kr.co.palnet.kac.socket.core.util.AuthKeyUtil;
import kr.co.palnet.kac.socket.core.storage.AuthKeyStorage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@ -17,12 +17,12 @@ import java.util.Map;
@Slf4j
@ChannelHandler.Sharable
@Component
public class DronSocketHandler extends SimpleChannelInboundHandler<DronRQ> {
public class DronHandler extends SimpleChannelInboundHandler<DronRQ> {
private final Map<String, DronCommand> commandMap;
public DronSocketHandler(List<DronCommand> commandList) {
public DronHandler(List<DronCommand> commandList) {
commandMap = new HashMap<>();
for (DronCommand command : commandList) {
commandMap.put(command.getCommandName(), command);
@ -58,7 +58,7 @@ public class DronSocketHandler extends SimpleChannelInboundHandler<DronRQ> {
return;
}
if (AuthKeyUtil.getInstance().checkAuthKey(rq.getCommand())) {
if (AuthKeyStorage.getInstance().checkAuthKey(rq.getCommand())) {
ctx.writeAndFlush(DronRS.builder()
.code("-2000")
.message("Invalid auth key.")

6
app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/socket/DronChannelInitializer.java → app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/socket/TcpChannelInitializer.java

@ -9,7 +9,7 @@ import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
import kr.co.palnet.kac.socket.core.codec.DronDecoder;
import kr.co.palnet.kac.socket.core.codec.DronEncoder;
import kr.co.palnet.kac.socket.core.handler.DronSocketHandler;
import kr.co.palnet.kac.socket.core.handler.DronHandler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@ -17,9 +17,9 @@ import org.springframework.stereotype.Component;
@Slf4j
@RequiredArgsConstructor
@Component
public class DronChannelInitializer extends ChannelInitializer<SocketChannel> {
public class TcpChannelInitializer extends ChannelInitializer<SocketChannel> {
private final DronSocketHandler dronSocketHandler;
private final DronHandler dronSocketHandler;
// 클라이언트 소켓 채널이 생성될 때 호출
@Override

2
app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/socket/DronServerSocket.java → app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/socket/TcpSocketServer.java

@ -13,7 +13,7 @@ import java.net.InetSocketAddress;
@Slf4j
@RequiredArgsConstructor
@Component
public class DronServerSocket {
public class TcpSocketServer {
private final ServerBootstrap serverBootstrap;
private final InetSocketAddress tcpPort;
private Channel serverChannel;

11
app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/util/AuthKeyUtil.java → app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/storage/AuthKeyStorage.java

@ -1,27 +1,26 @@
package kr.co.palnet.kac.socket.core.util;
package kr.co.palnet.kac.socket.core.storage;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import java.util.HashSet;
import java.util.Set;
@Slf4j
public class AuthKeyUtil {
public class AuthKeyStorage {
private Set<String> keys = new HashSet<>();
private AuthKeyUtil() {
private AuthKeyStorage() {
log.debug("AuthKeyUtil : init keys");
initKey();
}
public static AuthKeyUtil getInstance() {
public static AuthKeyStorage getInstance() {
return LazyHolder.INSTANCE;
}
public static class LazyHolder {
private static final AuthKeyUtil INSTANCE = new AuthKeyUtil();
private static final AuthKeyStorage INSTANCE = new AuthKeyStorage();
}
private void initKey() {

12
app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/util/ControlCacheUtil.java → app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/storage/ControlStorage.java

@ -1,4 +1,4 @@
package kr.co.palnet.kac.socket.core.util;
package kr.co.palnet.kac.socket.core.storage;
import kr.co.palnet.kac.socket.core.model.ControlDto;
import lombok.extern.slf4j.Slf4j;
@ -8,29 +8,29 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
public class ControlCacheUtil {
public class ControlStorage {
private final Map<String, ControlDto> controlMap;
private final int REMOVE_TIME = 1000 * 10;
private ControlCacheUtil() {
private ControlStorage() {
controlMap = new ConcurrentHashMap<>();
}
public static ControlCacheUtil getInstance() {
public static ControlStorage getInstance() {
return LazyHolder.INSTANCE;
}
public static class LazyHolder {
private static final ControlCacheUtil INSTANCE = new ControlCacheUtil();
private static final ControlStorage INSTANCE = new ControlStorage();
}
public ControlDto get(String objectId) {
return controlMap.get(objectId);
}
public ControlDto set(String objectId, ControlDto control) {
public ControlDto put(String objectId, ControlDto control) {
return controlMap.put(objectId, control);
}

15
app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/util/DronCacheUtil.java → app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/storage/DronStorage.java

@ -1,34 +1,31 @@
package kr.co.palnet.kac.socket.core.util;
package kr.co.palnet.kac.socket.core.storage;
import kr.co.palnet.kac.socket.core.model.ControlDto;
import kr.co.palnet.kac.socket.core.model.DronDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
public class DronCacheUtil {
public class DronStorage {
private final Map<String, List<DronDTO>> dronMap;
private final int REMOVE_TIME = 1000 * 10;
private DronCacheUtil() {
private DronStorage() {
dronMap = new ConcurrentHashMap<>();
}
public static DronCacheUtil getInstance() {
public static DronStorage getInstance() {
return LazyHolder.INSTANCE;
}
public static class LazyHolder {
private static final DronCacheUtil INSTANCE = new DronCacheUtil();
private static final DronStorage INSTANCE = new DronStorage();
}
public Map<String, List<DronDTO>> getAll() {
@ -48,7 +45,7 @@ public class DronCacheUtil {
return dronMap.get(objectId);
}
public void set(DronDTO dron) {
public void add(DronDTO dron) {
if (dron == null || dron.getObjectId() == null || dron.getObjectId().isEmpty()) {
return;
}

13
app/kac-socket-app/src/main/resources/application.yml

@ -1,10 +1,10 @@
netty:
socket:
tcp-port: 8200
tcp-port: 8003
boss-count: 1
worker-count: 10
keep-alive: false
backlog: 100
tcp-nodelay: false
backlog: 3000
app:
kac-app:
@ -12,6 +12,13 @@ app:
web-socket:
host: http://127.0.0.1:8100
server:
port: 8004
spring:
threads:
virtual:
enabled: true
---

8
app/kac-websocket-app/build.gradle

@ -0,0 +1,8 @@
dependencies {
implementation "$boot:spring-boot-starter-web"
implementation "io.netty:netty-all:4.1.68.Final"
implementation project(":common:util")
}

14
app/kac-websocket-app/src/main/java/kr/co/palnet/kac/websocket/KacWebSocketApplication.java

@ -0,0 +1,14 @@
package kr.co.palnet.kac.websocket;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@EnableScheduling
@SpringBootApplication
public class KacWebSocketApplication {
public static void main(String[] args) {
SpringApplication.run(KacWebSocketApplication.class, args);
}
}

37
app/kac-websocket-app/src/main/java/kr/co/palnet/kac/websocket/controller/SocketReceiverController.java

@ -0,0 +1,37 @@
package kr.co.palnet.kac.websocket.controller;
import kr.co.palnet.kac.websocket.core.model.ControlDTO;
import kr.co.palnet.kac.websocket.core.model.DronDTO;
import kr.co.palnet.kac.websocket.core.storage.ControlStorage;
import kr.co.palnet.kac.websocket.service.ControlService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping("/v1/api/ws")
public class SocketReceiverController {
private final ControlService controlService;
@PostMapping("/dron")
public ResponseEntity<Void> receiver(@RequestBody DronDTO dronDTO) {
log.info("websocket message receiver : {}", dronDTO);
ControlDTO history = controlService.dronDtoToControlDtoConvert(dronDTO);
// DRON의 대한 식별정보만 이력 관리
ControlStorage controlCache = ControlStorage.getInstance();
controlCache.put(history);
return ResponseEntity.ok().build();
}
}

25
app/kac-websocket-app/src/main/java/kr/co/palnet/kac/websocket/core/codec/Decoder.java

@ -0,0 +1,25 @@
package kr.co.palnet.kac.websocket.core.codec;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
@Slf4j
@RequiredArgsConstructor
public class Decoder extends MessageToMessageDecoder<Object> {
// private int DATA_LENGTH = 100;
// private final ObjectMapper objectMapper = ObjectMapperUtils.getObjectMapper();
@Override
protected void decode(ChannelHandlerContext ctx, Object in, List<Object> out) throws Exception {
log.info(">>>>> decode <<<<<");
try {
} catch (Exception e) {
log.warn("decode parsing error : {} :: {}", e.getMessage(), in);
}
}
}

26
app/kac-websocket-app/src/main/java/kr/co/palnet/kac/websocket/core/codec/Encoder.java

@ -0,0 +1,26 @@
package kr.co.palnet.kac.websocket.core.codec;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageEncoder;
import kr.co.palnet.kac.util.ObjectMapperUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
@Slf4j
@RequiredArgsConstructor
public class Encoder extends MessageToMessageEncoder<Object> {
private final ObjectMapper objectMapper = ObjectMapperUtils.getObjectMapper();
@Override
protected void encode(ChannelHandlerContext ctx, Object msg, List<Object> out) throws Exception {
log.info(">>>>> encode <<<");
try {
} catch (Exception e) {
log.warn("json parsing error : {} :: {}", e.getMessage(), msg);
}
}
}

22
app/kac-websocket-app/src/main/java/kr/co/palnet/kac/websocket/core/config/AppReadyEvent.java

@ -0,0 +1,22 @@
package kr.co.palnet.kac.websocket.core.config;
import kr.co.palnet.kac.websocket.core.socket.WebSocketServer;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
@Slf4j
@RequiredArgsConstructor
@Component
public class AppReadyEvent implements ApplicationListener<ApplicationReadyEvent> {
private final WebSocketServer socketServer;
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
log.info(">>>> onApplicationEvent <<<<<");
socketServer.start();
}
}

80
app/kac-websocket-app/src/main/java/kr/co/palnet/kac/websocket/core/config/NettyConfig.java

@ -0,0 +1,80 @@
package kr.co.palnet.kac.websocket.core.config;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import kr.co.palnet.kac.websocket.core.socket.DronChannelInitializer;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.net.InetSocketAddress;
@Slf4j
@RequiredArgsConstructor
@Configuration
public class NettyConfig {
@Value("${netty.socket.tcp-port}")
private int port;
@Value("${netty.socket.boss-count}")
private int bossCount;
@Value("${netty.socket.keep-alive}")
private boolean keepAlive;
@Value("${netty.socket.tcp-nodelay}")
private boolean tcpNodelay;
@Value("${netty.socket.backlog}")
private int backlog;
@Bean
public ServerBootstrap serverBootstrap(DronChannelInitializer channelInitializer) {
log.info(">>>>> serverBootstrap <<<<<");
// ServerBootstrap: 서버 설정을 도와주는 class
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup(), workerGroup());
// NioServerSocketChannel: incoming connections를 수락하기 위해 새로운 Channel을 객체화할 때 사용
b.channel(NioServerSocketChannel.class);
b.handler(new LoggingHandler(LogLevel.DEBUG));
// ChannelInitializer: 새로운 Channel을 구성할 때 사용되는 특별한 handler. 주로 ChannelPipeline으로 구성
b.childHandler(channelInitializer);
// ServerBootstarp에 다양한 Option 추가 가능
// SO_BACKLOG: 동시에 수용 가능한 최대 incoming connections 개수
// 이 외에도 SO_KEEPALIVE, TCP_NODELAY 등 옵션 제공
b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000);
b.option(ChannelOption.SO_BACKLOG, backlog);
b.childOption(ChannelOption.TCP_NODELAY, tcpNodelay); // 반응속도를 높이기 위해 Nagle 알고리즘을 비활성화 합니다
b.childOption(ChannelOption.SO_LINGER, 0); // 소켓이 close될 때 신뢰성있는 종료를 위해 4way-handshake가 발생하고 이때 TIME_WAIT로 리소스가 낭비됩니다. 이를 방지하기 위해 0으로 설정합니다.
b.childOption(ChannelOption.SO_KEEPALIVE, keepAlive); // Keep-alive를 켭니다.
b.childOption(ChannelOption.SO_REUSEADDR, true); // SO_LINGER설정이 있으면 안해도 되나 혹시나병(!)으로 TIME_WAIT걸린 포트를 재사용할 수 있도록 설정합니다.
return b;
}
// boss: incoming connection을 수락하고, 수락한 connection을 worker에게 등록(register)
@Bean(destroyMethod = "shutdownGracefully")
public NioEventLoopGroup bossGroup() {
log.info(">>>>> bossGroup <<<<<");
return new NioEventLoopGroup(bossCount);
}
// worker: boss가 수락한 연결의 트래픽 관리
@Bean(destroyMethod = "shutdownGracefully")
public NioEventLoopGroup workerGroup() {
log.info(">>>>> workerGroup <<<<<");
return new NioEventLoopGroup();
}
// IP 소켓 주소(IP 주소, Port 번호)를 구현
// 도메인 이름으로 객체 생성 가능
@Bean
public InetSocketAddress inetSocketAddress() {
log.info(">>>>> inetSocketAddress <<<<<");
return new InetSocketAddress(port);
}
}

42
app/kac-websocket-app/src/main/java/kr/co/palnet/kac/websocket/core/handler/WebSocketHandler.java

@ -0,0 +1,42 @@
package kr.co.palnet.kac.websocket.core.handler;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import kr.co.palnet.kac.websocket.core.storage.ChannelStorage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Slf4j
@ChannelHandler.Sharable
@Component
public class WebSocketHandler extends SimpleChannelInboundHandler<WebSocketFrame> {
public WebSocketHandler() {
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame msg) throws Exception {
log.info(">>>> channelRead0 <<<<<");
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
log.info(">>>>> channelRegistered <<<<<");
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.info(">>>>> channelActive <<<<<");
ChannelStorage channelStorage = ChannelStorage.getInstance();
log.info("active size before : {}", channelStorage.getAll().size());
channelStorage.add(ctx.channel());
log.info("active size after : {}", channelStorage.getAll().size());
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
log.info(">>>>> channelInactive <<<<<");
}
}

86
app/kac-websocket-app/src/main/java/kr/co/palnet/kac/websocket/core/model/ControlDTO.java

@ -0,0 +1,86 @@
package kr.co.palnet.kac.websocket.core.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.Instant;
import java.util.List;
import java.util.Map;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ControlDTO implements Comparable<ControlDTO> {
private String messageTypeCd;
private String controlId;
private String trmnlId;
private String objectTypeCd;
private String objectId;
private Double lat;
private Double lon;
private String elevType;
private Double elev;
private String speedType;
private Double speed;
private Double betteryLevel;
private Double betteryVoltage;
private String takeOffPositon;
private String dronStatus;
private Double heading;
private String terminalRcvDt;
private Instant serverRcvDt;
private Instant controlStartDt;
private Double moveDistance;
private String moveDistanceType;
// 환경센서 필드
private Double sensorCo;
private Double sensorSo2;
private Double sensorNo2;
private Double sensorO3;
private Double sensorDust;
private List<Map<String, Double>> lastHistory;
// 비정상 상황 식별 코드 (비정상: true)
private boolean controlWarnCd;
// 비정상 상황 알림 표출 코드 (알림: true, 미알림: false)
private boolean controlWarnNotyCd;
// 비정상 상황 알림 중복 체크
private Integer controlCacheCount;
// 큐가 Socket서버에 도착한 시간 TODO : 타입 문제로 인한 임시주석
private Instant regDt;
@Override
public int compareTo(ControlDTO o) {
if (o.getControlStartDt() != null && controlStartDt != null) {
return o.getControlStartDt().compareTo(controlStartDt);
}
return 0;
}
}

96
app/kac-websocket-app/src/main/java/kr/co/palnet/kac/websocket/core/model/DronDTO.java

@ -0,0 +1,96 @@
package kr.co.palnet.kac.websocket.core.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.Instant;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class DronDTO {
private String typeCd; // 01 : 최초 들어온 데이터 , 99 : 종료 시킬 데이터
private String messageType;
private String terminalId;
@Builder.Default
private Double moveDistance = 0.0;
private String moveDistanceType;
private String controlId; // 처음 위치 데이터가 들어 왔을때 생성 함
private String objectType;
private String objectId;
@Builder.Default
private Double lat = 0.0;
@Builder.Default
private Double lon = 0.0;
private String elevType;
@Builder.Default
private Double elev = 0.0;
private String speedType;
@Builder.Default
private Double speed = 0.0;
@Builder.Default
private Double betteryLevel = 0.0;
@Builder.Default
private Double betteryVoltage = 0.0;
private String dronStatus;
@Builder.Default
private Double heading = 0.0;
private String terminalRcvDt;
private Instant serverRcvDt;
private Instant controlStartDt;
private Instant controlEndDt;
private String areaTrnsYn;
// 환경센서 필드
@Builder.Default
private Double sensorCo = 0.0;
@Builder.Default
private Double sensorSo2 = 0.0;
@Builder.Default
private Double sensorNo2 = 0.0;
@Builder.Default
private Double sensorO3 = 0.0;
@Builder.Default
private Double sensorDust = 0.0;
//최근 5건만 저장
private List<DronHistoryDTO> recentPositionHistory;
// 전체 히스토리 저장
private List<DronHistoryDTO> postionHistory;
// 비정상 상황 식별 코드
private boolean controlWarnCd;
// 큐가 Socket서버에 도착한 시간
private Instant regDt;
// 큐가 Socket서버에 도착한 시간
private boolean sendUtm; // 불법드론 전송 여부
}

20
app/kac-websocket-app/src/main/java/kr/co/palnet/kac/websocket/core/model/DronHistoryDTO.java

@ -0,0 +1,20 @@
package kr.co.palnet.kac.websocket.core.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class DronHistoryDTO {
private String objectId;
@Builder.Default
private Double lat = 0.0;
@Builder.Default
private Double lon = 0.0;
}

41
app/kac-websocket-app/src/main/java/kr/co/palnet/kac/websocket/core/socket/DronChannelInitializer.java

@ -0,0 +1,41 @@
package kr.co.palnet.kac.websocket.core.socket;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketFrameAggregator;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler;
import io.netty.handler.timeout.IdleStateHandler;
import kr.co.palnet.kac.websocket.core.handler.WebSocketHandler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Slf4j
@RequiredArgsConstructor
@Component
public class DronChannelInitializer extends ChannelInitializer<SocketChannel> {
private final WebSocketHandler webSocketHandler;
// 클라이언트 소켓 채널이 생성될 때 호출
@Override
protected void initChannel(SocketChannel ch) {
log.info(">>>>> initChannel <<<<<");
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new HttpServerCodec());
pipeline.addLast(new HttpObjectAggregator(65536));
pipeline.addLast(new WebSocketServerCompressionHandler());
pipeline.addLast(new WebSocketServerProtocolHandler("/ws", null, true));
pipeline.addLast(new IdleStateHandler(0, 0, 180));
// pipeline.addLast(new DronEncoder(), new DronDecoder());
pipeline.addLast(webSocketHandler);
// pipeline.addLast(new WebSocketHandler());
log.info("pipeline : {}", pipeline);
}
}

45
app/kac-websocket-app/src/main/java/kr/co/palnet/kac/websocket/core/socket/WebSocketServer.java

@ -0,0 +1,45 @@
package kr.co.palnet.kac.websocket.core.socket;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import jakarta.annotation.PreDestroy;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.net.InetSocketAddress;
@Slf4j
@RequiredArgsConstructor
@Component
public class WebSocketServer {
private final ServerBootstrap serverBootstrap;
private final InetSocketAddress tcpPort;
private Channel serverChannel;
public void start() {
log.info(">>>>> start <<<<<");
try {
// ChannelFuture: I/O operation의 결과나 상태를 제공하는 객체
// 지정한 host, port로 소켓을 바인딩하고 incoming connections을 받도록 준비함
ChannelFuture serverChannelFuture = serverBootstrap.bind(tcpPort).sync();
// 서버 소켓이 닫힐 때까지 기다림
serverChannel = serverChannelFuture.channel().closeFuture().sync().channel();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
// Bean을 제거하기 전에 해야할 작업이 있을 때 설정
@PreDestroy
public void stop() {
log.info(">>>>> stop <<<<<");
if (serverChannel != null) {
serverChannel.close();
serverChannel.parent().closeFuture();
}
}
}

41
app/kac-websocket-app/src/main/java/kr/co/palnet/kac/websocket/core/storage/ChannelStorage.java

@ -0,0 +1,41 @@
package kr.co.palnet.kac.websocket.core.storage;
import io.netty.channel.Channel;
import io.netty.channel.ChannelId;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
import org.springframework.stereotype.Component;
@Component
public class ChannelStorage {
private final ChannelGroup channelGroup;
private ChannelStorage() {
channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
}
public static ChannelStorage getInstance() {
return ChannelStorage.LazyHolder.INSTANCE;
}
public static class LazyHolder {
private static final ChannelStorage INSTANCE = new ChannelStorage();
}
public void add(Channel channel) {
channelGroup.add(channel);
}
public Channel get(ChannelId channelId) {
return channelGroup.find(channelId);
}
public ChannelGroup getAll() {
return channelGroup;
}
}

83
app/kac-websocket-app/src/main/java/kr/co/palnet/kac/websocket/core/storage/ControlStorage.java

@ -0,0 +1,83 @@
package kr.co.palnet.kac.websocket.core.storage;
import kr.co.palnet.kac.websocket.core.model.ControlDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
@Slf4j
@Component
public class ControlStorage {
private final int REMOVE_TIME_SECOND = 60; // 화면 노출 시간
private final Map<String, ControlDTO> controlMap;
private ControlStorage() {
controlMap = new ConcurrentHashMap<>();
}
public static ControlStorage getInstance() {
return LazyHolder.INSTANCE;
}
public static class LazyHolder {
private static final ControlStorage INSTANCE = new ControlStorage();
}
public Map<String, ControlDTO> getAll() {
if (controlMap.keySet().isEmpty()) {
return null;
}
return controlMap;
}
public ControlDTO get(String objectId) {
if (objectId == null || objectId.isEmpty()) {
return null;
}
if (controlMap.get(objectId) == null) {
return null;
}
return controlMap.get(objectId);
}
public ControlDTO put(ControlDTO control) {
if (control == null || control.getObjectId() == null || control.getObjectId().isEmpty()) {
return null;
}
return controlMap.put(control.getObjectId(), control);
}
public List<ControlDTO> getList() {
return new ArrayList<>(controlMap.values());
}
public ControlDTO remove(String objectId) {
return controlMap.remove(objectId);
}
// 10초 마다 오랜된 데이터(1분 이상 새로운 데이터가 없는 경우) 제거
@Scheduled(fixedDelay = 1000 * 10)
private void remove() {
// Key 의 존재하는 데이터는 마지막 서버수신 History Data
if (Objects.nonNull(controlMap)) {
controlMap.forEach((objectId, controlDTO) -> {
Instant serverRcvDt = controlDTO.getServerRcvDt();
// Instant serverRcvDt와 now의 차이
long diffSecond = Instant.now().getEpochSecond() - serverRcvDt.getEpochSecond();
if (diffSecond > REMOVE_TIME_SECOND) {
this.remove(objectId);
}
});
}
}
}

10
app/kac-websocket-app/src/main/java/kr/co/palnet/kac/websocket/core/util/DronUtil.java

@ -0,0 +1,10 @@
package kr.co.palnet.kac.websocket.core.util;
public class DronUtil {
public static boolean latlonCheck(double lat, double lon) {
if (lat > 32 && lat < 44 && lon > 124 && lon < 133) {
return true;
}
return false;
}
}

113
app/kac-websocket-app/src/main/java/kr/co/palnet/kac/websocket/service/ControlService.java

@ -0,0 +1,113 @@
package kr.co.palnet.kac.websocket.service;
import kr.co.palnet.kac.websocket.core.model.ControlDTO;
import kr.co.palnet.kac.websocket.core.model.DronDTO;
import kr.co.palnet.kac.websocket.core.storage.ControlStorage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.*;
@Slf4j
@RequiredArgsConstructor
@Service
public class ControlService {
public List<ControlDTO> getList() {
List<ControlDTO> list = new ArrayList<>();
ControlStorage controlCache = ControlStorage.getInstance();
Map<String, ControlDTO> allHistory = controlCache.getAll();
log.info(">>> getList :: {}", allHistory);
if (Objects.nonNull(allHistory)) {
allHistory.forEach((k, v) -> {
/*
int cacheCount = v.getControlCacheCount();
// 데이터가 수신 되지 않고 이전 데이터를 표출하고 있는 경우
if (cacheCount == 1) {
if (v.isControlWarnCd() && v.isControlWarnNotyCd()) {
v.setControlCacheCount(2);
}
}
// 비정상 상황 판별하여 알림 표출 X
if (cacheCount == 2) {
if (v.isControlWarnCd() && v.isControlWarnNotyCd()) {
v.setControlWarnNotyCd(false);
}
}
*/
list.add(v);
});
}
// 기준 : 관제 시작일이 가장 느린순으로 상단에 올린다.
list.sort(Comparator.reverseOrder());
return list;
}
public ControlDTO dronDtoToControlDtoConvert(DronDTO dronDTO) {
ControlStorage controlCache = ControlStorage.getInstance();
ControlDTO prevControlDTO = controlCache.get(dronDTO.getObjectId());
ControlDTO controlDTO = new ControlDTO();
controlDTO.setObjectId(dronDTO.getObjectId());
controlDTO.setControlId(dronDTO.getControlId());
controlDTO.setControlStartDt(dronDTO.getControlStartDt());
controlDTO.setObjectTypeCd(dronDTO.getObjectType());
controlDTO.setLat(dronDTO.getLat());
controlDTO.setLon(dronDTO.getLon());
controlDTO.setElevType(dronDTO.getElevType());
controlDTO.setElev(dronDTO.getElev());
controlDTO.setSpeedType(dronDTO.getSpeedType());
controlDTO.setSpeed(dronDTO.getSpeed());
controlDTO.setBetteryLevel(dronDTO.getBetteryLevel());
controlDTO.setBetteryVoltage(dronDTO.getBetteryVoltage());
controlDTO.setDronStatus(dronDTO.getDronStatus());
controlDTO.setHeading(dronDTO.getHeading());
controlDTO.setMoveDistance(dronDTO.getMoveDistance());
controlDTO.setMoveDistanceType(dronDTO.getMoveDistanceType());
controlDTO.setServerRcvDt(dronDTO.getServerRcvDt());
// 환경 데이터 필드 추가
controlDTO.setSensorCo(dronDTO.getSensorCo());
controlDTO.setSensorSo2(dronDTO.getSensorSo2());
controlDTO.setSensorNo2(dronDTO.getSensorNo2());
controlDTO.setSensorO3(dronDTO.getSensorO3());
controlDTO.setSensorDust(dronDTO.getSensorDust());
// 비정상 상황 식별코드 추가
controlDTO.setControlWarnCd(dronDTO.isControlWarnCd());
if (prevControlDTO == null) {
if (controlDTO.isControlWarnCd()) {
controlDTO.setControlWarnNotyCd(true); // 최초 비정상 발생
}
} else {
if (prevControlDTO.isControlWarnCd() && controlDTO.isControlWarnCd()) {
controlDTO.setControlWarnNotyCd(false); // 비정상 -> 비정상
}
if (prevControlDTO.isControlWarnCd() && !controlDTO.isControlWarnCd()) {
controlDTO.setControlWarnNotyCd(false); // 비정상 -> 정상
}
if (!prevControlDTO.isControlWarnCd() && controlDTO.isControlWarnCd()) {
controlDTO.setControlWarnNotyCd(true); // 정상 -> 비정상상
}
}
controlDTO.setControlCacheCount(1);
controlDTO.setRegDt(dronDTO.getRegDt());
return controlDTO;
}
}

57
app/kac-websocket-app/src/main/java/kr/co/palnet/kac/websocket/service/ScheduledService.java

@ -0,0 +1,57 @@
package kr.co.palnet.kac.websocket.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.netty.channel.group.ChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import kr.co.palnet.kac.util.ObjectMapperUtils;
import kr.co.palnet.kac.websocket.core.model.ControlDTO;
import kr.co.palnet.kac.websocket.core.storage.ChannelStorage;
import kr.co.palnet.kac.websocket.core.storage.ControlStorage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.util.List;
@Slf4j
@RequiredArgsConstructor
@Service
public class ScheduledService {
private final ObjectMapper objectMapper = ObjectMapperUtils.getObjectMapper();
private int count = 0;
private int count2 = 0;
@Scheduled(fixedDelay = 1000)
public void test() {
log.info("test.... : {}", count++);
}
// 접속한 모든 채널에 데이터 전송
@Scheduled(fixedDelay = 10 * 1000) // 10초
public void sendAllChannel() {
log.info("sendAllChannel: {}", count2++);
// 채널 가져오기
ChannelStorage channelStorage = ChannelStorage.getInstance();
ChannelGroup channelGroup = channelStorage.getAll();
// 데이터 가져오기
ControlStorage controlStorage = ControlStorage.getInstance();
List<ControlDTO> controlDtoList = controlStorage.getList();
if (controlDtoList == null || controlDtoList.isEmpty()) return;
try {
String json = objectMapper.writeValueAsString(controlDtoList);
channelGroup.forEach(channel -> {
channel.writeAndFlush(new TextWebSocketFrame(json));
});
} catch (JsonProcessingException e) {
log.warn("send fail to all channel. : json parsing error : {}\n{}", e.getMessage(), e.getStackTrace());
} catch (Exception e) {
log.warn("send fail to all channel. : {}\n{}", e.getMessage(), e.getStackTrace());
}
}
}

30
app/kac-websocket-app/src/main/resources/application.yml

@ -0,0 +1,30 @@
netty:
socket:
tcp-port: 8001
boss-count: 1
keep-alive: true
tcp-nodelay: true
backlog: 3000
app:
kac-app:
host: http://127.0.0.1:8080
spring:
threads:
virtual:
enabled: true
server:
port: 8002
---
spring:
config:
activate:
on-profile: local
Loading…
Cancel
Save