Initial commit

This commit is contained in:
jooho
2021-10-20 17:12:00 +09:00
parent 0c884beff8
commit 8caa4bbc5a
487 changed files with 44198 additions and 0 deletions

View 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"]

View 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
View 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" "$@"

View 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

View 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

View File

@@ -0,0 +1 @@
rootProject.name = 'reserve-request-service'

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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 {
}

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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;
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1,13 @@
spring:
application:
name: reserve-request-service
server:
port: 0
# config server actuator
management:
endpoints:
web:
exposure:
include: refresh, health, beans

View File

@@ -0,0 +1,5 @@
spring:
cloud:
config:
uri: http://localhost:8888
name: reserve-request-service

View File

@@ -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 '예약 신청&확인';

View File

@@ -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() {
}
}

View File

@@ -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