Initial commit
This commit is contained in:
15
backend/reserve-request-service/Dockerfile
Normal file
15
backend/reserve-request-service/Dockerfile
Normal file
@@ -0,0 +1,15 @@
|
||||
# openjdk8 base image
|
||||
FROM openjdk:8-jre-alpine
|
||||
|
||||
# config server uri: dockder run --e 로 변경 가능
|
||||
ENV SPRING_CLOUD_CONFIG_URI https://egov-config.paas-ta.org
|
||||
# jar 파일이 복사되는 위치
|
||||
ENV APP_HOME=/usr/app/
|
||||
# 작업 시작 위치
|
||||
WORKDIR $APP_HOME
|
||||
# jar 파일 복사
|
||||
COPY build/libs/*.jar app.jar
|
||||
# application port
|
||||
#EXPOSE 8000
|
||||
# 실행 (application-cf.yml 프로필이 기본값)
|
||||
CMD ["java", "-Dspring.profiles.active=${profile:cf}", "-jar", "app.jar"]
|
||||
87
backend/reserve-request-service/build.gradle
Normal file
87
backend/reserve-request-service/build.gradle
Normal file
@@ -0,0 +1,87 @@
|
||||
plugins {
|
||||
id 'org.springframework.boot' version '2.4.5'
|
||||
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
|
||||
id 'java'
|
||||
}
|
||||
|
||||
group = 'org.egovframe.cloud'
|
||||
version = '0.1'
|
||||
sourceCompatibility = '1.8'
|
||||
|
||||
configurations {
|
||||
compileOnly {
|
||||
extendsFrom annotationProcessor
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url "https://maven.egovframe.go.kr/maven/" } // egovframe maven 원격 저장소
|
||||
}
|
||||
|
||||
ext {
|
||||
set('springCloudVersion', "2020.0.3")
|
||||
}
|
||||
|
||||
|
||||
dependencies {
|
||||
// implementation files('../../module-common/build/libs/module-common-0.1.jar') // @ComponentScan(basePackages={"org.egovframe.cloud"}) 추가해야 적용된다
|
||||
implementation 'org.egovframe.cloud:module-common:0.1'
|
||||
implementation('org.egovframe.rte:org.egovframe.rte.fdl.cmmn:4.0.0') {
|
||||
exclude group: 'org.egovframe.rte', module: 'org.egovframe.rte.fdl.logging'
|
||||
}
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-r2dbc'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-webflux'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-actuator'
|
||||
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
|
||||
implementation 'org.springframework.cloud:spring-cloud-starter-config' // config
|
||||
implementation 'org.springframework.cloud:spring-cloud-starter-bootstrap' // config
|
||||
implementation 'org.springframework.cloud:spring-cloud-starter-bus-amqp' // bus
|
||||
implementation 'org.springframework.cloud:spring-cloud-starter-circuitbreaker-reactor-resilience4j'
|
||||
implementation 'com.playtika.reactivefeign:feign-reactor-spring-cloud-starter:3.1.0'
|
||||
|
||||
//messaging
|
||||
implementation 'org.springframework.cloud:spring-cloud-stream'
|
||||
implementation 'org.springframework.cloud:spring-cloud-stream-binder-rabbit'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-amqp'
|
||||
implementation 'net.java.dev.jna:jna:5.9.0' // byte-buddy (No compatible attachment provider is available.)
|
||||
|
||||
|
||||
implementation 'io.jsonwebtoken:jjwt:0.9.1'
|
||||
|
||||
implementation 'dev.miku:r2dbc-mysql:0.8.2.RELEASE'
|
||||
implementation 'mysql:mysql-connector-java'
|
||||
|
||||
// swagger api docs
|
||||
implementation 'org.springdoc:springdoc-openapi-webflux-ui:1.5.10'
|
||||
|
||||
// bolcking 호출 감지
|
||||
implementation 'io.projectreactor:reactor-tools:3.4.9'
|
||||
implementation 'io.projectreactor.tools:blockhound:1.0.6.RELEASE'
|
||||
|
||||
//lombok
|
||||
implementation 'org.projectlombok:lombok'
|
||||
annotationProcessor 'org.projectlombok:lombok'
|
||||
testImplementation 'org.projectlombok:lombok'
|
||||
testAnnotationProcessor 'org.projectlombok:lombok'
|
||||
|
||||
testImplementation 'com.h2database:h2'
|
||||
testImplementation 'io.r2dbc:r2dbc-h2'
|
||||
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||
testImplementation 'io.projectreactor:reactor-test'
|
||||
testImplementation 'org.springframework.security:spring-security-test'
|
||||
testImplementation 'org.springframework.amqp:spring-rabbit-test'
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
dependencyManagement {
|
||||
imports {
|
||||
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
|
||||
}
|
||||
}
|
||||
185
backend/reserve-request-service/gradlew
vendored
Executable file
185
backend/reserve-request-service/gradlew
vendored
Executable file
@@ -0,0 +1,185 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
89
backend/reserve-request-service/gradlew.bat
vendored
Normal file
89
backend/reserve-request-service/gradlew.bat
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
16
backend/reserve-request-service/manifest.yml
Normal file
16
backend/reserve-request-service/manifest.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
applications:
|
||||
- name: egov-reserve-request-service # CF push 시 생성되는 이름
|
||||
# memory: 512M # 메모리
|
||||
instances: 1 # 인스턴스 수
|
||||
host: egov-reserve-request-service # host 명으로 유일해야 함
|
||||
path: build/libs/reserve-request-service-0.1.jar # build 후 생성된 jar 위치
|
||||
buildpack: java_buildpack # cf buildpacks 명령어로 java buildpack 이름 확인
|
||||
services:
|
||||
- egov-discovery-provided-service # discovery service binding
|
||||
env:
|
||||
spring_profiles_active: cf
|
||||
spring_cloud_config_uri: https://egov-config.paas-ta.org
|
||||
app_name: egov-reserve-request-service # logstash custom app name
|
||||
TZ: Asia/Seoul
|
||||
JAVA_OPTS: -Xss349k
|
||||
1
backend/reserve-request-service/settings.gradle
Normal file
1
backend/reserve-request-service/settings.gradle
Normal file
@@ -0,0 +1 @@
|
||||
rootProject.name = 'reserve-request-service'
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.egovframe.cloud.reserverequestservice;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import reactor.blockhound.BlockHound;
|
||||
|
||||
import java.security.Security;
|
||||
|
||||
@ComponentScan({"org.egovframe.cloud.common", "org.egovframe.cloud.reactive", "org.egovframe.cloud.reserverequestservice"}) // org.egovframe.cloud.common package 포함하기 위해
|
||||
@EnableDiscoveryClient
|
||||
@SpringBootApplication
|
||||
public class ReserveRequestServiceApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
// TLSv1/v1.1 No longer works after upgrade, "No appropriate protocol" error
|
||||
String property = Security.getProperty("jdk.tls.disabledAlgorithms").replace(", TLSv1", "").replace(", TLSv1.1", "");
|
||||
Security.setProperty("jdk.tls.disabledAlgorithms", property);
|
||||
|
||||
//blocking 코드 감지
|
||||
BlockHound.builder()
|
||||
//mysql r2dbc 에서 호출되는 FileInputStream.readBytes() 가 블로킹코드인데 이를 허용해주도록 한다.
|
||||
//해당 코드가 어디서 호출되는지 알지 못하는 상태에서 FileInputStream.readBytes() 자체를 허용해주는 것은 좋지 않다.
|
||||
// 누군가 무분별하게 사용하게 되면 검출해 낼 수ㅂ 없어 시스템의 위험요소로 남게 된다.
|
||||
// r2dbc를 사용하기 위해 해당 호출부분만 허용하고 나머지는 여전히 검출대상으로 남기도록 한다.
|
||||
.allowBlockingCallsInside("dev.miku.r2dbc.mysql.client.ReactorNettyClient", "init")
|
||||
.install();
|
||||
|
||||
SpringApplication.run(ReserveRequestServiceApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package org.egovframe.cloud.reserverequestservice.api;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.egovframe.cloud.reserverequestservice.api.dto.ReserveResponseDto;
|
||||
import org.egovframe.cloud.reserverequestservice.api.dto.ReserveSaveRequestDto;
|
||||
import org.egovframe.cloud.reserverequestservice.config.MessageListenerContainerFactory;
|
||||
import org.egovframe.cloud.reserverequestservice.domain.Category;
|
||||
import org.egovframe.cloud.reserverequestservice.service.ReserveService;
|
||||
import org.springframework.amqp.core.AmqpAdmin;
|
||||
import org.springframework.amqp.core.MessageListener;
|
||||
import org.springframework.amqp.rabbit.listener.MessageListenerContainer;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* org.egovframe.cloud.reserverequestservice.api.ReserveApiController
|
||||
*
|
||||
* 예약 신청 rest controller class
|
||||
*
|
||||
* @author 표준프레임워크센터 shinmj
|
||||
* @version 1.0
|
||||
* @since 2021/09/16
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/09/16 shinmj 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
public class ReserveApiController {
|
||||
|
||||
private final ReserveService reserveService;
|
||||
private final MessageListenerContainerFactory messageListenerContainerFactory;
|
||||
private final AmqpAdmin amqpAdmin;
|
||||
|
||||
/**
|
||||
* 예약 신청 - 심사
|
||||
*
|
||||
* @param saveRequestDtoMono
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/api/v1/requests/audit")
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public Mono<ReserveResponseDto> create(@RequestBody Mono<ReserveSaveRequestDto> saveRequestDtoMono) {
|
||||
return saveRequestDtoMono.flatMap(reserveService::create);
|
||||
}
|
||||
|
||||
/**
|
||||
* 예약 신청 - 실시간
|
||||
*
|
||||
* @param saveRequestDtoMono
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/api/v1/requests")
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public Mono<ReserveResponseDto> save(@RequestBody Mono<ReserveSaveRequestDto> saveRequestDtoMono) {
|
||||
return saveRequestDtoMono.flatMap(saveRequestDto -> {
|
||||
if (Category.EDUCATION.isEquals(saveRequestDto.getCategoryId())) {
|
||||
return reserveService.saveForEvent(saveRequestDto);
|
||||
}
|
||||
return reserveService.save(saveRequestDto);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 실시간 예약 신청 후 결과 여부 subscribe
|
||||
*
|
||||
* @param reserveId
|
||||
* @return
|
||||
*/
|
||||
@GetMapping(value = "/api/v1/requests/direct/{reserveId}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
public Flux<?> receiveReservationResult(@PathVariable String reserveId) {
|
||||
MessageListenerContainer mlc = messageListenerContainerFactory.createMessageListenerContainer(reserveId);
|
||||
Flux<String> f = Flux.create(emitter -> {
|
||||
mlc.setupMessageListener((MessageListener) m -> {
|
||||
String qname = m.getMessageProperties().getConsumerQueue();
|
||||
log.info("message received, queue={}", qname);
|
||||
|
||||
if (emitter.isCancelled()) {
|
||||
log.info("cancelled, queue={}", qname);
|
||||
mlc.stop();
|
||||
return;
|
||||
}
|
||||
|
||||
String payload = new String(m.getBody());
|
||||
log.info("message data = {}", payload);
|
||||
emitter.next(payload);
|
||||
|
||||
log.info("message sent to client, queue={}", qname);
|
||||
});
|
||||
|
||||
emitter.onRequest(v -> {
|
||||
log.info("starting container, queue={}", reserveId);
|
||||
mlc.start();
|
||||
});
|
||||
|
||||
emitter.onDispose(() -> {
|
||||
log.info("on dispose, queue={}", reserveId);
|
||||
mlc.stop();
|
||||
amqpAdmin.deleteQueue(reserveId);
|
||||
});
|
||||
|
||||
log.info("container started, queue={}", reserveId);
|
||||
});
|
||||
|
||||
return Flux.interval(Duration.ofSeconds(5))
|
||||
.map(v -> {
|
||||
log.info("sending keepalive message...");
|
||||
return "no news is good news";
|
||||
}).mergeWith(f);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.egovframe.cloud.reserverequestservice.api.dto;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import org.egovframe.cloud.reserverequestservice.domain.Reserve;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* org.egovframe.cloud.reserverequestservice.api.dto.ReserveResponseDto
|
||||
* <p>
|
||||
* 예약 신청 응답 dto class
|
||||
*
|
||||
* @author 표준프레임워크센터 shinmj
|
||||
* @version 1.0
|
||||
* @since 2021/09/17
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/09/17 shinmj 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@ToString
|
||||
public class ReserveResponseDto {
|
||||
|
||||
private String reserveId;
|
||||
|
||||
private Long reserveItemId;
|
||||
private Long locationId;
|
||||
private String categoryId;
|
||||
|
||||
private Integer reserveQty;
|
||||
private LocalDateTime reserveStartDate;
|
||||
private LocalDateTime reserveEndDate;
|
||||
private String reservePurposeContent;
|
||||
private String attachmentCode;
|
||||
|
||||
private String userId;
|
||||
private String userContactNo;
|
||||
private String userEmail;
|
||||
|
||||
@Builder
|
||||
public ReserveResponseDto(Reserve entity) {
|
||||
this.reserveId = entity.getReserveId();
|
||||
this.reserveItemId = entity.getReserveItemId();
|
||||
this.locationId = entity.getLocationId();
|
||||
this.categoryId = entity.getCategoryId();
|
||||
this.reserveQty = entity.getReserveQty();
|
||||
this.reserveStartDate = entity.getReserveStartDate();
|
||||
this.reserveEndDate = entity.getReserveEndDate();
|
||||
this.reservePurposeContent = entity.getReservePurposeContent();
|
||||
this.attachmentCode = entity.getAttachmentCode();
|
||||
this.userId = entity.getUserId();
|
||||
this.userContactNo = entity.getUserContactNo();
|
||||
this.userEmail = entity.getUserEmail();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package org.egovframe.cloud.reserverequestservice.api.dto;
|
||||
|
||||
import lombok.*;
|
||||
import org.egovframe.cloud.reserverequestservice.domain.Reserve;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* org.egovframe.cloud.reserverequestservice.api.dto.ReserveSaveRequestDto
|
||||
* <p>
|
||||
* 예약 신청 저장 요청 dto class
|
||||
*
|
||||
* @author 표준프레임워크센터 shinmj
|
||||
* @version 1.0
|
||||
* @since 2021/09/17
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/09/17 shinmj 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@ToString
|
||||
public class ReserveSaveRequestDto {
|
||||
|
||||
@Setter
|
||||
private String reserveId;
|
||||
@NotNull
|
||||
private Long reserveItemId;
|
||||
private Long locationId;
|
||||
private String categoryId;
|
||||
private Integer totalQty;
|
||||
private String reserveMethodId;
|
||||
private String reserveMeansId;
|
||||
private LocalDateTime operationStartDate;
|
||||
private LocalDateTime operationEndDate;
|
||||
private LocalDateTime requestStartDate;
|
||||
private LocalDateTime requestEndDate;
|
||||
private Boolean isPeriod;
|
||||
private Integer periodMaxCount;
|
||||
|
||||
private Integer reserveQty; //예약 신청 인원/수량
|
||||
@NotNull
|
||||
private String reservePurposeContent; //예약 목적
|
||||
private String attachmentCode; //첨부파일 코드
|
||||
private LocalDateTime reserveStartDate; //예약 신청 시작일
|
||||
private LocalDateTime reserveEndDate; //예약 신청 종료일
|
||||
@Setter
|
||||
private String reserveStatusId; //예약상태 - 공통코드(reserve-status)
|
||||
@NotNull
|
||||
private String userId; //예약자
|
||||
@NotNull
|
||||
private String userContactNo; //예약자 연락처
|
||||
@NotNull
|
||||
private String userEmail; //예약자 이메일
|
||||
|
||||
@Builder
|
||||
public ReserveSaveRequestDto(String reserveId, Long reserveItemId, Long locationId, String categoryId,
|
||||
Integer totalQty, String reserveMethodId, String reserveMeansId, LocalDateTime operationStartDate,
|
||||
LocalDateTime operationEndDate, LocalDateTime requestStartDate, LocalDateTime requestEndDate,
|
||||
Boolean isPeriod, Integer periodMaxCount, Integer reserveQty, String reservePurposeContent,
|
||||
String attachmentCode, LocalDateTime reserveStartDate, LocalDateTime reserveEndDate,
|
||||
String reserveStatusId, String userId, String userContactNo, String userEmail) {
|
||||
this.reserveId = reserveId;
|
||||
this.reserveItemId = reserveItemId;
|
||||
this.locationId = locationId;
|
||||
this.categoryId = categoryId;
|
||||
this.totalQty = totalQty;
|
||||
this.reserveMethodId = reserveMethodId;
|
||||
this.reserveMeansId = reserveMeansId;
|
||||
this.operationStartDate = operationStartDate;
|
||||
this.operationEndDate = operationEndDate;
|
||||
this.requestStartDate = requestStartDate;
|
||||
this.requestEndDate = requestEndDate;
|
||||
this.isPeriod = isPeriod;
|
||||
this.periodMaxCount = periodMaxCount;
|
||||
this.reserveQty = reserveQty;
|
||||
this.reservePurposeContent = reservePurposeContent;
|
||||
this.attachmentCode = attachmentCode;
|
||||
this.reserveStartDate = reserveStartDate;
|
||||
this.reserveEndDate = reserveEndDate;
|
||||
this.reserveStatusId = reserveStatusId;
|
||||
this.userId = userId;
|
||||
this.userContactNo = userContactNo;
|
||||
this.userEmail = userEmail;
|
||||
}
|
||||
|
||||
public Reserve toEntity() {
|
||||
return Reserve.builder()
|
||||
.reserveId(this.reserveId)
|
||||
.reserveItemId(this.reserveItemId)
|
||||
.locationId(this.locationId)
|
||||
.categoryId(this.categoryId)
|
||||
.reserveQty(this.reserveQty)
|
||||
.reservePurposeContent(this.reservePurposeContent)
|
||||
.attachmentCode(this.attachmentCode)
|
||||
.reserveStartDate(this.reserveStartDate)
|
||||
.reserveEndDate(this.reserveEndDate)
|
||||
.reserveStatusId(this.reserveStatusId)
|
||||
.userId(this.userId)
|
||||
.userContactNo(this.userContactNo)
|
||||
.userEmail(this.userEmail)
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.egovframe.cloud.reserverequestservice.config;
|
||||
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.amqp.core.AcknowledgeMode;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.listener.MessageListenerContainer;
|
||||
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* org.egovframe.cloud.reserverequestservice.config.MessageListenerContainerFactory
|
||||
*
|
||||
* 동적으로 이벤트 큐 생성하기 위한 component
|
||||
*
|
||||
* @author 표준프레임워크센터 shinmj
|
||||
* @version 1.0
|
||||
* @since 2021/09/30
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/09/30 shinmj 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
@Component
|
||||
public class MessageListenerContainerFactory {
|
||||
|
||||
@Autowired
|
||||
private ConnectionFactory connectionFactory;
|
||||
|
||||
public MessageListenerContainer createMessageListenerContainer(String queueName) {
|
||||
SimpleMessageListenerContainer mlc = new SimpleMessageListenerContainer();
|
||||
mlc.setConnectionFactory(connectionFactory);
|
||||
mlc.addQueueNames(queueName);
|
||||
mlc.setAcknowledgeMode(AcknowledgeMode.AUTO);
|
||||
|
||||
return mlc;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.egovframe.cloud.reserverequestservice.config;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* org.egovframe.cloud.reserverequestservice.config.RequestMessage
|
||||
*
|
||||
* 예약 신청 후 이벤트 스트림 message VO class
|
||||
*
|
||||
* @author 표준프레임워크센터 shinmj
|
||||
* @version 1.0
|
||||
* @since 2021/09/16
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/09/16 shinmj 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
@ToString
|
||||
public class RequestMessage {
|
||||
private String reserveId;
|
||||
private Boolean isItemUpdated;
|
||||
|
||||
@Builder
|
||||
public RequestMessage(String reserveId, Boolean isItemUpdated) {
|
||||
this.reserveId = reserveId;
|
||||
this.isItemUpdated = isItemUpdated;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package org.egovframe.cloud.reserverequestservice.config;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.egovframe.cloud.common.config.GlobalConstant;
|
||||
import org.egovframe.cloud.reserverequestservice.domain.ReserveStatus;
|
||||
import org.egovframe.cloud.reserverequestservice.service.ReserveService;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.messaging.Message;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* org.egovframe.cloud.reserverequestservice.config.ReserveEventConfig
|
||||
*
|
||||
* event stream 설정 class
|
||||
*
|
||||
* @author 표준프레임워크센터 shinmj
|
||||
* @version 1.0
|
||||
* @since 2021/09/16
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/09/16 shinmj 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class ReserveEventConfig {
|
||||
@Autowired
|
||||
private ReserveService reserveService;
|
||||
@Autowired
|
||||
private ConnectionFactory connectionFactory;
|
||||
|
||||
/**
|
||||
* 예약 신청(실시간) 후 재고 변경에 대한 성공 여부 consumer function
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public Consumer<Message<RequestMessage>> inventoryUpdated() {
|
||||
return message -> {
|
||||
log.info("receive message: {}, headers: {}", message.getPayload(), message.getHeaders());
|
||||
if (message.getPayload().getIsItemUpdated()) {
|
||||
reserveService.updateStatus(message.getPayload().getReserveId(), ReserveStatus.APPROVE).subscribe();
|
||||
}else {
|
||||
reserveService.delete(message.getPayload().getReserveId()).subscribe();
|
||||
}
|
||||
|
||||
RabbitTemplate rabbitTemplate = rabbitTemplate(connectionFactory);
|
||||
rabbitTemplate.convertAndSend(GlobalConstant.SUCCESS_OR_NOT_EX_NAME,
|
||||
message.getPayload().getReserveId(), message.getPayload().getIsItemUpdated());
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RabbitTemplate rabbitTemplate(final ConnectionFactory connectionFactory) {
|
||||
final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
|
||||
rabbitTemplate.setMessageConverter(messageConverter());
|
||||
return rabbitTemplate;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Jackson2JsonMessageConverter messageConverter() {
|
||||
return new Jackson2JsonMessageConverter();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.egovframe.cloud.reserverequestservice.domain;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public enum Category {
|
||||
EDUCATION("education", "교육"),
|
||||
EQUIPMENT("equipment", "장비"),
|
||||
SPACE("space", "공간");
|
||||
|
||||
private final String key;
|
||||
private final String title;
|
||||
|
||||
public boolean isEquals(String compare) {
|
||||
return this.getKey().equals(compare);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package org.egovframe.cloud.reserverequestservice.domain;
|
||||
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import org.egovframe.cloud.reactive.domain.BaseEntity;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.relational.core.mapping.Column;
|
||||
import org.springframework.data.relational.core.mapping.Table;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* org.egovframe.cloud.reserverequestservice.domain.Reserve
|
||||
*
|
||||
* 예약 도메인 클래스
|
||||
*
|
||||
* @author 표준프레임워크센터 shinmj
|
||||
* @version 1.0
|
||||
* @since 2021/09/15
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/09/15 shinmj 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@ToString
|
||||
@Table("reserve")
|
||||
public class Reserve extends BaseEntity {
|
||||
|
||||
@Id
|
||||
@Column
|
||||
private String reserveId; //예약 id
|
||||
|
||||
@Column
|
||||
private Long reserveItemId; //예약 물품 id
|
||||
|
||||
@Column
|
||||
private Long locationId; //예약 물품 - 지역 id
|
||||
|
||||
@Column
|
||||
private String categoryId; //예약 물품 - 유형 id
|
||||
|
||||
@Column
|
||||
private Integer reserveQty; //예약 신청 인원/수량
|
||||
|
||||
@Column
|
||||
private String reservePurposeContent; //예약 목적
|
||||
|
||||
@Column
|
||||
private String attachmentCode; //첨부파일 코드
|
||||
|
||||
@Column
|
||||
private LocalDateTime reserveStartDate; //예약 신청 시작일
|
||||
@Column
|
||||
private LocalDateTime reserveEndDate; //예약 신청 종료일
|
||||
|
||||
@Column
|
||||
private String reserveStatusId; //예약상태 - 공통코드(reserve-status)
|
||||
|
||||
@Column
|
||||
private String userId; //예약자
|
||||
|
||||
@Column
|
||||
private String userContactNo; //예약자 연락처
|
||||
|
||||
@Column("user_email_addr")
|
||||
private String userEmail; //예약자 이메일
|
||||
|
||||
@Builder
|
||||
public Reserve(String reserveId, Long reserveItemId, Long locationId, String categoryId, Integer reserveQty, String reservePurposeContent, String attachmentCode, LocalDateTime reserveStartDate, LocalDateTime reserveEndDate, String reserveStatusId, String userId, String userContactNo, String userEmail) {
|
||||
this.reserveId = reserveId;
|
||||
this.reserveItemId = reserveItemId;
|
||||
this.locationId = locationId;
|
||||
this.categoryId = categoryId;
|
||||
this.reserveQty = reserveQty;
|
||||
this.reservePurposeContent = reservePurposeContent;
|
||||
this.attachmentCode = attachmentCode;
|
||||
this.reserveStartDate = reserveStartDate;
|
||||
this.reserveEndDate = reserveEndDate;
|
||||
this.reserveStatusId = reserveStatusId;
|
||||
this.userId = userId;
|
||||
this.userContactNo = userContactNo;
|
||||
this.userEmail = userEmail;
|
||||
}
|
||||
|
||||
/**
|
||||
* 예약 상태 업데이트
|
||||
*
|
||||
* @param reserveStatusId
|
||||
* @return
|
||||
*/
|
||||
public Reserve updateStatus(String reserveStatusId) {
|
||||
this.reserveStatusId = reserveStatusId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* create 정보 세팅
|
||||
* insert 시 필요
|
||||
*
|
||||
* @param createdDate
|
||||
* @param createdBy
|
||||
* @return
|
||||
*/
|
||||
public Reserve setCreatedInfo(LocalDateTime createdDate, String createdBy) {
|
||||
this.createdBy = createdBy;
|
||||
this.createDate = createdDate;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.egovframe.cloud.reserverequestservice.domain;
|
||||
|
||||
import org.egovframe.cloud.reserverequestservice.domain.Reserve;
|
||||
import org.egovframe.cloud.reserverequestservice.domain.ReserveRepositoryCustom;
|
||||
import org.springframework.data.r2dbc.repository.R2dbcRepository;
|
||||
|
||||
/**
|
||||
* org.egovframe.cloud.reserverequestservice.domain.Reserve
|
||||
*
|
||||
* 예약 도메인 repository interface
|
||||
*
|
||||
* @author 표준프레임워크센터 shinmj
|
||||
* @version 1.0
|
||||
* @since 2021/09/15
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/09/15 shinmj 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
public interface ReserveRepository extends R2dbcRepository<Reserve, String>, ReserveRepositoryCustom {
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.egovframe.cloud.reserverequestservice.domain;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
/**
|
||||
* org.egovframe.cloud.reserverequestservice.domain.ReserveRepositoryCustom
|
||||
*
|
||||
* 예약 도메인 repository custom interface
|
||||
*
|
||||
* @author 표준프레임워크센터 shinmj
|
||||
* @version 1.0
|
||||
* @since 2021/09/27
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/09/27 shinmj 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
public interface ReserveRepositoryCustom {
|
||||
Mono<Reserve> insert(Reserve reserve);
|
||||
Flux<Reserve> findAllByReserveDateWithoutSelf(String reserveId, Long reserveItemId, LocalDateTime startDate, LocalDateTime endDate);
|
||||
Mono<Long> findAllByReserveDateWithoutSelfCount(String reserveId, Long reserveItemId, LocalDateTime startDate, LocalDateTime endDate);
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package org.egovframe.cloud.reserverequestservice.domain;
|
||||
|
||||
import static org.springframework.data.relational.core.query.Criteria.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
|
||||
import org.springframework.data.relational.core.query.Query;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
/**
|
||||
* org.egovframe.cloud.reserverequestservice.domain.ReserveRepositoryImpl
|
||||
*
|
||||
* 예약 도메인 repository custom interface 구현체
|
||||
*
|
||||
* @author 표준프레임워크센터 shinmj
|
||||
* @version 1.0
|
||||
* @since 2021/09/27
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/09/27 shinmj 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class ReserveRepositoryImpl implements ReserveRepositoryCustom {
|
||||
|
||||
private final R2dbcEntityTemplate entityTemplate;
|
||||
|
||||
/**
|
||||
* 예약 insert
|
||||
* pk(reserveId)를 서비스에서 생성하여 insert 하기 위함.
|
||||
*
|
||||
* @param reserve
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Mono<Reserve> insert(Reserve reserve) {
|
||||
return entityTemplate.insert(reserve);
|
||||
}
|
||||
|
||||
/**
|
||||
* 조회 기간에 예약된 건 조회
|
||||
* 현 예약건은 제외
|
||||
*
|
||||
* @param reserveItemId
|
||||
* @param startDate
|
||||
* @param endDate
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Flux<Reserve> findAllByReserveDateWithoutSelf(String reserveId, Long reserveItemId, LocalDateTime startDate, LocalDateTime endDate) {
|
||||
return entityTemplate.select(Reserve.class)
|
||||
.matching(Query.query(where("reserve_item_id").is(reserveItemId)
|
||||
.and ("reserve_start_date").lessThanOrEquals(endDate)
|
||||
.and("reserve_end_date").greaterThanOrEquals(startDate)
|
||||
.and("reserve_id").not(reserveId)
|
||||
))
|
||||
.all();
|
||||
}
|
||||
|
||||
/**
|
||||
* 조회 기간에 예약된 건수 조회
|
||||
* 현 예약건은 제외
|
||||
*
|
||||
* @param reserveItemId
|
||||
* @param startDate
|
||||
* @param endDate
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Mono<Long> findAllByReserveDateWithoutSelfCount(String reserveId, Long reserveItemId, LocalDateTime startDate, LocalDateTime endDate) {
|
||||
return entityTemplate.select(Reserve.class)
|
||||
.matching(Query.query(where("reserve_item_id").is(reserveItemId)
|
||||
.and ("reserve_start_date").lessThanOrEquals(endDate)
|
||||
.and("reserve_end_date").greaterThanOrEquals(startDate)
|
||||
.and("reserve_id").not(reserveId)
|
||||
))
|
||||
.count();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.egovframe.cloud.reserverequestservice.domain;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public enum ReserveStatus {
|
||||
REQUEST("request", "예약 신청"),
|
||||
APPROVE("approve", "예약 승인"),
|
||||
CANCEL("cancel", "예약 취소"),
|
||||
DONE("done", "완료");
|
||||
|
||||
private final String key;
|
||||
private final String title;
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
package org.egovframe.cloud.reserverequestservice.service;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.egovframe.cloud.common.config.GlobalConstant;
|
||||
import org.egovframe.cloud.common.exception.BusinessMessageException;
|
||||
import org.egovframe.cloud.reactive.service.ReactiveAbstractService;
|
||||
import org.egovframe.cloud.reserverequestservice.api.dto.ReserveResponseDto;
|
||||
import org.egovframe.cloud.reserverequestservice.api.dto.ReserveSaveRequestDto;
|
||||
import org.egovframe.cloud.reserverequestservice.domain.Category;
|
||||
import org.egovframe.cloud.reserverequestservice.domain.Reserve;
|
||||
import org.egovframe.cloud.reserverequestservice.domain.ReserveRepository;
|
||||
import org.egovframe.cloud.reserverequestservice.domain.ReserveStatus;
|
||||
import org.springframework.amqp.core.*;
|
||||
import org.springframework.cloud.stream.function.StreamBridge;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* org.egovframe.cloud.reserverequestservice.service.ReserveService
|
||||
* <p>
|
||||
* 예약 신청 service class
|
||||
*
|
||||
* @author 표준프레임워크센터 shinmj
|
||||
* @version 1.0
|
||||
* @since 2021/09/17
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/09/17 shinmj 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Transactional
|
||||
@Service
|
||||
public class ReserveService extends ReactiveAbstractService {
|
||||
private final ReserveRepository reserveRepository;
|
||||
private final StreamBridge streamBridge;
|
||||
private final AmqpAdmin amqpAdmin;
|
||||
|
||||
/**
|
||||
* entity -> dto 변환
|
||||
*
|
||||
* @param reserve
|
||||
* @return
|
||||
*/
|
||||
private Mono<ReserveResponseDto> convertReserveResponseDto(Reserve reserve) {
|
||||
return Mono.just(ReserveResponseDto.builder()
|
||||
.entity(reserve)
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* 현재 로그인 사용자 id
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private Mono<String> getUserId() {
|
||||
return ReactiveSecurityContextHolder.getContext()
|
||||
.map(SecurityContext::getAuthentication)
|
||||
.filter(Authentication::isAuthenticated)
|
||||
.map(Authentication::getPrincipal)
|
||||
.map(String.class::cast);
|
||||
}
|
||||
|
||||
/**
|
||||
* 예약 신청 저장
|
||||
*
|
||||
* @param saveRequestDto
|
||||
* @return
|
||||
*/
|
||||
public Mono<ReserveResponseDto> create(ReserveSaveRequestDto saveRequestDto) {
|
||||
return Mono.just(saveRequestDto)
|
||||
.flatMap(dto -> {
|
||||
String uuid = UUID.randomUUID().toString();
|
||||
dto.setReserveId(uuid);
|
||||
dto.setReserveStatusId(ReserveStatus.REQUEST.getKey());
|
||||
return Mono.just(dto.toEntity());
|
||||
})
|
||||
.zipWith(getUserId())
|
||||
.flatMap(tuple -> {
|
||||
tuple.getT1().setCreatedInfo(LocalDateTime.now(), tuple.getT2());
|
||||
return Mono.just(tuple.getT1());
|
||||
})
|
||||
.flatMap(reserveRepository::insert)
|
||||
.flatMap(this::convertReserveResponseDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 예약 신청 - 실시간
|
||||
* 예약 정보 저장 후 재고 변경을 위해 이벤트 publish
|
||||
*
|
||||
* @param saveRequestDto
|
||||
* @return
|
||||
*/
|
||||
public Mono<ReserveResponseDto> saveForEvent(ReserveSaveRequestDto saveRequestDto) {
|
||||
return create(saveRequestDto)
|
||||
.flatMap(reserveResponseDto ->
|
||||
Mono.fromCallable(() -> {
|
||||
//예약 저장 후 해당 id로 queue 생성
|
||||
Exchange ex = ExchangeBuilder.directExchange(GlobalConstant.SUCCESS_OR_NOT_EX_NAME)
|
||||
.durable(true).build();
|
||||
amqpAdmin.declareExchange(ex);
|
||||
|
||||
Queue queue = QueueBuilder.durable(reserveResponseDto.getReserveId()).build();
|
||||
amqpAdmin.declareQueue(queue);
|
||||
|
||||
Binding binding = BindingBuilder.bind(queue)
|
||||
.to(ex)
|
||||
.with(reserveResponseDto.getReserveId())
|
||||
.noargs();
|
||||
amqpAdmin.declareBinding(binding);
|
||||
|
||||
log.info("Biding successfully created");
|
||||
|
||||
streamBridge.send("reserveRequest-out-0", reserveResponseDto);
|
||||
|
||||
return reserveResponseDto;
|
||||
}).subscribeOn(Schedulers.boundedElastic())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 예약 신청 - 실시간
|
||||
* 이벤트 스트림을 타지 않는 경우 (재고 변경 이벤트가 없는 경우: 공간, 장비)
|
||||
*
|
||||
* @param saveRequestDto
|
||||
* @return
|
||||
*/
|
||||
public Mono<ReserveResponseDto> save(ReserveSaveRequestDto saveRequestDto) {
|
||||
return Mono.just(saveRequestDto)
|
||||
.flatMap(this::checkValidation)
|
||||
.onErrorResume(throwable -> Mono.error(throwable))
|
||||
.flatMap(dto -> {
|
||||
String uuid = UUID.randomUUID().toString();
|
||||
dto.setReserveId(uuid);
|
||||
dto.setReserveStatusId(ReserveStatus.APPROVE.getKey());
|
||||
return Mono.just(dto.toEntity());
|
||||
}).zipWith(getUserId())
|
||||
.flatMap(tuple -> Mono.just(tuple.getT1().setCreatedInfo(LocalDateTime.now(), tuple.getT2())))
|
||||
.flatMap(reserveRepository::insert)
|
||||
.flatMap(this::convertReserveResponseDto);
|
||||
}
|
||||
|
||||
private Mono<ReserveSaveRequestDto> checkValidation(ReserveSaveRequestDto saveRequestDto) {
|
||||
if (Category.EQUIPMENT.isEquals(saveRequestDto.getCategoryId())) {
|
||||
return checkEquipment(saveRequestDto);
|
||||
}else if (Category.SPACE.isEquals(saveRequestDto.getCategoryId())) {
|
||||
return checkSpace(saveRequestDto);
|
||||
}
|
||||
return Mono.error(new BusinessMessageException("저장 할 수 없습니다."));
|
||||
}
|
||||
|
||||
/**
|
||||
* 예약 날자 validation
|
||||
*
|
||||
* @param saveRequestDto
|
||||
* @return
|
||||
*/
|
||||
private Mono<ReserveSaveRequestDto> checkReserveDate(ReserveSaveRequestDto saveRequestDto) {
|
||||
LocalDateTime startDate = saveRequestDto.getReserveMeansId().equals("realtime") ?
|
||||
saveRequestDto.getRequestStartDate() : saveRequestDto.getOperationStartDate();
|
||||
LocalDateTime endDate = saveRequestDto.getReserveMeansId().equals("realtime") ?
|
||||
saveRequestDto.getRequestEndDate() : saveRequestDto.getOperationEndDate();
|
||||
|
||||
if (saveRequestDto.getReserveStartDate().isBefore(startDate)) {
|
||||
return Mono.error(new BusinessMessageException("시작일이 운영/예약 시작일 이전입니다."));
|
||||
}
|
||||
|
||||
if (saveRequestDto.getReserveEndDate().isAfter(endDate)) {
|
||||
return Mono.error(new BusinessMessageException("종료일이 운영/예약 종료일 이후입니다."));
|
||||
}
|
||||
if (saveRequestDto.getIsPeriod()) {
|
||||
long between = ChronoUnit.DAYS.between(saveRequestDto.getReserveStartDate(),
|
||||
saveRequestDto.getReserveEndDate());
|
||||
if (saveRequestDto.getPeriodMaxCount() < between) {
|
||||
return Mono.error(new BusinessMessageException("최대 예약 가능 일수보다 예약기간이 깁니다. (최대 예약 가능일 수 : "+saveRequestDto.getPeriodMaxCount()+")"));
|
||||
}
|
||||
}
|
||||
return Mono.just(saveRequestDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 공간 예약 시 예약 날짜에 다른 예약이 있는지 체크
|
||||
*
|
||||
* @param saveRequestDto
|
||||
* @return
|
||||
*/
|
||||
private Mono<ReserveSaveRequestDto> checkSpace(ReserveSaveRequestDto saveRequestDto) {
|
||||
return this.checkReserveDate(saveRequestDto)
|
||||
.flatMap(result -> reserveRepository.findAllByReserveDateWithoutSelfCount(
|
||||
result.getReserveId(),
|
||||
result.getReserveItemId(),
|
||||
result.getReserveStartDate(),
|
||||
result.getReserveEndDate())
|
||||
.flatMap(count -> {
|
||||
if (count > 0) {
|
||||
return Mono.error(new BusinessMessageException("해당 날짜에는 예약할 수 없습니다."));
|
||||
}
|
||||
return Mono.just(result);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 장비 예약 시 예약 날짜에 예약 가능한 재고 체크
|
||||
*
|
||||
* @param saveRequestDto
|
||||
* @return
|
||||
*/
|
||||
private Mono<ReserveSaveRequestDto> checkEquipment(ReserveSaveRequestDto saveRequestDto) {
|
||||
return this.checkReserveDate(saveRequestDto)
|
||||
.flatMap(result -> this.getMaxByReserveDateWithoutSelf(
|
||||
result.getReserveId(),
|
||||
result.getReserveItemId(),
|
||||
result.getReserveStartDate(),
|
||||
result.getReserveEndDate())
|
||||
.flatMap(max -> {
|
||||
if ((result.getTotalQty() - max) < result.getReserveQty()) {
|
||||
return Mono.just(false);
|
||||
}
|
||||
return Mono.just(true);
|
||||
})
|
||||
.flatMap(isValid -> {
|
||||
if (!isValid) {
|
||||
return Mono.error(new BusinessMessageException("해당 날짜에 예약할 수 있는 재고수량이 없습니다."));
|
||||
}
|
||||
return Mono.just(saveRequestDto);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 예약물품에 대해 날짜별 예약된 수량 max 조회
|
||||
* 현 예약 건 제외
|
||||
*
|
||||
* @param reserveItemId
|
||||
* @param startDate
|
||||
* @param endDate
|
||||
* @return
|
||||
*/
|
||||
private Mono<Integer> getMaxByReserveDateWithoutSelf(String reserveId, Long reserveItemId, LocalDateTime startDate, LocalDateTime endDate) {
|
||||
Flux<Reserve> reserveFlux = reserveRepository.findAllByReserveDateWithoutSelf(reserveId, reserveItemId, startDate, endDate)
|
||||
.switchIfEmpty(Flux.empty());
|
||||
|
||||
if (reserveFlux.equals(Flux.empty())) {
|
||||
return Mono.just(0);
|
||||
}
|
||||
|
||||
long between = ChronoUnit.DAYS.between(startDate, endDate);
|
||||
return Flux.fromStream(IntStream.iterate(0, i -> i + 1)
|
||||
.limit(between)
|
||||
.mapToObj(i -> startDate.plusDays(i)))
|
||||
.flatMap(localDateTime ->
|
||||
reserveFlux.map(findReserve -> {
|
||||
if (localDateTime.isAfter(findReserve.getReserveStartDate())
|
||||
|| localDateTime.isBefore(findReserve.getReserveEndDate())) {
|
||||
return findReserve.getReserveQty();
|
||||
}
|
||||
return 0;
|
||||
}).reduce(0, (x1, x2) -> x1 + x2))
|
||||
.groupBy(integer -> integer)
|
||||
.flatMap(group -> group.reduce((x1,x2) -> x1 > x2?x1:x2))
|
||||
.last();
|
||||
}
|
||||
|
||||
/**
|
||||
* 예약 신청 후 예약 물품 재고 변경 성공 시 예약승인으로 상태 변경
|
||||
*
|
||||
* @param reserveId
|
||||
* @param reserveStatus
|
||||
* @return
|
||||
*/
|
||||
public Mono<Void> updateStatus(String reserveId, ReserveStatus reserveStatus) {
|
||||
log.info("update : {} , {}", reserveId, reserveStatus);
|
||||
return reserveRepository.findById(reserveId)
|
||||
.map(reserve -> reserve.updateStatus(reserveStatus.getKey()))
|
||||
.flatMap(reserveRepository::save)
|
||||
.then();
|
||||
}
|
||||
|
||||
/**
|
||||
* 예약 신청 후 예약 물품 재고 변경 실패 시 해당 예약 건 삭제
|
||||
*
|
||||
* @param reserveId
|
||||
* @return
|
||||
*/
|
||||
public Mono<Void> delete(String reserveId) {
|
||||
log.info("delete {}", reserveId);
|
||||
return reserveRepository.findById(reserveId)
|
||||
.flatMap(reserveRepository::delete)
|
||||
.then();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
spring:
|
||||
application:
|
||||
name: reserve-request-service
|
||||
|
||||
server:
|
||||
port: 0
|
||||
|
||||
# config server actuator
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: refresh, health, beans
|
||||
@@ -0,0 +1,5 @@
|
||||
spring:
|
||||
cloud:
|
||||
config:
|
||||
uri: http://localhost:8888
|
||||
name: reserve-request-service
|
||||
@@ -0,0 +1,26 @@
|
||||
-- reserve Table Create SQL
|
||||
CREATE TABLE IF NOT EXISTS reserve
|
||||
(
|
||||
`reserve_id` VARCHAR(255) NOT NULL COMMENT '예약 id',
|
||||
`reserve_item_id` BIGINT NULL COMMENT '예약 물품 id',
|
||||
`location_id` BIGINT NULL COMMENT '예약 물품-지역 id',
|
||||
`category_id` VARCHAR(255) NULL COMMENT '예약 물품-유형 id',
|
||||
`reserve_qty` BIGINT(18) NULL COMMENT '예약 신청인원/수량',
|
||||
`reserve_purpose_content` VARCHAR(4000) NULL COMMENT '예약신청 목적',
|
||||
`attachment_code` VARCHAR(255) NULL COMMENT '첨부파일 코드',
|
||||
`reserve_start_date` DATETIME NULL COMMENT '예약 신청 시작일',
|
||||
`reserve_end_date` DATETIME NULL COMMENT '예약 신청 종료일',
|
||||
`reserve_status_id` VARCHAR(20) NULL COMMENT '예약상태 - 공통코드(reserve-status)',
|
||||
`reason_cancel_content` VARCHAR(4000) NULL COMMENT '예약 취소 사유',
|
||||
`user_id` VARCHAR(255) NULL COMMENT '예약자 id',
|
||||
`user_contact_no` VARCHAR(50) NULL COMMENT '예약자 연락처',
|
||||
`user_email_addr` VARCHAR(500) NULL COMMENT '예약자 이메일',
|
||||
`create_date` DATETIME NULL COMMENT '생성일',
|
||||
`created_by` VARCHAR(255) NULL COMMENT '생성자',
|
||||
`modified_date` DATETIME NULL COMMENT '수정일',
|
||||
`last_modified_by` VARCHAR(255) NULL COMMENT '수정자',
|
||||
PRIMARY KEY (reserve_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
ALTER TABLE reserve COMMENT '예약 신청&확인';
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.egovframe.cloud.reserverequestservice;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
class ReserveRequestServiceApplicationTests {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
spring:
|
||||
application:
|
||||
name: reserve-request-service
|
||||
|
||||
datasource:
|
||||
url: jdbc:h2:mem:testdb;MODE=MYSQL;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false
|
||||
username: sa
|
||||
password:
|
||||
driver-class-name: org.h2.Driver
|
||||
jpa:
|
||||
hibernate:
|
||||
generate-ddl: true
|
||||
ddl-auto: create-drop
|
||||
properties:
|
||||
hibernate:
|
||||
format_sql: true
|
||||
default_batch_fetch_size: 1000
|
||||
show-sql: true
|
||||
h2:
|
||||
console:
|
||||
enabled: true
|
||||
path: /h2
|
||||
|
||||
logging.level:
|
||||
org.hibernate.SQL: debug
|
||||
|
||||
file:
|
||||
directory: ${user.home}/msa-attach-volume
|
||||
messages:
|
||||
directory: ${file.directory}/messages
|
||||
|
||||
# jwt token
|
||||
token:
|
||||
secret: egovframe_user_token
|
||||
|
||||
# ftp server
|
||||
ftp:
|
||||
enabled: false # ftp 사용 여부, FTP 서버에 최상위 디렉토리 자동 생성 및 구현체를 결정하게 된다.
|
||||
|
||||
# eureka 가 포함되면 eureka server 도 등록되므로 해제한다.
|
||||
eureka:
|
||||
client:
|
||||
register-with-eureka: false
|
||||
fetch-registry: false
|
||||
Reference in New Issue
Block a user