🔧 Add 보안점검에 공통 모듈 포함
This commit is contained in:
94
backend/module-common/build.gradle
Normal file
94
backend/module-common/build.gradle
Normal file
@@ -0,0 +1,94 @@
|
||||
plugins {
|
||||
id 'org.springframework.boot' version '2.4.5'
|
||||
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
|
||||
// querydsl
|
||||
id 'com.ewerk.gradle.plugins.querydsl' version '1.0.10'
|
||||
id 'java'
|
||||
}
|
||||
|
||||
group 'org.egovframe.cloud'
|
||||
version '0.1'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url "http://www.egovframe.go.kr/maven/" } // egovframe maven 원격 저장소 TODO https
|
||||
}
|
||||
|
||||
bootJar {
|
||||
enabled(false)
|
||||
}
|
||||
jar {
|
||||
enabled(true)
|
||||
}
|
||||
|
||||
configurations {
|
||||
compileOnly {
|
||||
extendsFrom annotationProcessor
|
||||
}
|
||||
}
|
||||
|
||||
ext {
|
||||
set('springCloudVersion', "2020.0.3")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// EgovAbstractServiceImpl
|
||||
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-jpa'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-validation' // LocalValidatorFactoryBean
|
||||
implementation 'io.jsonwebtoken:jjwt:0.9.1'
|
||||
// querydsl
|
||||
implementation 'com.querydsl:querydsl-jpa'
|
||||
|
||||
//openapi docs
|
||||
implementation group: 'io.springfox', name: 'springfox-swagger2', version: '2.9.2'
|
||||
implementation group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.9.2'
|
||||
implementation 'org.springdoc:springdoc-openapi-webmvc-core:1.5.8'
|
||||
implementation 'org.springdoc:springdoc-openapi-webflux-ui:1.5.8'
|
||||
|
||||
//reactive
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-r2dbc'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-webflux'
|
||||
|
||||
annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
|
||||
|
||||
//messaging
|
||||
implementation 'org.springframework.cloud:spring-cloud-stream'
|
||||
implementation 'org.springframework.cloud:spring-cloud-stream-binder-rabbit'
|
||||
|
||||
// lombok
|
||||
implementation 'org.projectlombok:lombok'
|
||||
annotationProcessor 'org.projectlombok:lombok'
|
||||
testImplementation 'org.projectlombok:lombok'
|
||||
testAnnotationProcessor 'org.projectlombok:lombok'
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||
testImplementation 'io.projectreactor:reactor-test'
|
||||
testImplementation 'org.springframework.security:spring-security-test'
|
||||
}
|
||||
|
||||
dependencyManagement {
|
||||
imports {
|
||||
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
|
||||
}
|
||||
}
|
||||
|
||||
// querydsl 추가 시작
|
||||
def querydslDir = "$buildDir/generated/querydsl"
|
||||
querydsl {
|
||||
jpa = true
|
||||
querydslSourcesDir = querydslDir
|
||||
}
|
||||
sourceSets {
|
||||
main.java.srcDir querydslDir
|
||||
}
|
||||
configurations {
|
||||
querydsl.extendsFrom compileClasspath
|
||||
}
|
||||
compileQuerydsl {
|
||||
options.annotationProcessorPath = configurations.querydsl
|
||||
}
|
||||
// querydsl 추가 끝
|
||||
185
backend/module-common/gradlew
vendored
Executable file
185
backend/module-common/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/module-common/gradlew.bat
vendored
Normal file
89
backend/module-common/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
|
||||
2
backend/module-common/settings.gradle
Normal file
2
backend/module-common/settings.gradle
Normal file
@@ -0,0 +1,2 @@
|
||||
rootProject.name = 'module-common'
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.egovframe.cloud.common.config;
|
||||
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.InitBinder;
|
||||
|
||||
/**
|
||||
* org.egovframe.cloud.common.config.WebControllerAdvice
|
||||
*
|
||||
* 모든 컨트롤러에 적용되는 컨트롤러 어드바이스 클래스
|
||||
* 예외 처리 (@ExceptionHandler), 바인딩 설정(@InitBinder), 모델 객체(@ModelAttributes)
|
||||
*
|
||||
* @author 표준프레임워크센터 jooho
|
||||
* @version 1.0
|
||||
* @since 2021/07/12
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/07/12 jooho 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
@ControllerAdvice
|
||||
public class ApiControllerAdvice {
|
||||
|
||||
/**
|
||||
* 모든 컨트롤러로 들어오는 요청 초기화
|
||||
*
|
||||
* @param binder 웹 데이터 바인더
|
||||
*/
|
||||
@InitBinder
|
||||
public void initBinder(WebDataBinder binder) {
|
||||
binder.initDirectFieldAccess(); // Setter 구현 없이 DTO 클래스 필드에 접근
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.egovframe.cloud.common.config;
|
||||
|
||||
/**
|
||||
* org.egovframe.cloud.common.config.Constants
|
||||
*
|
||||
* 공통 전역 상수 정의
|
||||
*
|
||||
* @author 표준프레임워크센터 jaeyeolkim
|
||||
* @version 1.0
|
||||
* @since 2021/07/19
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/07/19 jaeyeolkim 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
public interface GlobalConstant {
|
||||
final String HEADER_SITE_ID = "X-Site-Id"; // header에 어떤 사이트에서 보내는 요청인지 구분하기 위한 정보
|
||||
final String AUTHORIZATION_URI = "/api/v1/authorizations/check";
|
||||
final String REFRESH_TOKEN_URI = "/api/v1/users/token/refresh";
|
||||
final String MESSAGES_URI = "/api/v1/messages/**";
|
||||
final String LOGIN_URI = "/login";
|
||||
final String[] SECURITY_PERMITALL_ANTPATTERNS = {AUTHORIZATION_URI, REFRESH_TOKEN_URI, MESSAGES_URI, LOGIN_URI, "/actuator/**", "/v3/api-docs/**", "/api/v1/images/**", "/swagger-ui.html"};
|
||||
final String USER_SERVICE_URI = "/user-service";
|
||||
//예약 신청 후 재고 변경 성공여부 exchange name
|
||||
final String SUCCESS_OR_NOT_EX_NAME = "success-or-not.direct";
|
||||
// 첨부파일 저장 후 entity 정보 update binding name
|
||||
final String ATTACHMENT_ENTITY_BINDING_NAME = "attachmentEntity-out-0";
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.egovframe.cloud.common.config;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.egovframe.rte.fdl.cmmn.trace.LeaveaTrace;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* org.egovframe.cloud.common.config.LeaveaTraceConfig
|
||||
* <p>
|
||||
* LeaveaTrace Bean 설정
|
||||
* EgovAbstractServiceImpl 클래스가 의존한다.
|
||||
*
|
||||
* @author 표준프레임워크센터 jaeyeolkim
|
||||
* @version 1.0
|
||||
* @since 2021/09/24
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/09/24 jaeyeolkim 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
@Configuration
|
||||
public class LeaveaTraceConfig {
|
||||
|
||||
@Bean
|
||||
public LeaveaTrace leaveaTrace() {
|
||||
return new LeaveaTrace();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package org.egovframe.cloud.common.config;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
/**
|
||||
* org.egovframe.cloud.common.config.MessageSourceConfig
|
||||
* <p>
|
||||
* Spring MessageSource 설정
|
||||
* Message Domain 이 있는 portal-service 에서 messages.properties 를 외부 위치에 생성한다.
|
||||
* 각 서비스에서 해당 파일을 통해 다국어를 지원하도록 한다.
|
||||
*
|
||||
* @author 표준프레임워크센터 jaeyeolkim
|
||||
* @version 1.0
|
||||
* @since 2021/08/09
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/08/09 jaeyeolkim 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class MessageSourceConfig {
|
||||
|
||||
@Value("${messages.directory}")
|
||||
private String messagesDirectory;
|
||||
|
||||
@Value("${spring.profiles.active:default}")
|
||||
private String profile;
|
||||
|
||||
@Bean
|
||||
public MessageSource messageSource() {
|
||||
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
|
||||
final String MESSAGES = "/messages";
|
||||
if ("default".equals(profile)) {
|
||||
Path fileStorageLocation = Paths.get(messagesDirectory).toAbsolutePath().normalize();
|
||||
String dbMessages = StringUtils.cleanPath("file://" + fileStorageLocation + MESSAGES);
|
||||
messageSource.setBasenames(dbMessages);
|
||||
} else {
|
||||
messageSource.setBasenames(messagesDirectory + MESSAGES);
|
||||
}
|
||||
messageSource.getBasenameSet().forEach(s -> log.info("messageSource getBasenameSet={}", s));
|
||||
|
||||
messageSource.setCacheSeconds(60); // 메세지 파일 변경 감지 간격
|
||||
messageSource.setUseCodeAsDefaultMessage(true); // 메세지가 없으면 코드를 메세지로 한다
|
||||
messageSource.setDefaultEncoding(StandardCharsets.UTF_8.name());
|
||||
return messageSource;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.egovframe.cloud.common.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import io.swagger.v3.oas.models.Components;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.servers.Server;
|
||||
|
||||
@Configuration
|
||||
public class OpenApiDocsConfig {
|
||||
|
||||
@Value("${spring.application.name}")
|
||||
private String appName;
|
||||
|
||||
/**
|
||||
* @TODO
|
||||
* api info update 필요
|
||||
*
|
||||
*/
|
||||
@Bean
|
||||
public OpenAPI customOpenAPI() {
|
||||
Server server = new Server();
|
||||
server.url("/"+appName);
|
||||
List<Server> servers = new ArrayList<>();
|
||||
servers.add(server);
|
||||
return new OpenAPI()
|
||||
.components(new Components())
|
||||
.servers(servers)
|
||||
.info(new io.swagger.v3.oas.models.info.Info().title(appName+" API"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.egovframe.cloud.common.domain;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* org.egovframe.cloud.common.domain.Role
|
||||
* <p>
|
||||
* 사용자 권한
|
||||
*
|
||||
* @author 표준프레임워크센터 jaeyeolkim
|
||||
* @version 1.0
|
||||
* @since 2021/06/30
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/06/30 jaeyeolkim 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public enum Role {
|
||||
|
||||
// 스프링 시큐리티에서는 권한 코드에 항상 ROLE_ 이 앞에 있어야 한다.
|
||||
ANONYMOUS("ROLE_ANONYMOUS", "손님"),
|
||||
USER("ROLE_USER", "일반 사용자"),
|
||||
EMPLOYEE("ROLE_EMPLOYEE", "내부 사용자"),
|
||||
ADMIN("ROLE_ADMIN", "시스템 관리자");
|
||||
|
||||
private final String key;
|
||||
private final String title;
|
||||
|
||||
/**
|
||||
* 권한 id로 상수 검색
|
||||
*
|
||||
* @param key 권한 id
|
||||
* @return Role 권한 상수
|
||||
*/
|
||||
public static Role findByKey(String key) {
|
||||
return Arrays.stream(Role.values()).filter(c -> c.getKey().equals(key)).findAny().orElse(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.egovframe.cloud.common.dto;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
public class AttachmentEntityMessage {
|
||||
|
||||
private String attachmentCode;
|
||||
private String entityName;
|
||||
private String entityId;
|
||||
|
||||
@Builder
|
||||
public AttachmentEntityMessage(String attachmentCode, String entityName, String entityId) {
|
||||
this.attachmentCode = attachmentCode;
|
||||
this.entityName = entityName;
|
||||
this.entityId = entityId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.egovframe.cloud.common.dto;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
|
||||
/**
|
||||
* org.egovframe.cloud.common.dto.RequestDto
|
||||
* <p>
|
||||
* 공통 조회조건 요청 파라미터 dto
|
||||
*
|
||||
* @author 표준프레임워크센터 jaeyeolkim
|
||||
* @version 1.0
|
||||
* @since 2021/07/15
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/07/15 jaeyeolkim 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@SuperBuilder
|
||||
public class RequestDto {
|
||||
private String keywordType; // 검색조건
|
||||
private String keyword; // 검색어
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.egovframe.cloud.common.exception;
|
||||
|
||||
import org.egovframe.cloud.common.exception.dto.ErrorCode;
|
||||
|
||||
/**
|
||||
* org.egovframe.cloud.common.exception.BusinessException
|
||||
* <p>
|
||||
* 런타임시 비즈니스 로직상 사용자에게 알려줄 오류 메시지를 만들어 던지는 처리를 담당한다
|
||||
* 이 클래스를 상속하여 다양한 형태의 business exception 을 만들 수 있고,
|
||||
* 그것들은 모두 ExceptionHandlerAdvice BusinessException 처리 메소드에서 잡아낸다.
|
||||
* 상황에 맞게 에러 코드를 추가하고 이 클래스를 상속하여 사용할 수 있다.
|
||||
*
|
||||
* @author 표준프레임워크센터 jaeyeolkim
|
||||
* @version 1.0
|
||||
* @since 2021/07/16
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/07/16 jaeyeolkim 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
public class BusinessException extends RuntimeException {
|
||||
|
||||
private String customMessage;
|
||||
private ErrorCode errorCode;
|
||||
|
||||
/**
|
||||
* 사용자 정의 메시지를 받아 처리하는 경우
|
||||
*
|
||||
* @param errorCode 400 에러
|
||||
* @param customMessage 사용자에게 표시할 메시지
|
||||
*/
|
||||
public BusinessException(ErrorCode errorCode, String customMessage) {
|
||||
super(customMessage);
|
||||
this.errorCode = errorCode;
|
||||
this.customMessage = customMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 사전 정의된 에러코드 객체를 넘기는 경우
|
||||
*
|
||||
* @param message 서버에 남길 메시지
|
||||
* @param errorCode 사전 정의된 에러코드
|
||||
*/
|
||||
public BusinessException(String message, ErrorCode errorCode) {
|
||||
super(message);
|
||||
this.errorCode = errorCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 사전 정의된 에러코드의 메시지를 서버에 남기고 에러코드 객체를 리턴한다
|
||||
* @param errorCode 사전 정의된 에러코드
|
||||
*/
|
||||
public BusinessException(ErrorCode errorCode) {
|
||||
super(errorCode.getMessage());
|
||||
this.errorCode = errorCode;
|
||||
}
|
||||
|
||||
public ErrorCode getErrorCode() {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
public String getCustomMessage() {
|
||||
return customMessage;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.egovframe.cloud.common.exception;
|
||||
|
||||
import org.egovframe.cloud.common.exception.dto.ErrorCode;
|
||||
|
||||
/**
|
||||
* org.egovframe.cloud.common.exception.BusinessMessageException
|
||||
* <p>
|
||||
* 런타임시 비즈니스 로직상 사용자에게 알려줄 오류 메시지를 만들어 던지는 처리를 담당한다
|
||||
*
|
||||
* @author 표준프레임워크센터 jaeyeolkim
|
||||
* @version 1.0
|
||||
* @since 2021/07/28
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/07/28 jaeyeolkim 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
public class BusinessMessageException extends BusinessException {
|
||||
|
||||
/**
|
||||
* 사용자에게 표시될 메시지와 상태코드 400 을 넘긴다
|
||||
*
|
||||
* @param customMessage
|
||||
*/
|
||||
public BusinessMessageException(String customMessage) {
|
||||
super(ErrorCode.BUSINESS_CUSTOM_MESSAGE, customMessage);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.egovframe.cloud.common.exception;
|
||||
|
||||
import org.egovframe.cloud.common.exception.dto.ErrorCode;
|
||||
|
||||
/**
|
||||
* org.egovframe.cloud.common.exception.EntityNotFoundException
|
||||
* <p>
|
||||
* 요청한 엔티티를 찾을 수 없을 경우 사용자에게 알려준다.
|
||||
* ExceptionHandlerAdvice BusinessException 처리 메소드에서 잡아낸다.
|
||||
*
|
||||
* @author 표준프레임워크센터 jaeyeolkim
|
||||
* @version 1.0
|
||||
* @since 2021/07/16
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/07/16 jaeyeolkim 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
public class EntityNotFoundException extends BusinessException {
|
||||
|
||||
public EntityNotFoundException(String message) {
|
||||
super(message, ErrorCode.ENTITY_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.egovframe.cloud.common.exception;
|
||||
|
||||
import org.egovframe.cloud.common.exception.dto.ErrorCode;
|
||||
|
||||
/**
|
||||
* org.egovframe.cloud.common.exception.InvalidValueException
|
||||
* <p>
|
||||
* 입력 받은 값이 잘못된 경우 사용자에게 알려준다.
|
||||
* ExceptionHandlerAdvice BusinessException 처리 메소드에서 잡아낸다.
|
||||
*
|
||||
* @author 표준프레임워크센터 jaeyeolkim
|
||||
* @version 1.0
|
||||
* @since 2021/07/16
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/07/16 jaeyeolkim 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
public class InvalidValueException extends BusinessException {
|
||||
|
||||
public InvalidValueException(String value) {
|
||||
super(value, ErrorCode.INVALID_INPUT_VALUE);
|
||||
}
|
||||
|
||||
public InvalidValueException(String value, ErrorCode errorCode) {
|
||||
super(value, errorCode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package org.egovframe.cloud.common.exception.dto;
|
||||
|
||||
/**
|
||||
* org.egovframe.cloud.common.exception.dto.ErrorCode
|
||||
* <p>
|
||||
* REST API 요청에 대한 오류 반환값을 정의
|
||||
* ErrorResponse 클래스에서 status, code, message 세 가지 속성을 의존한다
|
||||
* message 는 MessageSource 의 키 값을 정의하여 다국어 처리를 지원한다
|
||||
*
|
||||
* @author 표준프레임워크센터 jaeyeolkim
|
||||
* @version 1.0
|
||||
* @since 2021/07/16
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/07/16 jaeyeolkim 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
public enum ErrorCode {
|
||||
|
||||
INVALID_INPUT_VALUE(400, "E001", "err.invalid.input.value"), // Bad Request
|
||||
INVALID_TYPE_VALUE(400, "E002", "err.invalid.type.value"), // Bad Request
|
||||
ENTITY_NOT_FOUND(400, "E003", "err.entity.not.found"), // Bad Request
|
||||
UNAUTHORIZED(401, "E004", "err.unauthorized"), // The request requires an user authentication
|
||||
JWT_EXPIRED(401, "E005", "err.unauthorized"), // The request requires an user authentication
|
||||
ACCESS_DENIED(403, "E006", "err.access.denied"), // Forbidden, Access is Denied
|
||||
NOT_FOUND(404, "E010", "err.page.not.found"), // Not found
|
||||
METHOD_NOT_ALLOWED(405, "E011", "err.method.not.allowed"), // 요청 방법이 서버에 의해 알려졌으나, 사용 불가능한 상태
|
||||
REQUIRE_USER_JOIN(412, "E012", "err.user.notexists"), // Server Error
|
||||
UNPROCESSABLE_ENTITY(422, "E020", "err.unprocessable.entity"), // Unprocessable Entity
|
||||
INTERNAL_SERVER_ERROR(500, "E999", "err.internal.server"), // Server Error
|
||||
|
||||
// business error code
|
||||
BUSINESS_CUSTOM_MESSAGE(400, "B001", ""), // 사용자 정의 메시지를 넘기는 business exception
|
||||
DUPLICATE_INPUT_INVALID(400, "B002", "err.duplicate.input.value"), // 중복된 값을 입력하였습니다
|
||||
DB_CONSTRAINT_DELETE(400, "B003", "err.duplicate.input.value") // 참조하는 데이터가 있어서 삭제할 수 없습니다
|
||||
;
|
||||
|
||||
|
||||
private final int status;
|
||||
private final String code;
|
||||
private final String message;
|
||||
|
||||
ErrorCode(final int status, final String code, final String message) {
|
||||
this.status = status;
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public int getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
package org.egovframe.cloud.common.exception.dto;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static lombok.AccessLevel.PROTECTED;
|
||||
|
||||
/**
|
||||
* org.egovframe.cloud.common.exception.ErrorResponse
|
||||
* <p>
|
||||
* 일관된 예외처리를 제공하는 클래스
|
||||
* https://github.com/cheese10yun/spring-guide
|
||||
*
|
||||
* @author 표준프레임워크센터 jooho
|
||||
* @version 1.0
|
||||
* @since 2021/07/16
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/07/16 jaeyeolkim 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@NoArgsConstructor(access = PROTECTED)
|
||||
public class ErrorResponse {
|
||||
private LocalDateTime timestamp;
|
||||
private String message;
|
||||
private int status;
|
||||
private String code;
|
||||
private List<FieldError> errors;
|
||||
|
||||
private static final String DEFAULT_ERROR_MESSAGE = "ERROR";
|
||||
|
||||
private ErrorResponse(final ErrorCode code, final List<FieldError> errors, MessageSource messageSource) {
|
||||
this.timestamp = LocalDateTime.now();
|
||||
this.message = messageSource.getMessage(code.getMessage(), new Object[]{}, DEFAULT_ERROR_MESSAGE, LocaleContextHolder.getLocale());
|
||||
this.status = code.getStatus();
|
||||
this.code = code.getCode();
|
||||
this.errors = errors;
|
||||
}
|
||||
|
||||
private ErrorResponse(final ErrorCode code, MessageSource messageSource) {
|
||||
this.timestamp = LocalDateTime.now();
|
||||
this.message = messageSource.getMessage(code.getMessage(), new Object[]{}, DEFAULT_ERROR_MESSAGE, LocaleContextHolder.getLocale());
|
||||
this.status = code.getStatus();
|
||||
this.code = code.getCode();
|
||||
this.errors = new ArrayList<>();
|
||||
}
|
||||
|
||||
private ErrorResponse(final ErrorCode code, String customMessage) {
|
||||
this.timestamp = LocalDateTime.now();
|
||||
this.message = customMessage;
|
||||
this.status = code.getStatus();
|
||||
this.code = code.getCode();
|
||||
this.errors = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용자 정의 메시지를 받아 넘기는 경우
|
||||
*
|
||||
* @param code
|
||||
* @param customMessage
|
||||
* @return
|
||||
*/
|
||||
public static ErrorResponse of(final ErrorCode code, final String customMessage) {
|
||||
return new ErrorResponse(code, customMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* ErrorResponse 는 protected 를 선언하여 new 생성할 수 없도록 막아두고 static 메소드를 통해 생성할 수 있도록 하였다
|
||||
* ExceptionHandlerAdvice 에서 인자를 받아 ErrorResponse 객체를 생성한다
|
||||
*
|
||||
* @param code
|
||||
* @param bindingResult
|
||||
* @param messageSource
|
||||
* @return
|
||||
*/
|
||||
public static ErrorResponse of(final ErrorCode code, final BindingResult bindingResult, MessageSource messageSource) {
|
||||
return new ErrorResponse(code, FieldError.of(bindingResult), messageSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* ErrorResponse 는 protected 를 선언하여 new 생성할 수 없도록 막아두고 static 메소드를 통해 생성할 수 있도록 하였다
|
||||
* ExceptionHandlerAdvice 에서 인자를 받아 ErrorResponse 객체를 생성한다
|
||||
*
|
||||
* @param code
|
||||
* @param messageSource
|
||||
* @return
|
||||
*/
|
||||
public static ErrorResponse of(final ErrorCode code, MessageSource messageSource) {
|
||||
return new ErrorResponse(code, messageSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* ErrorResponse 는 protected 를 선언하여 new 생성할 수 없도록 막아두고 static 메소드를 통해 생성할 수 있도록 하였다
|
||||
* ExceptionHandlerAdvice 에서 인자를 받아 ErrorResponse 객체를 생성한다
|
||||
*
|
||||
* @param code
|
||||
* @param errors
|
||||
* @param messageSource
|
||||
* @return
|
||||
*/
|
||||
public static ErrorResponse of(final ErrorCode code, final List<FieldError> errors, MessageSource messageSource) {
|
||||
return new ErrorResponse(code, errors, messageSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* ErrorResponse 는 protected 를 선언하여 new 생성할 수 없도록 막아두고 static 메소드를 통해 생성할 수 있도록 하였다
|
||||
* ExceptionHandlerAdvice 에서 인자를 받아 ErrorResponse 객체를 생성한다
|
||||
* java validator 에러 발생 시 에러 정보 중 필요한 내용만 FieldError 로 반환한다
|
||||
*
|
||||
* @param e
|
||||
* @return
|
||||
*/
|
||||
public static ErrorResponse of(MethodArgumentTypeMismatchException e, MessageSource messageSource) {
|
||||
final String value = e.getValue() == null ? "" : e.getValue().toString();
|
||||
final List<ErrorResponse.FieldError> errors = ErrorResponse.FieldError.of(e.getName(), value, e.getErrorCode());
|
||||
return new ErrorResponse(ErrorCode.INVALID_TYPE_VALUE, errors, messageSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* java validator 에러 발생 시 에러 정보 중 필요한 내용만 반환한다
|
||||
*/
|
||||
@Getter
|
||||
@NoArgsConstructor(access = PROTECTED)
|
||||
public static class FieldError {
|
||||
private String message;
|
||||
private String field;
|
||||
private String rejectedValue;
|
||||
|
||||
private FieldError(final String field, final String rejectedValue, final String message) {
|
||||
this.field = field;
|
||||
this.rejectedValue = rejectedValue;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public static List<FieldError> of(final String field, final String rejectedValue, final String message) {
|
||||
List<FieldError> fieldErrors = new ArrayList<>();
|
||||
fieldErrors.add(new FieldError(field, rejectedValue, message));
|
||||
return fieldErrors;
|
||||
}
|
||||
|
||||
/**
|
||||
* BindingResult to FieldError
|
||||
*
|
||||
* @param bindingResult
|
||||
* @return
|
||||
*/
|
||||
private static List<FieldError> of(final BindingResult bindingResult) {
|
||||
final List<org.springframework.validation.FieldError> fieldErrors = bindingResult.getFieldErrors();
|
||||
return fieldErrors.stream()
|
||||
.map(error -> new FieldError(
|
||||
error.getField(),
|
||||
error.getRejectedValue() == null ? "" : error.getRejectedValue().toString(),
|
||||
error.getDefaultMessage()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package org.egovframe.cloud.common.service;
|
||||
|
||||
import static org.egovframe.cloud.common.config.GlobalConstant.*;
|
||||
|
||||
import org.egovframe.cloud.common.dto.AttachmentEntityMessage;
|
||||
import org.egovframe.cloud.common.util.MessageUtil;
|
||||
import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cloud.stream.function.StreamBridge;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* org.egovframe.cloud.common.service.AbstractService
|
||||
* <p>
|
||||
* 표준프레임워크 EgovAbstractServiceImpl 을 상속하는 공통 추상 클래스이다.
|
||||
* 각 @Service 클래스는 이 클래스를 반드시 상속하여야 한다.(표준프레임워크 준수사항)
|
||||
*
|
||||
* @author 표준프레임워크센터 jaeyeolkim
|
||||
* @version 1.0
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/07/28 jaeyeolkim 최초 생성
|
||||
* </pre>
|
||||
* @since 2021/07/28
|
||||
*/
|
||||
public abstract class AbstractService extends EgovAbstractServiceImpl {
|
||||
|
||||
@Resource(name = "messageUtil")
|
||||
protected MessageUtil messageUtil;
|
||||
|
||||
/**
|
||||
* messageSource 에 코드값을 넘겨 메시지를 찾아 리턴한다.
|
||||
*
|
||||
* @param code
|
||||
* @return
|
||||
*/
|
||||
protected String getMessage(String code) {
|
||||
return messageUtil.getMessage(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* messageSource 에 코드값과 인자를 넘겨 메시지를 찾아 리턴한다.
|
||||
*
|
||||
* @param code
|
||||
* @param args
|
||||
* @return
|
||||
*/
|
||||
protected String getMessage(String code, Object[] args) {
|
||||
return messageUtil.getMessage(code, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 게시물 저장 후 해당 정보를 첨부파일 entity에 입력하기 위해
|
||||
* 이벤트 메세지 발행
|
||||
*
|
||||
* @param entityMessage
|
||||
*/
|
||||
protected void sendAttachmentEntityInfo(StreamBridge streamBridge, AttachmentEntityMessage entityMessage) {
|
||||
streamBridge.send(ATTACHMENT_ENTITY_BINDING_NAME,
|
||||
MessageBuilder.withPayload(entityMessage).build());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package org.egovframe.cloud.common.util;
|
||||
|
||||
import org.egovframe.cloud.common.config.GlobalConstant;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
|
||||
/**
|
||||
* org.egovframe.cloud.common.util.LogUtil
|
||||
* <p>
|
||||
* 로그인, 접속 로그 입력 시 필요한 정보 제공
|
||||
*
|
||||
* @author 표준프레임워크센터 jaeyeolkim
|
||||
* @version 1.0
|
||||
* @since 2021/09/02
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/09/02 jaeyeolkim 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
public class LogUtil {
|
||||
|
||||
/**
|
||||
* 클라이언트 사용자의 IP 가져오기
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String getUserIp() {
|
||||
|
||||
String ip = null;
|
||||
HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest();
|
||||
|
||||
ip = request.getHeader("X-Forwarded-For");
|
||||
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("Proxy-Client-IP");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("WL-Proxy-Client-IP");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("HTTP_CLIENT_IP");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("X-Real-IP");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("X-RealIP");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("REMOTE_ADDR");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getRemoteAddr();
|
||||
}
|
||||
|
||||
return ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* 접속 사이트 정보를 넘긴다.
|
||||
*
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
public static Long getSiteId(HttpServletRequest request) {
|
||||
String header = request.getHeader(GlobalConstant.HEADER_SITE_ID);
|
||||
if (!StringUtils.hasLength(header)) {
|
||||
return null;
|
||||
}
|
||||
return Long.valueOf(header);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package org.egovframe.cloud.common.util;
|
||||
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* org.egovframe.cloud.common.util.MessageUtil
|
||||
* <p>
|
||||
* MessageSource 값을 읽을 수 있는 메소드를 제공한다.
|
||||
*
|
||||
* @author 표준프레임워크센터 jaeyeolkim
|
||||
* @version 1.0
|
||||
* @since 2021/09/08
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/09/08 jaeyeolkim 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
@Component
|
||||
public class MessageUtil {
|
||||
|
||||
@Resource(name = "messageSource")
|
||||
private MessageSource messageSource;
|
||||
|
||||
/**
|
||||
* messageSource 에 코드값을 넘겨 메시지를 찾아 리턴한다.
|
||||
*
|
||||
* @param code
|
||||
* @return
|
||||
*/
|
||||
public String getMessage(String code) {
|
||||
return this.getMessage(code, new Object[]{});
|
||||
}
|
||||
|
||||
/**
|
||||
* messageSource 에 코드값과 인자를 넘겨 메시지를 찾아 리턴한다.
|
||||
*
|
||||
* @param code
|
||||
* @param args
|
||||
* @return
|
||||
*/
|
||||
public String getMessage(String code, Object[] args) {
|
||||
return this.getMessage(code, args, LocaleContextHolder.getLocale());
|
||||
}
|
||||
|
||||
/**
|
||||
* messageSource 에 코드값, 인자, 지역정보를 넘겨 메시지를 찾아 리턴한다.
|
||||
*
|
||||
* @param code
|
||||
* @param args
|
||||
* @param locale
|
||||
* @return
|
||||
*/
|
||||
public String getMessage(String code, Object[] args, Locale locale) {
|
||||
return messageSource.getMessage(code, args, locale);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package org.egovframe.cloud.reactive.config;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* org.egovframe.cloud.reserveitemservice.config.AuthenticationConverter
|
||||
*
|
||||
* 요청을 authentiation으로 변환하는 클래스
|
||||
* AuthenticationWebFilter에서 호출됨.
|
||||
*
|
||||
* @author 표준프레임워크센터 shinmj
|
||||
* @version 1.0
|
||||
* @since 2021/09/06
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/09/06 shinmj 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class AuthenticationConverter implements ServerAuthenticationConverter {
|
||||
@Value("${token.secret}")
|
||||
private String TOKEN_SECRET;
|
||||
final String TOKEN_CLAIM_NAME = "authorities";
|
||||
|
||||
/**
|
||||
* 요청에 담긴 토큰을 조회하여 Authentication 정보를 설정한다.
|
||||
*
|
||||
* @param exchange
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Mono<Authentication> convert(ServerWebExchange exchange) {
|
||||
return Mono.justOrEmpty(exchange)
|
||||
.flatMap(e -> Mono.justOrEmpty(e.getRequest().getHeaders().get(HttpHeaders.AUTHORIZATION)))
|
||||
.flatMap(auth -> {
|
||||
if (auth == null) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
String token = auth.get(0);
|
||||
if (!StringUtils.hasText(token) || "undefined".equals(token)) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
Claims claims = getClaimsFromToken(token);
|
||||
String authorities = claims.get(TOKEN_CLAIM_NAME, String.class);
|
||||
List<SimpleGrantedAuthority> roleList = new ArrayList<>();
|
||||
roleList.add(new SimpleGrantedAuthority(authorities));
|
||||
|
||||
String username = claims.getSubject();
|
||||
|
||||
if (username == null) {
|
||||
ReactiveSecurityContextHolder.withAuthentication(null);
|
||||
return Mono.empty();
|
||||
}
|
||||
UsernamePasswordAuthenticationToken authenticationToken =
|
||||
new UsernamePasswordAuthenticationToken(username, null, roleList);
|
||||
|
||||
ReactiveSecurityContextHolder.withAuthentication(authenticationToken);
|
||||
return Mono.just(authenticationToken);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* AuthenticationFilter.doFilter 메소드에서 UsernamePasswordAuthenticationToken 정보를 세팅할 때 호출된다.
|
||||
*
|
||||
* @param token
|
||||
* @return
|
||||
*/
|
||||
public Claims getClaimsFromToken(String token) {
|
||||
return Jwts.parser()
|
||||
.setSigningKey(TOKEN_SECRET)
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.egovframe.cloud.reactive.config;
|
||||
|
||||
import io.r2dbc.spi.ConnectionFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.data.r2dbc.config.EnableR2dbcAuditing;
|
||||
import org.springframework.r2dbc.connection.init.CompositeDatabasePopulator;
|
||||
import org.springframework.r2dbc.connection.init.ConnectionFactoryInitializer;
|
||||
import org.springframework.r2dbc.connection.init.ResourceDatabasePopulator;
|
||||
|
||||
/**
|
||||
* org.egovframe.cloud.reserveitemservice.config.R2dbcConfig
|
||||
*
|
||||
* R2DBC configuration class
|
||||
*
|
||||
* @author 표준프레임워크센터 shinmj
|
||||
* @version 1.0
|
||||
* @since 2021/09/06
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/09/06 shinmj 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
@Profile("!test")
|
||||
@Configuration
|
||||
@EnableR2dbcAuditing(auditorAwareRef = "userAuditAware") //auditing
|
||||
public class R2dbcConfig {
|
||||
|
||||
@Bean
|
||||
public ConnectionFactoryInitializer initializer(ConnectionFactory connectionFactory) {
|
||||
ConnectionFactoryInitializer initializer = new ConnectionFactoryInitializer();
|
||||
initializer.setConnectionFactory(connectionFactory);
|
||||
|
||||
CompositeDatabasePopulator populator = new CompositeDatabasePopulator();
|
||||
populator.addPopulators(new ResourceDatabasePopulator(new ClassPathResource("schema.sql")));
|
||||
initializer.setDatabasePopulator(populator);
|
||||
|
||||
return initializer;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package org.egovframe.cloud.reactive.config;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
||||
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* org.egovframe.cloud.reserveitemservice.config.SecurityConfig
|
||||
*
|
||||
* Spring Security Config 클래스
|
||||
* AuthenticationFilter 를 추가하고 토큰으로 setAuthentication 인증처리를 한다
|
||||
*
|
||||
* @author 표준프레임워크센터 shinmj
|
||||
* @version 1.0
|
||||
* @since 2021/09/06
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/09/06 shinmj 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Configuration
|
||||
@EnableWebFluxSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
private final AuthenticationConverter authenticationConverter;
|
||||
|
||||
/**
|
||||
* Reactive Security 설정
|
||||
*
|
||||
* @param http
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
@Bean
|
||||
public SecurityWebFilterChain configure(ServerHttpSecurity http) throws Exception {
|
||||
return http
|
||||
.csrf().disable()
|
||||
.headers().frameOptions().disable()
|
||||
.and()
|
||||
.formLogin().disable()
|
||||
.httpBasic().disable()
|
||||
.logout().disable()
|
||||
.addFilterAt(authenticationWebFilter(), SecurityWebFiltersOrder.AUTHENTICATION)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* AuthenticationManager
|
||||
* Api Gateway에서 인증되기 때문에 따로 확인하지 않는다.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public ReactiveAuthenticationManager authenticationManager() {
|
||||
return authentication -> Mono.just(authentication);
|
||||
}
|
||||
|
||||
/**
|
||||
* 인증 요청 필터
|
||||
* AuthenticationConverter 를 적용하여 Authentication 정보를 설정한다.
|
||||
*
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public AuthenticationWebFilter authenticationWebFilter() throws Exception {
|
||||
AuthenticationWebFilter filter = new AuthenticationWebFilter(authenticationManager());
|
||||
filter.setServerAuthenticationConverter(authenticationConverter);
|
||||
return filter;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.egovframe.cloud.reactive.config;
|
||||
|
||||
import org.springframework.data.domain.ReactiveAuditorAware;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Component
|
||||
public class UserAuditAware implements ReactiveAuditorAware<String> {
|
||||
@Override
|
||||
public Mono<String> getCurrentAuditor() {
|
||||
return ReactiveSecurityContextHolder.getContext()
|
||||
.map(SecurityContext::getAuthentication)
|
||||
.filter(Authentication::isAuthenticated)
|
||||
.map(Authentication::getPrincipal)
|
||||
.map(String.class::cast);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.egovframe.cloud.reactive.domain;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.springframework.data.annotation.CreatedBy;
|
||||
import org.springframework.data.annotation.LastModifiedBy;
|
||||
|
||||
/**
|
||||
* org.egovframe.cloud.servlet.domain.BaseEntity
|
||||
* <p>
|
||||
* JPA Entity 클래스들이 BaseEntity 를 상속할 경우 createdBy, lastModifiedBy 필드들과
|
||||
* BaseTimeEntity 필드들(createdDate, modifiedDate)까지 컬럼으로 인식된다.
|
||||
*
|
||||
* @author 표준프레임워크센터 jaeyeolkim
|
||||
* @version 1.0
|
||||
* @since 2021/06/30
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/06/30 jaeyeolkim 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
public abstract class BaseEntity extends BaseTimeEntity{
|
||||
@CreatedBy
|
||||
protected String createdBy;
|
||||
|
||||
@LastModifiedBy
|
||||
protected String lastModifiedBy;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.egovframe.cloud.reactive.domain;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.springframework.data.annotation.CreatedDate;
|
||||
import org.springframework.data.annotation.LastModifiedDate;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* org.egovframe.cloud.servlet.domain.BaseTimeEntity
|
||||
* <p>
|
||||
* JPA Entity 클래스들이 BaseTimeEntity을 상속할 경우 필드들(createdDate, modifiedDate)도 컬럼으로 인식된다.
|
||||
*
|
||||
* @author 표준프레임워크센터 jaeyeolkim
|
||||
* @version 1.0
|
||||
* @since 2021/06/30
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/06/30 jaeyeolkim 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
public abstract class BaseTimeEntity {
|
||||
@CreatedDate
|
||||
protected LocalDateTime createDate;
|
||||
|
||||
@LastModifiedDate
|
||||
protected LocalDateTime modifiedDate;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
package org.egovframe.cloud.reactive.exception;
|
||||
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.egovframe.cloud.common.exception.BusinessException;
|
||||
import org.egovframe.cloud.common.exception.BusinessMessageException;
|
||||
import org.egovframe.cloud.common.exception.dto.ErrorCode;
|
||||
import org.egovframe.cloud.common.exception.dto.ErrorResponse;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.bind.support.WebExchangeBindException;
|
||||
import org.springframework.web.client.HttpClientErrorException;
|
||||
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
||||
import org.webjars.NotFoundException;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@RestControllerAdvice
|
||||
public class ExceptionHandlerAdvice {
|
||||
|
||||
private final MessageSource messageSource;
|
||||
|
||||
/**
|
||||
* javax.validation.Valid or @Validated 으로 binding error 발생시 발생한다.
|
||||
* HttpMessageConverter 에서 등록한 HttpMessageConverter binding 못할경우 발생
|
||||
* 주로 @RequestBody, @RequestPart 어노테이션에서 발생
|
||||
*
|
||||
* @param e
|
||||
* @return ResponseEntity<ErrorResponse>
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
protected Mono<ErrorResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
|
||||
log.error("handleMethodArgumentNotValidException", e);
|
||||
final ErrorResponse response = ErrorResponse.of(ErrorCode.INVALID_INPUT_VALUE, e.getBindingResult(), messageSource);
|
||||
return Mono.just(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 바인딩 객체 @ModelAttribute 으로 binding error 발생시 BindException 발생한다.
|
||||
* ref https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-modelattrib-method-args
|
||||
*
|
||||
* @param e
|
||||
* @return ResponseEntity<ErrorResponse>
|
||||
*/
|
||||
@ExceptionHandler(BindException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
protected Mono<ErrorResponse> handleBindException(BindException e) {
|
||||
log.error("handleBindException", e);
|
||||
final ErrorResponse response = ErrorResponse.of(ErrorCode.INVALID_INPUT_VALUE, e.getBindingResult(), messageSource);
|
||||
return Mono.just(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 요청은 잘 만들어졌지만, 문법 오류로 인하여 따를 수 없습니다
|
||||
*
|
||||
* @param e
|
||||
* @return ResponseEntity<ErrorResponse>
|
||||
*/
|
||||
@ExceptionHandler(HttpClientErrorException.UnprocessableEntity.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
protected Mono<ErrorResponse> handleUnprocessableEntityException(HttpClientErrorException.UnprocessableEntity e) {
|
||||
log.error("handleUnprocessableEntityException", e);
|
||||
final ErrorResponse response = ErrorResponse.of(ErrorCode.UNPROCESSABLE_ENTITY, messageSource);
|
||||
return Mono.just(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* enum type 일치하지 않아 binding 못할 경우 발생
|
||||
* 주로 @RequestParam enum으로 binding 못했을 경우 발생
|
||||
*
|
||||
* @param e
|
||||
* @return ResponseEntity<ErrorResponse>
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
protected Mono<ErrorResponse> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
|
||||
log.error("handleMethodArgumentTypeMismatchException", e);
|
||||
final ErrorResponse response = ErrorResponse.of(e, messageSource);
|
||||
return Mono.just(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 요청한 페이지가 존재하지 않는 경우
|
||||
*
|
||||
* @param e
|
||||
* @return ResponseEntity<ErrorResponse>
|
||||
*/
|
||||
@ExceptionHandler(NotFoundException.class)
|
||||
@ResponseStatus(HttpStatus.NOT_FOUND)
|
||||
protected Mono<ErrorResponse> handleNotFoundException(NotFoundException e) {
|
||||
log.error("handleNotFoundException", e);
|
||||
final ErrorResponse response = ErrorResponse.of(ErrorCode.NOT_FOUND, messageSource);
|
||||
return Mono.just(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Authentication 객체가 필요한 권한을 보유하지 않은 경우 발생
|
||||
*
|
||||
* @param e
|
||||
* @return ResponseEntity<ErrorResponse>
|
||||
*/
|
||||
@ExceptionHandler(AccessDeniedException.class)
|
||||
protected Mono<ResponseEntity<ErrorResponse>> handleAccessDeniedException(AccessDeniedException e) {
|
||||
log.error("handleAccessDeniedException", e);
|
||||
final ErrorResponse response = ErrorResponse.of(ErrorCode.ACCESS_DENIED, messageSource);
|
||||
return Mono.just(ResponseEntity.status(HttpStatus.valueOf(ErrorCode.ACCESS_DENIED.getStatus()))
|
||||
.body(response));
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용자 인증되지 않은 경우 발생
|
||||
*
|
||||
* @param e
|
||||
* @return ResponseEntity<ErrorResponse>
|
||||
*/
|
||||
@ExceptionHandler(HttpClientErrorException.Unauthorized.class)
|
||||
protected Mono<ResponseEntity<ErrorResponse>> handleUnauthorizedException(HttpClientErrorException.Unauthorized e) {
|
||||
log.error("handleUnauthorizedException", e);
|
||||
final ErrorResponse response = ErrorResponse.of(ErrorCode.UNAUTHORIZED, messageSource);
|
||||
return Mono.just(ResponseEntity.status(HttpStatus.valueOf(ErrorCode.ACCESS_DENIED.getStatus()))
|
||||
.body(response));
|
||||
}
|
||||
|
||||
/**
|
||||
* JWT 인증 만료
|
||||
*
|
||||
* @param e
|
||||
* @return ResponseEntity<ErrorResponse>
|
||||
*/
|
||||
@ExceptionHandler(ExpiredJwtException.class)
|
||||
protected Mono<ResponseEntity<ErrorResponse>> handleExpiredJwtException(ExpiredJwtException e) {
|
||||
log.error("handleExpiredJwtException", e);
|
||||
final ErrorResponse response = ErrorResponse.of(ErrorCode.JWT_EXPIRED, messageSource);
|
||||
return Mono.just(ResponseEntity.status(HttpStatus.valueOf(ErrorCode.ACCESS_DENIED.getStatus()))
|
||||
.body(response));
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용자에게 표시할 다양한 메시지를 직접 정의하여 처리하는 Business RuntimeException Handler
|
||||
* 개발자가 만들어 던지는 런타임 오류를 처리
|
||||
*
|
||||
* @param e
|
||||
* @return ResponseEntity<ErrorResponse>
|
||||
*/
|
||||
@ExceptionHandler(BusinessMessageException.class)
|
||||
protected Mono<ResponseEntity<ErrorResponse>> handleBusinessMessageException(BusinessMessageException e) {
|
||||
log.error("handleBusinessMessageException", e);
|
||||
final ErrorCode errorCode = e.getErrorCode();
|
||||
final String customMessage = e.getCustomMessage();
|
||||
final ErrorResponse response = ErrorResponse.of(errorCode, customMessage);
|
||||
return Mono.just(ResponseEntity.status(HttpStatus.valueOf(errorCode.getStatus()))
|
||||
.body(response));
|
||||
}
|
||||
|
||||
/**
|
||||
* 개발자 정의 ErrorCode 를 처리하는 Business RuntimeException Handler
|
||||
* 개발자가 만들어 던지는 런타임 오류를 처리
|
||||
*
|
||||
* @param e
|
||||
* @return ResponseEntity<ErrorResponse>
|
||||
*/
|
||||
@ExceptionHandler(BusinessException.class)
|
||||
protected Mono<ResponseEntity<ErrorResponse>> handleBusinessException(BusinessException e) {
|
||||
log.error("handleBusinessException", e);
|
||||
final ErrorCode errorCode = e.getErrorCode();
|
||||
final ErrorResponse response = ErrorResponse.of(errorCode, messageSource);
|
||||
return Mono.just(ResponseEntity.status(HttpStatus.valueOf(errorCode.getStatus()))
|
||||
.body(response));
|
||||
}
|
||||
|
||||
/**
|
||||
* default exception
|
||||
*
|
||||
* @param e
|
||||
* @return ResponseEntity<ErrorResponse>
|
||||
*/
|
||||
@ExceptionHandler(Exception.class)
|
||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
protected Mono<ErrorResponse> handleException(Exception e) {
|
||||
log.error("handleException", e);
|
||||
final ErrorResponse response = ErrorResponse.of(ErrorCode.INTERNAL_SERVER_ERROR, messageSource);
|
||||
return Mono.just(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* javax.validation.Valid or @Validated 으로 binding error 발생시 발생한다.
|
||||
*
|
||||
* @param e
|
||||
* @return ResponseEntity<ErrorResponse>
|
||||
*/
|
||||
@ExceptionHandler(WebExchangeBindException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
protected Mono<ErrorResponse> handleWebExchangeBindException(WebExchangeBindException e) {
|
||||
log.error("handleWebExchangeBindException", e);
|
||||
final ErrorResponse response = ErrorResponse.of(ErrorCode.INVALID_INPUT_VALUE, e.getBindingResult(), messageSource);
|
||||
return Mono.just(response);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.egovframe.cloud.reactive.service;
|
||||
|
||||
import org.egovframe.cloud.common.exception.EntityNotFoundException;
|
||||
import org.egovframe.cloud.common.service.AbstractService;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public class ReactiveAbstractService extends AbstractService {
|
||||
|
||||
/**
|
||||
* mono error entity not found exception
|
||||
*
|
||||
* @param id
|
||||
* @param <T>
|
||||
* @return
|
||||
*/
|
||||
protected <T> Mono<T> monoResponseStatusEntityNotFoundException(Object id) {
|
||||
return Mono.error( new EntityNotFoundException("해당 데이터가 존재하지 않습니다. ID =" + String.valueOf(id)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package org.egovframe.cloud.servlet.config;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* org.egovframe.cloud.servlet.config.AuthenticationFilter
|
||||
* <p>
|
||||
* Spring Security AuthenticationFilter 처리
|
||||
* 로그인 인증정보를 받아 토큰을 발급한다
|
||||
*
|
||||
* @author 표준프레임워크센터 jaeyeolkim
|
||||
* @version 1.0
|
||||
* @since 2021/06/30
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/06/30 jaeyeolkim 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {
|
||||
|
||||
private final String TOKEN_SECRET;
|
||||
|
||||
final String TOKEN_CLAIM_NAME = "authorities";
|
||||
|
||||
public AuthenticationFilter(AuthenticationManager authenticationManager, String tokenSecret) {
|
||||
super.setAuthenticationManager(authenticationManager);
|
||||
this.TOKEN_SECRET = tokenSecret;
|
||||
}
|
||||
|
||||
/**
|
||||
* AuthenticationFilter.doFilter 메소드에서 UsernamePasswordAuthenticationToken 정보를 세팅할 때 호출된다.
|
||||
*
|
||||
* @param token
|
||||
* @return
|
||||
*/
|
||||
public Claims getClaimsFromToken(String token) {
|
||||
return Jwts.parser()
|
||||
.setSigningKey(TOKEN_SECRET)
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 로그인 요청 뿐만 아니라 모든 요청시마다 호출된다.
|
||||
* 토큰에 담긴 정보로 Authentication 정보를 설정한다.
|
||||
* 이 처리를 하지 않으면 AnonymousAuthenticationToken 으로 처리된다.
|
||||
*
|
||||
* @param request
|
||||
* @param response
|
||||
* @param chain
|
||||
* @throws IOException
|
||||
* @throws ServletException
|
||||
*/
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||
HttpServletRequest httpRequest = (HttpServletRequest) request;
|
||||
String token = httpRequest.getHeader(HttpHeaders.AUTHORIZATION);
|
||||
if (token == null || "undefined".equals(token) || "".equals(token)) {
|
||||
super.doFilter(request, response, chain);
|
||||
} else {
|
||||
Claims claims = getClaimsFromToken(token);
|
||||
String authorities = claims.get(TOKEN_CLAIM_NAME, String.class);
|
||||
List<SimpleGrantedAuthority> roleList = new ArrayList<>();
|
||||
roleList.add(new SimpleGrantedAuthority(authorities));
|
||||
|
||||
String username = claims.getSubject();
|
||||
if (username != null) {
|
||||
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(username, null, roleList));
|
||||
chain.doFilter(request, response);
|
||||
} else {
|
||||
SecurityContextHolder.getContext().setAuthentication(null);
|
||||
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
|
||||
httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.egovframe.cloud.servlet.config;
|
||||
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
|
||||
/**
|
||||
* org.egovframe.cloud.servlet.config.JpaConfig
|
||||
*
|
||||
* JPA 설정 클래스
|
||||
*
|
||||
* @author 표준프레임워크센터 jooho
|
||||
* @version 1.0
|
||||
* @since 2021/07/07
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/07/07 jooho 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
@Configuration
|
||||
@EnableJpaAuditing(auditorAwareRef = "userAuditAware") // JPA Auditing 활성화
|
||||
@EnableJpaRepositories(basePackages = "org.egovframe.cloud.*.domain")
|
||||
public class JpaConfig {
|
||||
|
||||
/**
|
||||
* JpaQueryFactory 빈 등록
|
||||
*
|
||||
* @param entityManager 엔티티 매니저
|
||||
* @return JPAQueryFactory 쿼리 및 DML 절 생성을 위한 팩토리 클래스
|
||||
*/
|
||||
@Bean
|
||||
public JPAQueryFactory jpaQueryFactory(EntityManager entityManager) {
|
||||
return new JPAQueryFactory(entityManager);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.egovframe.cloud.servlet.config;
|
||||
|
||||
import org.springframework.data.domain.AuditorAware;
|
||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* org.egovframe.cloud.servlet.config.UserAuditAware
|
||||
* <p>
|
||||
* JPA Entity 생성자/수정자 정보를 자동 입력한다.
|
||||
* AuthenticationFilter.doFilter 메소드에서 UsernamePasswordAuthenticationToken 정보를 세팅해주기 때문에
|
||||
* Authentication 에서 userId 값을 받아올 수 있게 된다.
|
||||
*
|
||||
* @author 표준프레임워크센터 jaeyeolkim
|
||||
* @version 1.0
|
||||
* @since 2021/07/08
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/07/08 jaeyeolkim 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
@Component
|
||||
public class UserAuditAware implements AuditorAware<String> {
|
||||
|
||||
/**
|
||||
* Auditing 기능이 활성화된 엔티티에 변경이 감지되면 호출되어 생성자/수정자 정보를 반환한다.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Optional<String> getCurrentAuditor() {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (authentication == null || !authentication.isAuthenticated() || authentication instanceof AnonymousAuthenticationToken) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
String userId = authentication.getPrincipal() == null ? null : authentication.getPrincipal().toString();
|
||||
return Optional.of(userId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package org.egovframe.cloud.servlet.config;
|
||||
|
||||
import org.egovframe.cloud.servlet.interceptor.ApiLogInterceptor;
|
||||
import org.egovframe.cloud.servlet.service.ApiLogService;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* org.egovframe.cloud.userservice.domain.BaseTimeEntity
|
||||
* <p>
|
||||
* WebMvc 관련 Configuration 클래스
|
||||
*
|
||||
* @author 표준프레임워크센터 jaeyeolkim
|
||||
* @version 1.0
|
||||
* @since 2021/07/05
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/07/05 jaeyeolkim 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
@Configuration
|
||||
public class WebMvcConfig implements WebMvcConfigurer {
|
||||
|
||||
private final ApiLogService apiLogService;
|
||||
|
||||
public WebMvcConfig(ApiLogService apiLogService) {
|
||||
this.apiLogService = apiLogService;
|
||||
}
|
||||
|
||||
/**
|
||||
* LocalValidatorFactoryBean 에 messageSource Bean 주입하여 validtor 메시지를 지원한다.
|
||||
*
|
||||
* @param messageSource messages.properties
|
||||
* @return LocalValidatorFactoryBean
|
||||
*/
|
||||
@Bean
|
||||
public LocalValidatorFactoryBean validator(MessageSource messageSource) {
|
||||
LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
|
||||
bean.setValidationMessageSource(messageSource);
|
||||
return bean;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry
|
||||
.addInterceptor(new ApiLogInterceptor(apiLogService))
|
||||
.excludePathPatterns(
|
||||
"/",
|
||||
"/error",
|
||||
"/favicon.ico",
|
||||
"/api/v1/authorizations/check",
|
||||
"/api/v1/users/token/refresh",
|
||||
"/api/v1/menu-roles/**",
|
||||
"/api/v1/messages/**"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.egovframe.cloud.servlet.domain;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.springframework.data.annotation.CreatedBy;
|
||||
import org.springframework.data.annotation.LastModifiedBy;
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.EntityListeners;
|
||||
import javax.persistence.MappedSuperclass;
|
||||
|
||||
/**
|
||||
* org.egovframe.cloud.servlet.domain.BaseTimeEntity
|
||||
* <p>
|
||||
* JPA Entity 클래스들이 BaseEntity 를 상속할 경우 createdBy, lastModifiedBy 필드들과
|
||||
* BaseTimeEntity 필드들(createdDate, modifiedDate)까지 컬럼으로 인식된다.
|
||||
*
|
||||
* @author 표준프레임워크센터 jaeyeolkim
|
||||
* @version 1.0
|
||||
* @since 2021/06/30
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/06/30 jaeyeolkim 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@MappedSuperclass
|
||||
@EntityListeners(AuditingEntityListener.class) // Auditing 기능 포함
|
||||
public abstract class BaseEntity extends BaseTimeEntity {
|
||||
|
||||
@CreatedBy
|
||||
@Column(updatable = false)
|
||||
private String createdBy;
|
||||
|
||||
@LastModifiedBy
|
||||
private String lastModifiedBy;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package org.egovframe.cloud.servlet.domain;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.springframework.data.annotation.CreatedDate;
|
||||
import org.springframework.data.annotation.LastModifiedDate;
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
|
||||
import javax.persistence.EntityListeners;
|
||||
import javax.persistence.MappedSuperclass;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* org.egovframe.cloud.servlet.domain.BaseTimeEntity
|
||||
* <p>
|
||||
* JPA Entity 클래스들이 BaseTimeEntity을 상속할 경우 필드들(createdDate, modifiedDate)도 컬럼으로 인식된다.
|
||||
*
|
||||
* @author 표준프레임워크센터 jaeyeolkim
|
||||
* @version 1.0
|
||||
* @since 2021/06/30
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/06/30 jaeyeolkim 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@MappedSuperclass
|
||||
@EntityListeners(AuditingEntityListener.class) // Auditing 기능 포함
|
||||
public abstract class BaseTimeEntity {
|
||||
|
||||
@CreatedDate
|
||||
private LocalDateTime createdDate;
|
||||
|
||||
@LastModifiedDate
|
||||
private LocalDateTime modifiedDate;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package org.egovframe.cloud.servlet.domain.log;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.egovframe.cloud.servlet.domain.BaseTimeEntity;
|
||||
|
||||
import javax.persistence.*;
|
||||
|
||||
import static javax.persistence.GenerationType.IDENTITY;
|
||||
|
||||
/**
|
||||
* org.egovframe.cloud.servlet.domain.log.LoginLog
|
||||
* <p>
|
||||
* 로그인 로그 엔티티
|
||||
*
|
||||
* @author 표준프레임워크센터 jaeyeolkim
|
||||
* @version 1.0
|
||||
* @since 2021/09/01
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/09/01 jaeyeolkim 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@Entity
|
||||
public class ApiLog extends BaseTimeEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = IDENTITY)
|
||||
@Column(name = "log_id")
|
||||
private Long id;
|
||||
|
||||
private Long siteId;
|
||||
|
||||
private String userId;
|
||||
|
||||
@Column(length = 10)
|
||||
private String httpMethod;
|
||||
|
||||
@Column(length = 500)
|
||||
private String requestUrl;
|
||||
|
||||
@Column(name = "ip_addr", length = 100)
|
||||
private String remoteIp;
|
||||
|
||||
@Builder
|
||||
public ApiLog(Long siteId, String userId, String httpMethod, String requestUrl, String remoteIp) {
|
||||
this.siteId = siteId;
|
||||
this.userId = userId;
|
||||
this.httpMethod = httpMethod;
|
||||
this.requestUrl = requestUrl;
|
||||
this.remoteIp = remoteIp;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.egovframe.cloud.servlet.domain.log;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
/**
|
||||
* org.egovframe.cloud.servlet.domain.log.ApiLogRepository
|
||||
* <p>
|
||||
* Spring Data JPA 에서 제공되는 JpaRepository 를 상속하는 인터페이스
|
||||
*
|
||||
* @author 표준프레임워크센터 jaeyeolkim
|
||||
* @version 1.0
|
||||
* @since 2021/09/01
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/09/01 jaeyeolkim 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
public interface ApiLogRepository extends JpaRepository<ApiLog, Long> {
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
package org.egovframe.cloud.servlet.exception;
|
||||
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
import javassist.NotFoundException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.egovframe.cloud.common.exception.BusinessException;
|
||||
import org.egovframe.cloud.common.exception.BusinessMessageException;
|
||||
import org.egovframe.cloud.common.exception.dto.ErrorCode;
|
||||
import org.egovframe.cloud.common.exception.dto.ErrorResponse;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.client.HttpClientErrorException;
|
||||
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
||||
|
||||
/**
|
||||
* org.egovframe.cloud.common.exception.WebControllerAdvice
|
||||
* <p>
|
||||
* 모든 컨트롤러에 적용되는 컨트롤러 어드바이스 클래스
|
||||
* 예외 처리 (@ExceptionHandler), 바인딩 설정(@InitBinder), 모델 객체(@ModelAttributes)
|
||||
*
|
||||
* @author 표준프레임워크센터 jaeyeolkim
|
||||
* @version 1.0
|
||||
* @since 2021/07/15
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/07/15 jaeyeolkim 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
@Slf4j
|
||||
@RestControllerAdvice
|
||||
@RequiredArgsConstructor
|
||||
public class ExceptionHandlerAdvice {
|
||||
|
||||
protected final MessageSource messageSource;
|
||||
|
||||
/**
|
||||
* javax.validation.Valid or @Validated 으로 binding error 발생시 발생한다.
|
||||
* HttpMessageConverter 에서 등록한 HttpMessageConverter binding 못할경우 발생
|
||||
* 주로 @RequestBody, @RequestPart 어노테이션에서 발생
|
||||
*
|
||||
* @param e
|
||||
* @return ResponseEntity<ErrorResponse>
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
protected ResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
|
||||
log.error("handleMethodArgumentNotValidException", e);
|
||||
final ErrorResponse response = ErrorResponse.of(ErrorCode.INVALID_INPUT_VALUE, e.getBindingResult(), messageSource);
|
||||
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* 바인딩 객체 @ModelAttribute 으로 binding error 발생시 BindException 발생한다.
|
||||
* ref https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-modelattrib-method-args
|
||||
*
|
||||
* @param e
|
||||
* @return ResponseEntity<ErrorResponse>
|
||||
*/
|
||||
@ExceptionHandler(BindException.class)
|
||||
protected ResponseEntity<ErrorResponse> handleBindException(BindException e) {
|
||||
log.error("handleBindException", e);
|
||||
final ErrorResponse response = ErrorResponse.of(ErrorCode.INVALID_INPUT_VALUE, e.getBindingResult(), messageSource);
|
||||
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* 요청은 잘 만들어졌지만, 문법 오류로 인하여 따를 수 없습니다
|
||||
*
|
||||
* @param e
|
||||
* @return ResponseEntity<ErrorResponse>
|
||||
*/
|
||||
@ExceptionHandler(HttpClientErrorException.UnprocessableEntity.class)
|
||||
protected ResponseEntity<ErrorResponse> handleUnprocessableEntityException(HttpClientErrorException.UnprocessableEntity e) {
|
||||
log.error("handleUnprocessableEntityException", e);
|
||||
final ErrorResponse response = ErrorResponse.of(ErrorCode.UNPROCESSABLE_ENTITY, messageSource);
|
||||
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* 지원하지 않은 HTTP method 호출 할 경우 발생
|
||||
*
|
||||
* @param e
|
||||
* @return ResponseEntity<ErrorResponse>
|
||||
*/
|
||||
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
|
||||
protected ResponseEntity<ErrorResponse> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
|
||||
log.error("handleHttpRequestMethodNotSupportedException", e);
|
||||
final ErrorResponse response = ErrorResponse.of(ErrorCode.METHOD_NOT_ALLOWED, messageSource);
|
||||
return new ResponseEntity<>(response, HttpStatus.METHOD_NOT_ALLOWED);
|
||||
}
|
||||
|
||||
/**
|
||||
* enum type 일치하지 않아 binding 못할 경우 발생
|
||||
* 주로 @RequestParam enum으로 binding 못했을 경우 발생
|
||||
*
|
||||
* @param e
|
||||
* @return ResponseEntity<ErrorResponse>
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
|
||||
protected ResponseEntity<ErrorResponse> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
|
||||
log.error("handleMethodArgumentTypeMismatchException", e);
|
||||
final ErrorResponse response = ErrorResponse.of(e, messageSource);
|
||||
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* 요청한 페이지가 존재하지 않는 경우
|
||||
*
|
||||
* @param e
|
||||
* @return ResponseEntity<ErrorResponse>
|
||||
*/
|
||||
@ExceptionHandler(NotFoundException.class)
|
||||
protected ResponseEntity<ErrorResponse> handleNotFoundException(NotFoundException e) {
|
||||
log.error("handleNotFoundException", e);
|
||||
final ErrorResponse response = ErrorResponse.of(ErrorCode.NOT_FOUND, messageSource);
|
||||
return new ResponseEntity<>(response, HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Authentication 객체가 필요한 권한을 보유하지 않은 경우 발생
|
||||
*
|
||||
* @param e
|
||||
* @return ResponseEntity<ErrorResponse>
|
||||
*/
|
||||
@ExceptionHandler(AccessDeniedException.class)
|
||||
protected ResponseEntity<ErrorResponse> handleAccessDeniedException(AccessDeniedException e) {
|
||||
log.error("handleAccessDeniedException", e);
|
||||
final ErrorResponse response = ErrorResponse.of(ErrorCode.ACCESS_DENIED, messageSource);
|
||||
return new ResponseEntity<>(response, HttpStatus.valueOf(ErrorCode.ACCESS_DENIED.getStatus()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용자 인증되지 않은 경우 발생
|
||||
*
|
||||
* @param e
|
||||
* @return ResponseEntity<ErrorResponse>
|
||||
*/
|
||||
@ExceptionHandler(HttpClientErrorException.Unauthorized.class)
|
||||
protected ResponseEntity<ErrorResponse> handleUnauthorizedException(HttpClientErrorException.Unauthorized e) {
|
||||
log.error("handleUnauthorizedException", e);
|
||||
final ErrorResponse response = ErrorResponse.of(ErrorCode.UNAUTHORIZED, messageSource);
|
||||
return new ResponseEntity<>(response, HttpStatus.valueOf(ErrorCode.ACCESS_DENIED.getStatus()));
|
||||
}
|
||||
|
||||
/**
|
||||
* JWT 인증 만료
|
||||
*
|
||||
* @param e
|
||||
* @return ResponseEntity<ErrorResponse>
|
||||
*/
|
||||
@ExceptionHandler(ExpiredJwtException.class)
|
||||
protected ResponseEntity<ErrorResponse> handleExpiredJwtException(ExpiredJwtException e) {
|
||||
log.error("handleExpiredJwtException", e);
|
||||
final ErrorResponse response = ErrorResponse.of(ErrorCode.JWT_EXPIRED, messageSource);
|
||||
return new ResponseEntity<>(response, HttpStatus.valueOf(ErrorCode.ACCESS_DENIED.getStatus()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용자에게 표시할 다양한 메시지를 직접 정의하여 처리하는 Business RuntimeException Handler
|
||||
* 개발자가 만들어 던지는 런타임 오류를 처리
|
||||
*
|
||||
* @param e
|
||||
* @return ResponseEntity<ErrorResponse>
|
||||
*/
|
||||
@ExceptionHandler(BusinessMessageException.class)
|
||||
protected ResponseEntity<ErrorResponse> handleBusinessMessageException(BusinessMessageException e) {
|
||||
log.error("handleBusinessMessageException", e);
|
||||
final ErrorCode errorCode = e.getErrorCode();
|
||||
final String customMessage = e.getCustomMessage();
|
||||
final ErrorResponse response = ErrorResponse.of(errorCode, customMessage);
|
||||
return new ResponseEntity<>(response, HttpStatus.valueOf(errorCode.getStatus()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 개발자 정의 ErrorCode 를 처리하는 Business RuntimeException Handler
|
||||
* 개발자가 만들어 던지는 런타임 오류를 처리
|
||||
*
|
||||
* @param e
|
||||
* @return ResponseEntity<ErrorResponse>
|
||||
*/
|
||||
@ExceptionHandler(BusinessException.class)
|
||||
protected ResponseEntity<ErrorResponse> handleBusinessException(BusinessException e) {
|
||||
log.error("handleBusinessException", e);
|
||||
final ErrorCode errorCode = e.getErrorCode();
|
||||
final ErrorResponse response = ErrorResponse.of(errorCode, messageSource);
|
||||
return new ResponseEntity<>(response, HttpStatus.valueOf(errorCode.getStatus()));
|
||||
}
|
||||
|
||||
/**
|
||||
* default exception
|
||||
*
|
||||
* @param e
|
||||
* @return ResponseEntity<ErrorResponse>
|
||||
*/
|
||||
@ExceptionHandler(Exception.class)
|
||||
protected ResponseEntity<ErrorResponse> handleException(Exception e) {
|
||||
log.error("handleException", e);
|
||||
final ErrorResponse response = ErrorResponse.of(ErrorCode.INTERNAL_SERVER_ERROR, messageSource);
|
||||
return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.egovframe.cloud.servlet.interceptor;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.egovframe.cloud.servlet.service.ApiLogService;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
@Slf4j
|
||||
public class ApiLogInterceptor implements HandlerInterceptor {
|
||||
|
||||
private final ApiLogService apiLogService;
|
||||
|
||||
public ApiLogInterceptor(ApiLogService apiLogService) {
|
||||
this.apiLogService = apiLogService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
// 접근 로그 입력
|
||||
apiLogService.saveApiLog(request);
|
||||
log.info("[ApiLogInterceptor preHandle] {}, {}, {}", request.getMethod(), request.getRequestURI(), response.getStatus());
|
||||
return HandlerInterceptor.super.preHandle(request, response, handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
|
||||
log.info("[ApiLogInterceptor postHandle] {}, {}, {}", request.getMethod(), request.getRequestURI(), response.getStatus());
|
||||
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
|
||||
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package org.egovframe.cloud.servlet.service;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.egovframe.cloud.servlet.domain.log.ApiLog;
|
||||
import org.egovframe.cloud.common.service.AbstractService;
|
||||
import org.egovframe.cloud.servlet.domain.log.ApiLogRepository;
|
||||
import org.egovframe.cloud.common.util.LogUtil;
|
||||
import org.egovframe.cloud.servlet.interceptor.ApiLogInterceptor;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* org.egovframe.cloud.servlet.service.ApiLogService
|
||||
* <p>
|
||||
* API Log 처리 서비스
|
||||
*
|
||||
* @author 표준프레임워크센터 jaeyeolkim
|
||||
* @version 1.0
|
||||
* @since 2021/09/01
|
||||
*
|
||||
* <pre>
|
||||
* << 개정이력(Modification Information) >>
|
||||
*
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2021/09/01 jaeyeolkim 최초 생성
|
||||
* </pre>
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class ApiLogService extends AbstractService {
|
||||
|
||||
private final ApiLogRepository apiLogRepository;
|
||||
|
||||
public ApiLogService(ApiLogRepository apiLogRepository) {
|
||||
this.apiLogRepository = apiLogRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* API log 입력
|
||||
* LogInterceptor 에서 호출된다
|
||||
*
|
||||
* @param request
|
||||
* @see ApiLogInterceptor
|
||||
*/
|
||||
@Transactional
|
||||
public void saveApiLog(HttpServletRequest request) {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
apiLogRepository.save(
|
||||
ApiLog.builder()
|
||||
.siteId(LogUtil.getSiteId(request))
|
||||
.httpMethod(request.getMethod())
|
||||
.requestUrl(request.getRequestURI())
|
||||
.userId(authentication.getName())
|
||||
.remoteIp(LogUtil.getUserIp())
|
||||
.build()
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user