diff --git a/backend/module-common/build.gradle b/backend/module-common/build.gradle new file mode 100644 index 0000000..10cc97d --- /dev/null +++ b/backend/module-common/build.gradle @@ -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 추가 끝 diff --git a/backend/module-common/gradlew b/backend/module-common/gradlew new file mode 100755 index 0000000..4f906e0 --- /dev/null +++ b/backend/module-common/gradlew @@ -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" "$@" diff --git a/backend/module-common/gradlew.bat b/backend/module-common/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/backend/module-common/gradlew.bat @@ -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 diff --git a/backend/module-common/settings.gradle b/backend/module-common/settings.gradle new file mode 100644 index 0000000..0359454 --- /dev/null +++ b/backend/module-common/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'module-common' + diff --git a/backend/module-common/src/main/java/org/egovframe/cloud/common/config/ApiControllerAdvice.java b/backend/module-common/src/main/java/org/egovframe/cloud/common/config/ApiControllerAdvice.java new file mode 100644 index 0000000..5529405 --- /dev/null +++ b/backend/module-common/src/main/java/org/egovframe/cloud/common/config/ApiControllerAdvice.java @@ -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 + * + *
+ * << 개정이력(Modification Information) >> + * + * 수정일 수정자 수정내용 + * ---------- -------- --------------------------- + * 2021/07/12 jooho 최초 생성 + *+ */ +@ControllerAdvice +public class ApiControllerAdvice { + + /** + * 모든 컨트롤러로 들어오는 요청 초기화 + * + * @param binder 웹 데이터 바인더 + */ + @InitBinder + public void initBinder(WebDataBinder binder) { + binder.initDirectFieldAccess(); // Setter 구현 없이 DTO 클래스 필드에 접근 + } + +} diff --git a/backend/module-common/src/main/java/org/egovframe/cloud/common/config/GlobalConstant.java b/backend/module-common/src/main/java/org/egovframe/cloud/common/config/GlobalConstant.java new file mode 100644 index 0000000..4893568 --- /dev/null +++ b/backend/module-common/src/main/java/org/egovframe/cloud/common/config/GlobalConstant.java @@ -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 + * + *
+ * << 개정이력(Modification Information) >> + * + * 수정일 수정자 수정내용 + * ---------- -------- --------------------------- + * 2021/07/19 jaeyeolkim 최초 생성 + *+ */ +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"; +} diff --git a/backend/module-common/src/main/java/org/egovframe/cloud/common/config/LeaveaTraceConfig.java b/backend/module-common/src/main/java/org/egovframe/cloud/common/config/LeaveaTraceConfig.java new file mode 100644 index 0000000..fcf7ae8 --- /dev/null +++ b/backend/module-common/src/main/java/org/egovframe/cloud/common/config/LeaveaTraceConfig.java @@ -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 + *
+ * LeaveaTrace Bean 설정 + * EgovAbstractServiceImpl 클래스가 의존한다. + * + * @author 표준프레임워크센터 jaeyeolkim + * @version 1.0 + * @since 2021/09/24 + * + *
+ * << 개정이력(Modification Information) >> + * + * 수정일 수정자 수정내용 + * ---------- -------- --------------------------- + * 2021/09/24 jaeyeolkim 최초 생성 + *+ */ +@Configuration +public class LeaveaTraceConfig { + + @Bean + public LeaveaTrace leaveaTrace() { + return new LeaveaTrace(); + } + +} \ No newline at end of file diff --git a/backend/module-common/src/main/java/org/egovframe/cloud/common/config/MessageSourceConfig.java b/backend/module-common/src/main/java/org/egovframe/cloud/common/config/MessageSourceConfig.java new file mode 100644 index 0000000..fef988f --- /dev/null +++ b/backend/module-common/src/main/java/org/egovframe/cloud/common/config/MessageSourceConfig.java @@ -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 + *
+ * Spring MessageSource 설정 + * Message Domain 이 있는 portal-service 에서 messages.properties 를 외부 위치에 생성한다. + * 각 서비스에서 해당 파일을 통해 다국어를 지원하도록 한다. + * + * @author 표준프레임워크센터 jaeyeolkim + * @version 1.0 + * @since 2021/08/09 + * + *
+ * << 개정이력(Modification Information) >> + * + * 수정일 수정자 수정내용 + * ---------- -------- --------------------------- + * 2021/08/09 jaeyeolkim 최초 생성 + *+ */ +@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; + } + +} \ No newline at end of file diff --git a/backend/module-common/src/main/java/org/egovframe/cloud/common/config/OpenApiDocsConfig.java b/backend/module-common/src/main/java/org/egovframe/cloud/common/config/OpenApiDocsConfig.java new file mode 100644 index 0000000..c403206 --- /dev/null +++ b/backend/module-common/src/main/java/org/egovframe/cloud/common/config/OpenApiDocsConfig.java @@ -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
+ * 사용자 권한 + * + * @author 표준프레임워크센터 jaeyeolkim + * @version 1.0 + * @since 2021/06/30 + * + *
+ * << 개정이력(Modification Information) >> + * + * 수정일 수정자 수정내용 + * ---------- -------- --------------------------- + * 2021/06/30 jaeyeolkim 최초 생성 + *+ */ +@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); + } +} diff --git a/backend/module-common/src/main/java/org/egovframe/cloud/common/dto/AttachmentEntityMessage.java b/backend/module-common/src/main/java/org/egovframe/cloud/common/dto/AttachmentEntityMessage.java new file mode 100644 index 0000000..17b2036 --- /dev/null +++ b/backend/module-common/src/main/java/org/egovframe/cloud/common/dto/AttachmentEntityMessage.java @@ -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; + } +} diff --git a/backend/module-common/src/main/java/org/egovframe/cloud/common/dto/RequestDto.java b/backend/module-common/src/main/java/org/egovframe/cloud/common/dto/RequestDto.java new file mode 100644 index 0000000..2a9388c --- /dev/null +++ b/backend/module-common/src/main/java/org/egovframe/cloud/common/dto/RequestDto.java @@ -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 + *
+ * 공통 조회조건 요청 파라미터 dto + * + * @author 표준프레임워크센터 jaeyeolkim + * @version 1.0 + * @since 2021/07/15 + * + *
+ * << 개정이력(Modification Information) >> + * + * 수정일 수정자 수정내용 + * ---------- -------- --------------------------- + * 2021/07/15 jaeyeolkim 최초 생성 + *+ */ +@Getter +@NoArgsConstructor +@SuperBuilder +public class RequestDto { + private String keywordType; // 검색조건 + private String keyword; // 검색어 +} diff --git a/backend/module-common/src/main/java/org/egovframe/cloud/common/exception/BusinessException.java b/backend/module-common/src/main/java/org/egovframe/cloud/common/exception/BusinessException.java new file mode 100644 index 0000000..663c091 --- /dev/null +++ b/backend/module-common/src/main/java/org/egovframe/cloud/common/exception/BusinessException.java @@ -0,0 +1,70 @@ +package org.egovframe.cloud.common.exception; + +import org.egovframe.cloud.common.exception.dto.ErrorCode; + +/** + * org.egovframe.cloud.common.exception.BusinessException + *
+ * 런타임시 비즈니스 로직상 사용자에게 알려줄 오류 메시지를 만들어 던지는 처리를 담당한다 + * 이 클래스를 상속하여 다양한 형태의 business exception 을 만들 수 있고, + * 그것들은 모두 ExceptionHandlerAdvice BusinessException 처리 메소드에서 잡아낸다. + * 상황에 맞게 에러 코드를 추가하고 이 클래스를 상속하여 사용할 수 있다. + * + * @author 표준프레임워크센터 jaeyeolkim + * @version 1.0 + * @since 2021/07/16 + * + *
+ * << 개정이력(Modification Information) >> + * + * 수정일 수정자 수정내용 + * ---------- -------- --------------------------- + * 2021/07/16 jaeyeolkim 최초 생성 + *+ */ +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; + } + +} diff --git a/backend/module-common/src/main/java/org/egovframe/cloud/common/exception/BusinessMessageException.java b/backend/module-common/src/main/java/org/egovframe/cloud/common/exception/BusinessMessageException.java new file mode 100644 index 0000000..d3b8831 --- /dev/null +++ b/backend/module-common/src/main/java/org/egovframe/cloud/common/exception/BusinessMessageException.java @@ -0,0 +1,33 @@ +package org.egovframe.cloud.common.exception; + +import org.egovframe.cloud.common.exception.dto.ErrorCode; + +/** + * org.egovframe.cloud.common.exception.BusinessMessageException + *
+ * 런타임시 비즈니스 로직상 사용자에게 알려줄 오류 메시지를 만들어 던지는 처리를 담당한다 + * + * @author 표준프레임워크센터 jaeyeolkim + * @version 1.0 + * @since 2021/07/28 + * + *
+ * << 개정이력(Modification Information) >> + * + * 수정일 수정자 수정내용 + * ---------- -------- --------------------------- + * 2021/07/28 jaeyeolkim 최초 생성 + *+ */ +public class BusinessMessageException extends BusinessException { + + /** + * 사용자에게 표시될 메시지와 상태코드 400 을 넘긴다 + * + * @param customMessage + */ + public BusinessMessageException(String customMessage) { + super(ErrorCode.BUSINESS_CUSTOM_MESSAGE, customMessage); + } + +} diff --git a/backend/module-common/src/main/java/org/egovframe/cloud/common/exception/EntityNotFoundException.java b/backend/module-common/src/main/java/org/egovframe/cloud/common/exception/EntityNotFoundException.java new file mode 100644 index 0000000..0be0cf4 --- /dev/null +++ b/backend/module-common/src/main/java/org/egovframe/cloud/common/exception/EntityNotFoundException.java @@ -0,0 +1,28 @@ +package org.egovframe.cloud.common.exception; + +import org.egovframe.cloud.common.exception.dto.ErrorCode; + +/** + * org.egovframe.cloud.common.exception.EntityNotFoundException + *
+ * 요청한 엔티티를 찾을 수 없을 경우 사용자에게 알려준다. + * ExceptionHandlerAdvice BusinessException 처리 메소드에서 잡아낸다. + * + * @author 표준프레임워크센터 jaeyeolkim + * @version 1.0 + * @since 2021/07/16 + * + *
+ * << 개정이력(Modification Information) >> + * + * 수정일 수정자 수정내용 + * ---------- -------- --------------------------- + * 2021/07/16 jaeyeolkim 최초 생성 + *+ */ +public class EntityNotFoundException extends BusinessException { + + public EntityNotFoundException(String message) { + super(message, ErrorCode.ENTITY_NOT_FOUND); + } +} diff --git a/backend/module-common/src/main/java/org/egovframe/cloud/common/exception/InvalidValueException.java b/backend/module-common/src/main/java/org/egovframe/cloud/common/exception/InvalidValueException.java new file mode 100644 index 0000000..0b8a277 --- /dev/null +++ b/backend/module-common/src/main/java/org/egovframe/cloud/common/exception/InvalidValueException.java @@ -0,0 +1,32 @@ +package org.egovframe.cloud.common.exception; + +import org.egovframe.cloud.common.exception.dto.ErrorCode; + +/** + * org.egovframe.cloud.common.exception.InvalidValueException + *
+ * 입력 받은 값이 잘못된 경우 사용자에게 알려준다. + * ExceptionHandlerAdvice BusinessException 처리 메소드에서 잡아낸다. + * + * @author 표준프레임워크센터 jaeyeolkim + * @version 1.0 + * @since 2021/07/16 + * + *
+ * << 개정이력(Modification Information) >> + * + * 수정일 수정자 수정내용 + * ---------- -------- --------------------------- + * 2021/07/16 jaeyeolkim 최초 생성 + *+ */ +public class InvalidValueException extends BusinessException { + + public InvalidValueException(String value) { + super(value, ErrorCode.INVALID_INPUT_VALUE); + } + + public InvalidValueException(String value, ErrorCode errorCode) { + super(value, errorCode); + } +} diff --git a/backend/module-common/src/main/java/org/egovframe/cloud/common/exception/dto/ErrorCode.java b/backend/module-common/src/main/java/org/egovframe/cloud/common/exception/dto/ErrorCode.java new file mode 100644 index 0000000..ba4dd79 --- /dev/null +++ b/backend/module-common/src/main/java/org/egovframe/cloud/common/exception/dto/ErrorCode.java @@ -0,0 +1,65 @@ +package org.egovframe.cloud.common.exception.dto; + +/** + * org.egovframe.cloud.common.exception.dto.ErrorCode + *
+ * REST API 요청에 대한 오류 반환값을 정의 + * ErrorResponse 클래스에서 status, code, message 세 가지 속성을 의존한다 + * message 는 MessageSource 의 키 값을 정의하여 다국어 처리를 지원한다 + * + * @author 표준프레임워크센터 jaeyeolkim + * @version 1.0 + * @since 2021/07/16 + * + *
+ * << 개정이력(Modification Information) >> + * + * 수정일 수정자 수정내용 + * ---------- -------- --------------------------- + * 2021/07/16 jaeyeolkim 최초 생성 + *+ */ +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; + } + +} diff --git a/backend/module-common/src/main/java/org/egovframe/cloud/common/exception/dto/ErrorResponse.java b/backend/module-common/src/main/java/org/egovframe/cloud/common/exception/dto/ErrorResponse.java new file mode 100644 index 0000000..d058f2a --- /dev/null +++ b/backend/module-common/src/main/java/org/egovframe/cloud/common/exception/dto/ErrorResponse.java @@ -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 + *
+ * 일관된 예외처리를 제공하는 클래스 + * https://github.com/cheese10yun/spring-guide + * + * @author 표준프레임워크센터 jooho + * @version 1.0 + * @since 2021/07/16 + * + *
+ * << 개정이력(Modification Information) >> + * + * 수정일 수정자 수정내용 + * ---------- -------- --------------------------- + * 2021/07/16 jaeyeolkim 최초 생성 + *+ */ +@Getter +@NoArgsConstructor(access = PROTECTED) +public class ErrorResponse { + private LocalDateTime timestamp; + private String message; + private int status; + private String code; + private List
+ * 표준프레임워크 EgovAbstractServiceImpl 을 상속하는 공통 추상 클래스이다. + * 각 @Service 클래스는 이 클래스를 반드시 상속하여야 한다.(표준프레임워크 준수사항) + * + * @author 표준프레임워크센터 jaeyeolkim + * @version 1.0 + * + *
+ * << 개정이력(Modification Information) >> + * + * 수정일 수정자 수정내용 + * ---------- -------- --------------------------- + * 2021/07/28 jaeyeolkim 최초 생성 + *+ * @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()); + } + +} diff --git a/backend/module-common/src/main/java/org/egovframe/cloud/common/util/LogUtil.java b/backend/module-common/src/main/java/org/egovframe/cloud/common/util/LogUtil.java new file mode 100644 index 0000000..4986fee --- /dev/null +++ b/backend/module-common/src/main/java/org/egovframe/cloud/common/util/LogUtil.java @@ -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 + *
+ * 로그인, 접속 로그 입력 시 필요한 정보 제공 + * + * @author 표준프레임워크센터 jaeyeolkim + * @version 1.0 + * @since 2021/09/02 + * + *
+ * << 개정이력(Modification Information) >> + * + * 수정일 수정자 수정내용 + * ---------- -------- --------------------------- + * 2021/09/02 jaeyeolkim 최초 생성 + *+ */ +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); + } + +} diff --git a/backend/module-common/src/main/java/org/egovframe/cloud/common/util/MessageUtil.java b/backend/module-common/src/main/java/org/egovframe/cloud/common/util/MessageUtil.java new file mode 100644 index 0000000..77d21b2 --- /dev/null +++ b/backend/module-common/src/main/java/org/egovframe/cloud/common/util/MessageUtil.java @@ -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 + *
+ * MessageSource 값을 읽을 수 있는 메소드를 제공한다. + * + * @author 표준프레임워크센터 jaeyeolkim + * @version 1.0 + * @since 2021/09/08 + * + *
+ * << 개정이력(Modification Information) >> + * + * 수정일 수정자 수정내용 + * ---------- -------- --------------------------- + * 2021/09/08 jaeyeolkim 최초 생성 + *+ */ +@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); + } + +} diff --git a/backend/module-common/src/main/java/org/egovframe/cloud/reactive/config/AuthenticationConverter.java b/backend/module-common/src/main/java/org/egovframe/cloud/reactive/config/AuthenticationConverter.java new file mode 100644 index 0000000..0f5d29d --- /dev/null +++ b/backend/module-common/src/main/java/org/egovframe/cloud/reactive/config/AuthenticationConverter.java @@ -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 + * + *
+ * << 개정이력(Modification Information) >> + * + * 수정일 수정자 수정내용 + * ---------- -------- --------------------------- + * 2021/09/06 shinmj 최초 생성 + *+ */ +@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
+ * << 개정이력(Modification Information) >> + * + * 수정일 수정자 수정내용 + * ---------- -------- --------------------------- + * 2021/09/06 shinmj 최초 생성 + *+ */ +@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; + } + +} diff --git a/backend/module-common/src/main/java/org/egovframe/cloud/reactive/config/SecurityConfig.java b/backend/module-common/src/main/java/org/egovframe/cloud/reactive/config/SecurityConfig.java new file mode 100644 index 0000000..cdc0ad0 --- /dev/null +++ b/backend/module-common/src/main/java/org/egovframe/cloud/reactive/config/SecurityConfig.java @@ -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 + * + *
+ * << 개정이력(Modification Information) >> + * + * 수정일 수정자 수정내용 + * ---------- -------- --------------------------- + * 2021/09/06 shinmj 최초 생성 + *+ */ +@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; + } +} diff --git a/backend/module-common/src/main/java/org/egovframe/cloud/reactive/config/UserAuditAware.java b/backend/module-common/src/main/java/org/egovframe/cloud/reactive/config/UserAuditAware.java new file mode 100644 index 0000000..2e6c13f --- /dev/null +++ b/backend/module-common/src/main/java/org/egovframe/cloud/reactive/config/UserAuditAware.java @@ -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
+ * JPA Entity 클래스들이 BaseEntity 를 상속할 경우 createdBy, lastModifiedBy 필드들과 + * BaseTimeEntity 필드들(createdDate, modifiedDate)까지 컬럼으로 인식된다. + * + * @author 표준프레임워크센터 jaeyeolkim + * @version 1.0 + * @since 2021/06/30 + * + *
+ * << 개정이력(Modification Information) >> + * + * 수정일 수정자 수정내용 + * ---------- -------- --------------------------- + * 2021/06/30 jaeyeolkim 최초 생성 + *+ */ +@Getter +public abstract class BaseEntity extends BaseTimeEntity{ + @CreatedBy + protected String createdBy; + + @LastModifiedBy + protected String lastModifiedBy; +} diff --git a/backend/module-common/src/main/java/org/egovframe/cloud/reactive/domain/BaseTimeEntity.java b/backend/module-common/src/main/java/org/egovframe/cloud/reactive/domain/BaseTimeEntity.java new file mode 100644 index 0000000..e5bdd5f --- /dev/null +++ b/backend/module-common/src/main/java/org/egovframe/cloud/reactive/domain/BaseTimeEntity.java @@ -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 + *
+ * JPA Entity 클래스들이 BaseTimeEntity을 상속할 경우 필드들(createdDate, modifiedDate)도 컬럼으로 인식된다. + * + * @author 표준프레임워크센터 jaeyeolkim + * @version 1.0 + * @since 2021/06/30 + * + *
+ * << 개정이력(Modification Information) >> + * + * 수정일 수정자 수정내용 + * ---------- -------- --------------------------- + * 2021/06/30 jaeyeolkim 최초 생성 + *+ */ +@Getter +public abstract class BaseTimeEntity { + @CreatedDate + protected LocalDateTime createDate; + + @LastModifiedDate + protected LocalDateTime modifiedDate; + +} diff --git a/backend/module-common/src/main/java/org/egovframe/cloud/reactive/exception/ExceptionHandlerAdvice.java b/backend/module-common/src/main/java/org/egovframe/cloud/reactive/exception/ExceptionHandlerAdvice.java new file mode 100644 index 0000000..38c6594 --- /dev/null +++ b/backend/module-common/src/main/java/org/egovframe/cloud/reactive/exception/ExceptionHandlerAdvice.java @@ -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
+ * Spring Security AuthenticationFilter 처리 + * 로그인 인증정보를 받아 토큰을 발급한다 + * + * @author 표준프레임워크센터 jaeyeolkim + * @version 1.0 + * @since 2021/06/30 + * + *
+ * << 개정이력(Modification Information) >> + * + * 수정일 수정자 수정내용 + * ---------- -------- --------------------------- + * 2021/06/30 jaeyeolkim 최초 생성 + *+ */ +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
+ * << 개정이력(Modification Information) >> + * + * 수정일 수정자 수정내용 + * ---------- -------- --------------------------- + * 2021/07/07 jooho 최초 생성 + *+ */ +@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); + } + +} diff --git a/backend/module-common/src/main/java/org/egovframe/cloud/servlet/config/UserAuditAware.java b/backend/module-common/src/main/java/org/egovframe/cloud/servlet/config/UserAuditAware.java new file mode 100644 index 0000000..84fd848 --- /dev/null +++ b/backend/module-common/src/main/java/org/egovframe/cloud/servlet/config/UserAuditAware.java @@ -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 + *
+ * JPA Entity 생성자/수정자 정보를 자동 입력한다. + * AuthenticationFilter.doFilter 메소드에서 UsernamePasswordAuthenticationToken 정보를 세팅해주기 때문에 + * Authentication 에서 userId 값을 받아올 수 있게 된다. + * + * @author 표준프레임워크센터 jaeyeolkim + * @version 1.0 + * @since 2021/07/08 + * + *
+ * << 개정이력(Modification Information) >> + * + * 수정일 수정자 수정내용 + * ---------- -------- --------------------------- + * 2021/07/08 jaeyeolkim 최초 생성 + *+ */ +@Component +public class UserAuditAware implements AuditorAware
+ * WebMvc 관련 Configuration 클래스 + * + * @author 표준프레임워크센터 jaeyeolkim + * @version 1.0 + * @since 2021/07/05 + * + *
+ * << 개정이력(Modification Information) >> + * + * 수정일 수정자 수정내용 + * ---------- -------- --------------------------- + * 2021/07/05 jaeyeolkim 최초 생성 + *+ */ +@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/**" + ); + } +} diff --git a/backend/module-common/src/main/java/org/egovframe/cloud/servlet/domain/BaseEntity.java b/backend/module-common/src/main/java/org/egovframe/cloud/servlet/domain/BaseEntity.java new file mode 100644 index 0000000..7102d80 --- /dev/null +++ b/backend/module-common/src/main/java/org/egovframe/cloud/servlet/domain/BaseEntity.java @@ -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 + *
+ * JPA Entity 클래스들이 BaseEntity 를 상속할 경우 createdBy, lastModifiedBy 필드들과 + * BaseTimeEntity 필드들(createdDate, modifiedDate)까지 컬럼으로 인식된다. + * + * @author 표준프레임워크센터 jaeyeolkim + * @version 1.0 + * @since 2021/06/30 + * + *
+ * << 개정이력(Modification Information) >> + * + * 수정일 수정자 수정내용 + * ---------- -------- --------------------------- + * 2021/06/30 jaeyeolkim 최초 생성 + *+ */ +@Getter +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) // Auditing 기능 포함 +public abstract class BaseEntity extends BaseTimeEntity { + + @CreatedBy + @Column(updatable = false) + private String createdBy; + + @LastModifiedBy + private String lastModifiedBy; +} diff --git a/backend/module-common/src/main/java/org/egovframe/cloud/servlet/domain/BaseTimeEntity.java b/backend/module-common/src/main/java/org/egovframe/cloud/servlet/domain/BaseTimeEntity.java new file mode 100644 index 0000000..794af7a --- /dev/null +++ b/backend/module-common/src/main/java/org/egovframe/cloud/servlet/domain/BaseTimeEntity.java @@ -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 + *
+ * JPA Entity 클래스들이 BaseTimeEntity을 상속할 경우 필드들(createdDate, modifiedDate)도 컬럼으로 인식된다. + * + * @author 표준프레임워크센터 jaeyeolkim + * @version 1.0 + * @since 2021/06/30 + * + *
+ * << 개정이력(Modification Information) >> + * + * 수정일 수정자 수정내용 + * ---------- -------- --------------------------- + * 2021/06/30 jaeyeolkim 최초 생성 + *+ */ +@Getter +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) // Auditing 기능 포함 +public abstract class BaseTimeEntity { + + @CreatedDate + private LocalDateTime createdDate; + + @LastModifiedDate + private LocalDateTime modifiedDate; +} diff --git a/backend/module-common/src/main/java/org/egovframe/cloud/servlet/domain/log/ApiLog.java b/backend/module-common/src/main/java/org/egovframe/cloud/servlet/domain/log/ApiLog.java new file mode 100644 index 0000000..9d9e15f --- /dev/null +++ b/backend/module-common/src/main/java/org/egovframe/cloud/servlet/domain/log/ApiLog.java @@ -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 + *
+ * 로그인 로그 엔티티 + * + * @author 표준프레임워크센터 jaeyeolkim + * @version 1.0 + * @since 2021/09/01 + * + *
+ * << 개정이력(Modification Information) >> + * + * 수정일 수정자 수정내용 + * ---------- -------- --------------------------- + * 2021/09/01 jaeyeolkim 최초 생성 + *+ */ +@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; + } +} diff --git a/backend/module-common/src/main/java/org/egovframe/cloud/servlet/domain/log/ApiLogRepository.java b/backend/module-common/src/main/java/org/egovframe/cloud/servlet/domain/log/ApiLogRepository.java new file mode 100644 index 0000000..3715d22 --- /dev/null +++ b/backend/module-common/src/main/java/org/egovframe/cloud/servlet/domain/log/ApiLogRepository.java @@ -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 + *
+ * Spring Data JPA 에서 제공되는 JpaRepository 를 상속하는 인터페이스 + * + * @author 표준프레임워크센터 jaeyeolkim + * @version 1.0 + * @since 2021/09/01 + * + *
+ * << 개정이력(Modification Information) >> + * + * 수정일 수정자 수정내용 + * ---------- -------- --------------------------- + * 2021/09/01 jaeyeolkim 최초 생성 + *+ */ +public interface ApiLogRepository extends JpaRepository
+ * 모든 컨트롤러에 적용되는 컨트롤러 어드바이스 클래스 + * 예외 처리 (@ExceptionHandler), 바인딩 설정(@InitBinder), 모델 객체(@ModelAttributes) + * + * @author 표준프레임워크센터 jaeyeolkim + * @version 1.0 + * @since 2021/07/15 + * + *
+ * << 개정이력(Modification Information) >> + * + * 수정일 수정자 수정내용 + * ---------- -------- --------------------------- + * 2021/07/15 jaeyeolkim 최초 생성 + *+ */ +@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
+ * API Log 처리 서비스 + * + * @author 표준프레임워크센터 jaeyeolkim + * @version 1.0 + * @since 2021/09/01 + * + *
+ * << 개정이력(Modification Information) >> + * + * 수정일 수정자 수정내용 + * ---------- -------- --------------------------- + * 2021/09/01 jaeyeolkim 최초 생성 + *+ */ +@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() + ); + } +}