Compare commits
96 Commits
master
...
feature/so
Author | SHA1 | Date |
---|---|---|
지대한 | 06ab6ee75f | 6 months ago |
지대한 | 7f0a159b9e | 6 months ago |
지대한 | 3074ca6530 | 6 months ago |
지대한 | f39c5b1403 | 6 months ago |
지대한 | a553d64b7f | 6 months ago |
지대한 | 87cbef018e | 6 months ago |
지대한 | 32b003ec24 | 7 months ago |
지대한 | 8b576a31d1 | 7 months ago |
지대한 | 4c6cf6f540 | 7 months ago |
지대한 | d6a8d6effe | 7 months ago |
지대한 | 09547a55e7 | 7 months ago |
지대한 | 07804766ce | 7 months ago |
지대한 | 264d26d133 | 7 months ago |
지대한 | 71aee790ca | 7 months ago |
지대한 | 658388d917 | 7 months ago |
지대한 | 2d9588eacf | 7 months ago |
지대한 | 8fd0194ec3 | 7 months ago |
지대한 | 6670dfd803 | 7 months ago |
지대한 | e488d3be33 | 7 months ago |
지대한 | 04001446ed | 7 months ago |
지대한 | 9641c5ccf3 | 7 months ago |
지대한 | cd450abd99 | 7 months ago |
지대한 | a7b7ae30a5 | 7 months ago |
지대한 | c8e36fdd42 | 7 months ago |
지대한 | 9981f5d801 | 7 months ago |
지대한 | c36129450b | 7 months ago |
지대한 | 80836e34ea | 7 months ago |
지대한 | 6d5d09842e | 7 months ago |
지대한 | 6733144ba1 | 7 months ago |
지대한 | 7fbed6288a | 7 months ago |
지대한 | 946dc35c24 | 7 months ago |
지대한 | c22d5a1424 | 7 months ago |
지대한 | 05e2c63fec | 7 months ago |
지대한 | 4a787f054c | 7 months ago |
지대한 | 359c96ee8b | 7 months ago |
지대한 | 04b1913753 | 7 months ago |
지대한 | 322103ad9b | 7 months ago |
지대한 | 3d17bec1bb | 7 months ago |
지대한 | e28b1b01ea | 7 months ago |
지대한 | 56b0c72f63 | 7 months ago |
지대한 | 061663c8b5 | 7 months ago |
지대한 | ac6865a15e | 7 months ago |
지대한 | 395972cd3f | 7 months ago |
지대한 | 6c32044d93 | 7 months ago |
지대한 | 67ab2bba16 | 7 months ago |
lkd9125(이경도) | 1463649563 | 7 months ago |
lkd9125(이경도) | d69d14e15a | 8 months ago |
lkd9125(이경도) | b679f86a08 | 8 months ago |
lkd9125(이경도) | 1ccf1140a3 | 8 months ago |
지대한 | 5a974631e9 | 8 months ago |
지대한 | 90802890de | 8 months ago |
lkd9125(이경도) | e63d7a64db | 8 months ago |
qkr7828 | 6b714c0f46 | 8 months ago |
lkd9125(이경도) | 0fcad045a6 | 8 months ago |
lkd9125(이경도) | 17e1affce5 | 8 months ago |
lkd9125(이경도) | 487af94496 | 8 months ago |
lkd9125(이경도) | 562df6b0ce | 8 months ago |
qkr7828 | 536dff756b | 8 months ago |
지대한 | a085711156 | 8 months ago |
지대한 | 36d78d82c5 | 8 months ago |
lkd9125(이경도) | d584d8c0b7 | 8 months ago |
지대한 | c06fe45fe1 | 8 months ago |
지대한 | c9422c1a66 | 8 months ago |
지대한 | 1c27a48858 | 8 months ago |
lkd9125(이경도) | 1d5bed9ce7 | 8 months ago |
지대한 | 6fa8d61898 | 8 months ago |
지대한 | 1fc4f1c93d | 8 months ago |
지대한 | 1274960091 | 8 months ago |
lkd9125 | 27d26daa46 | 8 months ago |
lkd9125(이경도) | e5f77b3de6 | 8 months ago |
lkd9125(이경도) | 95562bf996 | 8 months ago |
지대한 | c736566683 | 8 months ago |
지대한 | 679e50bed0 | 8 months ago |
지대한 | 9806887ee8 | 8 months ago |
지대한 | 0970cba328 | 8 months ago |
박재우 | 59b6625881 | 8 months ago |
지대한 | 41aacc4c14 | 8 months ago |
지대한 | 0276f9b767 | 8 months ago |
지대한 | 7cc65313c4 | 8 months ago |
지대한 | 5b9398b44f | 8 months ago |
지대한 | 56bd7d5142 | 8 months ago |
지대한 | 069406969c | 8 months ago |
지대한 | 40c4e8dcb5 | 8 months ago |
박재우 | 708534570a | 8 months ago |
박재우 | cf07273d30 | 8 months ago |
지대한 | e986ad4848 | 8 months ago |
지대한 | c8c16724bc | 8 months ago |
lkd9125 | 7777f85203 | 8 months ago |
lkd9125(이경도) | 44214a819c | 8 months ago |
지대한 | 087d11efa1 | 8 months ago |
지대한 | 18d1e99b23 | 8 months ago |
지대한 | 4fd51aa4da | 8 months ago |
지대한 | f3edb2286e | 8 months ago |
지대한 | c36929b090 | 8 months ago |
지대한 | 0c0eb8ba30 | 8 months ago |
지대한 | d713a2cb8e | 9 months ago |
249 changed files with 17695 additions and 61 deletions
Binary file not shown.
@ -0,0 +1,61 @@ |
|||||||
|
# Spec |
||||||
|
|
||||||
|
## 환경 구성 |
||||||
|
|
||||||
|
### 기본 정보 |
||||||
|
|
||||||
|
- java 21 |
||||||
|
- spring boot 3.2.1 |
||||||
|
- jpa |
||||||
|
- mysql |
||||||
|
|
||||||
|
|
||||||
|
## 작업전 준비 |
||||||
|
|
||||||
|
### 1. mysql 설치 |
||||||
|
|
||||||
|
``` |
||||||
|
docker-compose up -d database |
||||||
|
``` |
||||||
|
|
||||||
|
### 2. application.yml 설정 변경 |
||||||
|
|
||||||
|
최초 실행시 table 생성하기 위한 작업 |
||||||
|
`ddl-auto: create`로 변경 후 실행한 다음 Table이 생성되었는지 확인하고 원래 설정값으로 변경한다. |
||||||
|
|
||||||
|
```yaml |
||||||
|
spring: |
||||||
|
jpa: |
||||||
|
hibernate: |
||||||
|
ddl-auto: create |
||||||
|
``` |
||||||
|
|
||||||
|
## 설정 정보 |
||||||
|
|
||||||
|
### log4jdbc.log4j2.properties 속성 |
||||||
|
|
||||||
|
| 속성 | 설명 | |
||||||
|
|--------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------| |
||||||
|
| log4jdbc.spylogdelegator.name | 로그4jdbc에서 사용할 로그 델리게이트(Delegate)의 이름을 지정합니다. 이 구성을 통해 로그 델리게이트를 변경할 수 있습니다. 이 코드에서는 SLF4J를 사용하는 net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator가 지정되어 있습니다. | |
||||||
|
| log4jdbc.dump.sql.maxlinelength | SQL문의 최대 길이를 지정합니다. 이 속성은 SQL문을 자동 줄 바꿈하는 데 사용됩니다. 이 코드에서는 0으로 설정되어 있으므로 SQL문의 길이에 제한이 없습니다. | |
||||||
|
| log4jdbc.spylogdelegator.name | 로그4jdbc에서 사용할 로그 델리게이트(Delegate)의 이름을 지정합니다. 이 구성을 통해 로그 델리게이트를 변경할 수 있습니다. 이 코드에서는 SLF4J를 사용하는 net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator가 지정되어 있습니다. | |
||||||
|
| log4jdbc.dump.sql.maxlinelength | SQL문의 최대 길이를 지정합니다. 이 속성은 SQL문을 자동 줄 바꿈하는 데 사용됩니다. 이 코드에서는 0으로 설정되어 있으므로 SQL문의 길이에 제한이 없습니다. | |
||||||
|
| log4jdbc.auto.load.popular.drivers | 자주 사용되는 드라이버를 자동으로 로드할지 여부를 지정합니다. 이 코드에서는 true로 설정되어 있습니다. | |
||||||
|
| log4jdbc.trim.sql.enabled | SQL 문의 앞뒤 공백을 자동으로 제거할지 여부를 지정합니다. 이 코드에서는 true로 설정되어 있습니다. | |
||||||
|
| log4jdbc.trim.sql.extrablanklines | SQL 문의 공백 라인을 제거할지 여부를 지정합니다. 이 코드에서는 false로 설정되어 있습니다. | |
||||||
|
| log4jdbc.suppress.generated.keys.exception | SQL 문에서 생성된 키 예외를 억제할지 여부를 지정합니다. 이 코드에서는 false로 설정되어 있습니다. |
||||||
|
|
||||||
|
|
||||||
|
## spring rest doc |
||||||
|
|
||||||
|
### URL |
||||||
|
|
||||||
|
- uri : `/docs/index.html` |
||||||
|
- local : http://localhost:8080/docs/index.html |
||||||
|
|
||||||
|
## spring swagger |
||||||
|
|
||||||
|
### URL |
||||||
|
|
||||||
|
- uri : `/swagger-ui/index.html` |
||||||
|
- local : http://localhost:8080/swagger-ui/index.html |
Binary file not shown.
Binary file not shown.
@ -0,0 +1,12 @@ |
|||||||
|
FROM openjdk:21 |
||||||
|
|
||||||
|
ENV TZ=Asia/Seoul |
||||||
|
#ENV JAVA_OPTS="-Xms512M -Xmx512M" |
||||||
|
ENV JAVA_OPTS="" |
||||||
|
|
||||||
|
EXPOSE 8000 |
||||||
|
|
||||||
|
WORKDIR /app |
||||||
|
|
||||||
|
#ENTRYPOINT ["java","-jar","/app/kac-app-1.0.0.jar"] |
||||||
|
CMD java $JAVA_OPTS -jar /app/kac-app-1.0.0.jar |
@ -1,9 +1,103 @@ |
|||||||
|
plugins { |
||||||
|
id "org.asciidoctor.jvm.convert" version "3.3.2" |
||||||
|
} |
||||||
|
|
||||||
|
configurations { |
||||||
|
compileOnly { |
||||||
|
extendsFrom annotationProcessor |
||||||
|
} |
||||||
|
asciidoctorExt |
||||||
|
} |
||||||
|
|
||||||
dependencies { |
dependencies { |
||||||
implementation "$boot:spring-boot-starter-web" |
implementation "$boot:spring-boot-starter-web" |
||||||
runtimeOnly 'com.mysql:mysql-connector-j' |
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0' |
||||||
|
// security |
||||||
|
implementation "$boot:spring-boot-starter-security" |
||||||
|
// implementation "com.auth0:java-jwt:4.4.0" |
||||||
|
|
||||||
|
// db |
||||||
|
// runtimeOnly "com.mysql:mysql-connector-j" |
||||||
|
// implementation "org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4:1.16" |
||||||
|
implementation("org.springframework:spring-tx") |
||||||
|
// jpa |
||||||
|
implementation "$boot:spring-boot-starter-data-jpa" |
||||||
|
// querydsl |
||||||
|
// implementation "com.querydsl:querydsl-jpa:5.0.0:jakarta" |
||||||
|
|
||||||
|
|
||||||
|
// rest doc |
||||||
|
asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor' |
||||||
|
|
||||||
|
// test |
||||||
|
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' |
||||||
|
testImplementation 'org.springframework.boot:spring-boot-starter-test' |
||||||
|
|
||||||
|
|
||||||
|
implementation project(":common:core") |
||||||
|
// implementation project(":common:config-db") |
||||||
|
implementation project(":common:util") |
||||||
|
implementation project(":common:model") |
||||||
|
implementation project(":web:security") |
||||||
|
implementation project(":web:api-common") |
||||||
|
implementation project(":data:ctr") |
||||||
|
implementation project(":data:com") |
||||||
|
implementation project(":data:flt") |
||||||
|
|
||||||
|
// implementation project(":data:cns") |
||||||
|
// implementation project(":data:com") |
||||||
|
// implementation project(":data:other") |
||||||
|
// implementation project(":data:pty") |
||||||
|
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-actuator' |
||||||
|
} |
||||||
|
|
||||||
|
ext { |
||||||
|
snippetsDir = file('build/generated-snippets') |
||||||
|
} |
||||||
|
|
||||||
|
tasks { |
||||||
|
def isTest = gradle.startParameter.excludedTaskNames.contains('test') |
||||||
|
|
||||||
|
if (!isTest) { |
||||||
|
asciidoctor { |
||||||
|
dependsOn test |
||||||
|
inputs.dir snippetsDir |
||||||
|
doFirst { |
||||||
|
delete file('src/main/resources/static/docs') |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
test { |
||||||
|
outputs.dir snippetsDir |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
bootJar { |
||||||
|
if (!isTest) { |
||||||
|
dependsOn asciidoctor |
||||||
|
from("${asciidoctor.outputDir}/html5") { |
||||||
|
into 'static/docs' |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
compileOnly project(":data-user") |
task copyDocument(type: Copy) { |
||||||
|
if (!isTest) { |
||||||
|
dependsOn asciidoctor |
||||||
|
from file("build/docs/asciidoc") |
||||||
|
into file("src/main/resources/static/docs") |
||||||
} |
} |
||||||
|
} |
||||||
|
|
||||||
|
build { |
||||||
|
if (!isTest) { |
||||||
|
dependsOn copyDocument |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Binary file not shown.
@ -0,0 +1,175 @@ |
|||||||
|
ifndef::snippets[] |
||||||
|
:snippets: ./build/generated-snippets |
||||||
|
endif::[] |
||||||
|
|
||||||
|
= KAC App Docs |
||||||
|
API 문서 |
||||||
|
:doctype: book |
||||||
|
:icons: font |
||||||
|
:source-highlighter: highlightjs |
||||||
|
:toc: left |
||||||
|
:toclevels: 2 |
||||||
|
:sectlinks: |
||||||
|
|
||||||
|
[[Common-Code-API]] |
||||||
|
== Common-Code API |
||||||
|
|
||||||
|
[[Code-All]] |
||||||
|
=== [Code 전체코드 조회] |
||||||
|
|
||||||
|
==== [HTTP REQUEST INFO] |
||||||
|
include::{snippets}/com/code/all/http-request.adoc[] |
||||||
|
include::{snippets}/com/code/all/query-parameters.adoc[] |
||||||
|
|
||||||
|
==== [HTTP RESPONSE INFO] |
||||||
|
include::{snippets}/com/code/all/http-response.adoc[] |
||||||
|
|
||||||
|
include::{snippets}/com/code/all/response-fields.adoc[] |
||||||
|
|
||||||
|
*** |
||||||
|
|
||||||
|
=== [Code 코드목록 조회] |
||||||
|
|
||||||
|
==== [HTTP REQUEST INFO] |
||||||
|
include::{snippets}/com/code/code/http-request.adoc[] |
||||||
|
|
||||||
|
include::{snippets}/com/code/code/query-parameters.adoc[] |
||||||
|
|
||||||
|
==== [HTTP RESPONSE INFO] |
||||||
|
include::{snippets}/com/code/code/http-response.adoc[] |
||||||
|
|
||||||
|
include::{snippets}/com/code/code/response-fields.adoc[] |
||||||
|
|
||||||
|
*** |
||||||
|
|
||||||
|
=== [Code 그룹목록 조회] |
||||||
|
|
||||||
|
==== [HTTP REQUEST INFO] |
||||||
|
include::{snippets}/com/code/group/http-request.adoc[] |
||||||
|
|
||||||
|
==== [HTTP RESPONSE INFO] |
||||||
|
include::{snippets}/com/code/group/http-response.adoc[] |
||||||
|
|
||||||
|
include::{snippets}/com/code/group/response-fields.adoc[] |
||||||
|
|
||||||
|
*** |
||||||
|
|
||||||
|
=== [Code 그룹 등록] |
||||||
|
|
||||||
|
==== [HTTP REQUEST INFO] |
||||||
|
include::{snippets}/com/code/group/create/http-request.adoc[] |
||||||
|
|
||||||
|
include::{snippets}/com/code/group/create/request-fields.adoc[] |
||||||
|
|
||||||
|
==== [HTTP RESPONSE INFO] |
||||||
|
include::{snippets}/com/code/group/create/http-response.adoc[] |
||||||
|
|
||||||
|
include::{snippets}/com/code/group/create/response-fields.adoc[] |
||||||
|
|
||||||
|
*** |
||||||
|
|
||||||
|
=== [Code 코드 등록] |
||||||
|
|
||||||
|
==== [HTTP REQUEST INFO] |
||||||
|
include::{snippets}/com/code/code/create/http-request.adoc[] |
||||||
|
|
||||||
|
include::{snippets}/com/code/code/create/request-fields.adoc[] |
||||||
|
|
||||||
|
==== [HTTP RESPONSE INFO] |
||||||
|
include::{snippets}/com/code/code/create/http-response.adoc[] |
||||||
|
|
||||||
|
include::{snippets}/com/code/code/create/response-fields.adoc[] |
||||||
|
|
||||||
|
*** |
||||||
|
|
||||||
|
=== [Code 코드언어 등록] |
||||||
|
|
||||||
|
==== [HTTP REQUEST INFO] |
||||||
|
include::{snippets}/com/code/lang/create/http-request.adoc[] |
||||||
|
|
||||||
|
include::{snippets}/com/code/lang/create/request-fields.adoc[] |
||||||
|
|
||||||
|
==== [HTTP RESPONSE INFO] |
||||||
|
include::{snippets}/com/code/lang/create/http-response.adoc[] |
||||||
|
|
||||||
|
include::{snippets}/com/code/lang/create/response-fields.adoc[] |
||||||
|
|
||||||
|
*** |
||||||
|
|
||||||
|
=== [Code 그룹 수정] |
||||||
|
|
||||||
|
==== [HTTP REQUEST INFO] |
||||||
|
include::{snippets}/com/code/group/update/http-request.adoc[] |
||||||
|
|
||||||
|
include::{snippets}/com/code/group/update/request-fields.adoc[] |
||||||
|
|
||||||
|
==== [HTTP RESPONSE INFO] |
||||||
|
include::{snippets}/com/code/group/update/http-response.adoc[] |
||||||
|
|
||||||
|
include::{snippets}/com/code/group/update/response-fields.adoc[] |
||||||
|
|
||||||
|
*** |
||||||
|
|
||||||
|
=== [Code 코드 수정] |
||||||
|
|
||||||
|
==== [HTTP REQUEST INFO] |
||||||
|
include::{snippets}/com/code/code/update/http-request.adoc[] |
||||||
|
|
||||||
|
include::{snippets}/com/code/code/update/request-fields.adoc[] |
||||||
|
|
||||||
|
==== [HTTP RESPONSE INFO] |
||||||
|
include::{snippets}/com/code/code/update/http-response.adoc[] |
||||||
|
|
||||||
|
include::{snippets}/com/code/code/update/response-fields.adoc[] |
||||||
|
|
||||||
|
*** |
||||||
|
|
||||||
|
=== [Code 코드언어 수정] |
||||||
|
|
||||||
|
==== [HTTP REQUEST INFO] |
||||||
|
include::{snippets}/com/code/lang/update/http-request.adoc[] |
||||||
|
|
||||||
|
include::{snippets}/com/code/lang/update/request-fields.adoc[] |
||||||
|
|
||||||
|
==== [HTTP RESPONSE INFO] |
||||||
|
include::{snippets}/com/code/lang/update/http-response.adoc[] |
||||||
|
|
||||||
|
include::{snippets}/com/code/lang/update/response-fields.adoc[] |
||||||
|
|
||||||
|
*** |
||||||
|
|
||||||
|
=== [Code 그룹 삭제] |
||||||
|
|
||||||
|
==== [HTTP REQUEST INFO] |
||||||
|
include::{snippets}/com/code/group/delete/http-request.adoc[] |
||||||
|
|
||||||
|
include::{snippets}/com/code/group/delete/query-parameters.adoc[] |
||||||
|
|
||||||
|
==== [HTTP RESPONSE INFO] |
||||||
|
include::{snippets}/com/code/group/delete/http-response.adoc[] |
||||||
|
|
||||||
|
*** |
||||||
|
|
||||||
|
=== [Code 코드 삭제] |
||||||
|
|
||||||
|
==== [HTTP REQUEST INFO] |
||||||
|
include::{snippets}/com/code/code/delete/http-request.adoc[] |
||||||
|
|
||||||
|
include::{snippets}/com/code/code/delete/query-parameters.adoc[] |
||||||
|
|
||||||
|
==== [HTTP RESPONSE INFO] |
||||||
|
include::{snippets}/com/code/code/delete/http-response.adoc[] |
||||||
|
|
||||||
|
*** |
||||||
|
|
||||||
|
=== [Code 언어 삭제] |
||||||
|
|
||||||
|
==== [HTTP REQUEST INFO] |
||||||
|
include::{snippets}/com/code/lang/delete/http-request.adoc[] |
||||||
|
|
||||||
|
include::{snippets}/com/code/lang/delete/query-parameters.adoc[] |
||||||
|
|
||||||
|
==== [HTTP RESPONSE INFO] |
||||||
|
include::{snippets}/com/code/lang/delete/http-response.adoc[] |
||||||
|
|
||||||
|
*** |
Binary file not shown.
Binary file not shown.
@ -0,0 +1,43 @@ |
|||||||
|
package kr.co.palnet.kac.app.api.v1.inner.socket.controller; |
||||||
|
|
||||||
|
import kr.co.palnet.kac.app.api.v1.inner.socket.service.InnerSocketService; |
||||||
|
import kr.co.palnet.kac.common.model.common.DroneDto; |
||||||
|
import kr.co.palnet.kac.common.model.common.SimpleControlDto; |
||||||
|
import lombok.RequiredArgsConstructor; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.springframework.http.ResponseEntity; |
||||||
|
import org.springframework.web.bind.annotation.*; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
@Slf4j |
||||||
|
@RequiredArgsConstructor |
||||||
|
@RestController |
||||||
|
@RequestMapping("/v1/inner/socket") |
||||||
|
public class InnerSocketController { |
||||||
|
|
||||||
|
private final InnerSocketService innerSocketService; |
||||||
|
|
||||||
|
// dorn의 control 조회
|
||||||
|
@GetMapping("/control/{objectId}") |
||||||
|
public ResponseEntity<SimpleControlDto> getControlId(@PathVariable String objectId) { |
||||||
|
SimpleControlDto simpleControlDto = innerSocketService.getControlId(objectId); |
||||||
|
return ResponseEntity.ok(simpleControlDto); |
||||||
|
} |
||||||
|
|
||||||
|
// dron 정보 저장
|
||||||
|
@PostMapping("/receiver/drone") |
||||||
|
public ResponseEntity<Void> receiverDronData(@RequestBody DroneDto droneDto) { |
||||||
|
innerSocketService.saveDronData(droneDto); |
||||||
|
return ResponseEntity.ok().build(); |
||||||
|
} |
||||||
|
|
||||||
|
// dron 정보 저장(전체)
|
||||||
|
@PostMapping("/receiver/drone/all") |
||||||
|
public ResponseEntity<Void> receiverDronDataAll(@RequestBody Map<String, List<DroneDto>> droneDtoAll) { |
||||||
|
innerSocketService.saveDronDataAll(droneDtoAll); |
||||||
|
return ResponseEntity.ok().build(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,23 @@ |
|||||||
|
package kr.co.palnet.kac.app.api.v1.inner.socket.scheduled; |
||||||
|
|
||||||
|
import kr.co.palnet.kac.app.api.v1.inner.socket.service.InnerSocketService; |
||||||
|
import lombok.RequiredArgsConstructor; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.springframework.scheduling.annotation.Scheduled; |
||||||
|
import org.springframework.stereotype.Component; |
||||||
|
|
||||||
|
@Slf4j |
||||||
|
@RequiredArgsConstructor |
||||||
|
@Component |
||||||
|
public class InnerSocketScheduled { |
||||||
|
|
||||||
|
private final InnerSocketService innerSocketService; |
||||||
|
|
||||||
|
/** |
||||||
|
* 최신 수신일로 부터 특정 시간동안 데이터 수신되지 않는 경우 종료 처리 |
||||||
|
*/ |
||||||
|
@Scheduled(fixedDelay = 10 * 1000) |
||||||
|
public void stopDronFlight() { |
||||||
|
innerSocketService.stopDronFlight(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,301 @@ |
|||||||
|
package kr.co.palnet.kac.app.api.v1.inner.socket.service; |
||||||
|
|
||||||
|
import kr.co.palnet.kac.app.api.v1.inner.socket.storage.ControlGpsStorage; |
||||||
|
import kr.co.palnet.kac.common.model.common.DroneDto; |
||||||
|
import kr.co.palnet.kac.common.model.common.SimpleControlDto; |
||||||
|
import kr.co.palnet.kac.core.exception.BaseErrorCode; |
||||||
|
import kr.co.palnet.kac.data.com.domain.ComArcrftBas; |
||||||
|
import kr.co.palnet.kac.data.com.domain.ComIdntfBas; |
||||||
|
import kr.co.palnet.kac.data.com.repository.ComArcrftBasRepository; |
||||||
|
import kr.co.palnet.kac.data.com.repository.ComIdntBasRepository; |
||||||
|
import kr.co.palnet.kac.data.ctr.model.CtrCntrlBas; |
||||||
|
import kr.co.palnet.kac.data.ctr.model.CtrCntrlHstry; |
||||||
|
import kr.co.palnet.kac.data.ctr.model.CtrCntrlHstryArea; |
||||||
|
import kr.co.palnet.kac.data.ctr.repository.CtrCntrlBasRepository; |
||||||
|
import kr.co.palnet.kac.data.ctr.repository.CtrCntrlHstryAreaRepository; |
||||||
|
import kr.co.palnet.kac.data.ctr.repository.CtrCntrlHstryRepository; |
||||||
|
import kr.co.palnet.kac.data.flt.model.FltPlanBas; |
||||||
|
import kr.co.palnet.kac.data.flt.model.FltPlanCtrCntrlRel; |
||||||
|
import kr.co.palnet.kac.data.flt.repository.FltPlanBasRepository; |
||||||
|
import kr.co.palnet.kac.data.flt.repository.FltPlanCtrCntrlRelRepository; |
||||||
|
import kr.co.palnet.kac.util.CoordUtil; |
||||||
|
import lombok.RequiredArgsConstructor; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.springframework.stereotype.Service; |
||||||
|
import org.springframework.transaction.annotation.Transactional; |
||||||
|
|
||||||
|
import java.time.Instant; |
||||||
|
import java.time.temporal.ChronoUnit; |
||||||
|
import java.util.*; |
||||||
|
|
||||||
|
@Slf4j |
||||||
|
@RequiredArgsConstructor |
||||||
|
@Service |
||||||
|
@Transactional |
||||||
|
public class InnerSocketService { |
||||||
|
private final FltPlanBasRepository fltPlanBasRepository; |
||||||
|
|
||||||
|
private final CtrCntrlBasRepository ctrCntrlBasRepository; |
||||||
|
private final CtrCntrlHstryRepository ctrCntrlHstryRepository; |
||||||
|
private final CtrCntrlHstryAreaRepository ctrCntrlHstryAreaRepository; |
||||||
|
private final ComArcrftBasRepository comArcrftBasRepository; |
||||||
|
private final ComIdntBasRepository comIdntBasRepository; |
||||||
|
private final FltPlanCtrCntrlRelRepository fltPlanCtrCntrlRelRepository; |
||||||
|
|
||||||
|
private long STOP_UNKON = 5; |
||||||
|
|
||||||
|
|
||||||
|
// socket에 보내줄 control 조회 (controlId 발급용도)
|
||||||
|
@Transactional(readOnly = true) |
||||||
|
public SimpleControlDto getControlId(String objectId) { |
||||||
|
CtrCntrlBas latestControl = ctrCntrlBasRepository.findFirstByIdntfNumOrderByCreateDtDesc(objectId).orElse(null); |
||||||
|
boolean isControl = false; |
||||||
|
if (latestControl != null) { |
||||||
|
CtrCntrlHstry latestHistory = ctrCntrlHstryRepository.findFirstByCntrlIdOrderBySrvrRcvDtDesc(latestControl.getCntrlId()).orElse(null); |
||||||
|
|
||||||
|
if (latestHistory != null) { |
||||||
|
long diffMinute = ChronoUnit.MINUTES.between(latestHistory.getSrvrRcvDt(), Instant.now()); |
||||||
|
// log.debug("DIFF MINUTE : {}", diffMinute);
|
||||||
|
|
||||||
|
if ("01".equals(latestControl.getEndTypeCd()) || diffMinute > 5) { |
||||||
|
isControl = false; |
||||||
|
} |
||||||
|
if (!"01".equals(latestControl.getEndTypeCd()) && diffMinute < 5) { |
||||||
|
isControl = true; |
||||||
|
} |
||||||
|
} else { |
||||||
|
isControl = false; |
||||||
|
} |
||||||
|
|
||||||
|
} else { |
||||||
|
isControl = false; |
||||||
|
} |
||||||
|
|
||||||
|
SimpleControlDto simpleControlDto = new SimpleControlDto(); |
||||||
|
if (isControl) { |
||||||
|
simpleControlDto.setControlId(latestControl.getCntrlId()); |
||||||
|
simpleControlDto.setTypeCd("02"); |
||||||
|
simpleControlDto.setAreaTrnsYn("E"); |
||||||
|
simpleControlDto.setControlStartDt(latestControl.getCntrlStDt()); |
||||||
|
} else { |
||||||
|
String controlId = UUID.randomUUID().toString(); |
||||||
|
simpleControlDto.setControlId(controlId); |
||||||
|
simpleControlDto.setTypeCd("01"); |
||||||
|
simpleControlDto.setAreaTrnsYn("N"); |
||||||
|
simpleControlDto.setControlStartDt(Instant.now()); |
||||||
|
|
||||||
|
// 기체 식별번호의 관제 ID 저장 (single ton)
|
||||||
|
ControlGpsStorage simpleControlStorage = ControlGpsStorage.getInstance(); |
||||||
|
simpleControlStorage.put(objectId, controlId); |
||||||
|
} |
||||||
|
return simpleControlDto; |
||||||
|
} |
||||||
|
|
||||||
|
// socket으로부터 받은 dron 정보 저장
|
||||||
|
public void saveDronData(DroneDto droneDto) { |
||||||
|
|
||||||
|
// 비행 이력 기본 생성
|
||||||
|
CtrCntrlBas ctrCntrlBas = ctrCntrlBasRepository.findById(droneDto.getControlId()).orElse(null); |
||||||
|
if (ctrCntrlBas == null) { |
||||||
|
CtrCntrlBas insertCtrCntrlBas = CtrCntrlBas.builder() |
||||||
|
.cntrlId(droneDto.getControlId()) |
||||||
|
.idntfNum(droneDto.getObjectId()) |
||||||
|
.objectTypeCd(droneDto.getObjectType()) |
||||||
|
.statusCd(droneDto.getTypeCd()) |
||||||
|
.procStatusYn("N") |
||||||
|
.cntrlStDt(droneDto.getControlStartDt()) |
||||||
|
.createDt(Instant.now()) |
||||||
|
.updateDt(Instant.now()) |
||||||
|
.build(); |
||||||
|
|
||||||
|
ctrCntrlBas = ctrCntrlBasRepository.save(insertCtrCntrlBas); |
||||||
|
} else { |
||||||
|
log.debug(BaseErrorCode.DATA_ALREADY_EXISTS.message()); |
||||||
|
} |
||||||
|
|
||||||
|
if ("01".equals(droneDto.getTypeCd())) { |
||||||
|
// 비행 이력 생성
|
||||||
|
// 비행 이력, 비행 계획서 mapping
|
||||||
|
if (!"PA".equals(droneDto.getObjectId().substring(0, 2))) return; |
||||||
|
|
||||||
|
FltPlanCtrCntrlRel fltPlanCtrCntrlRel = new FltPlanCtrCntrlRel(); |
||||||
|
|
||||||
|
// 1. 식별 번호의 소유자 정보 저장.
|
||||||
|
ComIdntfBas comIdntfBas = comIdntBasRepository.findFirstByIdntfNumOrderByCreateDtAsc(droneDto.getObjectId()).orElse(null); |
||||||
|
|
||||||
|
// 1-1. 식별 번호의 모델 정보 조회
|
||||||
|
// PAV-KAC에서는 기초데이터가 필수가 아니여서 idntBas가 null일 수 있음.
|
||||||
|
ComArcrftBas comArcrftBas = null; |
||||||
|
if (comIdntfBas != null && comIdntfBas.getArcrftSno() != null) { |
||||||
|
comArcrftBas = comArcrftBasRepository.findById(comIdntfBas.getArcrftSno()).orElse(null); |
||||||
|
} |
||||||
|
|
||||||
|
if (ctrCntrlBas.getCntrlId() != null && !ctrCntrlBas.getCntrlId().isEmpty()) { |
||||||
|
fltPlanCtrCntrlRel.setCntrlId(ctrCntrlBas.getCntrlId()); |
||||||
|
} |
||||||
|
|
||||||
|
if (ctrCntrlBas.getIdntfNum() != null && !ctrCntrlBas.getIdntfNum().isEmpty()) { |
||||||
|
fltPlanCtrCntrlRel.setIdntfNum(ctrCntrlBas.getIdntfNum()); |
||||||
|
} |
||||||
|
|
||||||
|
if (comIdntfBas != null && comIdntfBas.getCstmrSno() != null) { |
||||||
|
fltPlanCtrCntrlRel.setIdntfCstmrSno(comIdntfBas.getCstmrSno()); |
||||||
|
} |
||||||
|
|
||||||
|
if (comArcrftBas != null && comArcrftBas.getGroupId() != null && !comArcrftBas.getGroupId().isEmpty()) { |
||||||
|
fltPlanCtrCntrlRel.setGroupId(comArcrftBas.getGroupId()); |
||||||
|
} |
||||||
|
|
||||||
|
// 2. 해당 되는 비행계획서 정보 저장.
|
||||||
|
List<FltPlanBas> fltPlanBas = null; |
||||||
|
if (ctrCntrlBas.getCntrlId() != null && !ctrCntrlBas.getCntrlId().isEmpty()) { |
||||||
|
fltPlanBas = fltPlanBasRepository.findAllByServerRctDtAndIdntfNum(ctrCntrlBas.getIdntfNum(), droneDto.getServerRcvDt()); |
||||||
|
for (FltPlanBas plan : fltPlanBas) { |
||||||
|
fltPlanCtrCntrlRel.setPlanSno(plan.getPlanSno()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// 3. mapping 정보 주입.
|
||||||
|
if (fltPlanCtrCntrlRel.getCntrlId() != null && !fltPlanCtrCntrlRel.getCntrlId().isEmpty() && fltPlanCtrCntrlRel.getPlanSno() != null) { |
||||||
|
fltPlanCtrCntrlRelRepository.save(fltPlanCtrCntrlRel); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
// history 생성
|
||||||
|
CtrCntrlHstry ctrCntrlHstry = CtrCntrlHstry.builder() |
||||||
|
.cntrlId(droneDto.getControlId()) |
||||||
|
.trmnlId(droneDto.getTerminalId()) |
||||||
|
.mvDstnc(droneDto.getMoveDistance()) |
||||||
|
.mvDstncType(droneDto.getMoveDistanceType()) |
||||||
|
.mssgTypeCd(droneDto.getMessageType()) |
||||||
|
.statusCd(droneDto.getDroneStatus()) |
||||||
|
.lat(droneDto.getLat()) |
||||||
|
.lon(droneDto.getLon()) |
||||||
|
.speed(droneDto.getSpeed()) |
||||||
|
.speedType(droneDto.getSpeedType()) |
||||||
|
.heading(droneDto.getHeading()) |
||||||
|
.elev(droneDto.getElev()) |
||||||
|
.elevType(droneDto.getElevType()) |
||||||
|
.bttrLvl(droneDto.getBetteryLevel()) |
||||||
|
.bttrVltg(droneDto.getBetteryVoltage()) |
||||||
|
.trmnlRcvDt(droneDto.getTerminalRcvDt()) |
||||||
|
.srvrRcvDt(droneDto.getServerRcvDt()) |
||||||
|
|
||||||
|
.sensorCo(droneDto.getSensorCo()) |
||||||
|
.sensorSo2(droneDto.getSensorSo2()) |
||||||
|
.sensorNo2(droneDto.getSensorNo2()) |
||||||
|
.sensorO3(droneDto.getSensorO3()) |
||||||
|
.sensorDust(droneDto.getSensorDust()) |
||||||
|
|
||||||
|
.build(); |
||||||
|
|
||||||
|
ctrCntrlHstry = ctrCntrlHstryRepository.save(ctrCntrlHstry); |
||||||
|
|
||||||
|
if ("01".equals(droneDto.getTypeCd()) || "99".equals(droneDto.getTypeCd())) { |
||||||
|
|
||||||
|
CtrCntrlHstryArea ctrCntrlHstryArea = CtrCntrlHstryArea.builder() |
||||||
|
.cntrlId(droneDto.getControlId()) |
||||||
|
.actnType(droneDto.getTypeCd()) |
||||||
|
.prcsYn("N") |
||||||
|
.lat(droneDto.getLat()) |
||||||
|
.lon(droneDto.getLon()) |
||||||
|
.hstrySno(ctrCntrlHstry.getHstrySno()) |
||||||
|
.createDt(Instant.now()) |
||||||
|
.updateDt(Instant.now()) |
||||||
|
.build(); |
||||||
|
|
||||||
|
ctrCntrlHstryAreaRepository.save(ctrCntrlHstryArea); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 특정 시간동안 비행하지않는 데이터 종료처리 |
||||||
|
*/ |
||||||
|
public void stopDronFlight() { |
||||||
|
ControlGpsStorage simpleControlStorage = ControlGpsStorage.getInstance(); |
||||||
|
Map<String, String> keys = simpleControlStorage.getAll(); |
||||||
|
|
||||||
|
|
||||||
|
keys.forEach((objectId, controlId) -> { |
||||||
|
CtrCntrlHstry history = ctrCntrlHstryRepository.findFirstByCntrlIdOrderBySrvrRcvDtDesc(controlId).orElse(null); |
||||||
|
|
||||||
|
if (Objects.nonNull(history)) { |
||||||
|
long diffMinute = ChronoUnit.MINUTES.between(history.getSrvrRcvDt(), Instant.now()); |
||||||
|
|
||||||
|
if (diffMinute > 5) { |
||||||
|
CtrCntrlBas ctrCntrlBas = ctrCntrlBasRepository.findById(controlId).orElse(null); |
||||||
|
|
||||||
|
if (ctrCntrlBas != null) { |
||||||
|
// 기존
|
||||||
|
ctrCntrlBas.setEndTypeCd("01"); |
||||||
|
ctrCntrlBas.setCntrlEndDt(Instant.now()); |
||||||
|
ctrCntrlBas.setStatusCd("99"); |
||||||
|
|
||||||
|
// 총 비행거리 Update
|
||||||
|
Double distance = getDistancSum(history.getCntrlId()); |
||||||
|
if (distance == null) log.warn("No Search - CntrlId => {}", history.getCntrlId()); |
||||||
|
|
||||||
|
ctrCntrlBas.setTtlDstnc(distance); |
||||||
|
ctrCntrlBas.setTtlDstncType("M"); |
||||||
|
ctrCntrlBas.setUpdateDt(Instant.now()); |
||||||
|
|
||||||
|
ctrCntrlBasRepository.save(ctrCntrlBas); |
||||||
|
} |
||||||
|
|
||||||
|
/* 종료지점 주소 저장 */ |
||||||
|
CtrCntrlHstryArea area = CtrCntrlHstryArea.builder() |
||||||
|
.cntrlId(history.getCntrlId()) |
||||||
|
.actnType("99") |
||||||
|
.prcsYn("N") |
||||||
|
.lat(history.getLat()) |
||||||
|
.lon(history.getLon()) |
||||||
|
.hstrySno(history.getHstrySno()) |
||||||
|
.createDt(Instant.now()) |
||||||
|
.updateDt(Instant.now()) |
||||||
|
.build(); |
||||||
|
|
||||||
|
ctrCntrlHstryAreaRepository.save(area); |
||||||
|
|
||||||
|
simpleControlStorage.remove(objectId); |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
private Double getDistancSum(String cntrlId) { |
||||||
|
List<CtrCntrlHstry> result = ctrCntrlHstryRepository.findAllByCntrlIdOrderBySrvrRcvDt(cntrlId); |
||||||
|
if (result.size() == 0) return null; |
||||||
|
|
||||||
|
List<Double> distanceList = new ArrayList<>(); |
||||||
|
|
||||||
|
for (int i = 0; i < result.size(); i++) { |
||||||
|
if (i == 0) continue; |
||||||
|
|
||||||
|
CtrCntrlHstry prevNode = result.get(i - 1); |
||||||
|
CtrCntrlHstry node = result.get(i); |
||||||
|
|
||||||
|
CoordUtil.Coordinates prevCoord = CoordUtil.Coordinates.builder() |
||||||
|
.x(prevNode.getLat()) |
||||||
|
.y(prevNode.getLon()) |
||||||
|
.build(); |
||||||
|
CoordUtil.Coordinates coord = CoordUtil.Coordinates.builder() |
||||||
|
.x(node.getLat()) |
||||||
|
.y(node.getLon()) |
||||||
|
.build(); |
||||||
|
|
||||||
|
Double distance = CoordUtil.calculateDistance(prevCoord, coord); |
||||||
|
|
||||||
|
distanceList.add(distance); |
||||||
|
} |
||||||
|
|
||||||
|
return distanceList.stream().mapToDouble(Double::doubleValue).sum(); |
||||||
|
} |
||||||
|
|
||||||
|
public void saveDronDataAll(Map<String, List<DroneDto>> droneDtoAll) { |
||||||
|
droneDtoAll.values().forEach(droneDtoList -> { |
||||||
|
droneDtoList.forEach(this::saveDronData); |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,48 @@ |
|||||||
|
package kr.co.palnet.kac.app.api.v1.inner.socket.storage; |
||||||
|
|
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.concurrent.ConcurrentHashMap; |
||||||
|
|
||||||
|
public class ControlGpsStorage { |
||||||
|
|
||||||
|
private Map<String, String> mappingMap; |
||||||
|
|
||||||
|
private ControlGpsStorage() { |
||||||
|
mappingMap = new ConcurrentHashMap<>(); |
||||||
|
} |
||||||
|
|
||||||
|
public static ControlGpsStorage getInstance() { |
||||||
|
return LazyHolder.INSTANCE; |
||||||
|
} |
||||||
|
|
||||||
|
private static class LazyHolder { |
||||||
|
private static final ControlGpsStorage INSTANCE = new ControlGpsStorage(); |
||||||
|
} |
||||||
|
|
||||||
|
public void put(String objectId, String controlId) { |
||||||
|
mappingMap.put(objectId, controlId); |
||||||
|
} |
||||||
|
|
||||||
|
public String get(String objectId) { |
||||||
|
return mappingMap.get(objectId); |
||||||
|
} |
||||||
|
|
||||||
|
public String remove(String objectId) { |
||||||
|
return mappingMap.remove(objectId); |
||||||
|
} |
||||||
|
|
||||||
|
public boolean containsKey(String objectId) { |
||||||
|
return mappingMap.containsKey(objectId); |
||||||
|
} |
||||||
|
|
||||||
|
public List<String> getAllList() { |
||||||
|
return (List<String>) mappingMap.values(); |
||||||
|
} |
||||||
|
|
||||||
|
public Map<String, String> getAll() { |
||||||
|
return mappingMap; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,31 @@ |
|||||||
|
package kr.co.palnet.kac.app.config; |
||||||
|
|
||||||
|
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; |
||||||
|
|
||||||
|
import java.lang.management.ManagementFactory; |
||||||
|
import java.lang.management.MemoryMXBean; |
||||||
|
import java.lang.management.MemoryPoolMXBean; |
||||||
|
|
||||||
|
@Slf4j |
||||||
|
@RequiredArgsConstructor |
||||||
|
@Component |
||||||
|
public class AppReadyEvent implements ApplicationListener<ApplicationReadyEvent> { |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onApplicationEvent(ApplicationReadyEvent event) { |
||||||
|
try { |
||||||
|
float mb = 1024f * 1024f; |
||||||
|
MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean(); |
||||||
|
log.info("current heap memory init(xms): {}mb, max(xmx): {}mb", memoryBean.getHeapMemoryUsage().getInit() / mb, memoryBean.getHeapMemoryUsage().getMax() / mb); |
||||||
|
for (MemoryPoolMXBean mp : ManagementFactory.getMemoryPoolMXBeans()) { |
||||||
|
log.info("Pool: {} (type {}) = {}", mp.getName(), mp.getType(), mp.getUsage().getMax() / mb); |
||||||
|
} |
||||||
|
} catch (Exception e) { |
||||||
|
log.warn("when start app, not read jvm heap memory information."); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,22 @@ |
|||||||
|
package kr.co.palnet.kac.app.config; |
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper; |
||||||
|
import kr.co.palnet.kac.util.ObjectMapperUtil; |
||||||
|
import org.springframework.context.annotation.Bean; |
||||||
|
import org.springframework.context.annotation.Configuration; |
||||||
|
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; |
||||||
|
|
||||||
|
@Configuration |
||||||
|
public class JsonConfig { |
||||||
|
|
||||||
|
@Bean |
||||||
|
public ObjectMapper objectMapper() { |
||||||
|
return ObjectMapperUtil.getObjectMapper(); |
||||||
|
} |
||||||
|
|
||||||
|
@Bean |
||||||
|
public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder() { |
||||||
|
return ObjectMapperUtil.getObjectMapperBuilder(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,26 @@ |
|||||||
|
package kr.co.palnet.kac.app.config; |
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier; |
||||||
|
import org.springframework.context.MessageSource; |
||||||
|
import org.springframework.context.annotation.Bean; |
||||||
|
import org.springframework.context.annotation.Configuration; |
||||||
|
import org.springframework.context.support.MessageSourceAccessor; |
||||||
|
import org.springframework.context.support.ReloadableResourceBundleMessageSource; |
||||||
|
|
||||||
|
@Configuration |
||||||
|
public class MessageSourceConfig { |
||||||
|
|
||||||
|
@Bean(name = "errorMessageSource") |
||||||
|
public MessageSource getErrorMessageSource() { |
||||||
|
ReloadableResourceBundleMessageSource errorMessageSource = new ReloadableResourceBundleMessageSource(); |
||||||
|
errorMessageSource.setBasenames("classpath:messages/errors/error"); |
||||||
|
errorMessageSource.setDefaultEncoding("UTF-8"); |
||||||
|
errorMessageSource.setCacheSeconds(300); |
||||||
|
return errorMessageSource; |
||||||
|
} |
||||||
|
|
||||||
|
@Bean(name = "errorMessageSourceAccessor") |
||||||
|
public MessageSourceAccessor errorMessageSourceAccessor(@Qualifier("errorMessageSource") MessageSource errorMessageSource) { |
||||||
|
return new MessageSourceAccessor(errorMessageSource); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,112 @@ |
|||||||
|
package kr.co.palnet.kac.app.config; |
||||||
|
|
||||||
|
import io.swagger.v3.oas.models.Components; |
||||||
|
import io.swagger.v3.oas.models.OpenAPI; |
||||||
|
import io.swagger.v3.oas.models.info.Info; |
||||||
|
import io.swagger.v3.oas.models.security.SecurityRequirement; |
||||||
|
import io.swagger.v3.oas.models.security.SecurityScheme; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
|
||||||
|
import org.springdoc.core.models.GroupedOpenApi; |
||||||
|
import org.springframework.boot.web.servlet.FilterRegistrationBean; |
||||||
|
import org.springframework.context.annotation.Bean; |
||||||
|
import org.springframework.context.annotation.Configuration; |
||||||
|
import org.springframework.core.Ordered; |
||||||
|
import org.springframework.web.cors.CorsConfiguration; |
||||||
|
import org.springframework.web.cors.UrlBasedCorsConfigurationSource; |
||||||
|
import org.springframework.web.filter.CorsFilter; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
@Slf4j |
||||||
|
@Configuration |
||||||
|
public class SwaggerConfig { |
||||||
|
|
||||||
|
// @Bean
|
||||||
|
// public Docket api() {
|
||||||
|
// return new Docket(DocumentationType.OAS_30) // open api spec 3.0
|
||||||
|
// .apiInfo(new ApiInfoBuilder().version("1.0").title("PAV").build())
|
||||||
|
// .forCodeGeneration(true).securitySchemes(Arrays.asList(apiKey()))
|
||||||
|
// .select()
|
||||||
|
// .apis(RequestHandlerSelectors.any())
|
||||||
|
// .paths(PathSelectors.any())
|
||||||
|
// .build()
|
||||||
|
// .apiInfo(apiInfo())
|
||||||
|
// .directModelSubstitute(Date.class, String.class)
|
||||||
|
// .directModelSubstitute(LocalDate.class, String.class)
|
||||||
|
// .directModelSubstitute(LocalDateTime.class, String.class)
|
||||||
|
// .directModelSubstitute(Pageable.class, SwaggerPageable.class)
|
||||||
|
// .securityContexts(Arrays.asList(securityContext()))
|
||||||
|
// .securitySchemes(Arrays.asList(apiKey()));
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// private ApiInfo apiInfo() {
|
||||||
|
// return new ApiInfoBuilder()
|
||||||
|
// .title("PAV API")
|
||||||
|
// .description("----")
|
||||||
|
// .version("1.0")
|
||||||
|
// .build();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// //ApiKey 정의
|
||||||
|
// private ApiKey apiKey() {
|
||||||
|
// return new ApiKey("Authorization", "Authorization", "header");
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// //JWT SecurityContext 구성
|
||||||
|
// private SecurityContext securityContext() {
|
||||||
|
// return SecurityContext.builder().securityReferences(defaultAuth()).build();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private List<SecurityReference> defaultAuth() {
|
||||||
|
// AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEveryThing");
|
||||||
|
// AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
|
||||||
|
// authorizationScopes[0] = authorizationScope;
|
||||||
|
// return Arrays.asList(new SecurityReference("Authorization", authorizationScopes));
|
||||||
|
// }
|
||||||
|
|
||||||
|
private static final String BEARER_TOKEN_PREFIX = "palnet"; |
||||||
|
|
||||||
|
@Bean |
||||||
|
public OpenAPI openAPI() { |
||||||
|
final String securitySchemeName = "BearerAuth"; |
||||||
|
|
||||||
|
SecurityRequirement securityRequirement = new SecurityRequirement().addList(securitySchemeName, List.of("read", "write")); |
||||||
|
|
||||||
|
Components components = new Components() |
||||||
|
.addSecuritySchemes(securitySchemeName, new SecurityScheme() |
||||||
|
.type(SecurityScheme.Type.APIKEY) |
||||||
|
.in(SecurityScheme.In.HEADER) |
||||||
|
.name("Authorization") |
||||||
|
.description("prefix add 'palnet ' + token") |
||||||
|
); |
||||||
|
|
||||||
|
// Swagger UI 접속 후, 딱 한 번만 accessToken을 입력해주면 모든 API에 토큰 인증 작업이 적용됩니다.
|
||||||
|
return new OpenAPI() |
||||||
|
.addSecurityItem(securityRequirement) |
||||||
|
.components(components) |
||||||
|
.info(new Info() |
||||||
|
.title("PAV API") |
||||||
|
.version("1.0") |
||||||
|
.description("PAV API") |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
@Bean |
||||||
|
public GroupedOpenApi commonCode() { |
||||||
|
return GroupedOpenApi.builder() |
||||||
|
.group("공통-코드") |
||||||
|
.pathsToMatch("/v1/com/code/**") |
||||||
|
.build(); |
||||||
|
} |
||||||
|
|
||||||
|
@Bean |
||||||
|
public GroupedOpenApi selectAll() { |
||||||
|
return GroupedOpenApi.builder() |
||||||
|
.group("All") |
||||||
|
.pathsToMatch("/**") |
||||||
|
.build(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,19 @@ |
|||||||
|
package kr.co.palnet.kac.app.config; |
||||||
|
|
||||||
|
import kr.co.palnet.kac.app.core.web.BaseAcceptHeaderLocaleResolver; |
||||||
|
import org.springframework.context.annotation.Bean; |
||||||
|
import org.springframework.context.annotation.Configuration; |
||||||
|
import org.springframework.web.servlet.LocaleResolver; |
||||||
|
import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver; |
||||||
|
|
||||||
|
import java.util.Locale; |
||||||
|
|
||||||
|
@Configuration |
||||||
|
public class WebConfig { |
||||||
|
@Bean |
||||||
|
public LocaleResolver localeResolver() { |
||||||
|
AcceptHeaderLocaleResolver localeResolver = new BaseAcceptHeaderLocaleResolver(); |
||||||
|
localeResolver.setDefaultLocale(Locale.KOREA); |
||||||
|
return localeResolver; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,131 @@ |
|||||||
|
package kr.co.palnet.kac.app.core.exception; |
||||||
|
|
||||||
|
|
||||||
|
import kr.co.palnet.kac.core.exception.BaseErrorCode; |
||||||
|
import kr.co.palnet.kac.core.exception.BaseException; |
||||||
|
import kr.co.palnet.kac.core.exception.Level; |
||||||
|
import kr.co.palnet.kac.core.exception.model.BaseErrorModel; |
||||||
|
import lombok.RequiredArgsConstructor; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.springframework.beans.factory.annotation.Qualifier; |
||||||
|
import org.springframework.context.NoSuchMessageException; |
||||||
|
import org.springframework.context.support.MessageSourceAccessor; |
||||||
|
import org.springframework.http.ResponseEntity; |
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler; |
||||||
|
import org.springframework.web.bind.annotation.RestControllerAdvice; |
||||||
|
import org.springframework.web.context.request.RequestContextHolder; |
||||||
|
import org.springframework.web.context.request.ServletRequestAttributes; |
||||||
|
import org.springframework.web.servlet.resource.NoResourceFoundException; |
||||||
|
|
||||||
|
import java.time.Instant; |
||||||
|
|
||||||
|
@Slf4j |
||||||
|
@RequiredArgsConstructor |
||||||
|
@RestControllerAdvice |
||||||
|
public class ServerExceptionHandler { |
||||||
|
|
||||||
|
@Qualifier("errorMessageSourceAccessor") |
||||||
|
private final MessageSourceAccessor errorMessageSourceAccessor; |
||||||
|
|
||||||
|
@ExceptionHandler(BaseException.class) |
||||||
|
public ResponseEntity<BaseErrorModel> appExceptionHandle(BaseException e) { |
||||||
|
|
||||||
|
BaseErrorCode errorType = e.getErrorCode(); |
||||||
|
|
||||||
|
BaseErrorModel baseBody = new BaseErrorModel(); |
||||||
|
baseBody.setTimestamp(Instant.now()); |
||||||
|
baseBody.setStatus(errorType.status().value()); |
||||||
|
baseBody.setError(errorType.status().getReasonPhrase()); |
||||||
|
baseBody.setCode(errorType.code()); |
||||||
|
baseBody.setMessage(this.getMessage(errorType)); |
||||||
|
baseBody.setPath(this.getUrl()); |
||||||
|
|
||||||
|
this.printLog(e); |
||||||
|
|
||||||
|
return ResponseEntity.status(errorType.status()).body(baseBody); |
||||||
|
} |
||||||
|
|
||||||
|
@ExceptionHandler(NoResourceFoundException.class) |
||||||
|
public ResponseEntity<BaseErrorModel> noResourceFoundException(NoResourceFoundException e) { |
||||||
|
|
||||||
|
BaseErrorModel baseBody = new BaseErrorModel(); |
||||||
|
baseBody.setTimestamp(Instant.now()); |
||||||
|
baseBody.setStatus(e.getBody().getStatus()); |
||||||
|
baseBody.setError(e.getBody().getDetail()); |
||||||
|
baseBody.setCode(BaseErrorCode.WEB_NOT_FOUND.code()); |
||||||
|
baseBody.setMessage(this.getMessage(BaseErrorCode.WEB_NOT_FOUND)); |
||||||
|
baseBody.setPath(e.getResourcePath()); |
||||||
|
|
||||||
|
log.error("ERROR: ", e); |
||||||
|
|
||||||
|
return ResponseEntity.status(e.getBody().getStatus()).body(baseBody); |
||||||
|
} |
||||||
|
|
||||||
|
@ExceptionHandler(Exception.class) |
||||||
|
public ResponseEntity<BaseErrorModel> exceptionHandle(Exception e) { |
||||||
|
BaseErrorCode errorType = BaseErrorCode.UNKNOWN; |
||||||
|
|
||||||
|
BaseErrorModel baseBody = new BaseErrorModel(); |
||||||
|
baseBody.setTimestamp(Instant.now()); |
||||||
|
baseBody.setStatus(errorType.status().value()); |
||||||
|
baseBody.setError(errorType.status().getReasonPhrase()); |
||||||
|
baseBody.setCode(errorType.code()); |
||||||
|
baseBody.setMessage(this.getMessage(errorType)); |
||||||
|
baseBody.setPath(this.getUrl()); |
||||||
|
|
||||||
|
log.error("ERROR: ", e); |
||||||
|
|
||||||
|
return ResponseEntity.status(errorType.status()).body(baseBody); |
||||||
|
} |
||||||
|
|
||||||
|
private String getUrl() { |
||||||
|
ServletRequestAttributes servletRequestAttr = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); |
||||||
|
if (servletRequestAttr != null) return servletRequestAttr.getRequest().getRequestURI(); |
||||||
|
else return null; |
||||||
|
} |
||||||
|
|
||||||
|
private String getMessage(BaseErrorCode e) { |
||||||
|
String message = null; |
||||||
|
try { |
||||||
|
// message = errorMessageSourceAccessor.getMessage(e.code(), LocaleContextHolder.getLocale());
|
||||||
|
message = errorMessageSourceAccessor.getMessage(e.code()); |
||||||
|
} catch (NoSuchMessageException noSuchMessageException) { |
||||||
|
message = "관리자에게 문의해주세요."; |
||||||
|
} |
||||||
|
return message; |
||||||
|
} |
||||||
|
|
||||||
|
private void printLog(BaseException e) { |
||||||
|
Level level = e.getLevel(); |
||||||
|
String message = e.getLogMessage() == null ? e.getMessage() : e.getLogMessage(); |
||||||
|
Object[] paramArray = e.getParamArray(); |
||||||
|
|
||||||
|
switch (level) { |
||||||
|
case TRACE: |
||||||
|
log.trace("message: {}, params: {}, trace: ", message, paramArray, e); |
||||||
|
log.trace("", e); |
||||||
|
break; |
||||||
|
case DEBUG: |
||||||
|
log.debug("message: {}, params: {}, trace: ", message, paramArray, e); |
||||||
|
log.debug("", e); |
||||||
|
break; |
||||||
|
case INFO: |
||||||
|
log.info("message: {}, params: {}, trace: ", message, paramArray, e); |
||||||
|
log.info("", e); |
||||||
|
break; |
||||||
|
case WARN: |
||||||
|
log.warn("message: {}, params: {}, trace: ", message, paramArray, e); |
||||||
|
log.warn("", e); |
||||||
|
break; |
||||||
|
case ERROR: |
||||||
|
log.error("message: {}, params: {}, trace: ", message, paramArray, e); |
||||||
|
log.error("ERROR: ", e); |
||||||
|
break; |
||||||
|
default: |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
|
@ -0,0 +1,62 @@ |
|||||||
|
package kr.co.palnet.kac.app.core.security; |
||||||
|
|
||||||
|
import kr.co.palnet.kac.config.security.SecurityConfig; |
||||||
|
import kr.co.palnet.kac.config.security.exception.BaseAccessDeniedHandler; |
||||||
|
import kr.co.palnet.kac.config.security.exception.BaseAuthenticationEntryPoint; |
||||||
|
import kr.co.palnet.kac.config.security.service.BaseUserDetailsService; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.springframework.context.annotation.Configuration; |
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity; |
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; |
||||||
|
import org.springframework.security.web.SecurityFilterChain; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
|
||||||
|
@Slf4j |
||||||
|
@EnableWebSecurity |
||||||
|
@Configuration |
||||||
|
public class AppSecurityConfig extends SecurityConfig { |
||||||
|
|
||||||
|
// 시큐리티 적용 안하는 URL 목록
|
||||||
|
private final String[] IGNORE_URL = { |
||||||
|
"/v1/inner/socket/**", |
||||||
|
"/v1/com/code/**", |
||||||
|
"/actuator/**", |
||||||
|
"/test/**", |
||||||
|
}; |
||||||
|
|
||||||
|
// 권한(ROLE)별 URL
|
||||||
|
private final String[] USER_URL = { |
||||||
|
}; |
||||||
|
|
||||||
|
public AppSecurityConfig(BaseUserDetailsService baseUserDetailsService, BaseAuthenticationEntryPoint baseAuthenticationEntryPoint, BaseAccessDeniedHandler baseAccessDeniedHandler) { |
||||||
|
super(baseUserDetailsService, baseAuthenticationEntryPoint, baseAccessDeniedHandler); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { |
||||||
|
// 기본 security 설정을 불러온다.
|
||||||
|
this.setDefaultHttpSecurity(http); |
||||||
|
// 여기서는 role별 허용 url 설정을 한다.
|
||||||
|
http |
||||||
|
.securityMatchers(matchers -> matchers.requestMatchers("/**")) |
||||||
|
.authorizeHttpRequests(authz -> |
||||||
|
authz |
||||||
|
.requestMatchers(USER_URL).hasRole("USER") |
||||||
|
.anyRequest().authenticated() |
||||||
|
) |
||||||
|
; |
||||||
|
|
||||||
|
return http.build(); |
||||||
|
} |
||||||
|
|
||||||
|
// security filter 제외 - permission all
|
||||||
|
@Override |
||||||
|
protected List<String> getExcludeURI() { |
||||||
|
return new ArrayList<>(Arrays.asList(IGNORE_URL)); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,67 @@ |
|||||||
|
package kr.co.palnet.kac.app.core.web; |
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.springframework.util.StringUtils; |
||||||
|
import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver; |
||||||
|
|
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Locale; |
||||||
|
|
||||||
|
@Slf4j |
||||||
|
public class BaseAcceptHeaderLocaleResolver extends AcceptHeaderLocaleResolver { |
||||||
|
private final List<Locale> LOCALES = Arrays.asList( |
||||||
|
new Locale("ko"), |
||||||
|
new Locale("ko", "KR"), |
||||||
|
new Locale("en"), |
||||||
|
new Locale("en", "US") |
||||||
|
); |
||||||
|
private final String LOCALE_HEADER_KEY = "Accept-Language"; |
||||||
|
|
||||||
|
/** |
||||||
|
* resolve locale 구성 |
||||||
|
* |
||||||
|
* @param request the request to resolve the locale for |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public Locale resolveLocale(HttpServletRequest request) { |
||||||
|
if (!StringUtils.hasText(request.getHeader(LOCALE_HEADER_KEY))) { |
||||||
|
return this.getDefaultLocale(); |
||||||
|
} |
||||||
|
try { |
||||||
|
|
||||||
|
Locale locale = LOCALES.stream().filter(_locale -> { |
||||||
|
String defaultLocale = _locale.toString() |
||||||
|
.replaceAll("-", "") |
||||||
|
.replaceAll("_", "") |
||||||
|
.toLowerCase(); |
||||||
|
String headerLocale = request.getHeader(LOCALE_HEADER_KEY).toString() |
||||||
|
.replaceAll("-", "") |
||||||
|
.replaceAll("_", "") |
||||||
|
.toLowerCase(); |
||||||
|
return defaultLocale.equals(headerLocale); |
||||||
|
}).findFirst().orElse(null); |
||||||
|
|
||||||
|
if (locale == null) { |
||||||
|
locale = LOCALES.stream() |
||||||
|
.filter(_locale -> { |
||||||
|
String language = _locale.getLanguage(); |
||||||
|
return request.getHeader(LOCALE_HEADER_KEY).contains(language); |
||||||
|
}) |
||||||
|
.map(_locale -> { |
||||||
|
String language = _locale.getLanguage(); |
||||||
|
return new Locale(language); |
||||||
|
}) |
||||||
|
.distinct() |
||||||
|
.findFirst().orElse(Locale.KOREAN); |
||||||
|
} |
||||||
|
// log.debug("{}, {}", request.getHeader(LOCALE_HEADER_KEY), locale);
|
||||||
|
return locale; |
||||||
|
} catch (IllegalArgumentException e) { |
||||||
|
log.warn("{}", e.getMessage()); |
||||||
|
return this.getDefaultLocale(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,131 @@ |
|||||||
|
package kr.co.palnet.kac.app.ping.controller; |
||||||
|
|
||||||
|
import kr.co.palnet.kac.common.model.common.DroneDto; |
||||||
|
import kr.co.palnet.kac.core.exception.BaseErrorCode; |
||||||
|
import kr.co.palnet.kac.core.exception.BaseException; |
||||||
|
import kr.co.palnet.kac.util.EncryptUtil; |
||||||
|
import kr.co.palnet.kac.util.KisaEncryptUtil; |
||||||
|
import lombok.RequiredArgsConstructor; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.springframework.beans.factory.annotation.Qualifier; |
||||||
|
import org.springframework.context.MessageSource; |
||||||
|
import org.springframework.context.i18n.LocaleContextHolder; |
||||||
|
import org.springframework.context.support.MessageSourceAccessor; |
||||||
|
import org.springframework.web.bind.annotation.*; |
||||||
|
|
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.Locale; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
@Slf4j |
||||||
|
@RequiredArgsConstructor |
||||||
|
@RequestMapping("/test") |
||||||
|
@RestController |
||||||
|
public class TestErrorContoller { |
||||||
|
|
||||||
|
@Qualifier("errorMessageSource") |
||||||
|
private final MessageSource errorMessageSource; |
||||||
|
|
||||||
|
@Qualifier("errorMessageSourceAccessor") |
||||||
|
private final MessageSourceAccessor errorMessageSourceAccessor; |
||||||
|
|
||||||
|
@PostMapping("/json/parse") |
||||||
|
public DroneDto jsonTest(@RequestBody DroneDto dto){ |
||||||
|
return dto; |
||||||
|
} |
||||||
|
|
||||||
|
@GetMapping("/encrypt/{plainText}") |
||||||
|
public String ecrypt(@PathVariable String plainText) { |
||||||
|
String encrypt = KisaEncryptUtil.CbcEncrypt.encrypt(plainText); |
||||||
|
log.debug("e : {}", encrypt); |
||||||
|
String decrypt = KisaEncryptUtil.CbcEncrypt.decrypt(encrypt); |
||||||
|
log.debug("d : {}", decrypt); |
||||||
|
String encrypt1 = EncryptUtil.encrypt(plainText); |
||||||
|
log.debug("e : {}", encrypt1); |
||||||
|
String decrypt1 = EncryptUtil.decrypt(encrypt1); |
||||||
|
log.debug("d : {}", decrypt1); |
||||||
|
return decrypt; |
||||||
|
} |
||||||
|
|
||||||
|
@GetMapping("/error/{caseNumber}") |
||||||
|
public String errorTest(@PathVariable("caseNumber") Integer caseNumber) throws Exception { |
||||||
|
log.debug("{}", caseNumber); |
||||||
|
return switch (caseNumber) { |
||||||
|
case 1 -> throw new BaseException(BaseErrorCode.IO_ERROR); |
||||||
|
case 2 -> throw new Exception("test"); |
||||||
|
default -> "success"; |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
@GetMapping("/message/locale") |
||||||
|
public Map<String, String> locale() { |
||||||
|
Map<String, String> map = new HashMap<>(); |
||||||
|
map.put("message", errorMessageSourceAccessor.getMessage("TEST001")); |
||||||
|
map.put("locale", LocaleContextHolder.getLocale().toString()); |
||||||
|
return map; |
||||||
|
} |
||||||
|
|
||||||
|
@GetMapping("/message/error") |
||||||
|
public void messageSource() { |
||||||
|
Map<String, String> map = new HashMap<>(); |
||||||
|
try { |
||||||
|
String ko_KR = errorMessageSource.getMessage("TEST001", null, Locale.KOREA); |
||||||
|
map.put("ko_KR", ko_KR); |
||||||
|
} catch (Exception e) { |
||||||
|
log.error("{}", e.getMessage()); |
||||||
|
} |
||||||
|
try { |
||||||
|
String ko = errorMessageSource.getMessage("TEST001", null, Locale.KOREAN); |
||||||
|
map.put("ko", ko); |
||||||
|
|
||||||
|
} catch (Exception e) { |
||||||
|
log.error("{}", e.getMessage()); |
||||||
|
} |
||||||
|
try { |
||||||
|
String en = errorMessageSource.getMessage("TEST001", null, Locale.ENGLISH); |
||||||
|
map.put("en", en); |
||||||
|
|
||||||
|
} catch (Exception e) { |
||||||
|
log.error("{}", e.getMessage()); |
||||||
|
} |
||||||
|
try { |
||||||
|
String en_US = errorMessageSource.getMessage("TEST001", null, Locale.US); |
||||||
|
map.put("en_US", en_US); |
||||||
|
} catch (Exception e) { |
||||||
|
log.error("{}", e.getMessage()); |
||||||
|
} |
||||||
|
|
||||||
|
log.debug("=========================================================================================="); |
||||||
|
try { |
||||||
|
String df = errorMessageSourceAccessor.getMessage("TEST001"); |
||||||
|
map.put("df", df); |
||||||
|
} catch (Exception e) { |
||||||
|
log.error("{}", e.getMessage()); |
||||||
|
} |
||||||
|
try { |
||||||
|
String str = errorMessageSourceAccessor.getMessage("TEST001", Locale.KOREAN); |
||||||
|
map.put("s_ko", str); |
||||||
|
} catch (Exception e) { |
||||||
|
log.error("{}", e.getMessage()); |
||||||
|
} |
||||||
|
try { |
||||||
|
String str = errorMessageSourceAccessor.getMessage("TEST001", Locale.KOREA); |
||||||
|
map.put("s_ko_kr", str); |
||||||
|
} catch (Exception e) { |
||||||
|
log.error("{}", e.getMessage()); |
||||||
|
} |
||||||
|
try { |
||||||
|
String str = errorMessageSourceAccessor.getMessage("TEST001", Locale.ENGLISH); |
||||||
|
map.put("s_en", str); |
||||||
|
} catch (Exception e) { |
||||||
|
log.error("{}", e.getMessage()); |
||||||
|
} |
||||||
|
try { |
||||||
|
String str = errorMessageSourceAccessor.getMessage("TEST001", Locale.US); |
||||||
|
map.put("s_en_us", str); |
||||||
|
} catch (Exception e) { |
||||||
|
log.error("{}", e.getMessage()); |
||||||
|
} |
||||||
|
log.debug("{}", map); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,31 @@ |
|||||||
|
package kr.co.palnet.kac.app.ping.controller; |
||||||
|
|
||||||
|
import kr.co.palnet.kac.config.security.model.BaseUserDetails; |
||||||
|
import kr.co.palnet.kac.config.security.util.SessionHelper; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.springframework.web.bind.annotation.GetMapping; |
||||||
|
import org.springframework.web.bind.annotation.RequestMapping; |
||||||
|
import org.springframework.web.bind.annotation.RestController; |
||||||
|
|
||||||
|
@Slf4j |
||||||
|
@RestController |
||||||
|
@RequestMapping("/test/session") |
||||||
|
public class TestSessionContrller { |
||||||
|
|
||||||
|
@GetMapping("/auth") |
||||||
|
public void test() { |
||||||
|
BaseUserDetails userInfo = SessionHelper.getUserInfo(); |
||||||
|
log.debug("userInfo ::: {}", userInfo); |
||||||
|
Long userNo = SessionHelper.getCstmrSno(); |
||||||
|
log.debug("userNo ::: {}", userNo); |
||||||
|
String userId = SessionHelper.getUserId(); |
||||||
|
log.debug("userId ::: {}", userId); |
||||||
|
boolean isRole1 = SessionHelper.hasRole("USER"); |
||||||
|
log.debug("isRole1 ::: {}", isRole1); |
||||||
|
boolean isRole2 = SessionHelper.hasRole("ADMIN"); |
||||||
|
log.debug("isRole2 ::: {}", isRole2); |
||||||
|
boolean isRole3 = SessionHelper.hasRole("ADMINasdfasdf"); |
||||||
|
log.debug("isRole3 ::: {}", isRole3); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,3 @@ |
|||||||
|
lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Qualifier |
||||||
|
lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Value |
||||||
|
lombok.anyConstructor.addConstructorProperties=true |
@ -0,0 +1,41 @@ |
|||||||
|
springdoc: |
||||||
|
api-docs: |
||||||
|
path: /api-docs |
||||||
|
groups: |
||||||
|
enabled: true |
||||||
|
# paths-to-exclude: |
||||||
|
# - /api/v1/utm |
||||||
|
# - /api/bas/flight/** |
||||||
|
# - /api/ctr/cntrl/contains |
||||||
|
# - /api/file/** |
||||||
|
swagger-ui: |
||||||
|
# 각 API의 그룹 표시 순서 |
||||||
|
# path, query, body, response 순으로 출력 |
||||||
|
path: /index.html |
||||||
|
display-request-duration: true |
||||||
|
|
||||||
|
groups-order: ASC |
||||||
|
|
||||||
|
# 태그 정렬 순서. |
||||||
|
# alpha: 알파벳 순 정렬 |
||||||
|
# method: OpenAPI specification file에 원하는 태그 정렬 방식 직접 기재 |
||||||
|
tags-sorter: alpha |
||||||
|
|
||||||
|
# 컨트롤러 정렬 순서. |
||||||
|
# method는 delete - get - patch - post - put 순으로 정렬된다. |
||||||
|
# alpha를 사용해 알파벳 순으로 정렬할 수 있다. |
||||||
|
operations-sorter: alpha |
||||||
|
|
||||||
|
# Spring Actuator의 endpoint까지 보여줄 것인지? |
||||||
|
show-actuator: false |
||||||
|
|
||||||
|
# request media type 의 기본 값 |
||||||
|
default-consumes-media-type: application/json |
||||||
|
|
||||||
|
# response media type 의 기본 값 |
||||||
|
default-produces-media-type: application/json |
||||||
|
|
||||||
|
# Swagger UI에서 기본적으로 펼쳐져 보이는 경로의 수 |
||||||
|
doc-expansion : none |
||||||
|
|
||||||
|
|
@ -0,0 +1,101 @@ |
|||||||
|
spring: |
||||||
|
profiles: |
||||||
|
include: |
||||||
|
- swagger |
||||||
|
- db |
||||||
|
threads: |
||||||
|
virtual: |
||||||
|
enabled: true |
||||||
|
|
||||||
|
server.port: 8000 |
||||||
|
|
||||||
|
logging: |
||||||
|
level: |
||||||
|
kr.co.palnet: DEBUG |
||||||
|
# jdbc: |
||||||
|
# audit: OFF |
||||||
|
# resultset: OFF |
||||||
|
# resultsettable: INFO #SQL 결과 데이터 Table을 로그로 남긴다. |
||||||
|
# sqlonly: OFF #SQL만 로그로 남긴다. |
||||||
|
# sqltiming: info #SQL과 소요시간을 표기한다. |
||||||
|
# connection : trace # 커넥션 확인가능 |
||||||
|
# org.hibernate: |
||||||
|
# SQL: off |
||||||
|
# type.descriptor.sql.BasicBinder: TRACE |
||||||
|
# com.zaxxer.hikari.HikariConfig: DEBUG |
||||||
|
# com.zaxxer.hikari: TRACE |
||||||
|
# file: |
||||||
|
# name: ./logs/kac-app/data.log |
||||||
|
# logback: |
||||||
|
# rollingpolicy: |
||||||
|
# clean-history-on-start: false |
||||||
|
# # file-name-pattern: ${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz |
||||||
|
# file-name-pattern: ${LOG_FILE}.%d{yyyy-MM-dd}.%i.log |
||||||
|
# max-file-size: 10MB |
||||||
|
# max-history: 60 |
||||||
|
# total-size-cap: 100MB |
||||||
|
|
||||||
|
management: |
||||||
|
endpoints: |
||||||
|
web: |
||||||
|
exposure: |
||||||
|
include: '*' |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
spring: |
||||||
|
config: |
||||||
|
activate: |
||||||
|
on-profile: local |
||||||
|
|
||||||
|
logging: |
||||||
|
level: |
||||||
|
kr.co.palnet: info |
||||||
|
jdbc: off |
||||||
|
# audit: OFF |
||||||
|
# resultset: OFF |
||||||
|
# resultsettable: INFO #SQL 결과 데이터 Table을 로그로 남긴다. |
||||||
|
# sqlonly: OFF #SQL만 로그로 남긴다. |
||||||
|
# sqltiming: info #SQL과 소요시간을 표기한다. |
||||||
|
# connection : trace # 커넥션 확인가능 |
||||||
|
org.hibernate: off |
||||||
|
# SQL: off |
||||||
|
# type.descriptor.sql.BasicBinder: TRACE |
||||||
|
# com.zaxxer.hikari.HikariConfig: off |
||||||
|
com.zaxxer.hikari: off |
||||||
|
file: |
||||||
|
name: ./logs/kac-app/data.log |
||||||
|
logback: |
||||||
|
rollingpolicy: |
||||||
|
clean-history-on-start: false |
||||||
|
# file-name-pattern: ${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz |
||||||
|
file-name-pattern: ${LOG_FILE}.%d{yyyy-MM-dd}.%i.log |
||||||
|
max-file-size: 10MB |
||||||
|
max-history: 60 |
||||||
|
total-size-cap: 100MB |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
spring: |
||||||
|
config: |
||||||
|
activate: |
||||||
|
on-profile: docker |
||||||
|
|
||||||
|
|
||||||
|
logging: |
||||||
|
level: |
||||||
|
kr.co.palnet: info |
||||||
|
jdbc: off |
||||||
|
org.hibernate: off |
||||||
|
# jdbc: |
||||||
|
# audit: OFF |
||||||
|
# resultset: OFF |
||||||
|
# resultsettable: INFO #SQL 결과 데이터 Table을 로그로 남긴다. |
||||||
|
# sqlonly: OFF #SQL만 로그로 남긴다. |
||||||
|
# sqltiming: info #SQL과 소요시간을 표기한다. |
||||||
|
# connection : trace # 커넥션 확인가능 |
||||||
|
# org.hibernate: |
||||||
|
# SQL: off |
||||||
|
# type.descriptor.sql.BasicBinder: TRACE |
||||||
|
# com.zaxxer.hikari.HikariConfig: DEBUG |
||||||
|
# com.zaxxer.hikari: TRACE |
@ -0,0 +1,2 @@ |
|||||||
|
log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator |
||||||
|
log4jdbc.dump.sql.maxlinelength=0 |
@ -0,0 +1,22 @@ |
|||||||
|
CM001=\uC131\uACF5\uC785\uB2C8\uB2E4. |
||||||
|
CM900=\uC54C \uC218 \uC5C6\uB294 \uC624\uB958\uC785\uB2C8\uB2E4. |
||||||
|
CM999=\uC2E4\uD328\uC785\uB2C8\uB2E4. |
||||||
|
CM002=\uC694\uCCAD\uD558\uC2E0 \uB0B4\uC6A9\uC774 \uCC98\uB9AC \uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. |
||||||
|
CM003=\uC785\uCD9C\uB825 \uC624\uB958\uC785\uB2C8\uB2E4. |
||||||
|
CM100=\uB85C\uADF8\uC778\uC744 \uB2E4\uC2DC \uD574\uC8FC\uC138\uC694. |
||||||
|
CM101=\uC774\uBBF8 \uB85C\uADF8\uC778\uD55C \uC0AC\uC6A9\uC790\uC785\uB2C8\uB2E4. |
||||||
|
CM102=\uB85C\uADF8\uC778 \uC2DC\uB3C4\uAC00 \uC815\uD574\uC9C4 \uD69F\uC218\uB97C \uCD08\uACFC\uD558\uC600\uC2B5\uB2C8\uB2E4. |
||||||
|
CM110=SNS \uB85C\uADF8\uC778\uC744 \uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. |
||||||
|
CM111=\uC774\uBBF8 \uAC00\uC785\uB41C SNS \uACC4\uC815\uC785\uB2C8\uB2E4. |
||||||
|
CM120=\uD1A0\uD070\uC774 \uB9CC\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. |
||||||
|
CM121=\uC798\uBABB\uB41C \uD1A0\uD070\uC785\uB2C8\uB2E4. |
||||||
|
CM130=\uC0AC\uC6A9\uC790\uC758 \uACC4\uC815\uC774 \uC815\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4. |
||||||
|
CM131=\uC0AC\uC6A9\uC790\uC758 \uACC4\uC815\uC774 \uD734\uBA74\uC0C1\uD0DC\uC785\uB2C8\uB2E4. |
||||||
|
CM200=\uCE90\uC2DC \uB85C\uB529\uC5D0 \uC2E4\uD328\uD558\uC600\uC2B5\uB2C8\uB2E4. |
||||||
|
CM201=\uCE90\uC2DC \uB9AC\uB85C\uB529\uC5D0 \uC2E4\uD328\uD558\uC600\uC2B5\uB2C8\uB2E4. |
||||||
|
CM401=\uC0AC\uC6A9\uC790\uC758 \uAD8C\uD55C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. |
||||||
|
WB400=\uD30C\uB77C\uBBF8\uD130\uB97C \uB2E4\uC2DC \uD655\uC778 \uD6C4 \uC694\uCCAD\uD574\uC8FC\uC138\uC694. |
||||||
|
WB500=\uC678\uBD80 \uC5F0\uB3D9 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD558\uC600\uC2B5\uB2C8\uB2E4. |
||||||
|
WB404=\uC694\uCCAD\uD558\uC2E0 URL\uC774 \uC874\uC7AC\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. |
||||||
|
DT001=\uC774\uBBF8 \uB4F1\uB85D\uB41C \uB370\uC774\uD130 \uC785\uB2C8\uB2E4. |
||||||
|
DT002=\uC694\uCCAD\uD55C \uB370\uC774\uD130\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. |
@ -0,0 +1,22 @@ |
|||||||
|
CM001=It's a success. |
||||||
|
CM900=Unknown error. |
||||||
|
CM999=It's a failure. |
||||||
|
CM002=Your request has not been processed. |
||||||
|
CM003=Input/output error. |
||||||
|
CM100=Please log in again. |
||||||
|
CM101=This is a user who has already logged in. |
||||||
|
CM102=The login attempt exceeded the specified number of times. |
||||||
|
CM110=I can't log in to SNS. |
||||||
|
CM111=This is an SNS account that has already been subscribed. |
||||||
|
CM120=The token has expired. |
||||||
|
CM121=Invalid token. |
||||||
|
CM130=Your account has been suspended. |
||||||
|
CM131=Your account is dormant. |
||||||
|
CM200=Cache loading failed. |
||||||
|
CM201=Cache reloading failed. |
||||||
|
CM401=You do not have permissions. |
||||||
|
WB400=Please check the parameters again and request them. |
||||||
|
WB500=External interlocking error occurred. |
||||||
|
WB404=The URL you requested does not exist. |
||||||
|
DT001=Data already registered. |
||||||
|
DT002=No data requested. |
@ -0,0 +1,45 @@ |
|||||||
|
package kr.co.palnet.kac; |
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper; |
||||||
|
import kr.co.palnet.kac.app.KacAppApplication; |
||||||
|
import org.junit.jupiter.api.BeforeEach; |
||||||
|
import org.junit.jupiter.api.extension.ExtendWith; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.boot.test.context.SpringBootTest; |
||||||
|
import org.springframework.restdocs.RestDocumentationContextProvider; |
||||||
|
import org.springframework.restdocs.RestDocumentationExtension; |
||||||
|
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; |
||||||
|
import org.springframework.test.web.servlet.MockMvc; |
||||||
|
import org.springframework.test.web.servlet.setup.MockMvcBuilders; |
||||||
|
import org.springframework.web.context.WebApplicationContext; |
||||||
|
import org.springframework.web.filter.CharacterEncodingFilter; |
||||||
|
|
||||||
|
import static org.springframework.restdocs.operation.preprocess.Preprocessors.modifyUris; |
||||||
|
import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; |
||||||
|
|
||||||
|
@SpringBootTest(classes = KacAppApplication.class) |
||||||
|
@ExtendWith({RestDocumentationExtension.class}) |
||||||
|
public class BaseTest { |
||||||
|
|
||||||
|
@Autowired |
||||||
|
protected WebApplicationContext webApplicationContext; |
||||||
|
|
||||||
|
public MockMvc mockMvc; |
||||||
|
|
||||||
|
@BeforeEach |
||||||
|
public void setUpAll(RestDocumentationContextProvider restDocumentationContextProvider){ |
||||||
|
this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext) // @Autowired로 빈주입 받은 context
|
||||||
|
.addFilter(new CharacterEncodingFilter("UTF-8", true)) // UTF-8 인코딩 필터
|
||||||
|
.apply( |
||||||
|
MockMvcRestDocumentation.documentationConfiguration(restDocumentationContextProvider) |
||||||
|
.operationPreprocessors() |
||||||
|
.withRequestDefaults( |
||||||
|
modifyUris().scheme("http").host("localhost").port(8080), prettyPrint() // URL 정보를 넣어주시면 됩니다.
|
||||||
|
) |
||||||
|
.withResponseDefaults( |
||||||
|
prettyPrint() |
||||||
|
) |
||||||
|
) |
||||||
|
.build(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,600 @@ |
|||||||
|
package kr.co.palnet.kac.com.code; |
||||||
|
|
||||||
|
import kr.co.palnet.kac.BaseTest; |
||||||
|
import kr.co.palnet.kac.api.v1.common.code.model.FormCodeGroupRQ; |
||||||
|
import kr.co.palnet.kac.api.v1.common.code.model.FormCodeLangRQ; |
||||||
|
import kr.co.palnet.kac.api.v1.common.code.model.FormCodeRQ; |
||||||
|
import kr.co.palnet.kac.api.v1.common.code.service.ComCodeService; |
||||||
|
import org.junit.jupiter.api.DisplayName; |
||||||
|
import org.junit.jupiter.api.Test; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.http.MediaType; |
||||||
|
import org.springframework.restdocs.payload.JsonFieldType; |
||||||
|
import org.springframework.transaction.annotation.Propagation; |
||||||
|
import org.springframework.transaction.annotation.Transactional; |
||||||
|
|
||||||
|
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; |
||||||
|
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; |
||||||
|
import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; |
||||||
|
import static org.springframework.restdocs.payload.PayloadDocumentation.*; |
||||||
|
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; |
||||||
|
import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; |
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; |
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; |
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
||||||
|
|
||||||
|
|
||||||
|
public class ComnCodeControllerTest extends BaseTest { |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private ComCodeService comCodeService; |
||||||
|
|
||||||
|
private final String BASE_URL = "/v1/com/code"; |
||||||
|
|
||||||
|
@DisplayName("언어코드 삭제") |
||||||
|
@Test |
||||||
|
public void deleteLang() throws Exception { |
||||||
|
String groupCd = "NEW_GROUP001"; |
||||||
|
String cdId = "NEW_CODE001"; |
||||||
|
String langDivCd = "ko_KR"; |
||||||
|
|
||||||
|
this.mockMvc.perform( |
||||||
|
delete(this.BASE_URL + "/lang?groupCd=" + groupCd + "&cdId=" + cdId + "&langDivCd=" + langDivCd) |
||||||
|
.contentType(MediaType.APPLICATION_JSON_VALUE) |
||||||
|
.accept(MediaType.APPLICATION_JSON_VALUE) |
||||||
|
) |
||||||
|
.andDo(print()) |
||||||
|
.andDo( |
||||||
|
document( |
||||||
|
"com/code/lang/delete", // gradle build를 하게 되면 generated-snippets에 만들어질 폴더이름
|
||||||
|
preprocessResponse(prettyPrint()), |
||||||
|
queryParameters( |
||||||
|
parameterWithName("groupCd").description("그룹 아이디"), |
||||||
|
parameterWithName("cdId").description("코드 아이디"), |
||||||
|
parameterWithName("langDivCd").description("삭제할 언어코드") |
||||||
|
) |
||||||
|
) |
||||||
|
) |
||||||
|
.andExpect(status().isOk()); |
||||||
|
} |
||||||
|
|
||||||
|
@DisplayName("코드 삭제") |
||||||
|
@Test |
||||||
|
public void deleteCode() throws Exception { |
||||||
|
String groupCd = "NEW_GROUP001"; |
||||||
|
String cdId = "NEW_CODE001"; |
||||||
|
|
||||||
|
this.mockMvc.perform( |
||||||
|
delete(this.BASE_URL + "/code?groupCd=" + groupCd + "&cdId=" + cdId) |
||||||
|
.contentType(MediaType.APPLICATION_JSON_VALUE) |
||||||
|
.accept(MediaType.APPLICATION_JSON_VALUE) |
||||||
|
) |
||||||
|
.andDo(print()) |
||||||
|
.andDo( |
||||||
|
document( |
||||||
|
"com/code/code/delete", // gradle build를 하게 되면 generated-snippets에 만들어질 폴더이름
|
||||||
|
preprocessResponse(prettyPrint()), |
||||||
|
queryParameters( |
||||||
|
parameterWithName("groupCd").description("그룹 아이디"), |
||||||
|
parameterWithName("cdId").description("코드 아이디") |
||||||
|
) |
||||||
|
) |
||||||
|
) |
||||||
|
.andExpect(status().isOk()); |
||||||
|
} |
||||||
|
|
||||||
|
@DisplayName("그룹 삭제") |
||||||
|
@Test |
||||||
|
public void deleteCodeGroup() throws Exception { |
||||||
|
|
||||||
|
String deleteGroupCd = "NEW_GROUP001"; |
||||||
|
|
||||||
|
this.mockMvc.perform( |
||||||
|
delete(this.BASE_URL + "/group?groupCd=" + deleteGroupCd) |
||||||
|
.contentType(MediaType.APPLICATION_JSON_VALUE) |
||||||
|
.accept(MediaType.APPLICATION_JSON_VALUE) |
||||||
|
) |
||||||
|
.andDo(print()) |
||||||
|
.andDo( |
||||||
|
document( |
||||||
|
"com/code/group/delete", // gradle build를 하게 되면 generated-snippets에 만들어질 폴더이름
|
||||||
|
preprocessResponse(prettyPrint()), |
||||||
|
queryParameters( |
||||||
|
parameterWithName("groupCd").description("그룹 아이디") |
||||||
|
) |
||||||
|
) |
||||||
|
) |
||||||
|
.andExpect(status().isOk()); |
||||||
|
} |
||||||
|
|
||||||
|
@DisplayName("코드의 언어코드 수정") |
||||||
|
@Test |
||||||
|
@Transactional |
||||||
|
public void updateCodeLang() throws Exception { |
||||||
|
|
||||||
|
String randomNm = "NEW_GROUP_" + System.currentTimeMillis(); |
||||||
|
randomNm = randomNm.substring(0, 20); |
||||||
|
|
||||||
|
FormCodeGroupRQ groupRq = FormCodeGroupRQ.builder().groupCd(randomNm).siteCd("KAC").groupCdNm("신규그룹001").rm("비고....").build(); |
||||||
|
comCodeService.createCodeGroup(groupRq); |
||||||
|
|
||||||
|
FormCodeRQ codeRq = FormCodeRQ.builder().groupCd(randomNm).cdId("NEW_CODE001").upperCd(null).sortOrdr(0).addInfoValue("추가!!").useYn("Y").build(); |
||||||
|
comCodeService.createCode(codeRq); |
||||||
|
|
||||||
|
FormCodeLangRQ langRq = FormCodeLangRQ.builder().groupCd(randomNm).cdId("NEW_CODE001").langDivCd("ko_KR").cdNm("신규코드001").rm("비고....").build(); |
||||||
|
comCodeService.createCodeLang(langRq); |
||||||
|
|
||||||
|
String jsonRQ = """ |
||||||
|
{ |
||||||
|
"groupCd": "%s", |
||||||
|
"cdId": "NEW_CODE001", |
||||||
|
"langDivCd": "ko_KR", |
||||||
|
"cdNm": "신규코드001", |
||||||
|
"rm": "수정..." |
||||||
|
} |
||||||
|
""".formatted(randomNm); |
||||||
|
|
||||||
|
this.mockMvc.perform( |
||||||
|
put(this.BASE_URL + "/lang") |
||||||
|
.contentType(MediaType.APPLICATION_JSON_VALUE) |
||||||
|
.content(jsonRQ) |
||||||
|
.accept(MediaType.APPLICATION_JSON_VALUE) |
||||||
|
) |
||||||
|
.andDo(print()) |
||||||
|
.andDo( |
||||||
|
document( |
||||||
|
"com/code/lang/update", // gradle build를 하게 되면 generated-snippets에 만들어질 폴더이름
|
||||||
|
preprocessResponse(prettyPrint()), |
||||||
|
requestFields( |
||||||
|
fieldWithPath("groupCd").type(JsonFieldType.STRING).description("그룹코드"), |
||||||
|
fieldWithPath("cdId").type(JsonFieldType.STRING).description("코드 아이디"), |
||||||
|
fieldWithPath("langDivCd").type(JsonFieldType.STRING).description("언어코드"), |
||||||
|
fieldWithPath("cdNm").type(JsonFieldType.STRING).description("코드이름[언어별]"), |
||||||
|
fieldWithPath("rm").type(JsonFieldType.STRING).description("비고") |
||||||
|
), |
||||||
|
responseFields( |
||||||
|
fieldWithPath("groupCd").type(JsonFieldType.STRING).description("그룹코드"), |
||||||
|
fieldWithPath("cdId").type(JsonFieldType.STRING).description("코드 아이디"), |
||||||
|
fieldWithPath("langDivCd").type(JsonFieldType.STRING).description("언어코드"), |
||||||
|
fieldWithPath("cdNm").type(JsonFieldType.STRING).description("코드이름[언어별]"), |
||||||
|
fieldWithPath("rm").type(JsonFieldType.STRING).description("비고"), |
||||||
|
fieldWithPath("createUserId").type(JsonFieldType.STRING).description("등록한 유저 아이디"), |
||||||
|
fieldWithPath("createDt").type(JsonFieldType.STRING).description("등록일시"), |
||||||
|
fieldWithPath("updateUserId").type(JsonFieldType.STRING).description("수정한 유저 아이디"), |
||||||
|
fieldWithPath("updateDt").type(JsonFieldType.STRING).description("수정일시") |
||||||
|
) |
||||||
|
) |
||||||
|
) |
||||||
|
.andExpect(status().isOk()); |
||||||
|
} |
||||||
|
|
||||||
|
@DisplayName("코드 수정") |
||||||
|
@Test |
||||||
|
@Transactional |
||||||
|
public void updateCode() throws Exception { |
||||||
|
|
||||||
|
String randomNm = "NEW_GROUP_" + System.currentTimeMillis(); |
||||||
|
randomNm = randomNm.substring(0, 20); |
||||||
|
|
||||||
|
FormCodeGroupRQ groupRq = FormCodeGroupRQ.builder().groupCd(randomNm).siteCd("KAC").groupCdNm("신규그룹001").rm("비고....").build(); |
||||||
|
comCodeService.createCodeGroup(groupRq); |
||||||
|
|
||||||
|
FormCodeRQ codeRq = FormCodeRQ.builder().groupCd(randomNm).cdId("NEW_CODE001").upperCd(null).sortOrdr(0).addInfoValue("추가!!").useYn("Y").build(); |
||||||
|
comCodeService.createCode(codeRq); |
||||||
|
|
||||||
|
FormCodeLangRQ langRq = FormCodeLangRQ.builder().groupCd(randomNm).cdId("NEW_CODE001").langDivCd("ko_KR").cdNm("신규코드001").rm("비고....").build(); |
||||||
|
comCodeService.createCodeLang(langRq); |
||||||
|
|
||||||
|
String jsonRQ = """ |
||||||
|
{ |
||||||
|
"groupCd": "%s", |
||||||
|
"cdId": "NEW_CODE001", |
||||||
|
"sortOrdr": 0, |
||||||
|
"addInfoValue": "수정!!", |
||||||
|
"useYn": "Y" |
||||||
|
} |
||||||
|
""".formatted(randomNm); |
||||||
|
|
||||||
|
this.mockMvc.perform( |
||||||
|
put(this.BASE_URL + "/code") |
||||||
|
.contentType(MediaType.APPLICATION_JSON_VALUE) |
||||||
|
.content(jsonRQ) |
||||||
|
.accept(MediaType.APPLICATION_JSON_VALUE) |
||||||
|
) |
||||||
|
.andDo(print()) |
||||||
|
.andDo( |
||||||
|
document( |
||||||
|
"com/code/code/update", // gradle build를 하게 되면 generated-snippets에 만들어질 폴더이름
|
||||||
|
preprocessResponse(prettyPrint()), |
||||||
|
requestFields( |
||||||
|
fieldWithPath("groupCd").type(JsonFieldType.STRING).description("그룹코드"), |
||||||
|
fieldWithPath("cdId").type(JsonFieldType.STRING).description("코드 아이디"), |
||||||
|
fieldWithPath("sortOrdr").type(JsonFieldType.NUMBER).description("정렬순서"), |
||||||
|
fieldWithPath("addInfoValue").type(JsonFieldType.STRING).description("추가사항"), |
||||||
|
fieldWithPath("useYn").type(JsonFieldType.STRING).description("사용여부") |
||||||
|
), |
||||||
|
responseFields( |
||||||
|
fieldWithPath("groupCd").type(JsonFieldType.STRING).description("그룹코드"), |
||||||
|
fieldWithPath("cdId").type(JsonFieldType.STRING).description("코드 아이디"), |
||||||
|
fieldWithPath("upperCd").type(JsonFieldType.STRING).description("상위 아이디").optional(), |
||||||
|
fieldWithPath("sortOrdr").type(JsonFieldType.NUMBER).description("정렬순서"), |
||||||
|
fieldWithPath("addInfoValue").type(JsonFieldType.STRING).description("추가사항"), |
||||||
|
fieldWithPath("useYn").type(JsonFieldType.STRING).description("사용여부"), |
||||||
|
fieldWithPath("createUserId").type(JsonFieldType.STRING).description("등록한 유저 아이디"), |
||||||
|
fieldWithPath("createDt").type(JsonFieldType.STRING).description("등록일시"), |
||||||
|
fieldWithPath("updateUserId").type(JsonFieldType.STRING).description("수정한 유저 아이디"), |
||||||
|
fieldWithPath("updateDt").type(JsonFieldType.STRING).description("수정일시") |
||||||
|
) |
||||||
|
) |
||||||
|
) |
||||||
|
.andExpect(status().isOk()); |
||||||
|
} |
||||||
|
|
||||||
|
@DisplayName("그룹 수정") |
||||||
|
@Test |
||||||
|
@Transactional |
||||||
|
public void updateCodeGroup() throws Exception { |
||||||
|
|
||||||
|
String randomNm = "NEW_GROUP_" + System.currentTimeMillis(); |
||||||
|
randomNm = randomNm.substring(0, 20); |
||||||
|
|
||||||
|
FormCodeGroupRQ groupRq = FormCodeGroupRQ.builder().groupCd(randomNm).siteCd("KAC").groupCdNm("신규그룹001").rm("비고....").build(); |
||||||
|
comCodeService.createCodeGroup(groupRq); |
||||||
|
|
||||||
|
|
||||||
|
String jsonRQ = """ |
||||||
|
{ |
||||||
|
"groupCd": "%s", |
||||||
|
"siteCd": "KAC", |
||||||
|
"groupCdNm": "신규그룹001", |
||||||
|
"rm": "비고...." |
||||||
|
} |
||||||
|
""".formatted(randomNm); |
||||||
|
|
||||||
|
this.mockMvc.perform( |
||||||
|
put(this.BASE_URL + "/group") |
||||||
|
.contentType(MediaType.APPLICATION_JSON_VALUE) |
||||||
|
.content(jsonRQ) |
||||||
|
.accept(MediaType.APPLICATION_JSON_VALUE) |
||||||
|
) |
||||||
|
.andDo(print()) |
||||||
|
.andDo( |
||||||
|
document( |
||||||
|
"com/code/group/update", // gradle build를 하게 되면 generated-snippets에 만들어질 폴더이름
|
||||||
|
preprocessResponse(prettyPrint()), |
||||||
|
requestFields( |
||||||
|
fieldWithPath("groupCd").type(JsonFieldType.STRING).description("그룹코드"), |
||||||
|
fieldWithPath("siteCd").type(JsonFieldType.STRING).description("사이트코드"), |
||||||
|
fieldWithPath("groupCdNm").type(JsonFieldType.STRING).description("그룹이름"), |
||||||
|
fieldWithPath("rm").type(JsonFieldType.STRING).description("비고") |
||||||
|
), |
||||||
|
responseFields( |
||||||
|
fieldWithPath("groupCd").type(JsonFieldType.STRING).description("그룹코드"), |
||||||
|
fieldWithPath("siteCd").type(JsonFieldType.STRING).description("사이트코드"), |
||||||
|
fieldWithPath("groupCdNm").type(JsonFieldType.STRING).description("그룹이름"), |
||||||
|
fieldWithPath("rm").type(JsonFieldType.STRING).description("비고"), |
||||||
|
fieldWithPath("createUserId").type(JsonFieldType.STRING).description("등록한 유저 아이디"), |
||||||
|
fieldWithPath("createDt").type(JsonFieldType.STRING).description("등록일시"), |
||||||
|
fieldWithPath("updateUserId").type(JsonFieldType.STRING).description("수정한 유저 아이디"), |
||||||
|
fieldWithPath("updateDt").type(JsonFieldType.STRING).description("수정일시") |
||||||
|
) |
||||||
|
) |
||||||
|
) |
||||||
|
.andExpect(status().isOk()); |
||||||
|
} |
||||||
|
|
||||||
|
@DisplayName("코드의 언어코드 추가") |
||||||
|
@Test |
||||||
|
@Transactional |
||||||
|
public void createCodeLang() throws Exception { |
||||||
|
|
||||||
|
String langDivCd = "ko_KR"; |
||||||
|
|
||||||
|
String randomNm = "NEW_GROUP_" + System.currentTimeMillis(); |
||||||
|
randomNm = randomNm.substring(0, 20); |
||||||
|
|
||||||
|
FormCodeGroupRQ groupRq = FormCodeGroupRQ.builder().groupCd(randomNm).siteCd("KAC").groupCdNm("신규그룹001").rm("비고....").build(); |
||||||
|
comCodeService.createCodeGroup(groupRq); |
||||||
|
|
||||||
|
FormCodeRQ codeRq = FormCodeRQ.builder().groupCd(randomNm).cdId("NEW_CODE001").upperCd(null).sortOrdr(0).addInfoValue("추가!!").useYn("Y").build(); |
||||||
|
comCodeService.createCode(codeRq); |
||||||
|
|
||||||
|
String jsonRQ = """ |
||||||
|
{ |
||||||
|
"groupCd": "%s", |
||||||
|
"cdId": "NEW_CODE001", |
||||||
|
"langDivCd": "%s", |
||||||
|
"cdNm": "신규코드001", |
||||||
|
"rm": "비고...." |
||||||
|
} |
||||||
|
""".formatted(randomNm, langDivCd); |
||||||
|
|
||||||
|
this.mockMvc.perform( |
||||||
|
post(this.BASE_URL + "/lang") |
||||||
|
.contentType(MediaType.APPLICATION_JSON_VALUE) |
||||||
|
.content(jsonRQ) |
||||||
|
.accept(MediaType.APPLICATION_JSON_VALUE) |
||||||
|
) |
||||||
|
.andDo(print()) |
||||||
|
.andDo( |
||||||
|
document( |
||||||
|
"com/code/lang/create", // gradle build를 하게 되면 generated-snippets에 만들어질 폴더이름
|
||||||
|
preprocessResponse(prettyPrint()), |
||||||
|
requestFields( |
||||||
|
fieldWithPath("groupCd").type(JsonFieldType.STRING).description("그룹코드"), |
||||||
|
fieldWithPath("cdId").type(JsonFieldType.STRING).description("코드 아이디"), |
||||||
|
fieldWithPath("langDivCd").type(JsonFieldType.STRING).description("언어코드"), |
||||||
|
fieldWithPath("cdNm").type(JsonFieldType.STRING).description("코드이름[언어별]"), |
||||||
|
fieldWithPath("rm").type(JsonFieldType.STRING).description("비고") |
||||||
|
), |
||||||
|
responseFields( |
||||||
|
fieldWithPath("groupCd").type(JsonFieldType.STRING).description("그룹코드"), |
||||||
|
fieldWithPath("cdId").type(JsonFieldType.STRING).description("코드 아이디"), |
||||||
|
fieldWithPath("langDivCd").type(JsonFieldType.STRING).description("언어코드"), |
||||||
|
fieldWithPath("cdNm").type(JsonFieldType.STRING).description("코드이름[언어별]"), |
||||||
|
fieldWithPath("rm").type(JsonFieldType.STRING).description("비고"), |
||||||
|
fieldWithPath("createUserId").type(JsonFieldType.STRING).description("등록한 유저 아이디"), |
||||||
|
fieldWithPath("createDt").type(JsonFieldType.STRING).description("등록일시"), |
||||||
|
fieldWithPath("updateUserId").type(JsonFieldType.STRING).description("수정한 유저 아이디"), |
||||||
|
fieldWithPath("updateDt").type(JsonFieldType.STRING).description("수정일시") |
||||||
|
) |
||||||
|
) |
||||||
|
) |
||||||
|
.andExpect(status().isOk()); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@DisplayName("코드 추가") |
||||||
|
@Test |
||||||
|
@Transactional |
||||||
|
public void createCode() throws Exception { |
||||||
|
String codeNm = "NEW_CODE001"; |
||||||
|
|
||||||
|
String randomNm = "NEW_GROUP_" + System.currentTimeMillis(); |
||||||
|
randomNm = randomNm.substring(0, 20); |
||||||
|
|
||||||
|
FormCodeGroupRQ groupRq = FormCodeGroupRQ.builder().groupCd(randomNm).siteCd("KAC").groupCdNm("신규그룹001").rm("비고....").build(); |
||||||
|
comCodeService.createCodeGroup(groupRq); |
||||||
|
|
||||||
|
String jsonRQ = """ |
||||||
|
{ |
||||||
|
"groupCd": "%s", |
||||||
|
"cdId": "%s", |
||||||
|
"sortOrdr": 0, |
||||||
|
"addInfoValue": "추가!!", |
||||||
|
"useYn": "Y" |
||||||
|
} |
||||||
|
""".formatted(randomNm, codeNm); |
||||||
|
|
||||||
|
this.mockMvc.perform( |
||||||
|
post(this.BASE_URL + "/code") |
||||||
|
.contentType(MediaType.APPLICATION_JSON_VALUE) |
||||||
|
.content(jsonRQ) |
||||||
|
.accept(MediaType.APPLICATION_JSON_VALUE) |
||||||
|
) |
||||||
|
.andDo(print()) |
||||||
|
.andDo( |
||||||
|
document( |
||||||
|
"com/code/code/create", // gradle build를 하게 되면 generated-snippets에 만들어질 폴더이름
|
||||||
|
preprocessResponse(prettyPrint()), |
||||||
|
requestFields( |
||||||
|
fieldWithPath("groupCd").type(JsonFieldType.STRING).description("그룹코드"), |
||||||
|
fieldWithPath("cdId").type(JsonFieldType.STRING).description("코드 아이디"), |
||||||
|
fieldWithPath("sortOrdr").type(JsonFieldType.NUMBER).description("정렬순서"), |
||||||
|
fieldWithPath("addInfoValue").type(JsonFieldType.STRING).description("추가사항"), |
||||||
|
fieldWithPath("useYn").type(JsonFieldType.STRING).description("사용여부") |
||||||
|
), |
||||||
|
responseFields( |
||||||
|
fieldWithPath("groupCd").type(JsonFieldType.STRING).description("그룹코드"), |
||||||
|
fieldWithPath("cdId").type(JsonFieldType.STRING).description("코드 아이디"), |
||||||
|
fieldWithPath("upperCd").type(JsonFieldType.STRING).description("상위 아이디").optional(), |
||||||
|
fieldWithPath("sortOrdr").type(JsonFieldType.NUMBER).description("정렬순서"), |
||||||
|
fieldWithPath("addInfoValue").type(JsonFieldType.STRING).description("추가사항"), |
||||||
|
fieldWithPath("useYn").type(JsonFieldType.STRING).description("사용여부"), |
||||||
|
fieldWithPath("createUserId").type(JsonFieldType.STRING).description("등록한 유저 아이디"), |
||||||
|
fieldWithPath("createDt").type(JsonFieldType.STRING).description("등록일시"), |
||||||
|
fieldWithPath("updateUserId").type(JsonFieldType.STRING).description("수정한 유저 아이디"), |
||||||
|
fieldWithPath("updateDt").type(JsonFieldType.STRING).description("수정일시") |
||||||
|
) |
||||||
|
) |
||||||
|
) |
||||||
|
.andExpect(status().isOk()); |
||||||
|
} |
||||||
|
|
||||||
|
@DisplayName("코드그룹 추가") |
||||||
|
@Test |
||||||
|
@Transactional |
||||||
|
public void createCodeGroup() throws Exception { |
||||||
|
|
||||||
|
String randomNm = "NEW_GROUP_" + System.currentTimeMillis(); |
||||||
|
randomNm = randomNm.substring(0, 20); |
||||||
|
|
||||||
|
String jsonRQ = """ |
||||||
|
{ |
||||||
|
"groupCd": "%s", |
||||||
|
"siteCd": "KAC", |
||||||
|
"groupCdNm": "신규그룹001", |
||||||
|
"rm": "비고...." |
||||||
|
} |
||||||
|
""".formatted(randomNm); |
||||||
|
|
||||||
|
this.mockMvc.perform( |
||||||
|
post(this.BASE_URL + "/group") |
||||||
|
.contentType(MediaType.APPLICATION_JSON_VALUE) |
||||||
|
.content(jsonRQ) |
||||||
|
.accept(MediaType.APPLICATION_JSON_VALUE) |
||||||
|
) |
||||||
|
.andDo(print()) |
||||||
|
.andDo( |
||||||
|
document( |
||||||
|
"com/code/group/create", // gradle build를 하게 되면 generated-snippets에 만들어질 폴더이름
|
||||||
|
preprocessResponse(prettyPrint()), |
||||||
|
requestFields( |
||||||
|
fieldWithPath("groupCd").type(JsonFieldType.STRING).description("그룹코드"), |
||||||
|
fieldWithPath("siteCd").type(JsonFieldType.STRING).description("사이트코드"), |
||||||
|
fieldWithPath("groupCdNm").type(JsonFieldType.STRING).description("그룹이름"), |
||||||
|
fieldWithPath("rm").type(JsonFieldType.STRING).description("비고") |
||||||
|
), |
||||||
|
responseFields( |
||||||
|
fieldWithPath("groupCd").type(JsonFieldType.STRING).description("그룹코드"), |
||||||
|
fieldWithPath("siteCd").type(JsonFieldType.STRING).description("사이트코드"), |
||||||
|
fieldWithPath("groupCdNm").type(JsonFieldType.STRING).description("그룹이름"), |
||||||
|
fieldWithPath("rm").type(JsonFieldType.STRING).description("비고"), |
||||||
|
fieldWithPath("createUserId").type(JsonFieldType.STRING).description("등록한 유저 아이디"), |
||||||
|
fieldWithPath("createDt").type(JsonFieldType.STRING).description("등록일시"), |
||||||
|
fieldWithPath("updateUserId").type(JsonFieldType.STRING).description("수정한 유저 아이디"), |
||||||
|
fieldWithPath("updateDt").type(JsonFieldType.STRING).description("수정일시") |
||||||
|
) |
||||||
|
) |
||||||
|
) |
||||||
|
.andExpect(status().isOk()); |
||||||
|
} |
||||||
|
|
||||||
|
@DisplayName("모든 코드 조회") |
||||||
|
@Test |
||||||
|
@Transactional(propagation = Propagation.SUPPORTS) |
||||||
|
public void getAllCodeByGroup() throws Exception { |
||||||
|
|
||||||
|
String randomNm = "NEW_GROUP_" + System.currentTimeMillis(); |
||||||
|
randomNm = randomNm.substring(0, 20); |
||||||
|
|
||||||
|
FormCodeGroupRQ groupRq = FormCodeGroupRQ.builder().groupCd(randomNm).siteCd("KAC").groupCdNm("신규그룹001").rm("비고....").build(); |
||||||
|
comCodeService.createCodeGroup(groupRq); |
||||||
|
|
||||||
|
FormCodeRQ codeRq = FormCodeRQ.builder().groupCd(randomNm).cdId("NEW_CODE001").upperCd(null).sortOrdr(0).addInfoValue("추가!!").useYn("Y").build(); |
||||||
|
comCodeService.createCode(codeRq); |
||||||
|
|
||||||
|
FormCodeLangRQ langRq = FormCodeLangRQ.builder().groupCd(randomNm).cdId("NEW_CODE001").langDivCd("ko_KR").cdNm("신규코드001").rm("비고....").build(); |
||||||
|
comCodeService.createCodeLang(langRq); |
||||||
|
|
||||||
|
String langDivCd = "ko_KR"; |
||||||
|
|
||||||
|
this.mockMvc.perform( |
||||||
|
get(this.BASE_URL + "/code") |
||||||
|
.param("groupCd", randomNm) |
||||||
|
.param("langDivCd", langDivCd) |
||||||
|
.contentType(MediaType.APPLICATION_JSON_VALUE) |
||||||
|
.accept(MediaType.APPLICATION_JSON_VALUE) |
||||||
|
) |
||||||
|
.andDo(print()) |
||||||
|
.andDo( |
||||||
|
document( |
||||||
|
"com/code/code", // gradle build를 하게 되면 generated-snippets에 만들어질 폴더이름
|
||||||
|
preprocessResponse(prettyPrint()), |
||||||
|
queryParameters( |
||||||
|
parameterWithName("groupCd").description("그룹코드"), |
||||||
|
parameterWithName("langDivCd").description("언어코드") |
||||||
|
), |
||||||
|
responseFields( |
||||||
|
fieldWithPath("[].groupCd").type(JsonFieldType.STRING).description("코드의 그룹").optional(), |
||||||
|
fieldWithPath("[].cdId").type(JsonFieldType.STRING).description("코드의 ID").optional(), |
||||||
|
fieldWithPath("[].cdNm").type(JsonFieldType.STRING).description("코드의 이름").optional(), |
||||||
|
fieldWithPath("[].upperCd").type(JsonFieldType.STRING).description("상위 코드의 이름").optional(), |
||||||
|
fieldWithPath("[].sortOrdr").type(JsonFieldType.NUMBER).description("코드의 정렬순서").optional(), |
||||||
|
fieldWithPath("[].addInfoValue").type(JsonFieldType.STRING).description("코드의 추가 밸류").optional(), |
||||||
|
fieldWithPath("[].children").type(JsonFieldType.STRING).description("코드의 하위요소").optional() |
||||||
|
) |
||||||
|
) |
||||||
|
) |
||||||
|
.andExpect(status().isOk()); |
||||||
|
|
||||||
|
// comCodeService.deleteLang(randomNm, "NEW_CODE001", "ko_KR");
|
||||||
|
// comCodeService.deleteCode(randomNm, "NEW_CODE001");
|
||||||
|
comCodeService.deleteCodeGroup(randomNm); |
||||||
|
} |
||||||
|
|
||||||
|
@DisplayName("모든 그룹과 코드 조회") |
||||||
|
@Test |
||||||
|
@Transactional |
||||||
|
public void getAllGroupCode() throws Exception { |
||||||
|
|
||||||
|
String randomNm = "NEW_GROUP_" + System.currentTimeMillis(); |
||||||
|
randomNm = randomNm.substring(0, 20); |
||||||
|
|
||||||
|
FormCodeGroupRQ groupRq = FormCodeGroupRQ.builder().groupCd(randomNm).siteCd("KAC").groupCdNm("신규그룹001").rm("비고....").build(); |
||||||
|
comCodeService.createCodeGroup(groupRq); |
||||||
|
|
||||||
|
FormCodeRQ codeRq = FormCodeRQ.builder().groupCd(randomNm).cdId("NEW_CODE001").upperCd(null).sortOrdr(0).addInfoValue("추가!!").useYn("Y").build(); |
||||||
|
comCodeService.createCode(codeRq); |
||||||
|
|
||||||
|
FormCodeLangRQ langRq = FormCodeLangRQ.builder().groupCd(randomNm).cdId("NEW_CODE001").langDivCd("ko_KR").cdNm("신규코드001").rm("비고....").build(); |
||||||
|
comCodeService.createCodeLang(langRq); |
||||||
|
|
||||||
|
this.mockMvc.perform( |
||||||
|
get(this.BASE_URL + "/group") |
||||||
|
.contentType(MediaType.APPLICATION_JSON_VALUE) |
||||||
|
.accept(MediaType.APPLICATION_JSON_VALUE) |
||||||
|
) |
||||||
|
.andDo(print()) |
||||||
|
.andDo( |
||||||
|
document( |
||||||
|
"com/code/group", // gradle build를 하게 되면 generated-snippets에 만들어질 폴더이름
|
||||||
|
preprocessResponse(prettyPrint()), |
||||||
|
responseFields( |
||||||
|
fieldWithPath("[].groupCd").type(JsonFieldType.STRING).description("그룹 코드").optional(), |
||||||
|
fieldWithPath("[].siteCd").type(JsonFieldType.STRING).description("사이트 코드").optional(), |
||||||
|
fieldWithPath("[].groupCdNm").type(JsonFieldType.STRING).description("그룹 명").optional(), |
||||||
|
fieldWithPath("[].rm").type(JsonFieldType.STRING).description("비고").optional(), |
||||||
|
fieldWithPath("[].codeList").type(JsonFieldType.ARRAY).description("그룹에 포함된 코드 리스트").optional() |
||||||
|
) |
||||||
|
) |
||||||
|
) |
||||||
|
.andExpect(status().isOk()); |
||||||
|
} |
||||||
|
|
||||||
|
@DisplayName("모든 그룹 조회") |
||||||
|
@Test |
||||||
|
@Transactional |
||||||
|
public void getAllGroup() throws Exception { |
||||||
|
|
||||||
|
String randomNm = "NEW_GROUP_" + System.currentTimeMillis(); |
||||||
|
randomNm = randomNm.substring(0, 20); |
||||||
|
|
||||||
|
FormCodeGroupRQ groupRq = FormCodeGroupRQ.builder().groupCd(randomNm).siteCd("KAC").groupCdNm("신규그룹001").rm("비고....").build(); |
||||||
|
comCodeService.createCodeGroup(groupRq); |
||||||
|
|
||||||
|
FormCodeRQ codeRq = FormCodeRQ.builder().groupCd(randomNm).cdId("NEW_CODE001").upperCd(null).sortOrdr(0).addInfoValue("추가!!").useYn("Y").build(); |
||||||
|
comCodeService.createCode(codeRq); |
||||||
|
|
||||||
|
FormCodeLangRQ langRq = FormCodeLangRQ.builder().groupCd(randomNm).cdId("NEW_CODE001").langDivCd("ko_KR").cdNm("신규코드001").rm("비고....").build(); |
||||||
|
comCodeService.createCodeLang(langRq); |
||||||
|
|
||||||
|
String siteCd = "KAC"; |
||||||
|
String langDivCd = "ko_KR"; |
||||||
|
|
||||||
|
this.mockMvc.perform( |
||||||
|
get(this.BASE_URL + "/all") |
||||||
|
.param("siteCd", siteCd) |
||||||
|
.param("langDivCd", langDivCd) |
||||||
|
.contentType(MediaType.APPLICATION_JSON_VALUE) |
||||||
|
.accept(MediaType.APPLICATION_JSON_VALUE) |
||||||
|
) |
||||||
|
.andDo(print()) |
||||||
|
.andDo( |
||||||
|
document( |
||||||
|
"com/code/all", // gradle build를 하게 되면 generated-snippets에 만들어질 폴더이름
|
||||||
|
preprocessResponse(prettyPrint()), |
||||||
|
queryParameters( |
||||||
|
parameterWithName("siteCd").description("사이트코드"), |
||||||
|
parameterWithName("langDivCd").description("언어코드") |
||||||
|
), |
||||||
|
responseFields( |
||||||
|
fieldWithPath("[].groupCd").type(JsonFieldType.STRING).description("그룹 코드").optional(), |
||||||
|
fieldWithPath("[].siteCd").type(JsonFieldType.STRING).description("사이트 코드").optional(), |
||||||
|
fieldWithPath("[].groupCdNm").type(JsonFieldType.STRING).description("그룹 명").optional(), |
||||||
|
fieldWithPath("[].rm").type(JsonFieldType.STRING).description("비고").optional(), |
||||||
|
fieldWithPath("[].codeList").type(JsonFieldType.ARRAY).description("그룹에 포함된 코드 리스트").optional(), |
||||||
|
fieldWithPath("[].codeList[].groupCd").type(JsonFieldType.STRING).description("코드의 그룹").optional(), |
||||||
|
fieldWithPath("[].codeList[].cdId").type(JsonFieldType.STRING).description("코드의 ID").optional(), |
||||||
|
fieldWithPath("[].codeList[].cdNm").type(JsonFieldType.STRING).description("코드의 이름").optional(), |
||||||
|
fieldWithPath("[].codeList[].upperCd").type(JsonFieldType.STRING).description("상위 코드의 이름").optional(), |
||||||
|
fieldWithPath("[].codeList[].sortOrdr").type(JsonFieldType.NUMBER).description("코드의 정렬순서").optional(), |
||||||
|
fieldWithPath("[].codeList[].addInfoValue").type(JsonFieldType.STRING).description("코드의 추가 밸류").optional(), |
||||||
|
fieldWithPath("[].codeList[].children").type(JsonFieldType.STRING).description("코드의 하위요소").optional() |
||||||
|
) |
||||||
|
) |
||||||
|
) |
||||||
|
.andExpect(status().isOk()); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,11 @@ |
|||||||
|
.+{{path}}+ |
||||||
|
|=== |
||||||
|
|파라미터|설명|필수 |
||||||
|
|
||||||
|
{{#parameters}} |
||||||
|
|{{#tableCellContent}}`+{{name}}+`{{/tableCellContent}} |
||||||
|
|{{#tableCellContent}}{{description}}{{/tableCellContent}} |
||||||
|
|{{#tableCellContent}}{{#optional}}false{{/optional}}{{^optional}}true{{/optional}}{{/tableCellContent}} |
||||||
|
|
||||||
|
{{/parameters}} |
||||||
|
|=== |
@ -0,0 +1,10 @@ |
|||||||
|
|=== |
||||||
|
|파라미터|설명|필수 |
||||||
|
|
||||||
|
{{#parameters}} |
||||||
|
|{{#tableCellContent}}`+{{name}}+`{{/tableCellContent}} |
||||||
|
|{{#tableCellContent}}{{description}}{{/tableCellContent}} |
||||||
|
|{{#tableCellContent}}{{#optional}}false{{/optional}}{{^optional}}true{{/optional}}{{/tableCellContent}} |
||||||
|
|
||||||
|
{{/parameters}} |
||||||
|
|=== |
@ -0,0 +1,12 @@ |
|||||||
|
|=== |
||||||
|
|파라미터|타입|설명|필수 |
||||||
|
|
||||||
|
{{#fields}} |
||||||
|
|
||||||
|
|{{#tableCellContent}}`+{{path}}+`{{/tableCellContent}} |
||||||
|
|{{#tableCellContent}}`+{{type}}+`{{/tableCellContent}} |
||||||
|
|{{#tableCellContent}}{{description}}{{/tableCellContent}} |
||||||
|
|{{#tableCellContent}}{{#optional}}false{{/optional}}{{^optional}}true{{/optional}}{{/tableCellContent}} |
||||||
|
|
||||||
|
{{/fields}} |
||||||
|
|=== |
@ -0,0 +1,10 @@ |
|||||||
|
|=== |
||||||
|
|파라미터|설명|필수 |
||||||
|
|
||||||
|
{{#headers}} |
||||||
|
|{{#tableCellContent}}`+{{name}}+`{{/tableCellContent}} |
||||||
|
|{{#tableCellContent}}{{description}}{{/tableCellContent}} |
||||||
|
|{{#tableCellContent}}{{#optional}}false{{/optional}}{{^optional}}true{{/optional}}{{/tableCellContent}} |
||||||
|
|
||||||
|
{{/headers}} |
||||||
|
|=== |
@ -0,0 +1,12 @@ |
|||||||
|
|=== |
||||||
|
|파라미터|타입|설명|필수 |
||||||
|
|
||||||
|
{{#fields}} |
||||||
|
|
||||||
|
|{{#tableCellContent}}`+{{path}}+`{{/tableCellContent}} |
||||||
|
|{{#tableCellContent}}`+{{type}}+`{{/tableCellContent}} |
||||||
|
|{{#tableCellContent}}{{description}}{{/tableCellContent}} |
||||||
|
|{{#tableCellContent}}{{#optional}}false{{/optional}}{{^optional}}true{{/optional}}{{/tableCellContent}} |
||||||
|
|
||||||
|
{{/fields}} |
||||||
|
|=== |
@ -0,0 +1,12 @@ |
|||||||
|
FROM openjdk:21 |
||||||
|
|
||||||
|
ENV TZ=Asia/Seoul |
||||||
|
#ENV JAVA_OPTS="-Xms512M -Xmx512M" |
||||||
|
ENV JAVA_OPTS="" |
||||||
|
|
||||||
|
EXPOSE 8003 |
||||||
|
|
||||||
|
WORKDIR /app |
||||||
|
|
||||||
|
#ENTRYPOINT ["java",${JAVA_OPTS},"-jar","/app/kac-socket-app-1.0.0.jar"] |
||||||
|
CMD java $JAVA_OPTS -jar /app/kac-socket-app-1.0.0.jar |
@ -0,0 +1,10 @@ |
|||||||
|
dependencies { |
||||||
|
|
||||||
|
implementation "$boot:spring-boot-starter" |
||||||
|
implementation "$spring:spring-web" |
||||||
|
implementation "io.netty:netty-all:4.1.68.Final" |
||||||
|
implementation project(":common:util") |
||||||
|
implementation project(":common:model") |
||||||
|
|
||||||
|
} |
||||||
|
|
@ -0,0 +1,15 @@ |
|||||||
|
package kr.co.palnet.kac.socket; |
||||||
|
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication; |
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication; |
||||||
|
import org.springframework.scheduling.annotation.EnableScheduling; |
||||||
|
|
||||||
|
|
||||||
|
@EnableScheduling |
||||||
|
@SpringBootApplication |
||||||
|
public class KacSocketApplication { |
||||||
|
public static void main(String[] args) { |
||||||
|
SpringApplication.run(KacSocketApplication.class, args); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,31 @@ |
|||||||
|
package kr.co.palnet.kac.socket.core.codec; |
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper; |
||||||
|
import io.netty.channel.ChannelHandlerContext; |
||||||
|
import io.netty.handler.codec.MessageToMessageDecoder; |
||||||
|
import kr.co.palnet.kac.socket.core.model.DroneRq; |
||||||
|
import kr.co.palnet.kac.util.ObjectMapperUtil; |
||||||
|
import lombok.RequiredArgsConstructor; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
|
||||||
|
@Slf4j |
||||||
|
@RequiredArgsConstructor |
||||||
|
public class DroneDecoder extends MessageToMessageDecoder<String> { |
||||||
|
|
||||||
|
private final ObjectMapper objectMapper = ObjectMapperUtil.getObjectMapper(); |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void decode(ChannelHandlerContext ctx, String in, List<Object> out) throws Exception { |
||||||
|
try { |
||||||
|
DroneRq droneRq = objectMapper.readValue(in, DroneRq.class); |
||||||
|
if (droneRq != null) { |
||||||
|
out.add(droneRq); |
||||||
|
} |
||||||
|
} catch (Exception e) { |
||||||
|
log.warn("decode parsing error : {}\ndata in : {}", e.getMessage(), in); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
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.util.ObjectMapperUtil; |
||||||
|
import lombok.RequiredArgsConstructor; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
@Slf4j |
||||||
|
@RequiredArgsConstructor |
||||||
|
public class DroneEncoder extends MessageToMessageEncoder<Object> { |
||||||
|
|
||||||
|
private final ObjectMapper objectMapper = ObjectMapperUtil.getObjectMapper(); |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void encode(ChannelHandlerContext ctx, Object in, List<Object> out) throws Exception { |
||||||
|
try { |
||||||
|
String json = objectMapper.writeValueAsString(in); |
||||||
|
out.add(json); |
||||||
|
} catch (Exception e) { |
||||||
|
log.warn("json parsing error : {}\ndata in : {}", e.getMessage(), in); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,8 @@ |
|||||||
|
package kr.co.palnet.kac.socket.core.command; |
||||||
|
|
||||||
|
import kr.co.palnet.kac.socket.core.model.DroneRq; |
||||||
|
|
||||||
|
public interface DroneCommand { |
||||||
|
void command(final DroneRq rq); |
||||||
|
String getCommandName(); |
||||||
|
} |
@ -0,0 +1,119 @@ |
|||||||
|
package kr.co.palnet.kac.socket.core.command.impl; |
||||||
|
|
||||||
|
import kr.co.palnet.kac.common.model.common.DroneDto; |
||||||
|
import kr.co.palnet.kac.common.model.common.DroneHistoryDto; |
||||||
|
import kr.co.palnet.kac.common.model.common.SimpleControlDto; |
||||||
|
import kr.co.palnet.kac.socket.core.command.DroneCommand; |
||||||
|
import kr.co.palnet.kac.socket.core.model.DroneRq; |
||||||
|
import kr.co.palnet.kac.socket.core.storage.DroneStorage; |
||||||
|
import kr.co.palnet.kac.socket.service.KacAppService; |
||||||
|
import kr.co.palnet.kac.socket.service.WebSocketService; |
||||||
|
import kr.co.palnet.kac.util.DroneUtil; |
||||||
|
import lombok.RequiredArgsConstructor; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.springframework.stereotype.Component; |
||||||
|
|
||||||
|
import java.time.Instant; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
@Slf4j |
||||||
|
@RequiredArgsConstructor |
||||||
|
@Component |
||||||
|
public class AdsbDroneCommandImpl implements DroneCommand { |
||||||
|
|
||||||
|
private final WebSocketService webSocketService; |
||||||
|
private final KacAppService kacAppService; |
||||||
|
|
||||||
|
@Override |
||||||
|
public String getCommandName() { |
||||||
|
return "ADS-B"; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void command(DroneRq rq) { |
||||||
|
List<DroneDto> resultList = rq.getBody(); |
||||||
|
|
||||||
|
/** 전문 설정 ***/ |
||||||
|
final String messageType = "LTEM"; |
||||||
|
final String objectType = "DRON"; |
||||||
|
|
||||||
|
/** 데이터 모델링 **/ |
||||||
|
for (DroneDto drone : resultList) { |
||||||
|
// 위,경도 좌표가 0으로 들어오는 것은 무시 처리
|
||||||
|
if (DroneUtil.checkCoordinates(drone.getLat(), drone.getLon())) { |
||||||
|
|
||||||
|
drone.setObjectType(objectType); |
||||||
|
drone.setMessageType(messageType); |
||||||
|
drone.setTerminalId(rq.getTerminalId()); |
||||||
|
drone.setRegDt(Instant.now()); |
||||||
|
|
||||||
|
// 서버 수신 시간 정보
|
||||||
|
drone.setServerRcvDt(Instant.now()); |
||||||
|
|
||||||
|
// 관제 이력 정보
|
||||||
|
List<DroneHistoryDto> hisList; |
||||||
|
|
||||||
|
DroneHistoryDto history = DroneHistoryDto.builder() |
||||||
|
.objectId(drone.getObjectId()) |
||||||
|
.lat(drone.getLat()) |
||||||
|
.lon(drone.getLon()) |
||||||
|
.build(); |
||||||
|
|
||||||
|
if (drone.getPostionHistory() != null) { |
||||||
|
hisList = drone.getPostionHistory(); |
||||||
|
} else { |
||||||
|
hisList = new ArrayList<>(); |
||||||
|
} |
||||||
|
|
||||||
|
hisList.add(history); |
||||||
|
drone.setPostionHistory(hisList); |
||||||
|
|
||||||
|
|
||||||
|
// STEP 1. 전에 내부 메모리에서 controlId 조회
|
||||||
|
DroneStorage droneStorage = DroneStorage.getInstance(); |
||||||
|
DroneDto lastDroneDto = droneStorage.getLast(drone.getObjectId()); |
||||||
|
|
||||||
|
if (lastDroneDto == null) { |
||||||
|
// STEP 1. Control ID 발급 -> Application Server Http 통신
|
||||||
|
try { |
||||||
|
SimpleControlDto simpleControlDto = kacAppService.getControlId(drone.getObjectId()); |
||||||
|
|
||||||
|
drone.setControlId(simpleControlDto.getControlId()); |
||||||
|
drone.setTypeCd(simpleControlDto.getTypeCd()); |
||||||
|
drone.setAreaTrnsYn(simpleControlDto.getAreaTrnsYn()); |
||||||
|
drone.setControlStartDt(Instant.now()); |
||||||
|
drone.setRegDt(Instant.now()); |
||||||
|
|
||||||
|
} catch (Exception e) { |
||||||
|
log.error("ERROR : {}", e.getMessage(), e); |
||||||
|
} |
||||||
|
|
||||||
|
} else { |
||||||
|
drone.setControlId(lastDroneDto.getControlId()); |
||||||
|
drone.setTypeCd("02"); |
||||||
|
drone.setAreaTrnsYn(lastDroneDto.getAreaTrnsYn()); |
||||||
|
drone.setControlWarnCd(lastDroneDto.isControlWarnCd()); |
||||||
|
drone.setControlStartDt(lastDroneDto.getControlStartDt()); |
||||||
|
|
||||||
|
drone.setRegDt(Instant.now()); |
||||||
|
} |
||||||
|
|
||||||
|
// STEP 2. 이력 생성할 전문 전달 -> DRON의 대한 식별정보만 이력 관리
|
||||||
|
// STEP 3. 화면에 표출할 정보 WebSocket 전달
|
||||||
|
try { |
||||||
|
// 저장 해 놓았다가 한거번에 전송 - 필요한 곳에 전송(HISTORY, UTM, WEB-SOCKET)
|
||||||
|
droneStorage.add(drone); |
||||||
|
} catch (Exception e) { |
||||||
|
log.error("ERROR : {}", e.getMessage(), e); |
||||||
|
} |
||||||
|
|
||||||
|
} else { |
||||||
|
log.error("좌표 정보가 존재하지 않습니다."); |
||||||
|
throw new IllegalArgumentException("좌표 정보가 존재하지 않습니다."); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
@ -0,0 +1,117 @@ |
|||||||
|
package kr.co.palnet.kac.socket.core.command.impl; |
||||||
|
|
||||||
|
import kr.co.palnet.kac.common.model.common.DroneDto; |
||||||
|
import kr.co.palnet.kac.common.model.common.DroneHistoryDto; |
||||||
|
import kr.co.palnet.kac.common.model.common.SimpleControlDto; |
||||||
|
import kr.co.palnet.kac.socket.core.command.DroneCommand; |
||||||
|
import kr.co.palnet.kac.socket.core.model.DroneRq; |
||||||
|
import kr.co.palnet.kac.socket.core.storage.DroneStorage; |
||||||
|
import kr.co.palnet.kac.socket.service.KacAppService; |
||||||
|
import kr.co.palnet.kac.socket.service.WebSocketService; |
||||||
|
import kr.co.palnet.kac.util.DroneUtil; |
||||||
|
import lombok.RequiredArgsConstructor; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.springframework.stereotype.Component; |
||||||
|
|
||||||
|
import java.time.Instant; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
@Slf4j |
||||||
|
@RequiredArgsConstructor |
||||||
|
@Component |
||||||
|
public class AntosDroneCommandImpl implements DroneCommand { |
||||||
|
|
||||||
|
private final WebSocketService webSocketService; |
||||||
|
private final KacAppService kacAppService; |
||||||
|
|
||||||
|
@Override |
||||||
|
public String getCommandName() { |
||||||
|
return "ANTOS"; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void command(DroneRq rq) { |
||||||
|
List<DroneDto> resultList = rq.getBody(); |
||||||
|
|
||||||
|
/** 전문 설정 ***/ |
||||||
|
final String messageType = "LTEM"; |
||||||
|
final String objectType = "DRON"; |
||||||
|
|
||||||
|
/** 데이터 모델링 **/ |
||||||
|
for (DroneDto drone : resultList) { |
||||||
|
// 위,경도 좌표가 0으로 들어오는 것은 무시 처리
|
||||||
|
if (DroneUtil.checkCoordinates(drone.getLat(), drone.getLon())) { |
||||||
|
|
||||||
|
drone.setObjectType(objectType); |
||||||
|
drone.setMessageType(messageType); |
||||||
|
drone.setTerminalId(rq.getTerminalId()); |
||||||
|
drone.setRegDt(Instant.now()); |
||||||
|
|
||||||
|
// 서버 수신 시간 정보
|
||||||
|
drone.setServerRcvDt(Instant.now()); |
||||||
|
|
||||||
|
// 관제 이력 정보
|
||||||
|
List<DroneHistoryDto> hisList; |
||||||
|
|
||||||
|
DroneHistoryDto history = DroneHistoryDto.builder() |
||||||
|
.objectId(drone.getObjectId()) |
||||||
|
.lat(drone.getLat()) |
||||||
|
.lon(drone.getLon()) |
||||||
|
.build(); |
||||||
|
|
||||||
|
if (drone.getPostionHistory() != null) { |
||||||
|
hisList = drone.getPostionHistory(); |
||||||
|
} else { |
||||||
|
hisList = new ArrayList<>(); |
||||||
|
} |
||||||
|
|
||||||
|
hisList.add(history); |
||||||
|
drone.setPostionHistory(hisList); |
||||||
|
|
||||||
|
|
||||||
|
// STEP 1. 전에 내부 메모리에서 controlId 조회
|
||||||
|
DroneStorage droneStorage = DroneStorage.getInstance(); |
||||||
|
DroneDto lastDroneDto = droneStorage.getLast(drone.getObjectId()); |
||||||
|
|
||||||
|
if (lastDroneDto == null) { |
||||||
|
// STEP 1. Control ID 발급 -> Application Server Http 통신
|
||||||
|
try { |
||||||
|
SimpleControlDto simpleControlDto = kacAppService.getControlId(drone.getObjectId()); |
||||||
|
|
||||||
|
drone.setControlId(simpleControlDto.getControlId()); |
||||||
|
drone.setTypeCd(simpleControlDto.getTypeCd()); |
||||||
|
drone.setAreaTrnsYn(simpleControlDto.getAreaTrnsYn()); |
||||||
|
drone.setControlStartDt(simpleControlDto.getControlStartDt() != null ? simpleControlDto.getControlStartDt() : Instant.now()); |
||||||
|
|
||||||
|
drone.setRegDt(Instant.now()); |
||||||
|
|
||||||
|
} catch (Exception e) { |
||||||
|
log.error("ERROR : {}", e.getMessage(), e); |
||||||
|
} |
||||||
|
|
||||||
|
} else { |
||||||
|
drone.setControlId(lastDroneDto.getControlId()); |
||||||
|
drone.setTypeCd("02"); |
||||||
|
drone.setAreaTrnsYn(lastDroneDto.getAreaTrnsYn()); |
||||||
|
drone.setControlWarnCd(lastDroneDto.isControlWarnCd()); |
||||||
|
drone.setControlStartDt(lastDroneDto.getControlStartDt()); |
||||||
|
|
||||||
|
drone.setRegDt(Instant.now()); |
||||||
|
} |
||||||
|
// STEP 2. 이력 생성할 전문 전달 -> DRON의 대한 식별정보만 이력 관리
|
||||||
|
// STEP 3. 화면에 표출할 정보 WebSocket 전달
|
||||||
|
try { |
||||||
|
// 저장 해 놓았다가 한거번에 전송 - 필요한 곳에 전송(HISTORY, UTM, WEB-SOCKET)
|
||||||
|
droneStorage.add(drone); |
||||||
|
} catch (Exception e) { |
||||||
|
log.error("ERROR : {}", e.getMessage(), e); |
||||||
|
} |
||||||
|
|
||||||
|
} else { |
||||||
|
log.error("좌표 정보가 존재하지 않습니다."); |
||||||
|
throw new IllegalArgumentException("좌표 정보가 존재하지 않습니다."); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,119 @@ |
|||||||
|
package kr.co.palnet.kac.socket.core.command.impl; |
||||||
|
|
||||||
|
import kr.co.palnet.kac.common.model.common.DroneDto; |
||||||
|
import kr.co.palnet.kac.common.model.common.DroneHistoryDto; |
||||||
|
import kr.co.palnet.kac.common.model.common.SimpleControlDto; |
||||||
|
import kr.co.palnet.kac.socket.core.command.DroneCommand; |
||||||
|
import kr.co.palnet.kac.socket.core.model.DroneRq; |
||||||
|
import kr.co.palnet.kac.socket.core.storage.DroneStorage; |
||||||
|
import kr.co.palnet.kac.socket.service.KacAppService; |
||||||
|
import kr.co.palnet.kac.socket.service.WebSocketService; |
||||||
|
import kr.co.palnet.kac.util.DroneUtil; |
||||||
|
import lombok.RequiredArgsConstructor; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.springframework.stereotype.Component; |
||||||
|
|
||||||
|
import java.time.Instant; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
@Slf4j |
||||||
|
@RequiredArgsConstructor |
||||||
|
@Component |
||||||
|
public class SandboxDroneCommandImpl implements DroneCommand { |
||||||
|
|
||||||
|
private final WebSocketService webSocketService; |
||||||
|
private final KacAppService kacAppService; |
||||||
|
|
||||||
|
@Override |
||||||
|
public String getCommandName() { |
||||||
|
return "SANDBOX"; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void command(DroneRq rq) { |
||||||
|
List<DroneDto> resultList = rq.getBody(); |
||||||
|
/** 전문 설정 ***/ |
||||||
|
final String messageType = "LTEM"; |
||||||
|
final String objectType = "DRON"; |
||||||
|
|
||||||
|
/** 데이터 모델링 **/ |
||||||
|
for (DroneDto drone : resultList) { |
||||||
|
// 위,경도 좌표가 0으로 들어오는 것은 무시 처리
|
||||||
|
if (DroneUtil.checkCoordinates(drone.getLat(), drone.getLon())) { |
||||||
|
drone.setObjectType(objectType); |
||||||
|
drone.setMessageType(messageType); |
||||||
|
drone.setTerminalId(rq.getTerminalId()); |
||||||
|
drone.setRegDt(Instant.now()); |
||||||
|
|
||||||
|
// 서버 수신 시간 정보
|
||||||
|
drone.setServerRcvDt(Instant.now()); |
||||||
|
|
||||||
|
// 관제 이력 정보
|
||||||
|
List<DroneHistoryDto> hisList; |
||||||
|
|
||||||
|
DroneHistoryDto history = DroneHistoryDto.builder() |
||||||
|
.objectId(drone.getObjectId()) |
||||||
|
.lat(drone.getLat()) |
||||||
|
.lon(drone.getLon()) |
||||||
|
.build(); |
||||||
|
|
||||||
|
if (drone.getPostionHistory() != null) { |
||||||
|
hisList = drone.getPostionHistory(); |
||||||
|
} else { |
||||||
|
hisList = new ArrayList<>(); |
||||||
|
} |
||||||
|
|
||||||
|
hisList.add(history); |
||||||
|
drone.setPostionHistory(hisList); |
||||||
|
|
||||||
|
Long start = System.currentTimeMillis(); |
||||||
|
|
||||||
|
// STEP 1. 전에 내부 메모리에서 controlId 조회
|
||||||
|
DroneStorage droneStorage = DroneStorage.getInstance(); |
||||||
|
DroneDto lastDroneDto = droneStorage.getLast(drone.getObjectId()); |
||||||
|
|
||||||
|
if (lastDroneDto == null) { |
||||||
|
// STEP 1. Control ID 발급 -> Application Server Http 통신
|
||||||
|
try { |
||||||
|
SimpleControlDto simpleControlDto = kacAppService.getControlId(drone.getObjectId()); |
||||||
|
|
||||||
|
drone.setControlId(simpleControlDto.getControlId()); |
||||||
|
drone.setTypeCd(simpleControlDto.getTypeCd()); |
||||||
|
drone.setAreaTrnsYn(simpleControlDto.getAreaTrnsYn()); |
||||||
|
drone.setControlStartDt(Instant.now()); |
||||||
|
|
||||||
|
} catch (Exception e) { |
||||||
|
log.error("ERROR : {}", e.getMessage(), e); |
||||||
|
} |
||||||
|
|
||||||
|
} else { |
||||||
|
drone.setControlId(lastDroneDto.getControlId()); |
||||||
|
drone.setTypeCd("02"); |
||||||
|
drone.setAreaTrnsYn(lastDroneDto.getAreaTrnsYn()); |
||||||
|
drone.setControlWarnCd(lastDroneDto.isControlWarnCd()); |
||||||
|
drone.setControlStartDt(lastDroneDto.getControlStartDt()); |
||||||
|
|
||||||
|
drone.setRegDt(Instant.now()); |
||||||
|
} |
||||||
|
|
||||||
|
// STEP 2. 이력 생성할 전문 전달 -> DRON의 대한 식별정보만 이력 관리
|
||||||
|
// STEP 3. 화면에 표출할 정보 WebSocket 전달
|
||||||
|
try { |
||||||
|
// 저장 해 놓았다가 한거번에 전송 - 필요한 곳에 전송(HISTORY, UTM, WEB-SOCKET)
|
||||||
|
droneStorage.add(drone); |
||||||
|
} catch (Exception e) { |
||||||
|
log.error("ERROR : {}", e.getMessage(), e); |
||||||
|
} |
||||||
|
|
||||||
|
Long end = System.currentTimeMillis(); |
||||||
|
// log.debug(">>> during time : {} - {}", drone.getObjectId(), end - start);
|
||||||
|
|
||||||
|
} else { |
||||||
|
log.error("좌표 정보가 존재하지 않습니다."); |
||||||
|
throw new IllegalArgumentException("좌표 정보가 존재하지 않습니다."); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,35 @@ |
|||||||
|
package kr.co.palnet.kac.socket.core.config; |
||||||
|
|
||||||
|
import kr.co.palnet.kac.socket.core.socket.SocketServer; |
||||||
|
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; |
||||||
|
|
||||||
|
import java.lang.management.ManagementFactory; |
||||||
|
import java.lang.management.MemoryMXBean; |
||||||
|
import java.lang.management.MemoryPoolMXBean; |
||||||
|
|
||||||
|
@Slf4j |
||||||
|
@RequiredArgsConstructor |
||||||
|
@Component |
||||||
|
public class AppReadyEvent implements ApplicationListener<ApplicationReadyEvent> { |
||||||
|
|
||||||
|
private final SocketServer socketServer; |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onApplicationEvent(ApplicationReadyEvent event) { |
||||||
|
try { |
||||||
|
float mb = 1024f * 1024f; |
||||||
|
MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean(); |
||||||
|
log.info("current heap memory init(xms): {}mb, max(xmx): {}mb", memoryBean.getHeapMemoryUsage().getInit() / mb, memoryBean.getHeapMemoryUsage().getMax() / mb); |
||||||
|
for (MemoryPoolMXBean mp : ManagementFactory.getMemoryPoolMXBeans()) { |
||||||
|
log.info("Pool: {} (type {}) = {}", mp.getName(), mp.getType(), mp.getUsage().getMax() / mb); |
||||||
|
} |
||||||
|
} catch (Exception e) { |
||||||
|
log.warn("when start app, not read jvm heap memory information."); |
||||||
|
} |
||||||
|
socketServer.start(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,44 @@ |
|||||||
|
package kr.co.palnet.kac.socket.core.config; |
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; |
||||||
|
import org.springframework.context.annotation.Bean; |
||||||
|
import org.springframework.context.annotation.Configuration; |
||||||
|
import org.springframework.scheduling.annotation.AsyncConfigurer; |
||||||
|
import org.springframework.scheduling.annotation.EnableAsync; |
||||||
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; |
||||||
|
|
||||||
|
import java.util.concurrent.Executor; |
||||||
|
|
||||||
|
@Slf4j |
||||||
|
@EnableAsync |
||||||
|
@Configuration |
||||||
|
public class AsyncConfig implements AsyncConfigurer { |
||||||
|
@Override |
||||||
|
public Executor getAsyncExecutor() { |
||||||
|
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); |
||||||
|
executor.setCorePoolSize(20); |
||||||
|
executor.setMaxPoolSize(100); |
||||||
|
executor.setQueueCapacity(500); |
||||||
|
executor.setThreadNamePrefix("th-async-default-"); |
||||||
|
executor.initialize(); |
||||||
|
return executor; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { |
||||||
|
return (ex, method, params) -> log.error("ERROR : {}", ex.getMessage(), ex); |
||||||
|
} |
||||||
|
|
||||||
|
@Bean |
||||||
|
public Executor restClientThreadPoolTaskExecutor() { |
||||||
|
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); |
||||||
|
executor.setCorePoolSize(10); |
||||||
|
executor.setMaxPoolSize(1000); |
||||||
|
executor.setQueueCapacity(1000); |
||||||
|
executor.setThreadNamePrefix("th-async-rc-"); |
||||||
|
executor.initialize(); |
||||||
|
return executor; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,30 @@ |
|||||||
|
package kr.co.palnet.kac.socket.core.config; |
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper; |
||||||
|
import kr.co.palnet.kac.util.ObjectMapperUtil; |
||||||
|
import org.springframework.context.annotation.Bean; |
||||||
|
import org.springframework.context.annotation.Configuration; |
||||||
|
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; |
||||||
|
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; |
||||||
|
|
||||||
|
@Configuration |
||||||
|
public class JsonConfig { |
||||||
|
|
||||||
|
|
||||||
|
@Bean |
||||||
|
public ObjectMapper objectMapper() { |
||||||
|
return ObjectMapperUtil.getObjectMapper(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Bean |
||||||
|
public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder() { |
||||||
|
return ObjectMapperUtil.getObjectMapperBuilder(); |
||||||
|
} |
||||||
|
|
||||||
|
@Bean |
||||||
|
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() { |
||||||
|
return new MappingJackson2HttpMessageConverter(objectMapper()); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,76 @@ |
|||||||
|
package kr.co.palnet.kac.socket.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.socket.core.socket.ChannelInitializer; |
||||||
|
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(ChannelInitializer channelInitializer) { |
||||||
|
// ServerBootstrap: 서버 설정을 도와주는 class
|
||||||
|
ServerBootstrap b = new ServerBootstrap(); |
||||||
|
b.group(bossGroup(), workerGroup()) |
||||||
|
// NioServerSocketChannel: incoming connections를 수락하기 위해 새로운 Channel을 객체화할 때 사용
|
||||||
|
.channel(NioServerSocketChannel.class) |
||||||
|
.handler(new LoggingHandler(LogLevel.DEBUG)) |
||||||
|
// ChannelInitializer: 새로운 Channel을 구성할 때 사용되는 특별한 handler. 주로 ChannelPipeline으로 구성
|
||||||
|
.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, false); // SO_LINGER설정이 있으면 안해도 되나 혹시나병(!)으로 TIME_WAIT걸린 포트를 재사용할 수 있도록 설정합니다.
|
||||||
|
|
||||||
|
return b; |
||||||
|
} |
||||||
|
|
||||||
|
// boss: incoming connection을 수락하고, 수락한 connection을 worker에게 등록(register)
|
||||||
|
@Bean(destroyMethod = "shutdownGracefully") |
||||||
|
public NioEventLoopGroup bossGroup() { |
||||||
|
return new NioEventLoopGroup(bossCount); |
||||||
|
} |
||||||
|
|
||||||
|
// worker: boss가 수락한 연결의 트래픽 관리
|
||||||
|
@Bean(destroyMethod = "shutdownGracefully") |
||||||
|
public NioEventLoopGroup workerGroup() { |
||||||
|
return new NioEventLoopGroup(); |
||||||
|
} |
||||||
|
|
||||||
|
// IP 소켓 주소(IP 주소, Port 번호)를 구현
|
||||||
|
// 도메인 이름으로 객체 생성 가능
|
||||||
|
@Bean |
||||||
|
public InetSocketAddress inetSocketAddress() { |
||||||
|
return new InetSocketAddress(port); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,105 @@ |
|||||||
|
package kr.co.palnet.kac.socket.core.handler; |
||||||
|
|
||||||
|
import io.netty.channel.ChannelHandler; |
||||||
|
import io.netty.channel.ChannelHandlerContext; |
||||||
|
import io.netty.channel.SimpleChannelInboundHandler; |
||||||
|
import kr.co.palnet.kac.socket.core.command.DroneCommand; |
||||||
|
import kr.co.palnet.kac.socket.core.model.DroneRq; |
||||||
|
import kr.co.palnet.kac.socket.core.model.DroneRs; |
||||||
|
import kr.co.palnet.kac.socket.core.storage.AuthKeyStorage; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.springframework.stereotype.Component; |
||||||
|
|
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
@Slf4j |
||||||
|
@ChannelHandler.Sharable |
||||||
|
@Component |
||||||
|
public class DroneHandler extends SimpleChannelInboundHandler<DroneRq> { |
||||||
|
|
||||||
|
private final Map<String, DroneCommand> commandMap; |
||||||
|
|
||||||
|
|
||||||
|
public DroneHandler(List<DroneCommand> commandList) { |
||||||
|
commandMap = new HashMap<>(); |
||||||
|
for (DroneCommand command : commandList) { |
||||||
|
commandMap.put(command.getCommandName(), command); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
//
|
||||||
|
@Override |
||||||
|
protected void channelRead0(ChannelHandlerContext ctx, DroneRq rq) throws Exception { |
||||||
|
// key 검사
|
||||||
|
if (rq == null) { |
||||||
|
ctx.writeAndFlush(DroneRs.builder() |
||||||
|
.code("-1000") |
||||||
|
.message("data is empty.") |
||||||
|
.build()); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (rq.getCommand() == null || rq.getCommand().isEmpty()) { |
||||||
|
ctx.writeAndFlush(DroneRs.builder() |
||||||
|
.code("-1001") |
||||||
|
.message("command is empty.") |
||||||
|
.build()); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (rq.getAuthKey() == null || rq.getAuthKey().isEmpty()) { |
||||||
|
ctx.writeAndFlush(DroneRs.builder() |
||||||
|
.code("-2000") |
||||||
|
.message("auth key is empty.") |
||||||
|
.build()); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (AuthKeyStorage.getInstance().checkAuthKey(rq.getCommand())) { |
||||||
|
ctx.writeAndFlush(DroneRs.builder() |
||||||
|
.code("-2000") |
||||||
|
.message("Invalid auth key.") |
||||||
|
.build()); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
DroneCommand commandService = commandMap.get(rq.getCommand().trim()); |
||||||
|
|
||||||
|
if (commandService == null) { |
||||||
|
ctx.writeAndFlush(DroneRs.builder() |
||||||
|
.code("-2100") |
||||||
|
.message("this command is not exist.") |
||||||
|
.build()); |
||||||
|
return; |
||||||
|
} |
||||||
|
try { |
||||||
|
commandService.command(rq); |
||||||
|
} catch (Exception e) { |
||||||
|
ctx.writeAndFlush(DroneRs.builder() |
||||||
|
.code("-500") |
||||||
|
.message("server error - execute command") |
||||||
|
.build()); |
||||||
|
return; |
||||||
|
} |
||||||
|
ctx.writeAndFlush(DroneRs.builder() |
||||||
|
.code("0") |
||||||
|
.message("success") |
||||||
|
.build()); |
||||||
|
} |
||||||
|
|
||||||
|
// 에러 처리
|
||||||
|
@Override |
||||||
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { |
||||||
|
log.error("ERROR: {}", cause.getMessage(), cause); |
||||||
|
DroneRs rs = DroneRs.builder() |
||||||
|
.code("-9999") |
||||||
|
.message("error - etc") |
||||||
|
.build(); |
||||||
|
ctx.writeAndFlush(rs); |
||||||
|
ctx.close(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
@ -0,0 +1,25 @@ |
|||||||
|
package kr.co.palnet.kac.socket.core.model; |
||||||
|
|
||||||
|
import kr.co.palnet.kac.common.model.common.DroneDto; |
||||||
|
import lombok.AllArgsConstructor; |
||||||
|
import lombok.Builder; |
||||||
|
import lombok.Data; |
||||||
|
import lombok.NoArgsConstructor; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
@Data |
||||||
|
@NoArgsConstructor |
||||||
|
@AllArgsConstructor |
||||||
|
@Builder |
||||||
|
public class DroneRq { |
||||||
|
|
||||||
|
private String authKey; |
||||||
|
|
||||||
|
private String terminalId; |
||||||
|
|
||||||
|
private String command; |
||||||
|
|
||||||
|
private List<DroneDto> body; |
||||||
|
|
||||||
|
} |
@ -0,0 +1,15 @@ |
|||||||
|
package kr.co.palnet.kac.socket.core.model; |
||||||
|
|
||||||
|
import lombok.AllArgsConstructor; |
||||||
|
import lombok.Builder; |
||||||
|
import lombok.Data; |
||||||
|
import lombok.NoArgsConstructor; |
||||||
|
|
||||||
|
@Data |
||||||
|
@NoArgsConstructor |
||||||
|
@AllArgsConstructor |
||||||
|
@Builder |
||||||
|
public class DroneRs { |
||||||
|
private String code; |
||||||
|
private String message; |
||||||
|
} |
@ -0,0 +1,37 @@ |
|||||||
|
package kr.co.palnet.kac.socket.core.model; |
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty; |
||||||
|
import lombok.AllArgsConstructor; |
||||||
|
import lombok.Builder; |
||||||
|
import lombok.Data; |
||||||
|
import lombok.NoArgsConstructor; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
@Data |
||||||
|
@NoArgsConstructor |
||||||
|
@AllArgsConstructor |
||||||
|
@Builder |
||||||
|
public class UtmDto { |
||||||
|
@JsonProperty("GPSime") |
||||||
|
private String GPSime; // 시간 yyyyMMddHHmmss
|
||||||
|
@JsonProperty("droneInfo") |
||||||
|
private List<DroneInfo> droneInfo; |
||||||
|
|
||||||
|
@Data |
||||||
|
@NoArgsConstructor |
||||||
|
@AllArgsConstructor |
||||||
|
@Builder |
||||||
|
public static class DroneInfo { |
||||||
|
@JsonProperty("Id") |
||||||
|
private String id; |
||||||
|
@JsonProperty("Latitude") |
||||||
|
private String latitude; |
||||||
|
@JsonProperty("Longitude") |
||||||
|
private String longitude; |
||||||
|
@JsonProperty("Height") |
||||||
|
private String height; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
@ -0,0 +1,37 @@ |
|||||||
|
package kr.co.palnet.kac.socket.core.socket; |
||||||
|
|
||||||
|
import io.netty.channel.ChannelPipeline; |
||||||
|
import io.netty.channel.socket.SocketChannel; |
||||||
|
import io.netty.handler.codec.json.JsonObjectDecoder; |
||||||
|
import io.netty.handler.codec.string.StringDecoder; |
||||||
|
import io.netty.handler.codec.string.StringEncoder; |
||||||
|
import io.netty.util.CharsetUtil; |
||||||
|
import kr.co.palnet.kac.socket.core.codec.DroneDecoder; |
||||||
|
import kr.co.palnet.kac.socket.core.codec.DroneEncoder; |
||||||
|
import kr.co.palnet.kac.socket.core.handler.DroneHandler; |
||||||
|
import lombok.RequiredArgsConstructor; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.springframework.stereotype.Component; |
||||||
|
|
||||||
|
@Slf4j |
||||||
|
@RequiredArgsConstructor |
||||||
|
@Component |
||||||
|
public class ChannelInitializer extends io.netty.channel.ChannelInitializer<SocketChannel> { |
||||||
|
|
||||||
|
private final DroneHandler dronSocketHandler; |
||||||
|
|
||||||
|
// 클라이언트 소켓 채널이 생성될 때 호출
|
||||||
|
@Override |
||||||
|
protected void initChannel(SocketChannel ch) { |
||||||
|
ChannelPipeline pipeline = ch.pipeline(); |
||||||
|
|
||||||
|
// 뒤이어 처리할 디코더 및 핸들러 추가
|
||||||
|
pipeline.addLast("json-decoder", new JsonObjectDecoder()); |
||||||
|
pipeline.addLast("string-decoder", new StringDecoder(CharsetUtil.UTF_8)); |
||||||
|
pipeline.addLast("string-encoder", new StringEncoder(CharsetUtil.UTF_8)); |
||||||
|
pipeline.addLast("drone-decoder", new DroneDecoder()); |
||||||
|
pipeline.addLast("drone-encoder", new DroneEncoder()); |
||||||
|
pipeline.addLast("drone-handler", dronSocketHandler); |
||||||
|
|
||||||
|
} |
||||||
|
} |
@ -0,0 +1,43 @@ |
|||||||
|
package kr.co.palnet.kac.socket.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 SocketServer { |
||||||
|
private final ServerBootstrap serverBootstrap; |
||||||
|
private final InetSocketAddress tcpPort; |
||||||
|
private Channel serverChannel; |
||||||
|
|
||||||
|
public void 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() { |
||||||
|
if (serverChannel != null) { |
||||||
|
serverChannel.close(); |
||||||
|
serverChannel.parent().closeFuture(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,58 @@ |
|||||||
|
package kr.co.palnet.kac.socket.core.storage; |
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
|
||||||
|
import java.util.HashSet; |
||||||
|
import java.util.Set; |
||||||
|
|
||||||
|
@Slf4j |
||||||
|
public class AuthKeyStorage { |
||||||
|
|
||||||
|
private Set<String> keys = new HashSet<>(); |
||||||
|
|
||||||
|
private AuthKeyStorage() { |
||||||
|
log.debug("AuthKeyUtil : init keys"); |
||||||
|
initKey(); |
||||||
|
} |
||||||
|
|
||||||
|
public static AuthKeyStorage getInstance() { |
||||||
|
return LazyHolder.INSTANCE; |
||||||
|
} |
||||||
|
|
||||||
|
public static class LazyHolder { |
||||||
|
private static final AuthKeyStorage INSTANCE = new AuthKeyStorage(); |
||||||
|
} |
||||||
|
|
||||||
|
private void initKey() { |
||||||
|
keys.add("1cc2e08e-0c5c-43b2-8d4d-cddd3de558e3");// 지텔인증키
|
||||||
|
keys.add("35ea4080-a3f2-4e34-8361-78db06bac6fc");// PAL 인증키
|
||||||
|
keys.add("4d244c0a-6cf5-4d57-ae48-331a71010c3d");// ADS-B
|
||||||
|
} |
||||||
|
|
||||||
|
public boolean checkAuthKey(String authKey) { |
||||||
|
// keys에서 authKey와 동일한 값이 잇으면 true반환
|
||||||
|
return keys.contains(authKey); |
||||||
|
} |
||||||
|
|
||||||
|
public boolean addKey(String authKey) { |
||||||
|
try { |
||||||
|
return keys.add(authKey); |
||||||
|
} catch (Exception e) { |
||||||
|
log.debug("fail add key."); |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public boolean removeKey(String authKey) { |
||||||
|
try { |
||||||
|
if (keys.contains(authKey)) { |
||||||
|
return keys.remove(authKey); |
||||||
|
} |
||||||
|
} catch (Exception e) { |
||||||
|
log.debug("fail remove key."); |
||||||
|
return false; |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,143 @@ |
|||||||
|
package kr.co.palnet.kac.socket.core.storage; |
||||||
|
|
||||||
|
import kr.co.palnet.kac.common.model.common.DroneDto; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
|
||||||
|
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 DroneStorage { |
||||||
|
|
||||||
|
private final Map<String, List<DroneDto>> droneMap; |
||||||
|
|
||||||
|
private final int REMOVE_TIME = 1000 * 10; |
||||||
|
|
||||||
|
private DroneStorage() { |
||||||
|
droneMap = new ConcurrentHashMap<>(); |
||||||
|
} |
||||||
|
|
||||||
|
public static DroneStorage getInstance() { |
||||||
|
return LazyHolder.INSTANCE; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
public static class LazyHolder { |
||||||
|
private static final DroneStorage INSTANCE = new DroneStorage(); |
||||||
|
} |
||||||
|
|
||||||
|
public Map<String, List<DroneDto>> getAll() { |
||||||
|
if (droneMap.keySet().isEmpty()) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
return droneMap; |
||||||
|
} |
||||||
|
|
||||||
|
public List<DroneDto> get(String objectId) { |
||||||
|
if (objectId == null || objectId.isEmpty()) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
if (droneMap.get(objectId) == null) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
return droneMap.get(objectId); |
||||||
|
} |
||||||
|
|
||||||
|
public DroneDto getLast(String objectId) { |
||||||
|
List<DroneDto> droneDtoList = droneMap.get(objectId); |
||||||
|
if (droneDtoList == null || droneDtoList.isEmpty()) return null; |
||||||
|
return droneDtoList.getLast(); |
||||||
|
// return droneDtoList.get(droneDtoList.size() - 1);
|
||||||
|
} |
||||||
|
|
||||||
|
public void add(DroneDto drone) { |
||||||
|
if (drone == null || drone.getObjectId() == null || drone.getObjectId().isEmpty()) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
List<DroneDto> list = this.droneMap.get(drone.getObjectId()); |
||||||
|
|
||||||
|
if (list == null) { |
||||||
|
list = new ArrayList<>(); |
||||||
|
} |
||||||
|
list.add(drone); |
||||||
|
this.droneMap.put(drone.getObjectId(), list); |
||||||
|
// list.offer(drone);
|
||||||
|
if ("PA-DRON-004-000".equals(drone.getObjectId())) |
||||||
|
log.info("add :: {}::{}::{}", drone.getObjectId(), droneMap.keySet().size(), list.size()); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// 1분 이상된 데이터 삭제 또는 처리가 끝난 데이터 삭제
|
||||||
|
public void removeByCondition() { |
||||||
|
for (String objectId : droneMap.keySet()) { |
||||||
|
List<DroneDto> list = droneMap.get(objectId); |
||||||
|
if (list == null) { |
||||||
|
continue; |
||||||
|
} else if (list.isEmpty()) { |
||||||
|
droneMap.remove(objectId); |
||||||
|
continue; |
||||||
|
} |
||||||
|
// 1분 전 시간
|
||||||
|
Instant compareTime = Instant.now().minusSeconds(60); |
||||||
|
|
||||||
|
// 마지막 데이터가 1분 이상된 데이터라면 삭제
|
||||||
|
// DroneDto lastDroneDto = list.stream().toList().get(list.size() - 1);
|
||||||
|
DroneDto lastDroneDto = list.stream().toList().getLast(); |
||||||
|
|
||||||
|
if ((lastDroneDto.isSendAll()) || compareTime.isAfter(lastDroneDto.getRegDt())) { |
||||||
|
droneMap.remove(objectId); |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
// 그외 데이터에서 조건에 맞는 데이터들만 삭제
|
||||||
|
list.removeIf(drone -> (drone.isSendAll()) || compareTime.isAfter(drone.getRegDt())); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public List<DroneDto> getAllByUtm() { |
||||||
|
// 보내지 않은 모든 데이터 추출
|
||||||
|
List<DroneDto> resultList = new ArrayList<>(); |
||||||
|
droneMap.values().forEach(droneDtoList -> { |
||||||
|
List<DroneDto> list = droneDtoList.stream().filter(droneDto -> !droneDto.isSendUtm()).toList(); |
||||||
|
resultList.addAll(list); |
||||||
|
}); |
||||||
|
return resultList; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
public Map<String, List<DroneDto>> getAllByHistory() { |
||||||
|
// 보내지 않은 모든 데이터 추출
|
||||||
|
Map<String, List<DroneDto>> sendList = new HashMap<>(); |
||||||
|
for (String objectId : droneMap.keySet()) { |
||||||
|
List<DroneDto> droneDtoList = droneMap.get(objectId); |
||||||
|
if (droneDtoList != null) { |
||||||
|
List<DroneDto> list = droneDtoList.stream().filter(droneDto -> !droneDto.isSendHistory()).toList(); |
||||||
|
if (!list.isEmpty()) { |
||||||
|
sendList.put(objectId, list); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return sendList; |
||||||
|
} |
||||||
|
|
||||||
|
public Map<String, List<DroneDto>> getAllByWebSocket() { |
||||||
|
// 보내지 않은 모든 데이터 추출
|
||||||
|
Map<String, List<DroneDto>> sendList = new HashMap<>(); |
||||||
|
for (String objectId : droneMap.keySet()) { |
||||||
|
List<DroneDto> droneDtoList = droneMap.get(objectId); |
||||||
|
if (droneDtoList != null) { |
||||||
|
List<DroneDto> list = droneDtoList.stream().filter(droneDto -> !droneDto.isSendWebSocket()).toList(); |
||||||
|
if (!list.isEmpty()) { |
||||||
|
sendList.put(objectId, list); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return sendList; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,52 @@ |
|||||||
|
package kr.co.palnet.kac.socket.service; |
||||||
|
|
||||||
|
import kr.co.palnet.kac.socket.core.model.UtmDto; |
||||||
|
import kr.co.palnet.kac.util.ObjectMapperUtil; |
||||||
|
import lombok.RequiredArgsConstructor; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.springframework.http.HttpStatus; |
||||||
|
import org.springframework.http.MediaType; |
||||||
|
import org.springframework.http.ResponseEntity; |
||||||
|
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; |
||||||
|
import org.springframework.stereotype.Service; |
||||||
|
import org.springframework.web.client.RestClient; |
||||||
|
|
||||||
|
@Slf4j |
||||||
|
@RequiredArgsConstructor |
||||||
|
@Service |
||||||
|
public class ExternalService { |
||||||
|
|
||||||
|
public boolean sendDataToUtm(UtmDto utmDto) { |
||||||
|
|
||||||
|
// 임시 하드코딩
|
||||||
|
final String UTM_HOST = "http://192.168.0.133:9000"; |
||||||
|
final String UTM_REALTIME_URI = "/api/v1/utm"; |
||||||
|
|
||||||
|
RestClient client = RestClient.builder() |
||||||
|
.baseUrl(UTM_HOST) |
||||||
|
.messageConverters(converts -> { |
||||||
|
converts.removeIf(converter -> converter instanceof MappingJackson2HttpMessageConverter); |
||||||
|
converts.add(new MappingJackson2HttpMessageConverter(ObjectMapperUtil.getObjectMapper())); |
||||||
|
}) |
||||||
|
.build(); |
||||||
|
|
||||||
|
try { |
||||||
|
|
||||||
|
ResponseEntity<Void> res = client.post() |
||||||
|
.uri(UTM_REALTIME_URI) |
||||||
|
.contentType(MediaType.APPLICATION_JSON) |
||||||
|
.body(utmDto) |
||||||
|
.retrieve() |
||||||
|
.toEntity(Void.class); |
||||||
|
|
||||||
|
if (res.getStatusCode() == HttpStatus.OK) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
} catch (Exception e) { |
||||||
|
log.warn("fail send to utm. {}", e.getMessage()); |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
@ -0,0 +1,81 @@ |
|||||||
|
package kr.co.palnet.kac.socket.service; |
||||||
|
|
||||||
|
import kr.co.palnet.kac.common.model.common.DroneDto; |
||||||
|
import kr.co.palnet.kac.common.model.common.SimpleControlDto; |
||||||
|
import kr.co.palnet.kac.util.ObjectMapperUtil; |
||||||
|
import lombok.RequiredArgsConstructor; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.springframework.beans.factory.annotation.Value; |
||||||
|
import org.springframework.http.HttpStatus; |
||||||
|
import org.springframework.http.MediaType; |
||||||
|
import org.springframework.http.ResponseEntity; |
||||||
|
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; |
||||||
|
import org.springframework.scheduling.annotation.Async; |
||||||
|
import org.springframework.stereotype.Service; |
||||||
|
import org.springframework.web.client.RestClient; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
@Slf4j |
||||||
|
@RequiredArgsConstructor |
||||||
|
@Service |
||||||
|
public class KacAppService { |
||||||
|
@Value("${app.kac-app.host}") |
||||||
|
private String kacAppHost; |
||||||
|
|
||||||
|
private final String GET_CONTROL_ID = "/v1/inner/socket/control"; |
||||||
|
private final String SEND_HISTORY = "/v1/inner/socket/receiver/drone"; |
||||||
|
private final String SEND_HISTORY_ALL = "/v1/inner/socket/receiver/drone/all"; |
||||||
|
|
||||||
|
// control id 가져오기
|
||||||
|
public SimpleControlDto getControlId(String objectId) { |
||||||
|
RestClient client = getRestClient(); |
||||||
|
|
||||||
|
ResponseEntity<SimpleControlDto> resp = client.get() |
||||||
|
.uri(GET_CONTROL_ID + "/" + objectId) |
||||||
|
.retrieve() |
||||||
|
.toEntity(SimpleControlDto.class); |
||||||
|
|
||||||
|
return resp.getBody(); |
||||||
|
} |
||||||
|
|
||||||
|
// drone 데이터 전송
|
||||||
|
public void sendData(DroneDto dto) { |
||||||
|
RestClient client = getRestClient(); |
||||||
|
client.post() |
||||||
|
.uri(SEND_HISTORY) |
||||||
|
.contentType(MediaType.APPLICATION_JSON) |
||||||
|
.body(dto) |
||||||
|
.retrieve() |
||||||
|
.toBodilessEntity(); |
||||||
|
} |
||||||
|
|
||||||
|
// 비동기
|
||||||
|
@Async("restClientThreadPoolTaskExecutor") |
||||||
|
public void asyncSendData(DroneDto dto) { |
||||||
|
sendData(dto); |
||||||
|
} |
||||||
|
|
||||||
|
public boolean sendDataAll(Map<String, List<DroneDto>> history) { |
||||||
|
RestClient client = getRestClient(); |
||||||
|
ResponseEntity<Void> resp = client.post() |
||||||
|
.uri(SEND_HISTORY_ALL) |
||||||
|
.contentType(MediaType.APPLICATION_JSON) |
||||||
|
.body(history) |
||||||
|
.retrieve() |
||||||
|
.toBodilessEntity(); |
||||||
|
return resp.getStatusCode() == HttpStatus.OK; |
||||||
|
} |
||||||
|
|
||||||
|
private RestClient getRestClient() { |
||||||
|
return RestClient.builder() |
||||||
|
.baseUrl(kacAppHost) |
||||||
|
.messageConverters(converts -> { |
||||||
|
converts.removeIf(converter -> converter instanceof MappingJackson2HttpMessageConverter); |
||||||
|
converts.add(new MappingJackson2HttpMessageConverter(ObjectMapperUtil.getObjectMapper())); |
||||||
|
}) |
||||||
|
.build(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,113 @@ |
|||||||
|
package kr.co.palnet.kac.socket.service; |
||||||
|
|
||||||
|
import kr.co.palnet.kac.common.model.common.DroneDto; |
||||||
|
import kr.co.palnet.kac.socket.core.model.UtmDto; |
||||||
|
import kr.co.palnet.kac.socket.core.storage.DroneStorage; |
||||||
|
import lombok.RequiredArgsConstructor; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.springframework.scheduling.annotation.Scheduled; |
||||||
|
import org.springframework.stereotype.Service; |
||||||
|
|
||||||
|
import java.time.Instant; |
||||||
|
import java.time.ZoneId; |
||||||
|
import java.time.format.DateTimeFormatter; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.stream.Collectors; |
||||||
|
|
||||||
|
@Slf4j |
||||||
|
@RequiredArgsConstructor |
||||||
|
@Service |
||||||
|
public class ScheduledService { |
||||||
|
|
||||||
|
private final ExternalService externalService; |
||||||
|
private final KacAppService kacAppService; |
||||||
|
private final WebSocketService webSocketService; |
||||||
|
|
||||||
|
@Scheduled(fixedDelay = 2 * 1000) |
||||||
|
public void sendDataByWebSocket() { |
||||||
|
DroneStorage droneStorage = DroneStorage.getInstance(); |
||||||
|
Map<String, List<DroneDto>> droneMap = droneStorage.getAllByWebSocket(); |
||||||
|
|
||||||
|
if (droneMap == null || droneMap.isEmpty()) { |
||||||
|
log.debug("drone data for websocket is empty."); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (webSocketService.sendDataAll(droneMap)) { |
||||||
|
droneMap.values().forEach(droneDtoList -> droneDtoList.forEach(droneDto -> droneDto.setSendWebSocket(true))); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@Scheduled(fixedDelay = 2 * 1000) |
||||||
|
public void sendDataByHistory() { |
||||||
|
|
||||||
|
DroneStorage droneStorage = DroneStorage.getInstance(); |
||||||
|
Map<String, List<DroneDto>> history = droneStorage.getAllByHistory(); |
||||||
|
|
||||||
|
// list 합
|
||||||
|
if (history == null || history.isEmpty()) { |
||||||
|
log.debug("drone data for history is empty."); |
||||||
|
return; |
||||||
|
} |
||||||
|
if (kacAppService.sendDataAll(history)) { |
||||||
|
history.values().forEach(droneDtoList -> droneDtoList.forEach(droneDto -> droneDto.setSendHistory(true))); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// utm에 전송
|
||||||
|
@Scheduled(fixedDelay = 2 * 1000) |
||||||
|
public void sendDataToUtm() { |
||||||
|
DroneStorage droneStorage = DroneStorage.getInstance(); |
||||||
|
List<DroneDto> list = droneStorage.getAllByUtm(); |
||||||
|
|
||||||
|
try { |
||||||
|
|
||||||
|
List<UtmDto.DroneInfo> droneInfoList = list.stream().map( |
||||||
|
model -> UtmDto.DroneInfo.builder() |
||||||
|
.id(model.getObjectId()) |
||||||
|
.latitude(model.getLat().toString()) |
||||||
|
.longitude(model.getLon().toString()) |
||||||
|
.height(model.getElev().toString()) |
||||||
|
.build() |
||||||
|
).collect(Collectors.toList()); |
||||||
|
// 가공
|
||||||
|
|
||||||
|
if (droneInfoList.isEmpty()) return; |
||||||
|
|
||||||
|
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss") |
||||||
|
.withZone(ZoneId.of("Asia/Seoul")); |
||||||
|
String dateStr = formatter.format(Instant.now()); |
||||||
|
|
||||||
|
UtmDto utmDto = UtmDto.builder() |
||||||
|
.GPSime(dateStr) |
||||||
|
.droneInfo(droneInfoList) |
||||||
|
.build(); |
||||||
|
|
||||||
|
// if (externalService.sendDataToUtm(utmDto)) {
|
||||||
|
// list.forEach(drone -> drone.setSendUtm(true));
|
||||||
|
// }
|
||||||
|
// TODO 현재 통신이 안되므로 모두 보낸다는 가정으로 진행
|
||||||
|
list.forEach(drone -> drone.setSendUtm(true)); |
||||||
|
|
||||||
|
} catch (Exception e) { |
||||||
|
log.error("ERROR : {}", e.getMessage(), e); |
||||||
|
if (list != null) { |
||||||
|
log.warn("list : {}", list.size()); |
||||||
|
} else { |
||||||
|
log.warn("list is null"); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 사용을 다한 데이터 제거
|
||||||
|
@Scheduled(fixedDelay = 30 * 1000) |
||||||
|
public void removeDrone() { |
||||||
|
DroneStorage droneStorage = DroneStorage.getInstance(); |
||||||
|
droneStorage.removeByCondition(); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
@ -0,0 +1,67 @@ |
|||||||
|
package kr.co.palnet.kac.socket.service; |
||||||
|
|
||||||
|
import kr.co.palnet.kac.common.model.common.DroneDto; |
||||||
|
import kr.co.palnet.kac.util.ObjectMapperUtil; |
||||||
|
import lombok.RequiredArgsConstructor; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.springframework.beans.factory.annotation.Value; |
||||||
|
import org.springframework.http.HttpStatus; |
||||||
|
import org.springframework.http.MediaType; |
||||||
|
import org.springframework.http.ResponseEntity; |
||||||
|
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; |
||||||
|
import org.springframework.scheduling.annotation.Async; |
||||||
|
import org.springframework.stereotype.Service; |
||||||
|
import org.springframework.web.client.RestClient; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
@Slf4j |
||||||
|
@RequiredArgsConstructor |
||||||
|
@Service |
||||||
|
public class WebSocketService { |
||||||
|
|
||||||
|
@Value("${app.web-socket-web.host}") |
||||||
|
private String webSocketWebHost; |
||||||
|
private final String SEND_DRONE = "/v1/api/ws/drone"; |
||||||
|
private final String SEND_DRONE_ALL = "/v1/api/ws/drone/all"; |
||||||
|
|
||||||
|
public void sendData(DroneDto dto) { |
||||||
|
RestClient client = getRestClient(); |
||||||
|
|
||||||
|
client.post() |
||||||
|
.uri(SEND_DRONE) |
||||||
|
.contentType(MediaType.APPLICATION_JSON) |
||||||
|
.body(dto) |
||||||
|
.retrieve() |
||||||
|
.toBodilessEntity(); |
||||||
|
} |
||||||
|
|
||||||
|
@Async("restClientThreadPoolTaskExecutor") |
||||||
|
public void asyncSendData(DroneDto dto) { |
||||||
|
sendData(dto); |
||||||
|
} |
||||||
|
|
||||||
|
public boolean sendDataAll(Map<String, List<DroneDto>> droneDtoList) { |
||||||
|
RestClient client = getRestClient(); |
||||||
|
ResponseEntity<Void> resp = client.post() |
||||||
|
.uri(SEND_DRONE_ALL) |
||||||
|
.contentType(MediaType.APPLICATION_JSON) |
||||||
|
.body(droneDtoList) |
||||||
|
.retrieve() |
||||||
|
.toBodilessEntity(); |
||||||
|
return resp.getStatusCode() == HttpStatus.OK; |
||||||
|
} |
||||||
|
|
||||||
|
private RestClient getRestClient() { |
||||||
|
return RestClient.builder() |
||||||
|
.baseUrl(webSocketWebHost) |
||||||
|
.messageConverters(converts -> { |
||||||
|
converts.removeIf(converter -> converter instanceof MappingJackson2HttpMessageConverter); |
||||||
|
converts.add(new MappingJackson2HttpMessageConverter(ObjectMapperUtil.getObjectMapper())); |
||||||
|
}) |
||||||
|
.build(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
@ -0,0 +1,68 @@ |
|||||||
|
spring: |
||||||
|
threads: |
||||||
|
virtual: |
||||||
|
enabled: true |
||||||
|
|
||||||
|
netty: |
||||||
|
socket: |
||||||
|
tcp-port: 8003 |
||||||
|
boss-count: 1 |
||||||
|
keep-alive: false |
||||||
|
tcp-nodelay: false |
||||||
|
backlog: 3000 |
||||||
|
|
||||||
|
server: |
||||||
|
port: 8004 |
||||||
|
|
||||||
|
app: |
||||||
|
kac-app: |
||||||
|
host: http://127.0.0.1:8000 |
||||||
|
web-socket: |
||||||
|
host: http://127.0.0.1:8001 |
||||||
|
web-socket-web: |
||||||
|
host: http://127.0.0.1:8002 |
||||||
|
|
||||||
|
logging: |
||||||
|
level: |
||||||
|
kr.co.palnet: debug |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
spring: |
||||||
|
config: |
||||||
|
activate: |
||||||
|
on-profile: local |
||||||
|
|
||||||
|
app: |
||||||
|
kac-app: |
||||||
|
host: http://127.0.0.1:8000 |
||||||
|
web-socket: |
||||||
|
host: http://127.0.0.1:8001 |
||||||
|
web-socket-web: |
||||||
|
host: http://127.0.0.1:8002 |
||||||
|
|
||||||
|
logging: |
||||||
|
level: |
||||||
|
kr.co.palnet: debug |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
spring: |
||||||
|
config: |
||||||
|
activate: |
||||||
|
on-profile: docker |
||||||
|
|
||||||
|
app: |
||||||
|
kac-app: |
||||||
|
host: http://kac-app:8000 |
||||||
|
web-socket: |
||||||
|
host: http://kac-websocket-app:8001 |
||||||
|
web-socket-web: |
||||||
|
host: http://kac-websocket-app:8002 |
||||||
|
|
||||||
|
logging: |
||||||
|
level: |
||||||
|
kr.co.palnet: info |
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,13 @@ |
|||||||
|
FROM openjdk:21 |
||||||
|
|
||||||
|
ENV TZ=Asia/Seoul |
||||||
|
#ENV JAVA_OPTS="-Xms512M -Xmx512M" |
||||||
|
ENV JAVA_OPTS="" |
||||||
|
|
||||||
|
EXPOSE 8001 |
||||||
|
EXPOSE 8002 |
||||||
|
|
||||||
|
WORKDIR /app |
||||||
|
|
||||||
|
#ENTRYPOINT ["java","-jar","/app/kac-websocket-app-1.0.0.jar"] |
||||||
|
CMD java $JAVA_OPTS -jar /app/kac-websocket-app-1.0.0.jar |
@ -0,0 +1,8 @@ |
|||||||
|
dependencies { |
||||||
|
|
||||||
|
implementation "$boot:spring-boot-starter-web" |
||||||
|
implementation "io.netty:netty-all:4.1.68.Final" |
||||||
|
implementation project(":common:util") |
||||||
|
implementation project(":common:model") |
||||||
|
|
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,53 @@ |
|||||||
|
package kr.co.palnet.kac.websocket.controller; |
||||||
|
|
||||||
|
|
||||||
|
import kr.co.palnet.kac.common.model.common.DroneControlDto; |
||||||
|
import kr.co.palnet.kac.common.model.common.DroneDto; |
||||||
|
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; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
@Slf4j |
||||||
|
@RequiredArgsConstructor |
||||||
|
@RestController |
||||||
|
@RequestMapping("/v1/api/ws") |
||||||
|
public class SocketReceiverController { |
||||||
|
|
||||||
|
private final ControlService controlService; |
||||||
|
|
||||||
|
@PostMapping("/drone") |
||||||
|
public ResponseEntity<Void> receiver(@RequestBody DroneDto droneDto) { |
||||||
|
DroneControlDto history = controlService.dronDtoToControlDtoConvert(droneDto); |
||||||
|
|
||||||
|
// DRON의 대한 식별정보만 이력 관리
|
||||||
|
ControlStorage controlCache = ControlStorage.getInstance(); |
||||||
|
controlCache.put(history); |
||||||
|
|
||||||
|
return ResponseEntity.ok().build(); |
||||||
|
} |
||||||
|
|
||||||
|
@PostMapping("/drone/all") |
||||||
|
public ResponseEntity<Void> receiver(@RequestBody Map<String, List<DroneDto>> droneDtoAll) { |
||||||
|
|
||||||
|
// DRON의 대한 식별정보만 이력 관리
|
||||||
|
ControlStorage controlCache = ControlStorage.getInstance(); |
||||||
|
droneDtoAll.values().forEach(droneDtoList -> { |
||||||
|
droneDtoList.forEach(droneDto -> { |
||||||
|
DroneControlDto history = controlService.dronDtoToControlDtoConvert(droneDto); |
||||||
|
controlCache.put(history); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
return ResponseEntity.ok().build(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,49 @@ |
|||||||
|
package kr.co.palnet.kac.websocket.core.codec; |
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference; |
||||||
|
import com.fasterxml.jackson.databind.JsonNode; |
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper; |
||||||
|
import io.netty.channel.ChannelHandlerContext; |
||||||
|
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; |
||||||
|
import io.netty.handler.codec.http.websocketx.WebSocketFrame; |
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionDecoder; |
||||||
|
import kr.co.palnet.kac.util.ObjectMapperUtil; |
||||||
|
import kr.co.palnet.kac.websocket.core.model.BoundaryCoordinates; |
||||||
|
import kr.co.palnet.kac.websocket.core.model.ControlRq; |
||||||
|
import lombok.RequiredArgsConstructor; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
|
||||||
|
@Slf4j |
||||||
|
@RequiredArgsConstructor |
||||||
|
public class ControlDecoder extends WebSocketExtensionDecoder { |
||||||
|
|
||||||
|
private final ObjectMapper objectMapper = ObjectMapperUtil.getObjectMapper(); |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void decode(ChannelHandlerContext ctx, WebSocketFrame msg, List<Object> out) throws Exception { |
||||||
|
try { |
||||||
|
if (msg instanceof TextWebSocketFrame textWebSocketFrame) { |
||||||
|
String json = textWebSocketFrame.text(); |
||||||
|
JsonNode jsonNode = objectMapper.readTree(json); |
||||||
|
String type = jsonNode.get("type").asText(); |
||||||
|
switch (type) { |
||||||
|
case "CURRENT_DRONE": |
||||||
|
ControlRq<List<BoundaryCoordinates>> controlRq = objectMapper.readValue(json, new TypeReference<>() { |
||||||
|
}); |
||||||
|
out.add(controlRq); |
||||||
|
break; |
||||||
|
default: |
||||||
|
log.warn("decode type is empty."); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} catch (Exception e) { |
||||||
|
log.warn("decode parsing error : {}\ndata in : {}", e.getMessage(), msg); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
@ -0,0 +1,35 @@ |
|||||||
|
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; |
||||||
|
|
||||||
|
import java.lang.management.ManagementFactory; |
||||||
|
import java.lang.management.MemoryMXBean; |
||||||
|
import java.lang.management.MemoryPoolMXBean; |
||||||
|
|
||||||
|
@Slf4j |
||||||
|
@RequiredArgsConstructor |
||||||
|
@Component |
||||||
|
public class AppReadyEvent implements ApplicationListener<ApplicationReadyEvent> { |
||||||
|
|
||||||
|
private final WebSocketServer socketServer; |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onApplicationEvent(ApplicationReadyEvent event) { |
||||||
|
try { |
||||||
|
float mb = 1024f * 1024f; |
||||||
|
MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean(); |
||||||
|
log.info("current heap memory init(xms): {}mb, max(xmx): {}mb", memoryBean.getHeapMemoryUsage().getInit() / mb, memoryBean.getHeapMemoryUsage().getMax() / mb); |
||||||
|
for (MemoryPoolMXBean mp : ManagementFactory.getMemoryPoolMXBeans()) { |
||||||
|
log.info("Pool: {} (type {}) = {}", mp.getName(), mp.getType(), mp.getUsage().getMax() / mb); |
||||||
|
} |
||||||
|
} catch (Exception e) { |
||||||
|
log.warn("when start app, not read jvm heap memory information."); |
||||||
|
} |
||||||
|
socketServer.start(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,30 @@ |
|||||||
|
package kr.co.palnet.kac.websocket.core.config; |
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper; |
||||||
|
import kr.co.palnet.kac.util.ObjectMapperUtil; |
||||||
|
import org.springframework.context.annotation.Bean; |
||||||
|
import org.springframework.context.annotation.Configuration; |
||||||
|
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; |
||||||
|
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; |
||||||
|
|
||||||
|
@Configuration |
||||||
|
public class JsonConfig { |
||||||
|
|
||||||
|
|
||||||
|
@Bean |
||||||
|
public ObjectMapper objectMapper() { |
||||||
|
return ObjectMapperUtil.getObjectMapper(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Bean |
||||||
|
public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder() { |
||||||
|
return ObjectMapperUtil.getObjectMapperBuilder(); |
||||||
|
} |
||||||
|
|
||||||
|
@Bean |
||||||
|
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() { |
||||||
|
return new MappingJackson2HttpMessageConverter(objectMapper()); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,76 @@ |
|||||||
|
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.ChannelInitializer; |
||||||
|
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(ChannelInitializer channelInitializer) { |
||||||
|
// 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() { |
||||||
|
return new NioEventLoopGroup(bossCount); |
||||||
|
} |
||||||
|
|
||||||
|
// worker: boss가 수락한 연결의 트래픽 관리
|
||||||
|
@Bean(destroyMethod = "shutdownGracefully") |
||||||
|
public NioEventLoopGroup workerGroup() { |
||||||
|
return new NioEventLoopGroup(); |
||||||
|
} |
||||||
|
|
||||||
|
// IP 소켓 주소(IP 주소, Port 번호)를 구현
|
||||||
|
// 도메인 이름으로 객체 생성 가능
|
||||||
|
@Bean |
||||||
|
public InetSocketAddress inetSocketAddress() { |
||||||
|
return new InetSocketAddress(port); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,70 @@ |
|||||||
|
package kr.co.palnet.kac.websocket.core.handler; |
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper; |
||||||
|
import io.netty.channel.ChannelHandler; |
||||||
|
import io.netty.channel.ChannelHandlerContext; |
||||||
|
import io.netty.channel.SimpleChannelInboundHandler; |
||||||
|
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; |
||||||
|
import kr.co.palnet.kac.util.ObjectMapperUtil; |
||||||
|
import kr.co.palnet.kac.websocket.core.model.BoundaryCoordinates; |
||||||
|
import kr.co.palnet.kac.websocket.core.model.ControlRq; |
||||||
|
import kr.co.palnet.kac.websocket.core.model.ErrorRs; |
||||||
|
import kr.co.palnet.kac.websocket.core.storage.ChannelStorage; |
||||||
|
import kr.co.palnet.kac.websocket.service.ControlService; |
||||||
|
import lombok.RequiredArgsConstructor; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.springframework.stereotype.Component; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
@Slf4j |
||||||
|
@RequiredArgsConstructor |
||||||
|
@ChannelHandler.Sharable |
||||||
|
@Component |
||||||
|
public class WebSocketHandler extends SimpleChannelInboundHandler<ControlRq<Object>> { |
||||||
|
|
||||||
|
private final ControlService controlService; |
||||||
|
private final ObjectMapper objectMapper = ObjectMapperUtil.getObjectMapper(); |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void channelRead0(ChannelHandlerContext ctx, ControlRq rq) throws Exception { |
||||||
|
log.info("read :: {}", rq); |
||||||
|
Object result = null; |
||||||
|
switch (rq.getType()) { |
||||||
|
case "CURRENT_DRONE": |
||||||
|
if (rq.getBody() == null) { |
||||||
|
// BoundaryCoordinates
|
||||||
|
log.warn("CURRENT_DRONE::boundary coordinates is null"); |
||||||
|
return; |
||||||
|
} |
||||||
|
log.info(">>class {}", rq.getBody().getClass().getName()); |
||||||
|
List<BoundaryCoordinates> list = (List<BoundaryCoordinates>) rq.getBody(); |
||||||
|
result = controlService.getList(list); |
||||||
|
break; |
||||||
|
default: |
||||||
|
log.warn("type is not supported."); |
||||||
|
result = ErrorRs.builder() |
||||||
|
.code("-400") |
||||||
|
.message("bad request : type error") |
||||||
|
.build(); |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
String json = objectMapper.writeValueAsString(result); |
||||||
|
ctx.channel().writeAndFlush(new TextWebSocketFrame(json)); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void channelRegistered(ChannelHandlerContext ctx) throws Exception { |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void channelActive(ChannelHandlerContext ctx) throws Exception { |
||||||
|
ChannelStorage channelStorage = ChannelStorage.getInstance(); |
||||||
|
channelStorage.add(ctx.channel()); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void channelInactive(ChannelHandlerContext ctx) throws Exception { |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,15 @@ |
|||||||
|
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 BoundaryCoordinates { |
||||||
|
private double lat; |
||||||
|
private double lon; |
||||||
|
} |
@ -0,0 +1,19 @@ |
|||||||
|
package kr.co.palnet.kac.websocket.core.model; |
||||||
|
|
||||||
|
import lombok.AllArgsConstructor; |
||||||
|
import lombok.Builder; |
||||||
|
import lombok.Data; |
||||||
|
import lombok.NoArgsConstructor; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
@Data |
||||||
|
@NoArgsConstructor |
||||||
|
@AllArgsConstructor |
||||||
|
@Builder |
||||||
|
public class ControlRq<T> { |
||||||
|
|
||||||
|
private String type; |
||||||
|
private T body; |
||||||
|
|
||||||
|
} |
@ -0,0 +1,15 @@ |
|||||||
|
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 ErrorRs { |
||||||
|
private String code; |
||||||
|
private String message; |
||||||
|
} |
@ -0,0 +1,36 @@ |
|||||||
|
package kr.co.palnet.kac.websocket.core.socket; |
||||||
|
|
||||||
|
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.WebSocketServerProtocolHandler; |
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler; |
||||||
|
import io.netty.handler.timeout.IdleStateHandler; |
||||||
|
import kr.co.palnet.kac.websocket.core.codec.ControlDecoder; |
||||||
|
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 ChannelInitializer extends io.netty.channel.ChannelInitializer<SocketChannel> { |
||||||
|
|
||||||
|
private final WebSocketHandler webSocketHandler; |
||||||
|
|
||||||
|
// 클라이언트 소켓 채널이 생성될 때 호출
|
||||||
|
@Override |
||||||
|
protected void initChannel(SocketChannel ch) { |
||||||
|
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 ControlDecoder()); |
||||||
|
pipeline.addLast(webSocketHandler); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,42 @@ |
|||||||
|
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() { |
||||||
|
try { |
||||||
|
// ChannelFuture: I/O operation의 결과나 상태를 제공하는 객체
|
||||||
|
// 지정한 host, port로 소켓을 바인딩하고 incoming connections을 받도록 준비함
|
||||||
|
ChannelFuture serverChannelFuture = serverBootstrap.bind(tcpPort).sync(); |
||||||
|
|
||||||
|
// 서버 소켓이 닫힐 때까지 기다림
|
||||||
|
serverChannel = serverChannelFuture.channel().closeFuture().sync().channel(); |
||||||
|
} catch (InterruptedException e) { |
||||||
|
log.error("ERROR: {}", e.getMessage(), e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Bean을 제거하기 전에 해야할 작업이 있을 때 설정
|
||||||
|
@PreDestroy |
||||||
|
public void stop() { |
||||||
|
if (serverChannel != null) { |
||||||
|
serverChannel.close(); |
||||||
|
serverChannel.parent().closeFuture(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -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 getGroup() { |
||||||
|
return channelGroup; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,82 @@ |
|||||||
|
package kr.co.palnet.kac.websocket.core.storage; |
||||||
|
|
||||||
|
import kr.co.palnet.kac.common.model.common.DroneControlDto; |
||||||
|
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; |
||||||
|
|
||||||
|
@Slf4j |
||||||
|
@Component |
||||||
|
public class ControlStorage { |
||||||
|
|
||||||
|
private final int REMOVE_TIME_SECOND = 60; // 화면 노출 시간
|
||||||
|
|
||||||
|
private final Map<String, DroneControlDto> 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, DroneControlDto> getAll() { |
||||||
|
if (controlMap.keySet().isEmpty()) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
return controlMap; |
||||||
|
} |
||||||
|
|
||||||
|
public DroneControlDto get(String objectId) { |
||||||
|
if (objectId == null || objectId.isEmpty()) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
if (controlMap.get(objectId) == null) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
return controlMap.get(objectId); |
||||||
|
} |
||||||
|
|
||||||
|
public DroneControlDto put(DroneControlDto control) { |
||||||
|
if (control == null || control.getObjectId() == null || control.getObjectId().isEmpty()) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
return controlMap.put(control.getObjectId(), control); |
||||||
|
} |
||||||
|
|
||||||
|
public List<DroneControlDto> getList() { |
||||||
|
return new ArrayList<>(controlMap.values()); |
||||||
|
} |
||||||
|
|
||||||
|
public DroneControlDto 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); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,96 @@ |
|||||||
|
package kr.co.palnet.kac.websocket.service; |
||||||
|
|
||||||
|
import kr.co.palnet.kac.common.model.common.DroneControlDto; |
||||||
|
import kr.co.palnet.kac.common.model.common.DroneDto; |
||||||
|
import kr.co.palnet.kac.util.CoordUtil; |
||||||
|
import kr.co.palnet.kac.websocket.core.model.BoundaryCoordinates; |
||||||
|
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.ArrayList; |
||||||
|
import java.util.Comparator; |
||||||
|
import java.util.List; |
||||||
|
import java.util.stream.Collectors; |
||||||
|
|
||||||
|
@Slf4j |
||||||
|
@RequiredArgsConstructor |
||||||
|
@Service |
||||||
|
public class ControlService { |
||||||
|
|
||||||
|
public List<DroneControlDto> getList(List<BoundaryCoordinates> coordinatesList) { |
||||||
|
List<CoordUtil.Coordinates> polygon = coordinatesList.stream().map(bc -> CoordUtil.Coordinates.builder().x(bc.getLon()).y(bc.getLat()).build()).toList(); |
||||||
|
ControlStorage controlStorage = ControlStorage.getInstance(); |
||||||
|
List<DroneControlDto> controlDtoList = controlStorage.getList(); |
||||||
|
|
||||||
|
if (controlDtoList == null || controlDtoList.isEmpty()) return new ArrayList<>(); |
||||||
|
|
||||||
|
List<DroneControlDto> result = controlDtoList.stream().filter(controlDto -> |
||||||
|
// CoordUtil.isInsidePolygon(polygon, CoordUtil.Coordinates.builder().x(controlDto.getLon()).y(controlDto.getLat()).build())
|
||||||
|
CoordUtil.isInsideRectangle(polygon, CoordUtil.Coordinates.builder().x(controlDto.getLon()).y(controlDto.getLat()).build()) |
||||||
|
).sorted(Comparator.reverseOrder()).toList(); |
||||||
|
|
||||||
|
|
||||||
|
return result; |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
public DroneControlDto dronDtoToControlDtoConvert(DroneDto dronDto) { |
||||||
|
|
||||||
|
ControlStorage controlCache = ControlStorage.getInstance(); |
||||||
|
DroneControlDto prevControlDto = controlCache.get(dronDto.getObjectId()); |
||||||
|
|
||||||
|
DroneControlDto controlDto = new DroneControlDto(); |
||||||
|
|
||||||
|
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.setDroneStatus(dronDto.getDroneStatus()); |
||||||
|
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; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,54 @@ |
|||||||
|
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.common.model.common.DroneControlDto; |
||||||
|
import kr.co.palnet.kac.util.ObjectMapperUtil; |
||||||
|
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.Comparator; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
@Slf4j |
||||||
|
@RequiredArgsConstructor |
||||||
|
@Service |
||||||
|
public class ScheduledService { |
||||||
|
|
||||||
|
private final ObjectMapper objectMapper = ObjectMapperUtil.getObjectMapper(); |
||||||
|
|
||||||
|
|
||||||
|
// 접속한 모든 채널에 데이터 전송
|
||||||
|
// @Scheduled(fixedDelay = 10 * 1000) // 10초
|
||||||
|
public void sendAllChannel() { |
||||||
|
// 채널 가져오기
|
||||||
|
ChannelStorage channelStorage = ChannelStorage.getInstance(); |
||||||
|
ChannelGroup channelGroup = channelStorage.getGroup(); |
||||||
|
// 데이터 가져오기
|
||||||
|
ControlStorage controlStorage = ControlStorage.getInstance(); |
||||||
|
List<DroneControlDto> controlDtoList = controlStorage.getList(); |
||||||
|
|
||||||
|
if (controlDtoList == null || controlDtoList.isEmpty()) return; |
||||||
|
|
||||||
|
controlDtoList.sort(Comparator.reverseOrder()); |
||||||
|
|
||||||
|
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 : {}", e.getMessage(), e); |
||||||
|
} catch (Exception e) { |
||||||
|
log.warn("send fail to all channel. : {}", e.getMessage(), e); |
||||||
|
} |
||||||
|
log.debug("done send to all channel"); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,52 @@ |
|||||||
|
spring: |
||||||
|
threads: |
||||||
|
virtual: |
||||||
|
enabled: true |
||||||
|
|
||||||
|
netty: |
||||||
|
socket: |
||||||
|
tcp-port: 8001 |
||||||
|
boss-count: 1 |
||||||
|
keep-alive: true |
||||||
|
tcp-nodelay: true |
||||||
|
backlog: 3000 |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
spring: |
||||||
|
config: |
||||||
|
activate: |
||||||
|
on-profile: local |
||||||
|
|
||||||
|
app: |
||||||
|
kac-app: |
||||||
|
host: http://kac-app:8000 |
||||||
|
|
||||||
|
server: |
||||||
|
port: 8002 |
||||||
|
|
||||||
|
logging: |
||||||
|
level: |
||||||
|
kr.co.palnet: debug |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
spring: |
||||||
|
config: |
||||||
|
activate: |
||||||
|
on-profile: docker |
||||||
|
|
||||||
|
app: |
||||||
|
kac-app: |
||||||
|
host: http://kac-app:8000 |
||||||
|
|
||||||
|
server: |
||||||
|
port: 8002 |
||||||
|
|
||||||
|
logging: |
||||||
|
level: |
||||||
|
kr.co.palnet: info |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,12 @@ |
|||||||
|
|
||||||
|
|
||||||
|
dependencies { |
||||||
|
// jpa |
||||||
|
implementation "$boot:spring-boot-starter-data-jpa" |
||||||
|
// querydsl |
||||||
|
implementation "com.querydsl:querydsl-jpa:5.0.0:jakarta" |
||||||
|
|
||||||
|
runtimeOnly "com.mysql:mysql-connector-j" |
||||||
|
implementation "org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4:1.16" |
||||||
|
} |
||||||
|
|
@ -0,0 +1,69 @@ |
|||||||
|
package kr.co.palnet.kac.config.db; |
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Value; |
||||||
|
import org.springframework.boot.autoconfigure.orm.jpa.HibernateProperties; |
||||||
|
import org.springframework.boot.autoconfigure.orm.jpa.HibernateSettings; |
||||||
|
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties; |
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties; |
||||||
|
import org.springframework.boot.jdbc.DataSourceBuilder; |
||||||
|
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; |
||||||
|
import org.springframework.context.annotation.Bean; |
||||||
|
import org.springframework.context.annotation.Configuration; |
||||||
|
import org.springframework.context.annotation.Primary; |
||||||
|
import org.springframework.data.jpa.repository.config.EnableJpaRepositories; |
||||||
|
import org.springframework.orm.jpa.JpaTransactionManager; |
||||||
|
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; |
||||||
|
import org.springframework.transaction.PlatformTransactionManager; |
||||||
|
|
||||||
|
import javax.sql.DataSource; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Objects; |
||||||
|
|
||||||
|
@Configuration |
||||||
|
@EnableJpaRepositories( |
||||||
|
basePackages = "kr.co.palnet.kac.data.*.repository", |
||||||
|
entityManagerFactoryRef = "kacEntityManagerFactory", |
||||||
|
transactionManagerRef = "kacTransactionManager" |
||||||
|
) |
||||||
|
public class KacJpaConfig { |
||||||
|
|
||||||
|
private final JpaProperties jpaProperties; |
||||||
|
private final HibernateProperties hibernateProperties; |
||||||
|
|
||||||
|
public KacJpaConfig(JpaProperties jpaProperties, HibernateProperties hibernateProperties) { |
||||||
|
this.jpaProperties = jpaProperties; |
||||||
|
this.hibernateProperties = hibernateProperties; |
||||||
|
} |
||||||
|
|
||||||
|
@Bean(name = "kacDataSource") |
||||||
|
@Primary |
||||||
|
@ConfigurationProperties(prefix = "spring.datasource.pav-kac") |
||||||
|
public DataSource kacDataSource() { |
||||||
|
return DataSourceBuilder.create().build(); |
||||||
|
} |
||||||
|
|
||||||
|
@Bean(name = "kacEntityManagerFactory") |
||||||
|
@Primary |
||||||
|
public LocalContainerEntityManagerFactoryBean kacEntityManagerFactory(EntityManagerFactoryBuilder builder) { |
||||||
|
Map<String, Object> prop = hibernateProperties.determineHibernateProperties( |
||||||
|
jpaProperties.getProperties(), |
||||||
|
new HibernateSettings() |
||||||
|
); |
||||||
|
|
||||||
|
return builder.dataSource(kacDataSource()) |
||||||
|
.properties(prop) |
||||||
|
.packages( |
||||||
|
"kr.co.palnet.kac.data.**.model", |
||||||
|
"kr.co.palnet.kac.data.**.domain" |
||||||
|
) |
||||||
|
.persistenceUnit("kac") |
||||||
|
.build(); |
||||||
|
} |
||||||
|
|
||||||
|
@Bean(name = "kacTransactionManager") |
||||||
|
@Primary |
||||||
|
public PlatformTransactionManager kacTransactionManager(EntityManagerFactoryBuilder builder) { |
||||||
|
return new JpaTransactionManager(Objects.requireNonNull(kacEntityManagerFactory(builder).getObject())); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,20 @@ |
|||||||
|
package kr.co.palnet.kac.config.db; |
||||||
|
|
||||||
|
import com.querydsl.jpa.impl.JPAQueryFactory; |
||||||
|
import jakarta.persistence.EntityManager; |
||||||
|
import jakarta.persistence.PersistenceContext; |
||||||
|
import org.springframework.context.annotation.Bean; |
||||||
|
import org.springframework.context.annotation.Configuration; |
||||||
|
|
||||||
|
@Configuration |
||||||
|
public class QueryDslConfig { |
||||||
|
|
||||||
|
@PersistenceContext |
||||||
|
private EntityManager entityManager; |
||||||
|
|
||||||
|
@Bean |
||||||
|
public JPAQueryFactory jpaQueryFactory() { |
||||||
|
return new JPAQueryFactory(entityManager); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,36 @@ |
|||||||
|
spring: |
||||||
|
jpa: |
||||||
|
hibernate: |
||||||
|
ddl-auto: none |
||||||
|
naming: |
||||||
|
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl |
||||||
|
datasource: |
||||||
|
pav-kac: |
||||||
|
driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy |
||||||
|
jdbc-url: jdbc:log4jdbc:mysql://localhost:13306/PAV_KAC?characterEncoding=UTF-8&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true |
||||||
|
username: kac |
||||||
|
password: palnet!234 |
||||||
|
minimumidle: 5 |
||||||
|
maximumpoolsize: 5 |
||||||
|
idletimeout: 60000 |
||||||
|
maxlifetime: 300000 |
||||||
|
connectiontimeout: 30000 |
||||||
|
validationtimeout: 5000 |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
spring: |
||||||
|
config: |
||||||
|
activate: |
||||||
|
on-profile: docker |
||||||
|
jpa: |
||||||
|
hibernate: |
||||||
|
ddl-auto: none |
||||||
|
naming: |
||||||
|
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl |
||||||
|
datasource: |
||||||
|
pav-kac: |
||||||
|
driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy |
||||||
|
jdbc-url: jdbc:log4jdbc:mysql://database:3306/PAV_KAC?characterEncoding=UTF-8&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true |
||||||
|
username: kac |
||||||
|
password: palnet!234 |
@ -0,0 +1,6 @@ |
|||||||
|
|
||||||
|
|
||||||
|
dependencies { |
||||||
|
implementation "$boot:spring-boot-starter-web" |
||||||
|
} |
||||||
|
|
@ -0,0 +1,4 @@ |
|||||||
|
package kr.co.palnet.kac.core; |
||||||
|
|
||||||
|
public class Sample { |
||||||
|
} |
@ -0,0 +1,54 @@ |
|||||||
|
package kr.co.palnet.kac.core.exception; |
||||||
|
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus; |
||||||
|
|
||||||
|
public enum BaseErrorCode { |
||||||
|
SUCCESS("CM001", HttpStatus.OK, "성공"), |
||||||
|
FAILED("CM900", HttpStatus.INTERNAL_SERVER_ERROR, "실패"), |
||||||
|
UNKNOWN("CM999", HttpStatus.INTERNAL_SERVER_ERROR, "실패(사용자 정의 없는 에러)"), |
||||||
|
NOT_PROCESS("CM002", HttpStatus.INTERNAL_SERVER_ERROR, "처리되지 않음"), |
||||||
|
IO_ERROR("CM003", HttpStatus.INTERNAL_SERVER_ERROR, "IO 에러"), |
||||||
|
LOGIN_FAILED("CM100", HttpStatus.INTERNAL_SERVER_ERROR, "로그인 실패"), |
||||||
|
LOGIN_DUPLICATED("CM101", HttpStatus.INTERNAL_SERVER_ERROR, "중복 로그인"), |
||||||
|
LOGIN_ATTEMPT_COUNT("CM102", HttpStatus.INTERNAL_SERVER_ERROR, "로그인 실패 횟수 초과"), |
||||||
|
LOGIN_SNS_FAILED("CM110", HttpStatus.INTERNAL_SERVER_ERROR, "SNS 로그인 실패"), |
||||||
|
LOGIN_SNS_DUPLICATED("CM111", HttpStatus.INTERNAL_SERVER_ERROR, "SNS 가입 중복"), |
||||||
|
TOKEN_EXPIRED("CM120", HttpStatus.INTERNAL_SERVER_ERROR, "토큰 만료"), |
||||||
|
TOKEN_INVALID("CM121", HttpStatus.INTERNAL_SERVER_ERROR, "잘못된 토큰"), |
||||||
|
ACCOUNT_LOCK("CM130", HttpStatus.INTERNAL_SERVER_ERROR, "계정 잠김"), |
||||||
|
ACCOUNT_DORMANT("CM131", HttpStatus.INTERNAL_SERVER_ERROR, "휴면 계정"), |
||||||
|
CACHE_LOAD_FAILED("CM200", HttpStatus.INTERNAL_SERVER_ERROR, "캐시 로딩 실패"), |
||||||
|
CACHE_RELOAD_FAILED("CM201", HttpStatus.INTERNAL_SERVER_ERROR, "캐시 리로딩 실패"), |
||||||
|
AUTH_EMPTY("CM401", HttpStatus.UNAUTHORIZED, "권한 없음"), |
||||||
|
WEB_PARAM_INVALID("WB400", HttpStatus.INTERNAL_SERVER_ERROR, "잘못된 파라미터"), |
||||||
|
WEB_NOT_FOUND("WB404", HttpStatus.INTERNAL_SERVER_ERROR, "찾을수 없는 페이지"), |
||||||
|
WEB_API_ERROR("WB500", HttpStatus.INTERNAL_SERVER_ERROR, "외부 연동 에러"), |
||||||
|
DATA_ALREADY_EXISTS("DT001", HttpStatus.INTERNAL_SERVER_ERROR, "이미 등록된 데이터"), |
||||||
|
DATA_EMPTY("DT002", HttpStatus.INTERNAL_SERVER_ERROR, "데이터 없음"); |
||||||
|
|
||||||
|
|
||||||
|
private final String code; |
||||||
|
|
||||||
|
private final HttpStatus status; |
||||||
|
|
||||||
|
private final String message; |
||||||
|
|
||||||
|
private BaseErrorCode(String code, HttpStatus status, String message) { |
||||||
|
this.code = code; |
||||||
|
this.status = status; |
||||||
|
this.message = message; |
||||||
|
} |
||||||
|
|
||||||
|
public String code() { |
||||||
|
return this.code; |
||||||
|
} |
||||||
|
|
||||||
|
public String message() { |
||||||
|
return this.message; |
||||||
|
} |
||||||
|
|
||||||
|
public HttpStatus status() { |
||||||
|
return this.status; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,132 @@ |
|||||||
|
package kr.co.palnet.kac.core.exception; |
||||||
|
|
||||||
|
import lombok.Getter; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.springframework.context.MessageSource; |
||||||
|
|
||||||
|
import java.util.Locale; |
||||||
|
|
||||||
|
@Getter |
||||||
|
public class BaseException extends RuntimeException { |
||||||
|
|
||||||
|
|
||||||
|
private final BaseErrorCode errorCode; |
||||||
|
|
||||||
|
private final String logMessage; |
||||||
|
|
||||||
|
private final Level level; |
||||||
|
|
||||||
|
private final Object[] paramArray; |
||||||
|
|
||||||
|
public BaseException() { |
||||||
|
this(BaseErrorCode.FAILED, null, Level.WARN, null); |
||||||
|
} |
||||||
|
|
||||||
|
public BaseException(Object[] paramArray) { |
||||||
|
this(BaseErrorCode.FAILED, null, Level.WARN, paramArray); |
||||||
|
} |
||||||
|
|
||||||
|
public BaseException(Level level) { |
||||||
|
this(BaseErrorCode.FAILED, null, level, null); |
||||||
|
} |
||||||
|
|
||||||
|
public BaseException(Level level, Object[] paramArray) { |
||||||
|
this(BaseErrorCode.FAILED, null, level, paramArray); |
||||||
|
} |
||||||
|
|
||||||
|
public BaseException(String logMessage) { |
||||||
|
this(BaseErrorCode.FAILED, logMessage, Level.WARN, null); |
||||||
|
} |
||||||
|
|
||||||
|
public BaseException(String logMessage, Object[] paramArray) { |
||||||
|
this(BaseErrorCode.FAILED, logMessage, Level.WARN, paramArray); |
||||||
|
} |
||||||
|
|
||||||
|
public BaseException(String logMessage, Level level) { |
||||||
|
this(BaseErrorCode.FAILED, logMessage, level, null); |
||||||
|
} |
||||||
|
|
||||||
|
public BaseException(String logMessage, Level level, Object[] paramArray) { |
||||||
|
this(BaseErrorCode.FAILED, logMessage, level, paramArray); |
||||||
|
} |
||||||
|
|
||||||
|
public BaseException(BaseErrorCode errorCode) { |
||||||
|
this(errorCode, null, Level.WARN, null); |
||||||
|
} |
||||||
|
|
||||||
|
public BaseException(BaseErrorCode errorCode, Object[] paramArray) { |
||||||
|
this(errorCode, null, Level.WARN, paramArray); |
||||||
|
} |
||||||
|
|
||||||
|
public BaseException(BaseErrorCode errorCode, Level level) { |
||||||
|
this(errorCode, null, level, null); |
||||||
|
} |
||||||
|
|
||||||
|
public BaseException(BaseErrorCode errorCode, Level level, Object[] paramArray) { |
||||||
|
this(errorCode, null, level, paramArray); |
||||||
|
} |
||||||
|
|
||||||
|
public BaseException(BaseErrorCode errorCode, String logMessage) { |
||||||
|
this(errorCode, logMessage, Level.WARN, null); |
||||||
|
} |
||||||
|
|
||||||
|
public BaseException(BaseErrorCode errorCode, String logMessage, Object[] paramArray) { |
||||||
|
this(errorCode, logMessage, Level.WARN, paramArray); |
||||||
|
} |
||||||
|
|
||||||
|
public BaseException(BaseErrorCode errorCode, String logMessage, Level level) { |
||||||
|
this(errorCode, logMessage, level, null); |
||||||
|
} |
||||||
|
|
||||||
|
public BaseException(BaseErrorCode errorCode, String logMessage, Level level, Object[] paramArray) { |
||||||
|
this.errorCode = errorCode; |
||||||
|
this.logMessage = logMessage; |
||||||
|
this.level = level; |
||||||
|
this.paramArray = paramArray; |
||||||
|
} |
||||||
|
|
||||||
|
public BaseException(String message, BaseErrorCode errorCode, String logMessage, Level level, Object[] paramArray) { |
||||||
|
super(message); |
||||||
|
this.errorCode = errorCode; |
||||||
|
this.logMessage = logMessage; |
||||||
|
this.level = level; |
||||||
|
this.paramArray = paramArray; |
||||||
|
} |
||||||
|
|
||||||
|
public BaseException(String message, Throwable cause, BaseErrorCode errorCode, String logMessage, Level level, Object[] paramArray) { |
||||||
|
super(message, cause); |
||||||
|
this.errorCode = errorCode; |
||||||
|
this.logMessage = logMessage; |
||||||
|
this.level = level; |
||||||
|
this.paramArray = paramArray; |
||||||
|
} |
||||||
|
|
||||||
|
public BaseException(Throwable cause, BaseErrorCode errorCode, String logMessage, Level level, Object[] paramArray) { |
||||||
|
super(cause); |
||||||
|
this.errorCode = errorCode; |
||||||
|
this.logMessage = logMessage; |
||||||
|
this.level = level; |
||||||
|
this.paramArray = paramArray; |
||||||
|
} |
||||||
|
|
||||||
|
public BaseException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, BaseErrorCode errorCode, String logMessage, Level level, Object[] paramArray) { |
||||||
|
super(message, cause, enableSuppression, writableStackTrace); |
||||||
|
this.errorCode = errorCode; |
||||||
|
this.logMessage = logMessage; |
||||||
|
this.level = level; |
||||||
|
this.paramArray = paramArray; |
||||||
|
} |
||||||
|
|
||||||
|
public String getCode() { |
||||||
|
return errorCode.code(); |
||||||
|
} |
||||||
|
|
||||||
|
public String getErrorMessage(MessageSource ms) { |
||||||
|
if (ms == null) { |
||||||
|
return BaseErrorCode.UNKNOWN.message(); |
||||||
|
} |
||||||
|
return ms.getMessage(getCode(), getParamArray(), BaseErrorCode.UNKNOWN.message(), Locale.getDefault()); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
@ -0,0 +1,9 @@ |
|||||||
|
package kr.co.palnet.kac.core.exception; |
||||||
|
|
||||||
|
public enum Level { |
||||||
|
TRACE, |
||||||
|
DEBUG, |
||||||
|
INFO, |
||||||
|
WARN, |
||||||
|
ERROR |
||||||
|
} |
@ -0,0 +1,28 @@ |
|||||||
|
package kr.co.palnet.kac.core.exception.model; |
||||||
|
|
||||||
|
import lombok.AllArgsConstructor; |
||||||
|
import lombok.Builder; |
||||||
|
import lombok.Data; |
||||||
|
import lombok.NoArgsConstructor; |
||||||
|
|
||||||
|
import java.time.Instant; |
||||||
|
|
||||||
|
@Data |
||||||
|
@NoArgsConstructor |
||||||
|
@AllArgsConstructor |
||||||
|
@Builder |
||||||
|
public class BaseErrorModel { |
||||||
|
|
||||||
|
private Instant timestamp; |
||||||
|
|
||||||
|
private int status; |
||||||
|
|
||||||
|
private String error; |
||||||
|
|
||||||
|
private String path; |
||||||
|
|
||||||
|
private String message; |
||||||
|
|
||||||
|
private String code; |
||||||
|
|
||||||
|
} |
@ -0,0 +1,4 @@ |
|||||||
|
dependencies { |
||||||
|
implementation project(":common:util") |
||||||
|
} |
||||||
|
|
@ -0,0 +1,86 @@ |
|||||||
|
package kr.co.palnet.kac.common.model.common; |
||||||
|
|
||||||
|
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 DroneControlDto implements Comparable<DroneControlDto> { |
||||||
|
|
||||||
|
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 droneStatus; |
||||||
|
|
||||||
|
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(DroneControlDto o) { |
||||||
|
if (o.getControlStartDt() != null && controlStartDt != null) { |
||||||
|
return o.getControlStartDt().compareTo(controlStartDt); |
||||||
|
} |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,105 @@ |
|||||||
|
package kr.co.palnet.kac.common.model.common; |
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize; |
||||||
|
import kr.co.palnet.kac.common.model.core.convert.InstantConvert; |
||||||
|
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 DroneDto { |
||||||
|
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 droneStatus; |
||||||
|
|
||||||
|
@Builder.Default |
||||||
|
private Double heading = 0.0; |
||||||
|
|
||||||
|
@JsonDeserialize(using = InstantConvert.Deserializer.class) |
||||||
|
private Instant 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<DroneHistoryDto> recentPositionHistory; |
||||||
|
|
||||||
|
// 전체 히스토리 저장
|
||||||
|
private List<DroneHistoryDto> postionHistory; |
||||||
|
|
||||||
|
// 비정상 상황 식별 코드
|
||||||
|
private boolean controlWarnCd; |
||||||
|
|
||||||
|
// 큐가 Socket서버에 도착한 시간
|
||||||
|
private Instant regDt; |
||||||
|
// 큐가 Socket서버에 도착한 시간
|
||||||
|
private boolean isSendUtm; // 불법드론 전송 여부
|
||||||
|
private boolean isSendHistory; // 서버 전송 여부
|
||||||
|
private boolean isSendWebSocket; // WebSocket 전송 여부
|
||||||
|
|
||||||
|
public boolean isSendAll() { |
||||||
|
return isSendHistory && isSendUtm && isSendWebSocket; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,20 @@ |
|||||||
|
package kr.co.palnet.kac.common.model.common; |
||||||
|
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor; |
||||||
|
import lombok.Builder; |
||||||
|
import lombok.Data; |
||||||
|
import lombok.NoArgsConstructor; |
||||||
|
|
||||||
|
@Data |
||||||
|
@NoArgsConstructor |
||||||
|
@AllArgsConstructor |
||||||
|
@Builder |
||||||
|
public class DroneHistoryDto { |
||||||
|
private String objectId; |
||||||
|
|
||||||
|
@Builder.Default |
||||||
|
private Double lat = 0.0; |
||||||
|
@Builder.Default |
||||||
|
private Double lon = 0.0; |
||||||
|
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue