This commit is contained in:
Rinjae
2025-08-20 15:45:42 +09:00
commit 176b25b6fe
157 changed files with 7130 additions and 0 deletions
+231
View File
@@ -0,0 +1,231 @@
# Package Files #
*.war
src/main/generated
# Eclipse #
.metadata
bin/
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.settings/
.loadpath
.recommenders
.classpath
.project
# External tool builders
.externalToolBuilders/
# Locally stored "Eclipse launch configurations"
*.launch
# PyDev specific (Python IDE for Eclipse)
*.pydevproject
# CDT-specific (C/C++ Development Tooling)
.cproject
# CDT- autotools
.autotools
# Java annotation processor (APT)
.factorypath
# PDT-specific (PHP Development Tools)
.buildpath
# sbteclipse plugin
.target
# Tern plugin
.tern-project
# TeXlipse plugin
.texlipse
# STS (Spring Tool Suite)
.springBeans
# Code Recommenders
.recommenders/
# Annotation Processing
.apt_generated/
.apt_generated_test/
# Scala IDE specific (Scala & Java development for Eclipse)
.cache-main
.scala_dependencies
.worksheet
# Uncomment this line if you wish to ignore the project description file.
# Typically, this file would be tracked if it contains build/dependency configurations:
#.project
### Intellij ###
.idea
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### Intellij Patch ###
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
# *.iml
# modules.xml
# .idea/misc.xml
# *.ipr
# Sonarlint plugin
.idea/**/sonarlint/
# SonarQube Plugin
.idea/**/sonarIssues.xml
# Markdown Navigator plugin
.idea/**/markdown-navigator.xml
.idea/**/markdown-navigator/
### Java ###
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### Gradle ###
.gradle
build/
# Ignore Gradle GUI config
gradle-app.setting
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar
# Cache of project
.gradletasknamecache
# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
# gradle/wrapper/gradle-wrapper.properties
### Gradle Patch ###
**/build/
# End of https://www.gitignore.io/api/java,macos,gradle,intellij
/EAISIMWeb/
+3
View File
@@ -0,0 +1,3 @@
# elink-portal-common
eLink 개발자 포탈, 관리자 포탈(EMS)의 공통 자원을 관리하는 프로젝트<br>
공통 entity, service, repository 등으로 구성한다.
+189
View File
@@ -0,0 +1,189 @@
plugins {
id 'java-library'
id 'maven-publish'
id 'eclipse-wtp'
id 'idea'
id 'com.diffplug.eclipse.apt' version '3.41.1'
id 'net.researchgate.release' version '3.0.2'
}
idea {
module {
downloadSources = true
}
}
def nexusUrl = "https://nexus.eactive.synology.me:8090"
def springVersion = "5.3.27"
//def quartzVersion = "2.2.1"
def queryDslVersion = "5.0.0"
def hibernateVersion = "5.6.15.Final"
def generatedJavaDir = "$buildDir/generated/java"
def generatedTestJavaDir = "$buildDir/generated-test/java"
repositories {
maven {
url "${nexusUrl}/repository/maven-public/"
allowInsecureProtocol = true
}
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(8)
}
}
sourceSets {
main {
java {
srcDirs = [
'src/main/java',
generatedJavaDir
]
}
resources {
srcDirs = ['src/main/resources']
}
}
test {
java {
srcDirs = [
'src/test/java',
generatedTestJavaDir
]
}
resources {
srcDirs = ['src/test/resources']
}
}
}
compileJava {
options.annotationProcessorGeneratedSourcesDirectory = project.file(generatedJavaDir)
}
compileTestJava {
options.annotationProcessorGeneratedSourcesDirectory = project.file(generatedTestJavaDir)
}
tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
options.compilerArgs += [
'-Amapstruct.verbose=false'
]
aptOptions {
processorArgs = ['querydsl.generatedAnnotationClass': 'com.querydsl.core.annotations.Generated']
}
}
dependencies {
api "com.querydsl:querydsl-jpa:${queryDslVersion}"
api "com.querydsl:querydsl-apt:${queryDslVersion}"
api "org.mapstruct:mapstruct:1.5.5.Final", "org.projectlombok:lombok:1.18.28"
// project > properties > Java Compiler > Annoation Processing
annotationProcessor "org.projectlombok:lombok:1.18.28", "org.projectlombok:lombok-mapstruct-binding:0.2.0", "org.mapstruct:mapstruct-processor:1.5.5.Final"
annotationProcessor "com.querydsl:querydsl-apt:${queryDslVersion}:jpa", "jakarta.persistence:jakarta.persistence-api:2.2.3", "jakarta.annotation:jakarta.annotation-api:1.3.5"
// implementation files(rootProject.file('libs/elink-common-data-4.5.jar'));
implementation 'com.eactive.elink.common:elink-common-data:4.5.5'
api 'org.springframework.data:spring-data-jpa:2.7.17'
api "org.hibernate:hibernate-core:${hibernateVersion}"
api "org.hibernate:hibernate-entitymanager:${hibernateVersion}"
api "org.hibernate:hibernate-ehcache:${hibernateVersion}"
api "org.hibernate:hibernate-validator:5.4.3.Final"
api "org.hibernate:hibernate-envers:${hibernateVersion}"
api 'org.springframework.data:spring-data-envers:2.7.17'
api "org.springframework:spring-aspects:${springVersion}"
api group: 'org.apache.commons', name: 'commons-lang3', version: '3.9'
api "org.springframework:spring-web:${springVersion}"
api group: 'commons-io', name: 'commons-io', version: '2.11.0'
testRuntimeOnly 'com.h2database:h2:2.1.214'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.8.2'
}
test {
useJUnitPlatform()
}
task sourcesJar(type: Jar, dependsOn: classes) {
archiveClassifier = 'sources'
from sourceSets.main.allSource
}
publishing {
repositories {
maven {
name = 'eactiveRepository'
def releasesRepoUrl = "${nexusUrl}/repository/maven-releases/"
def snapshotsRepoUrl = "${nexusUrl}/repository/maven-snapshots/"
url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
credentials(PasswordCredentials)
}
}
publications {
mavenJava(MavenPublication) {
from components.java
pom {
description = "eLink Core JPA module"
licenses {
license {
name = "Commercial License"
url = "http://eactive.co.kr"
}
}
}
}
}
}
task settingEclipseEncoding {
if (project.file('.settings').exists()) {
File f = file('.settings/org.eclipse.core.resources.prefs')
f.write('eclipse.preferences.version=1\n')
f.append('encoding/<project>=utf-8')
}
}
task initDirs() {
if (!project.file(generatedJavaDir).exists()) {
file(generatedJavaDir).mkdirs()
}
if (!project.file(generatedTestJavaDir).exists()) {
file(generatedTestJavaDir).mkdirs()
}
}
eclipse {
synchronizationTasks settingEclipseEncoding, initDirs
jdt {
apt {
genSrcDir = file(generatedJavaDir)
genTestSrcDir = file(generatedTestJavaDir)
}
}
project {
if (!natures.contains('org.eclipse.buildship.core.gradleprojectnature')) {
natures.add('org.eclipse.buildship.core.gradleprojectnature')
buildCommand 'org.eclipse.buildship.core.gradleprojectbuilder'
}
}
}
tasks.named('eclipse').configure {
finalizedBy 'compileJava', 'compileTestJava'
}
+2
View File
@@ -0,0 +1,2 @@
group=com.eactive
version=4.5.1-SNAPSHOT
Binary file not shown.
+6
View File
@@ -0,0 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
networkTimeout=10000
Vendored
+234
View File
@@ -0,0 +1,234 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original 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 POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${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 "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# 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 ;; #(
MSYS* | 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" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
Vendored
+89
View File
@@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
@@ -0,0 +1,38 @@
package com.eactive.apim.portal.agreements.entity;
// 약관, 개인정보처리방침, 개인정보수집동의서 유형을 정의하는 Enum
public enum AgreementType {
TERMS_OF_USE("TERMS_OF_USE", "이용약관"),
PRIVACY_POLICY("PRIVACY_POLICY", "개인정보처리방침"),
PRIVACY_COLLECT_IND("PRIVACY_COLLECT_IND", "개인용 개인정보수집동의서"),
PRIVACY_COLLECT_ORG("PRIVACY_COLLECT_ORG", "법인용 개인정보수집동의서");
private final String code;
private final String description;
AgreementType(String code, String description) {
this.code = code;
this.description = description;
}
public String getCode() {
return code;
}
public String getDescription() {
return description;
}
public static AgreementType fromCode(String code) {
for (AgreementType type : AgreementType.values()) {
if (type.getCode().equals(code)) {
return type;
}
}
throw new IllegalArgumentException("Unknown agreement type code: " + code);
}
public boolean isPrivacyCollect() {
return this == PRIVACY_COLLECT_IND || this == PRIVACY_COLLECT_ORG;
}
}
@@ -0,0 +1,64 @@
package com.eactive.apim.portal.agreements.entity;
import com.eactive.apim.portal.common.entity.Auditable;
import com.eactive.eai.data.converter.LocalDateTimeToStringConverter14;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Comment;
import javax.persistence.*;
import java.time.LocalDateTime;
/**
* PORTAL_TERMS 이용약관 관리
*
* @author Sungpil Hyun
* @version 1.0
*/
@Data
@Entity
@Table(name = "ptl_terms")
@IdClass(AgreementsId.class)
@EqualsAndHashCode(callSuper = true)
public class Agreements extends Auditable {
public Agreements() {
}
public Agreements(AgreementType agreementsType) {
this.agreementsType = agreementsType;
this.status = AgreementsStatus.TEMP;
}
// 약관 유형
@Enumerated(EnumType.STRING)
@Id
@Column(name = "terms_type", nullable = false)
private AgreementType agreementsType;
@Id
@Column(name = "terms_version", nullable = false)
private int revision;
@Column(name = "term_code", length = 255)
@Comment("약관 코드")
private String termCode;
@Column(name = "terms_name", nullable = false)
private String name;
@Column(name = "terms_detail", nullable = false)
private String contents;
@Enumerated(EnumType.STRING)
@Column(name = "terms_status", nullable = false)
private AgreementsStatus status;
@Column(name = "publish_date")
@Convert(converter = LocalDateTimeToStringConverter14.class)
private LocalDateTime publishedOn;
@Column(name = "SCHEDULED_ON")
@Convert(converter = LocalDateTimeToStringConverter14.class)
private LocalDateTime scheduledOn;
}
@@ -0,0 +1,34 @@
package com.eactive.apim.portal.agreements.entity;
import java.io.Serializable;
public class AgreementsId implements Serializable {
public AgreementsId() {
}
public AgreementsId(AgreementType agreementType, int revision) {
this.agreementsType = agreementType;
this.revision = revision;
}
private AgreementType agreementsType;
private int revision;
public AgreementType getAgreementsType() {
return agreementsType;
}
public void setAgreementsType(AgreementType agreementsType) {
this.agreementsType = agreementsType;
}
public int getRevision() {
return revision;
}
public void setRevision(int revision) {
this.revision = revision;
}
}
@@ -0,0 +1,10 @@
package com.eactive.apim.portal.agreements.entity;
public enum AgreementsStatus {
TEMP, //신규
DRAFT, //임시저장
PUBLISHED, //발행
ARCHIVED, //보관
PENDING_REVIEW, //검토 대기
SCHEDULED //발행 예정
}
@@ -0,0 +1,38 @@
package com.eactive.apim.portal.agreements.repository;
import com.eactive.apim.portal.agreements.entity.AgreementType;
import com.eactive.apim.portal.agreements.entity.Agreements;
import com.eactive.apim.portal.agreements.entity.AgreementsId;
import com.eactive.apim.portal.agreements.entity.AgreementsStatus;
import com.eactive.eai.rms.data.EMSDataSource;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
@Repository
@EMSDataSource
public interface AgreementsRepository extends JpaRepository<Agreements, AgreementsId>, JpaSpecificationExecutor<Agreements> {
List<Agreements> findAllByAgreementsTypeOrderByRevisionDesc(AgreementType agreementsType);
List<Agreements> findAllByAgreementsTypeAndStatus(AgreementType agreementsType, AgreementsStatus status);
List<Agreements> findAllByAgreementsTypeOrderByPublishedOnDesc(AgreementType agreementsType);
@Query("SELECT a FROM Agreements a " + "WHERE a.agreementsType = :type " +
"AND a.publishedOn <= :date " + "ORDER BY a.publishedOn DESC, a.revision DESC")
List<Agreements> findLastesBeforeDate(@Param("type") AgreementType agreementsType,
@Param("date") LocalDateTime date);
@Query("SELECT a FROM Agreements a " + "WHERE a.agreementsType = :type " +
"AND a.publishedOn > :date " + "ORDER BY a.publishedOn ASC, a.revision ASC")
List<Agreements> findEarliesAfterDate(@Param("type") AgreementType agreementsType,
@Param("date") LocalDateTime date);
}
@@ -0,0 +1,118 @@
package com.eactive.apim.portal.agreements.service;
import com.eactive.apim.portal.agreements.entity.AgreementType;
import com.eactive.apim.portal.agreements.entity.Agreements;
import com.eactive.apim.portal.agreements.entity.AgreementsId;
import com.eactive.apim.portal.agreements.entity.AgreementsStatus;
import com.eactive.apim.portal.agreements.repository.AgreementsRepository;
import com.eactive.apim.portal.common.exception.NotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
@Service
@Transactional
public class AgreementsService {
private static final String NOT_FOUND_MESSAGE = "해당 버전의 약관이 존재하지 않습니다.";
private static final String CANNOT_MODIFY_MESSAGE = "임시저장 상태가 아닌 약관은 수정할 수 없습니다.";
private static final String CANNOT_PUBLISH_MESSAGE = "발행할 수 없는 상태입니다.";
private static final String CANNOT_DELETE_MESSAGE = "임시저장 상태가 아닌 약관은 삭제할 수 없습니다.";
private static final String NOT_FOUND_BY_DATE_MESSAGE = "해당 시행일자의 약관이 존재하지 않습니다.";
private final AgreementsRepository agreementsRepository;
@Autowired
public AgreementsService(AgreementsRepository agreementsRepository) {
this.agreementsRepository = agreementsRepository;
}
public Agreements save(Agreements agreements) {
if (agreements.getRevision() == 0) {
agreements.setRevision(getNextRevision(agreements.getAgreementsType()));
agreements.setStatus(AgreementsStatus.DRAFT);
agreements.setTermCode(agreements.getAgreementsType().getCode() + agreements.getRevision());
} else {
AgreementsId id = new AgreementsId(agreements.getAgreementsType(), agreements.getRevision());
Agreements existing = agreementsRepository.findById(id)
.orElseThrow(() -> new NotFoundException(NOT_FOUND_MESSAGE));
if (!AgreementsStatus.DRAFT.equals(existing.getStatus())) {
throw new IllegalStateException(CANNOT_MODIFY_MESSAGE);
}
existing.setName(agreements.getName());
existing.setContents(agreements.getContents());
agreements = existing;
}
return agreementsRepository.save(agreements);
}
private int getNextRevision(AgreementType agreementType) {
return agreementsRepository.findAllByAgreementsTypeOrderByRevisionDesc(agreementType)
.stream()
.findFirst()
.map(a -> a.getRevision() + 1)
.orElse(1);
}
public void publish(AgreementType agreementType, int revision) {
AgreementsId id = new AgreementsId(agreementType, revision);
Agreements current = agreementsRepository.findById(id)
.orElseThrow(() -> new NotFoundException(NOT_FOUND_MESSAGE));
agreementsRepository.findAllByAgreementsTypeAndStatus(agreementType, AgreementsStatus.PUBLISHED)
.forEach(agreements -> {
agreements.setStatus(AgreementsStatus.ARCHIVED);
agreementsRepository.save(agreements);
});
if (current.getStatus().equals(AgreementsStatus.DRAFT) ||
current.getStatus().equals(AgreementsStatus.PENDING_REVIEW) ||
current.getStatus().equals(AgreementsStatus.SCHEDULED)) {
current.setStatus(AgreementsStatus.PUBLISHED);
current.setPublishedOn(LocalDateTime.now());
agreementsRepository.save(current);
} else {
throw new IllegalStateException(CANNOT_PUBLISH_MESSAGE);
}
}
public Optional<Agreements> findLastesBeforeDate(AgreementType agreementType, LocalDateTime date) {
List<Agreements> agreements = agreementsRepository.findLastesBeforeDate(agreementType, date);
return agreements.isEmpty() ? Optional.empty() : Optional.of(agreements.get(0));
}
public Optional<Agreements> findEarliesAfterDate(String agreementTypeCode, LocalDateTime date) {
AgreementType agreementType = AgreementType.fromCode(agreementTypeCode);
List<Agreements> agreements = agreementsRepository.findEarliesAfterDate(agreementType, date);
return agreements.isEmpty() ? Optional.empty() : Optional.of(agreements.get(0));
}
public void delete(String agreementType, Integer revision) {
AgreementsId id = new AgreementsId(AgreementType.fromCode(agreementType), revision);
Agreements agreements = agreementsRepository.findById(id)
.orElseThrow(() -> new NotFoundException(NOT_FOUND_MESSAGE));
if (agreements.getStatus().equals(AgreementsStatus.DRAFT)) {
agreementsRepository.delete(agreements);
} else {
throw new IllegalStateException(CANNOT_DELETE_MESSAGE);
}
}
public Agreements findById(String agreementType, int revision) {
AgreementsId id = new AgreementsId(AgreementType.fromCode(agreementType), revision);
return agreementsRepository.findById(id).orElseThrow(() -> new NotFoundException(NOT_FOUND_MESSAGE));
}
public List<Agreements> findAllAgreementsSortPublishedOn(AgreementType agreementType) {
List<Agreements> agreements = agreementsRepository.findAllByAgreementsTypeOrderByPublishedOnDesc(agreementType);
if(agreements.isEmpty()) {
throw new IllegalStateException(NOT_FOUND_BY_DATE_MESSAGE);
}
return agreements;
}
}
@@ -0,0 +1,108 @@
package com.eactive.apim.portal.apispec.entity;
import com.eactive.eai.data.converter.LocalDateTimeToStringConverter17;
import com.eactive.eai.data.entity.AbstractEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NonNull;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import javax.persistence.*;
import java.io.Serializable;
import java.time.LocalDateTime;
@SuppressWarnings("serial")
@Entity
@Table(name = "ptl_api_spec_info")
@Data
@EqualsAndHashCode(callSuper = false)
public class ApiSpecInfo extends AbstractEntity<String> implements Serializable, com.eactive.eai.data.Data {
@Id
@Column(length = 30, name = "api_id")
private String apiId;
@Column(name = "api_name")
private String apiName;
@Column(name = "api_simple_description")
private String apiSimpleDescription; //간단 설명
@Column(name = "api_url")
private String apiUrl;
@Column(name = "api_method")
private String apiMethod;
@Column(name = "api_content_type")
private String apiContentType;
@Lob
@Column(name = "api_request_spec", columnDefinition = "TEXT")
private String apiRequestSpec;
@Lob
@Column(name = "api_response_spec", columnDefinition = "TEXT")
private String apiResponseSpec;
@Lob
@Column(name = "sample_request", columnDefinition = "TEXT")
private String sampleRequest; //요청 메시지 예제
@Lob
@Column(name = "sample_response", columnDefinition = "TEXT")
private String sampleResponse; //응답 메시지 예제
@Column(name = "response_type")
private String responseType; //sample, mock
@Column(name = "mock_url")
private String mockUrl;
@Column(name = "file_id")
private String fileId;
@Lob
@Column(name = "description", columnDefinition = "TEXT")
private String description; //설명
@Lob
@Column(name = "testbed_spec", columnDefinition = "TEXT")
private String testbedSpec;
@Column(name = "display_yn", length = 1)
private String displayYn;
@Column(name = "display_org")
private String displayOrg;
@Column(name = "display_role_code")
private String displayRoleCode;
@CreatedBy
@Column(name = "created_by", length = 50)
private String createdBy;
@CreatedDate
@Column(length = 17, name = "created_date")
@Convert(converter = LocalDateTimeToStringConverter17.class)
private LocalDateTime createdDate;
@LastModifiedBy
@Column(name = "last_modified_by", length = 50)
private String lastModifiedBy;
@LastModifiedDate
@Column(length = 17, name = "last_modified_date")
@Convert(converter = LocalDateTimeToStringConverter17.class)
private LocalDateTime lastModifiedDate;
@Override
public @NonNull String getId() {
return apiId;
}
}
@@ -0,0 +1,26 @@
package com.eactive.apim.portal.apispec.repository;
import com.eactive.apim.portal.apispec.entity.ApiSpecInfo;
import com.eactive.eai.rms.data.EMSDataSource;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
@EMSDataSource
public interface ApiSpecInfoRepository extends JpaRepository<ApiSpecInfo, String>, JpaSpecificationExecutor<ApiSpecInfo> {
List<ApiSpecInfo> findAllByDisplayYn(String displayYn);
List<ApiSpecInfo> findAllByDisplayYnAndApiIdIn(String displayYn, List<String> apiIds);
@Query("SELECT a FROM ApiSpecInfo a WHERE a.displayYn = 'Y' AND (" +
"LOWER(a.apiName) LIKE LOWER(CONCAT('%', :keyword, '%')) OR " +
"LOWER(a.apiSimpleDescription) LIKE LOWER(CONCAT('%', :keyword, '%')))")
List<ApiSpecInfo> searchByKeyword(@Param("keyword") String keyword);
Optional<ApiSpecInfo> findApiSpecInfoByApiUrlAndApiMethodAndDisplayYn(String apiUrl, String apiMethod, String displayYn);
}
@@ -0,0 +1,76 @@
package com.eactive.apim.portal.apispec.service;
import com.eactive.apim.portal.apispec.entity.ApiSpecInfo;
import com.eactive.apim.portal.apispec.repository.ApiSpecInfoRepository;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
@RequiredArgsConstructor
public class ApiSpecInfoService {
private final ApiSpecInfoRepository apiSpecInfoRepository;
public Optional<ApiSpecInfo> findById(String id) {
return apiSpecInfoRepository.findById(id);
}
public Optional<ApiSpecInfo> findByUrlAndMethod(String url, String method) {
return apiSpecInfoRepository.findApiSpecInfoByApiUrlAndApiMethodAndDisplayYn(url, method, "Y");
}
//2. create
public ApiSpecInfo create(ApiSpecInfo apiSpecInfo) {
return apiSpecInfoRepository.save(apiSpecInfo);
}
public ApiSpecInfo updateById(String id, ApiSpecInfo updatedApiSpecInfo) {
Optional<ApiSpecInfo> existingApiSpecInfo = apiSpecInfoRepository.findById(id);
if (existingApiSpecInfo.isPresent()) {
ApiSpecInfo apiSpecInfoToUpdate = existingApiSpecInfo.get();
apiSpecInfoToUpdate.setApiName(updatedApiSpecInfo.getApiName());
apiSpecInfoToUpdate.setApiSimpleDescription(updatedApiSpecInfo.getApiSimpleDescription());
apiSpecInfoToUpdate.setApiUrl(updatedApiSpecInfo.getApiUrl());
apiSpecInfoToUpdate.setApiMethod(updatedApiSpecInfo.getApiMethod());
apiSpecInfoToUpdate.setApiContentType(updatedApiSpecInfo.getApiContentType());
apiSpecInfoToUpdate.setApiRequestSpec(updatedApiSpecInfo.getApiRequestSpec());
apiSpecInfoToUpdate.setApiResponseSpec(updatedApiSpecInfo.getApiResponseSpec());
apiSpecInfoToUpdate.setSampleRequest(updatedApiSpecInfo.getSampleRequest());
apiSpecInfoToUpdate.setSampleResponse(updatedApiSpecInfo.getSampleResponse());
apiSpecInfoToUpdate.setResponseType(updatedApiSpecInfo.getResponseType());
apiSpecInfoToUpdate.setMockUrl(updatedApiSpecInfo.getMockUrl());
apiSpecInfoToUpdate.setFileId(updatedApiSpecInfo.getFileId());
apiSpecInfoToUpdate.setDescription(updatedApiSpecInfo.getDescription());
apiSpecInfoToUpdate.setTestbedSpec(updatedApiSpecInfo.getTestbedSpec());
apiSpecInfoToUpdate.setDisplayYn(updatedApiSpecInfo.getDisplayYn());
apiSpecInfoToUpdate.setDisplayRoleCode(updatedApiSpecInfo.getDisplayRoleCode());
apiSpecInfoToUpdate.setDisplayOrg(updatedApiSpecInfo.getDisplayOrg());
return apiSpecInfoRepository.save(apiSpecInfoToUpdate);
}
return null;
}
public void deleteById(String id) {
apiSpecInfoRepository.deleteById(id);
}
public List<ApiSpecInfo> findPublicApis() {
return apiSpecInfoRepository.findAllByDisplayYn("Y");
}
public List<ApiSpecInfo> findApiSpecsByApiIds(List<String> apiIds) {
return apiSpecInfoRepository.findAllByDisplayYnAndApiIdIn("Y", apiIds);
}
public List<ApiSpecInfo> searchApis(String keyword) {
if (keyword == null) keyword = "";
return apiSpecInfoRepository.searchByKeyword(keyword);
}
}
@@ -0,0 +1,126 @@
package com.eactive.apim.portal.app.entity;
import com.eactive.apim.portal.apispec.entity.ApiSpecInfo;
import com.eactive.eai.data.converter.LocalDateTimeToStringConverter14;
import com.eactive.eai.data.entity.AbstractEntity;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Convert;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import lombok.Data;
import lombok.NonNull;
import org.hibernate.annotations.Comment;
import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
@Entity
@Data
@Table(name = "ptl_credential")
@org.hibernate.annotations.Table(appliesTo = "ptl_credential", comment = "환경 인증 정보")
public class Credential extends AbstractEntity<String> implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@Column(unique = true, nullable = false, length = 256)
@Comment("client id")
private String clientid;
@Column(length = 11)
@Comment("Access Token 유효 기간(초)")
private String accesstokenvalidityseconds;
@Column(length = 256)
@Comment("허용된 IP")
private String allowedips;
@Column(length = 256)
@Comment("권한")
private String authorities;
@Column(length = 256)
@Comment("자동 승인 스코프")
private String autoapprove;
@Column(length = 40)
@Comment("client명")
private String clientname;
@Column(length = 512)
@Comment("Client 비밀번호")
private String clientsecret;
@Column
@Comment("서버")
private String server;
@Column(length = 256)
@Comment("권한타입")
private String granttypes;
@Column(length = 8)
@Comment("수정인")
@LastModifiedBy
private String modifiedby;
@Column(length = 14)
@Comment("수정일")
@Convert(converter = LocalDateTimeToStringConverter14.class)
@LastModifiedDate
private LocalDateTime modifiedon;
// @Column(length = 256, name="uri")
@Column(length = 256)
@Comment("Redirect URI")
private String redirecturi;
@Column(length = 11)
@Comment("Refresh Token 유효 기간(초)")
private String refreshtokenvalidityseconds;
@Column(length = 4000)
@Comment("리소스 id")
private String resourceids;
@Column(length = 1024)
@Comment("scope")
private String scope;
@Column(length = 4000)
@Comment("암호key")
private String securitykey;
@Column(length = 38)
@Comment("기관ID")
private String orgid;
@Comment("APP 상태")
private String appstatus; //0: 차단, 1: 정상
@Column(length = 255)
@Comment("기관이름")
private String orgname;
@Comment("일별 토큰 제한 수 (0:무제한)")
private int dailytokenlimit;
@Override
public @NonNull String getId() {
return clientid;
}
@ManyToMany
@LazyCollection(LazyCollectionOption.FALSE)
@JoinTable(name = "ptl_credential_api", joinColumns = @JoinColumn(name = "client_id"), inverseJoinColumns = @JoinColumn(name = "eaisvcname"))
private List<ApiSpecInfo> apiList = new ArrayList<>();
}
@@ -0,0 +1,121 @@
package com.eactive.apim.portal.app.entity;
import com.eactive.apim.portal.apispec.entity.ApiSpecInfo;
import com.eactive.eai.data.converter.LocalDateTimeToStringConverter14;
import com.eactive.eai.data.entity.AbstractEntity;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Convert;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import lombok.Data;
import lombok.NonNull;
import org.hibernate.annotations.Comment;
import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
@Entity
@Data
@Table(name = "ptl_prod_client")
@org.hibernate.annotations.Table(appliesTo = "ptl_prod_client", comment = "사용자 정보")
public class ProdClient extends AbstractEntity<String> implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@Column(unique = true, nullable = false, length = 256)
@Comment("client id")
private String clientid;
@Column(length = 11)
@Comment("Access Token 유효 기간(초)")
private String accesstokenvalidityseconds;
@Column(length = 256)
@Comment("허용된 IP")
private String allowedips;
@Column(length = 256)
@Comment("권한")
private String authorities;
@Column(length = 256)
@Comment("자동 승인 스코프")
private String autoapprove;
@Column(length = 40)
@Comment("client명")
private String clientname;
@Column(length = 512)
@Comment("Client 비밀번호")
private String clientsecret;
@Column(length = 256)
@Comment("권한타입")
private String granttypes;
@Column(length = 8)
@Comment("수정인")
@LastModifiedBy
private String modifiedby;
@Column(length = 14)
@Comment("수정일")
@Convert(converter = LocalDateTimeToStringConverter14.class)
@LastModifiedDate
private LocalDateTime modifiedon;
@Column(length = 256)
@Comment("Redirect URI")
private String redirecturi;
@Column(length = 11)
@Comment("Refresh Token 유효 기간(초)")
private String refreshtokenvalidityseconds;
@Column(length = 4000)
@Comment("리소스 id")
private String resourceids;
@Column(length = 1024)
@Comment("scope")
private String scope;
@Column(length = 4000)
@Comment("암호key")
private String securitykey;
@Column(length = 38)
@Comment("기관ID")
private String orgid;
@Comment("APP 상태")
private String appstatus; //0: 차단, 1: 정상
@Column(length = 255)
@Comment("기관이름")
private String orgname;
@Comment("일별 토큰 제한 수 (0:무제한)")
private int dailytokenlimit;
@Override
public @NonNull String getId() {
return clientid;
}
@ManyToMany
@LazyCollection(LazyCollectionOption.FALSE)
@JoinTable(name = "ptl_prod_client_api", joinColumns = @JoinColumn(name = "client_id"), inverseJoinColumns = @JoinColumn(name = "eaisvcname"))
private List<ApiSpecInfo> apiList = new ArrayList<>();
}
@@ -0,0 +1,47 @@
package com.eactive.apim.portal.app.event;
import com.eactive.apim.portal.template.service.MessageEventHandler;
import com.eactive.apim.portal.template.service.MessageRecipient;
import com.eactive.apim.portal.template.service.MessageSendEvent;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Component
public class ApikeyRegisterApprovedEvent implements MessageEventHandler {
public static final String KEY = "apikey_register_approved";
@Override
public boolean allowAdditionalRecipients() {
return false;
}
@Override
public MessageSendEvent createEvent(Object source, MessageRecipient recipient, Map<String, Object> params) {
Map<String, String> requestParams = new HashMap<>();
requestParams.put("apikeyName", (String) params.get("apiKeyName"));
return new MessageSendEvent(source, KEY, recipient, requestParams);
}
@Override
public String getKey() {
return KEY;
}
@Override
public String getDisplayName() {
return "API Key 사용 승인 완료";
}
@Override
public String getDescription() {
return "<div>사용 가능 변수: </div><br/>"
+ "<div> - userName 수신자 이름 </div><br/>"
+ "<div> - userId 수신자 ID (이메일) </div><br/>"
+ "<div> - apiKeyName API Key 이름 </div><br/>";
}
}
@@ -0,0 +1,47 @@
package com.eactive.apim.portal.app.event;
import com.eactive.apim.portal.template.service.MessageEventHandler;
import com.eactive.apim.portal.template.service.MessageRecipient;
import com.eactive.apim.portal.template.service.MessageSendEvent;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Component
public class ApikeyRegisterDeniedEvent implements MessageEventHandler {
public static final String KEY = "apikey_register_denied";
@Override
public boolean allowAdditionalRecipients() {
return false;
}
@Override
public MessageSendEvent createEvent(Object source, MessageRecipient recipient, Map<String, Object> params) {
Map<String, String> requestParams = new HashMap<>();
requestParams.put("apikeyName", (String) params.get("apiKeyName"));
return new MessageSendEvent(source, KEY, recipient, requestParams);
}
@Override
public String getKey() {
return KEY;
}
@Override
public String getDisplayName() {
return "API Key 사용 거절";
}
@Override
public String getDescription() {
return "<div>사용 가능 변수: </div><br/>"
+ "<div> - userName 수신자 이름 </div><br/>"
+ "<div> - userId 수신자 ID (이메일) </div><br/>"
+ "<div> - apiKeyName API Key 이름 </div><br/>";
}
}
@@ -0,0 +1,51 @@
package com.eactive.apim.portal.app.event;
import com.eactive.apim.portal.template.service.MessageEventHandler;
import com.eactive.apim.portal.template.service.MessageRecipient;
import com.eactive.apim.portal.template.service.MessageSendEvent;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Component
public class ApikeyRegisterRequestEvent implements MessageEventHandler {
public static final String KEY = "apikey_register_request";
@Override
public boolean allowAdditionalRecipients() {
return true;
}
@Override
public MessageSendEvent createEvent(Object source, MessageRecipient recipient, Map<String, Object> params) {
Map<String, String> requestParams = new HashMap<>();
requestParams.put("apikeyName", (String) params.get("apiKeyName"));
requestParams.put("approvalName", (String) params.get("approvalName"));
requestParams.put("requesterName", (String) params.get("requesterName"));
return new MessageSendEvent(source, KEY, recipient, requestParams);
}
@Override
public String getKey() {
return KEY;
}
@Override
public String getDisplayName() {
return "API Key 사용 승인 요청";
}
@Override
public String getDescription() {
return "<div>사용 가능 변수: </div><br/>"
+ "<div> - userName 수신자 이름 </div><br/>"
+ "<div> - userId 수신자 ID (이메일) </div><br/>"
+ "<div> - apiKeyName API Key 이름 </div><br/>"
+ "<div> - approvalName 승인 요청 제목 </div><br/>"
+ "<div> - requesterName 요청 사용자 이름 </div><br/>";
}
}
@@ -0,0 +1,21 @@
package com.eactive.apim.portal.app.repository;
import com.eactive.apim.portal.app.entity.Credential;
import com.eactive.eai.data.jpa.BaseRepository;
import com.eactive.eai.rms.data.EMSDataSource;
import java.util.List;
import java.util.Optional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
@EMSDataSource
public interface CredentialRepository extends BaseRepository<Credential, String> {
Page<Credential> findAllByOrgidAndServer(Pageable pageable, String id, String server);
int countAllByOrgidAndServer(String orgid, String server);
List<Credential> findAllByOrgidAndServer(String orgid, String server);
Optional<Credential> findByClientidAndOrgidAndServer(String clientid, String orgid, String server);
}
@@ -0,0 +1,18 @@
package com.eactive.apim.portal.app.repository;
import com.eactive.apim.portal.app.entity.ProdClient;
import com.eactive.apim.portal.approval.entity.Approval;
import com.eactive.apim.portal.approval.entity.ApprovalType;
import com.eactive.eai.data.jpa.BaseRepository;
import com.eactive.eai.rms.data.EMSDataSource;
import java.util.Optional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
@EMSDataSource
public interface ProdClientRepository extends BaseRepository<ProdClient, String> {
Page<ProdClient> findAllByOrgid(Pageable pageable, String id);
Optional<ProdClient> findByClientidAndOrgid(String clientid, String orgid);
}
@@ -0,0 +1,104 @@
package com.eactive.apim.portal.apprequest.entity;
import com.eactive.apim.portal.approval.entity.Approval;
import com.eactive.apim.portal.approval.entity.ApprovalStatus;
import com.eactive.apim.portal.portalorg.entity.PortalOrg;
import com.eactive.eai.data.converter.LocalDateTimeToStringConverter17;
import java.time.LocalDateTime;
import javax.persistence.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.GenericGenerator;
@Data
@EqualsAndHashCode
@Entity
@Table(name = "ptl_app_request")
public class AppRequest implements com.eactive.eai.data.Data {
private static final String CLIENT_SEPARATOR = "::";
@Id
@GenericGenerator(name = "uuid-gen", strategy = "uuid2")
@GeneratedValue(generator = "uuid-gen", strategy = GenerationType.IDENTITY)
private String id;
@ManyToOne
@JoinColumn(name = "org_id")
private PortalOrg org;
@Enumerated(EnumType.STRING)
private ApprovalStatus status;
@Enumerated(EnumType.STRING)
private AppRequestType type;
@OneToOne
@JoinColumn(name = "approval_id")
private Approval approval;
@Column(name = "api_list")
private String apiList = ""; //comma separated api id list
@Column(name = "api_group_list")
private String apiGroupList = ""; //comma separated api group id list
@Column(name = "prev_api_list")
private String prevApiList = "";
@Column(name = "client_id")
private String clientIds; //for modifying existing app
@Transient
private String clientId;
@Transient
private String refClient;
@Column(name = "client_name")
private String clientName;
@Column(name = "reason")
private String reason;
@Column(length = 17, name = "created_date")
@Convert(converter = LocalDateTimeToStringConverter17.class)
@CreationTimestamp
private LocalDateTime createdDate;
@PrePersist
@PreUpdate
private void beforeSave() {
if (clientId == null && refClient == null) {
this.clientIds = null;
return;
}
if (refClient != null && !refClient.isEmpty()) {
this.clientIds = (this.clientId != null ? this.clientId : "") + CLIENT_SEPARATOR + refClient;
} else if (clientId != null) {
this.clientIds = clientId;
}
}
@PostLoad
private void afterLoad() {
if (this.clientIds == null) {
this.clientId = null;
this.refClient = null;
return;
}
if (this.clientIds.contains(CLIENT_SEPARATOR)) {
String[] parts = this.clientIds.split(CLIENT_SEPARATOR, 2);
this.clientId = parts[0].isEmpty() ? null : parts[0];
this.refClient = parts[1];
} else {
this.clientId = this.clientIds;
this.refClient = null;
}
}
}
@@ -0,0 +1,29 @@
package com.eactive.apim.portal.apprequest.entity;
public enum AppRequestType {
NEW("API키 신규"),
MODIFY("API 변경"),
DELETE("API키 삭제"),
PROD_NEW("운영 API키 신규"),
PROD_MODIFY("운영 API 변경"),
PROD_DELETE("운영 API키 삭제");
private final String description;
AppRequestType(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
public static AppRequestType fromString(String value) {
try {
return AppRequestType.valueOf(value.toUpperCase());
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Invalid AppRequestType: " + value +
". Allowed values are: " + java.util.Arrays.toString(AppRequestType.values()));
}
}
}
@@ -0,0 +1,32 @@
package com.eactive.apim.portal.apprequest.repository;
import com.eactive.apim.portal.apprequest.entity.AppRequest;
import com.eactive.apim.portal.apprequest.entity.AppRequestType;
import com.eactive.apim.portal.approval.entity.Approval;
import com.eactive.apim.portal.approval.entity.ApprovalStatus;
import com.eactive.apim.portal.approval.statemachine.ApprovalState;
import com.eactive.apim.portal.portalorg.entity.PortalOrg;
import com.eactive.eai.data.jpa.BaseRepository;
import com.eactive.eai.rms.data.EMSDataSource;
import java.util.List;
import java.util.Optional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
@EMSDataSource
public interface AppRequestRepository extends BaseRepository<AppRequest, String> {
Page<AppRequest> findAllByOrgAndTypeIsIn(Pageable pageable, PortalOrg org, List<AppRequestType> types);
Optional<AppRequest> findAppRequestByApproval(Approval approval);
int countAllByOrgAndTypeIsInAndApproval_ApprovalStatusIn(PortalOrg org, List<AppRequestType> types, List<ApprovalState> approvalStates);
Optional<AppRequest> findByIdAndOrg(String id, PortalOrg org);
Optional<AppRequest> findByIdAndOrgAndTypeIsIn(String id, PortalOrg org, List<AppRequestType> types);
List<AppRequest> findAllByClientIdsContainsAndTypeIsIn(String clientId, List<AppRequestType> types);
List<AppRequest> findAllByOrgAndStatus(PortalOrg org, ApprovalStatus status);
}
@@ -0,0 +1,76 @@
package com.eactive.apim.portal.approval.entity;
import com.eactive.apim.portal.approval.statemachine.ApprovalState;
import com.eactive.apim.portal.approval.statemachine.CreatedState;
import com.eactive.apim.portal.portaluser.entity.PortalUser;
import com.eactive.eai.data.converter.LocalDateTimeToStringConverter17;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.hibernate.annotations.*;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import static javax.persistence.EnumType.STRING;
@Entity
@Table(name = "PTL_APPROVAL")
@TypeDef(name = "ApprovalState", typeClass = ApprovalStatusUserType.class, defaultForType = ApprovalState.class)
@Data
@ToString(exclude = "approvers")
@EqualsAndHashCode(of = "id")
public class Approval implements Serializable, com.eactive.eai.data.Data {
public Approval() {
this.approvalStatus = new CreatedState();
}
@Id
@GenericGenerator(name = "uuid-gen", strategy = "uuid2")
@GeneratedValue(generator = "uuid-gen", strategy = GenerationType.IDENTITY)
private String id;
@Column(name = "approval_status", nullable = false)
@Type(type = "ApprovalState")
private ApprovalState approvalStatus; //승인 진행 상태 //statemachine 에 정의됨.
@Enumerated(value = STRING)
@Column(name = "approval_type", nullable = false)
private ApprovalType approvalType; //승인 유형: APP, USER
@Column(name = "target_id", nullable = false)
private String targetId; //승인하는 대상 객체의 ID
@NotNull
@ManyToOne
@JoinColumn(name = "REQUESTER_ID", nullable = false)
private PortalUser requester;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "approval")
@LazyCollection(LazyCollectionOption.FALSE)
private List<Approver> approvers = new ArrayList<>();
@Column(name = "approval_subject", nullable = false)
private String approvalSubject; //제목
@Column(name = "approval_detail", columnDefinition = "TEXT")
private String approvalDetail; //승인 요청 내용
@Column(length = 17, name = "created_date")
@Convert(converter = LocalDateTimeToStringConverter17.class)
@CreationTimestamp
private LocalDateTime createdDate;
@Column(length = 17, name = "end_date")
@Convert(converter = LocalDateTimeToStringConverter17.class)
private LocalDateTime approvalDate;
}
@@ -0,0 +1,25 @@
package com.eactive.apim.portal.approval.entity;
public enum ApprovalStatus {
CREATED("생성됨"),
REQUESTED("요청됨"),
PROCESSING("진행중"),
CHECKING("확인"),
CANCELED("취소됨"),
DENIED("반려됨"),
APPROVED("승인됨"),
PENDING("대기중"),
EXECUTION("후처리"),
END("승인됨"),
FAILED("배포실패");
private String description;
ApprovalStatus(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
@@ -0,0 +1,124 @@
package com.eactive.apim.portal.approval.entity;
import com.eactive.apim.portal.approval.statemachine.ApprovalState;
import com.eactive.apim.portal.approval.statemachine.ApprovedState;
import com.eactive.apim.portal.approval.statemachine.CanceledState;
import com.eactive.apim.portal.approval.statemachine.CreatedState;
import com.eactive.apim.portal.approval.statemachine.DeniedState;
import com.eactive.apim.portal.approval.statemachine.DeployFailedState;
import com.eactive.apim.portal.approval.statemachine.EndState;
import com.eactive.apim.portal.approval.statemachine.ProcessingState;
import com.eactive.apim.portal.approval.statemachine.RequestedState;
import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.usertype.UserType;
public class ApprovalStatusUserType implements UserType {
@Override
public int[] sqlTypes() {
return new int[]{Types.VARCHAR};
}
@Override
public Class returnedClass() {
return ApprovalState.class;
}
@Override
public boolean equals(Object x, Object y) throws HibernateException {
if (x == null) {
return y == null;
} else {
if (y == null) {
return false;
} else {
return x.toString().equalsIgnoreCase(y.toString());
}
}
}
@Override
public int hashCode(Object x) throws HibernateException {
return x.hashCode();
}
public static ApprovalState createState(String state) {
switch (state) {
case "APPROVED":
return new ApprovedState();
case "CANCELED":
return new CanceledState();
case "PROCESSING":
return new ProcessingState();
case "DENIED":
return new DeniedState();
case "REQUESTED":
return new RequestedState();
case "CREATED":
return new CreatedState();
case "END":
return new EndState();
case "FAILED":
return new DeployFailedState();
default:
return null;
}
}
@Override
public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws HibernateException, SQLException {
String value = rs.getString(names[0]);
if (value == null) {
return null;
}
return createState(value);
}
@Override
public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException {
if (value == null) {
st.setNull(index, Types.VARCHAR);
} else {
st.setString(index, value.toString());
}
}
@Override
public Object deepCopy(Object value) throws HibernateException {
if (value == null) return null;
return createState(value.toString());
}
@Override
public boolean isMutable() {
return false;
}
@Override
public Serializable disassemble(Object value) throws HibernateException {
return value.toString();
}
@Override
public Object assemble(Serializable cached, Object owner) throws HibernateException {
if (cached == null) {
return null;
}
return createState(cached.toString());
}
@Override
public Object replace(Object original, Object target, Object owner) throws HibernateException {
if (original instanceof ApprovalState) {
return deepCopy(original);
} else {
return original;
}
}
}
@@ -0,0 +1,17 @@
package com.eactive.apim.portal.approval.entity;
public enum ApprovalType {
USER("사용자"),
APP("APP");
// ORG("기관");
private String description;
ApprovalType(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
@@ -0,0 +1,47 @@
package com.eactive.apim.portal.approval.entity;
import com.eactive.apim.portal.user.entity.UserInfo;
import com.eactive.eai.data.converter.LocalDateTimeToStringConverter17;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.time.LocalDateTime;
import static javax.persistence.EnumType.STRING;
@Entity
@Table(name = "PTL_APPROVER")
@Data
@EqualsAndHashCode(of = "id")
@ToString(exclude = "approval")
public class Approver implements Serializable, com.eactive.eai.data.Data {
@EmbeddedId
private ApproverId id;
@ManyToOne
@MapsId("approval")
@JoinColumn(name = "APPROVAL_ID", nullable = false)
private Approval approval;
@NotNull
@ManyToOne
@JoinColumn(name = "USER_ID", nullable = false)
private UserInfo user;
@Enumerated(value = STRING)
@Column(name = "approval_status")
private ApproverStatus approverStatus;
@Column(name = "approval_message")
private String approvalMessage;
@Column(length = 17, name = "approved_date")
@Convert(converter = LocalDateTimeToStringConverter17.class)
private LocalDateTime approvedDate;
}
@@ -0,0 +1,24 @@
package com.eactive.apim.portal.approval.entity;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@Embeddable
@Data
@EqualsAndHashCode
@AllArgsConstructor
@NoArgsConstructor
public class ApproverId implements Serializable {
@Column(name = "approval_id")
private String approval;
@Column(name = "approval_order")
private Integer ordinal;
}
@@ -0,0 +1,17 @@
package com.eactive.apim.portal.approval.entity;
public enum ApproverStatus {
PENDING("대기중"),
CURRENT("승인전"),
APPROVED("승인됨"),
DENIED("반려됨"),
CANCELED("취소됨");
private final String description;
ApproverStatus(String description) { this.description = description; }
public String getDescription() { return description; }
}
@@ -0,0 +1,13 @@
package com.eactive.apim.portal.approval.repository;
import com.eactive.apim.portal.approval.entity.Approval;
import com.eactive.apim.portal.approval.entity.ApprovalType;
import com.eactive.eai.data.jpa.BaseRepository;
import com.eactive.eai.rms.data.EMSDataSource;
import java.util.Optional;
@EMSDataSource
public interface ApprovalRepository extends BaseRepository<Approval, String> {
Optional<Approval> findApprovalByTargetIdAndApprovalType(String targetId, ApprovalType approvalType);
}
@@ -0,0 +1,8 @@
package com.eactive.apim.portal.approval.service;
public class ApprovalDeployException extends Exception {
public ApprovalDeployException(String message) {
super(message);
}
}
@@ -0,0 +1,8 @@
package com.eactive.apim.portal.approval.service;
public class ApprovalException extends RuntimeException {
public ApprovalException(String message) {
super(message);
}
}
@@ -0,0 +1,8 @@
package com.eactive.apim.portal.approval.statemachine;
import com.eactive.apim.portal.approval.entity.Approval;
public interface ApprovalAuthorizer {
boolean isAuthorized(Approval approval, String approverId);
}
@@ -0,0 +1,7 @@
package com.eactive.apim.portal.approval.statemachine;
public class ApprovalConstants {
public static final String APPROVER = "approverId";
public static final Object MESSAGE = "message";
}
@@ -0,0 +1,10 @@
package com.eactive.apim.portal.approval.statemachine;
public enum ApprovalEvent {
BEGIN,
APPROVE,
CANCEL,
DENY,
END,
REDEPLOY
}
@@ -0,0 +1,31 @@
package com.eactive.apim.portal.approval.statemachine;
import com.eactive.apim.portal.approval.entity.Approval;
import com.eactive.apim.portal.approval.statemachine.listener.ApprovalListener;
import com.eactive.apim.portal.approval.statemachine.listener.DefaultApprovalListener;
import java.io.Serializable;
import java.util.Map;
import java.util.Optional;
public interface ApprovalState extends Serializable {
void handle(Approval approval, ApprovalEvent event, Map<String, Object> options);
default ApprovalListener getApprovalListener(Map<String, Object> options) {
return (ApprovalListener) options.getOrDefault("listener", new DefaultApprovalListener());
}
default ApprovalAuthorizer getApprovalAuthorizer(Map<String, Object> options) {
return (ApprovalAuthorizer) options.getOrDefault("authorizer", new DefaultApprovalAuthorizer());
}
default boolean isAuthorizedApprover(Approval approval, String approverId, Map<String, Object> options) {
return Optional.ofNullable(getApprovalAuthorizer(options))
.map(authorizer -> authorizer.isAuthorized(approval, approverId))
.orElse(false);
}
String getDescription();
}
@@ -0,0 +1,30 @@
package com.eactive.apim.portal.approval.statemachine;
import com.eactive.apim.portal.approval.entity.Approval;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional(propagation = Propagation.REQUIRED)
public class ApprovalStateMachine {
private static final Logger logger = LoggerFactory.getLogger(ApprovalStateMachine.class);
public void transition(Approval approval, ApprovalEvent event, Map<String, Object> options) {
ApprovalState currentState = approval.getApprovalStatus();
logger.info("Transitioning approval {} from {} state with event {}", approval.getId(), currentState, event);
try {
currentState.handle(approval, event, options);
logger.info("Transitioned approval {} to {} state", approval.getId(), approval.getApprovalStatus());
} catch (UnsupportedOperationException e) {
logger.error("Invalid transition for approval {} from {} state with event {}", approval.getId(), currentState, event);
throw new InvalidApprovalTransitionException("Invalid state transition", e);
}
}
}
@@ -0,0 +1,51 @@
package com.eactive.apim.portal.approval.statemachine;
import com.eactive.apim.portal.approval.entity.Approval;
import com.eactive.apim.portal.approval.entity.ApprovalStatus;
import com.eactive.apim.portal.approval.service.ApprovalDeployException;
import java.time.LocalDateTime;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Transactional;
public class ApprovedState implements ApprovalState {
private static final Logger logger = LoggerFactory.getLogger(ApprovedState.class);
@Override
@Transactional(noRollbackFor = ApprovalDeployException.class)
public void handle(Approval approval, ApprovalEvent event, Map<String, Object> options) {
if (event == ApprovalEvent.END) {
handleEnd(approval, options);
} else {
logger.warn("Invalid event {} for ApprovedState in approval {}", event, approval.getId());
throw new UnsupportedOperationException("Can only end from Approved state");
}
}
private void handleEnd(Approval approval, Map<String, Object> options) {
approval.setApprovalDate(LocalDateTime.now());
approval.setApprovalStatus(new EndState());
try {
getApprovalListener(options).execute(approval, options);
logger.info("Approval {} ended successfully", approval.getId());
} catch (ApprovalDeployException e) {
logger.error("Approval {} ended with error, {}", approval.getId(), e);
approval.setApprovalStatus(new DeployFailedState());
}
}
@Override
public String getDescription() {
return ApprovalStatus.APPROVED.getDescription();
}
@Override
public String toString() {
return ApprovalStatus.APPROVED.toString();
}
}
@@ -0,0 +1,30 @@
package com.eactive.apim.portal.approval.statemachine;
import com.eactive.apim.portal.approval.entity.Approval;
import com.eactive.apim.portal.approval.entity.ApprovalStatus;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CanceledState implements ApprovalState {
private static final Logger logger = LoggerFactory.getLogger(CanceledState.class);
@Override
public void handle(Approval approval, ApprovalEvent event, Map<String, Object> options) {
logger.warn("Invalid event {} for CanceledState in approval {}", event, approval.getId());
throw new UnsupportedOperationException("No transitions allowed from Canceled state");
}
@Override
public String toString() {
return ApprovalStatus.CANCELED.toString();
}
@Override
public String getDescription() {
return ApprovalStatus.CANCELED.getDescription();
}
}
@@ -0,0 +1,48 @@
package com.eactive.apim.portal.approval.statemachine;
import com.eactive.apim.portal.approval.entity.Approval;
import com.eactive.apim.portal.approval.entity.ApprovalStatus;
import com.eactive.apim.portal.approval.entity.Approver;
import com.eactive.apim.portal.approval.entity.ApproverStatus;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CreatedState implements ApprovalState {
private static final Logger logger = LoggerFactory.getLogger(CreatedState.class);
@Override
public void handle(Approval approval, ApprovalEvent event, Map<String, Object> options) {
if (event == ApprovalEvent.BEGIN) {
List<Approver> approvers = approval.getApprovers();
if (approvers == null || approvers.isEmpty()) {
logger.error("No approvers found for approval {}", approval.getId());
throw new IllegalStateException("No approvers found.");
}
approvers.forEach(approver -> approver.setApproverStatus(ApproverStatus.PENDING));
approvers.get(0).setApproverStatus(ApproverStatus.CURRENT);
approval.setCreatedDate(LocalDateTime.now());
approval.setApprovalStatus(new RequestedState());
logger.info("Approval {} began successfully", approval.getId());
} else {
logger.warn("Invalid event {} for CreatedState in approval {}", event, approval.getId());
throw new UnsupportedOperationException("Can only begin from Created state");
}
}
@Override
public String getDescription() {
return ApprovalStatus.CREATED.getDescription();
}
@Override
public String toString() {
return ApprovalStatus.CREATED.toString();
}
}
@@ -0,0 +1,12 @@
package com.eactive.apim.portal.approval.statemachine;
import com.eactive.apim.portal.approval.entity.Approval;
public class DefaultApprovalAuthorizer implements ApprovalAuthorizer {
@Override
public boolean isAuthorized(Approval approval, String approverId) {
//anyone can approve
return true;
}
}
@@ -0,0 +1,41 @@
package com.eactive.apim.portal.approval.statemachine;
import com.eactive.apim.portal.approval.entity.Approval;
import com.eactive.apim.portal.approval.entity.ApprovalStatus;
import java.time.LocalDateTime;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DeniedState implements ApprovalState {
private static final Logger logger = LoggerFactory.getLogger(DeniedState.class);
@Override
public void handle(Approval approval, ApprovalEvent event, Map<String, Object> options) {
if (event == ApprovalEvent.END) {
handleEnd(approval, options);
} else {
logger.warn("Invalid event {} for DeniedState in approval {}", event, approval.getId());
throw new UnsupportedOperationException("Can only end from Denied state");
}
}
private void handleEnd(Approval approval, Map<String, Object> options) {
approval.setApprovalDate(LocalDateTime.now());
// approval.setApprovalStatus(new EndState());
getApprovalListener(options).rollback(approval);
logger.info("Approval {} ended (denied) and rolled back", approval.getId());
}
@Override
public String toString() {
return ApprovalStatus.DENIED.toString();
}
@Override
public String getDescription() {
return ApprovalStatus.DENIED.getDescription();
}
}
@@ -0,0 +1,40 @@
package com.eactive.apim.portal.approval.statemachine;
import com.eactive.apim.portal.approval.entity.Approval;
import com.eactive.apim.portal.approval.entity.ApprovalStatus;
import com.eactive.apim.portal.approval.service.ApprovalDeployException;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DeployFailedState implements ApprovalState {
private static final Logger logger = LoggerFactory.getLogger(DeployFailedState.class);
@Override
public void handle(Approval approval, ApprovalEvent event, Map<String, Object> options) {
if (event == ApprovalEvent.REDEPLOY) {
try {
getApprovalListener(options).execute(approval, options);
approval.setApprovalStatus(new EndState());
} catch (ApprovalDeployException e) {
logger.error(e.getMessage(), e);
approval.setApprovalStatus(new DeployFailedState());
}
} else {
logger.warn("Invalid event {} for DeployFailedState in approval {}", event, approval.getId());
throw new UnsupportedOperationException("Not supported yet.");
}
}
@Override
public String toString() {
return ApprovalStatus.FAILED.toString();
}
@Override
public String getDescription() {
return ApprovalStatus.FAILED.getDescription();
}
}
@@ -0,0 +1,29 @@
package com.eactive.apim.portal.approval.statemachine;
import com.eactive.apim.portal.approval.entity.Approval;
import com.eactive.apim.portal.approval.entity.ApprovalStatus;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class EndState implements ApprovalState{
private static final Logger logger = LoggerFactory.getLogger(EndState.class);
@Override
public void handle(Approval approval, ApprovalEvent event, Map<String, Object> options) {
logger.warn("Invalid event {} for EndState in approval {}", event, approval.getId());
throw new UnsupportedOperationException("No transitions allowed from End state");
}
@Override
public String toString() {
return ApprovalStatus.END.toString();
}
@Override
public String getDescription() {
return ApprovalStatus.END.getDescription();
}
}
@@ -0,0 +1,8 @@
package com.eactive.apim.portal.approval.statemachine;
public class InvalidApprovalTransitionException extends RuntimeException {
public InvalidApprovalTransitionException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -0,0 +1,103 @@
package com.eactive.apim.portal.approval.statemachine;
import com.eactive.apim.portal.approval.entity.Approval;
import com.eactive.apim.portal.approval.entity.ApprovalStatus;
import com.eactive.apim.portal.approval.entity.Approver;
import com.eactive.apim.portal.approval.entity.ApproverStatus;
import java.time.LocalDateTime;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ProcessingState implements ApprovalState {
private static final Logger logger = LoggerFactory.getLogger(ProcessingState.class);
@Override
public void handle(Approval approval, ApprovalEvent event, Map<String, Object> options) {
switch (event) {
case APPROVE:
handleApprove(approval, options);
break;
case DENY:
handleDeny(approval, options);
break;
default:
logger.warn("Invalid event {} for ProcessingState in approval {}", event, approval.getId());
throw new UnsupportedOperationException("Invalid event for Processing state");
}
}
private void updateApprovalStatus(Approval approval) {
boolean allApproved = approval.getApprovers().stream()
.allMatch(approver -> ApproverStatus.APPROVED.equals(approver.getApproverStatus()));
ApprovalState newStatus = allApproved
? new ApprovedState()
: new ProcessingState();
approval.setApprovalStatus(newStatus);
}
private void handleApprove(Approval approval, Map<String, Object> options) {
String approverId = (String) options.getOrDefault(ApprovalConstants.APPROVER, "");
if (isAuthorizedApprover(approval, approverId, options)) {
boolean statusChanged = false;
// Update current approver status and track if it changed
for (Approver approver : approval.getApprovers()) {
if (approver.getUser().getId().equals(approverId) && !ApproverStatus.APPROVED.equals(approver.getApproverStatus())) {
approver.setApproverStatus(ApproverStatus.APPROVED);
approver.setApprovedDate(LocalDateTime.now());
statusChanged = true;
break; // Found and updated the current approver
}
}
// If status changed, set next pending approver to CURRENT
if (statusChanged) {
boolean nextApproverFound = false;
for (Approver approver : approval.getApprovers()) {
if (ApproverStatus.PENDING.equals(approver.getApproverStatus()) && !nextApproverFound) {
approver.setApproverStatus(ApproverStatus.CURRENT);
nextApproverFound = true;
}
}
}
updateApprovalStatus(approval);
logger.info("Approval {} approved by approver {} in Processing state", approval.getId(), approverId);
} else {
logger.warn("Unauthorized approval attempt for approval {} by approver {} in Processing state", approval.getId(), approverId);
throw new UnauthorizedApprovalException("Unauthorized approver");
}
}
private void handleDeny(Approval approval, Map<String, Object> options) {
String approverId = (String) options.getOrDefault(ApprovalConstants.APPROVER, "");
if (isAuthorizedApprover(approval, approverId, options)) {
approval.getApprovers().forEach(approver -> {
if(approver.getUser().getId().equals(approverId)) {
approver.setApproverStatus(ApproverStatus.DENIED);
approver.setApprovedDate(LocalDateTime.now());
String message = (String) options.getOrDefault("message", "");
approver.setApprovalMessage(message);
}
});
approval.setApprovalStatus(new DeniedState());
logger.info("Approval {} denied by approver {} in Processing state", approval.getId(), approverId);
} else {
logger.warn("Unauthorized denial attempt for approval {} by approver {} in Processing state", approval.getId(), approverId);
throw new UnauthorizedApprovalException("Unauthorized approver");
}
}
@Override
public String toString() {
return ApprovalStatus.PROCESSING.toString();
}
@Override
public String getDescription() {
return ApprovalStatus.PROCESSING.getDescription();
}
}
@@ -0,0 +1,125 @@
package com.eactive.apim.portal.approval.statemachine;
import com.eactive.apim.portal.approval.entity.Approval;
import com.eactive.apim.portal.approval.entity.ApprovalStatus;
import com.eactive.apim.portal.approval.entity.Approver;
import com.eactive.apim.portal.approval.entity.ApproverStatus;
import java.time.LocalDateTime;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RequestedState implements ApprovalState {
private static final Logger logger = LoggerFactory.getLogger(RequestedState.class);
@Override
public void handle(Approval approval, ApprovalEvent event, Map<String, Object> options) {
switch (event) {
case APPROVE:
handleApprove(approval, options);
break;
case DENY:
handleDeny(approval, options);
break;
case CANCEL:
handleCancel(approval, options);
break;
default:
logger.warn("Invalid event {} for RequestedState in approval {}", event, approval.getId());
throw new UnsupportedOperationException("Invalid event for Requested state");
}
}
private void updateApprovalStatus(Approval approval) {
boolean allApproved = approval.getApprovers().stream()
.allMatch(approver -> ApproverStatus.APPROVED.equals(approver.getApproverStatus()));
ApprovalState newStatus = allApproved
? new ApprovedState()
: new ProcessingState();
approval.setApprovalStatus(newStatus);
}
private void handleApprove(Approval approval, Map<String, Object> options) {
String approverId = (String) options.getOrDefault(ApprovalConstants.APPROVER, "");
if (isAuthorizedApprover(approval, approverId, options)) {
boolean statusChanged = false;
// Update current approver status and track if it changed
for (Approver approver : approval.getApprovers()) {
if (approver.getUser().getId().equals(approverId) && !ApproverStatus.APPROVED.equals(approver.getApproverStatus())) {
approver.setApproverStatus(ApproverStatus.APPROVED);
approver.setApprovedDate(LocalDateTime.now());
statusChanged = true;
break; // Found and updated the current approver
}
}
// If status changed, set next pending approver to CURRENT
if (statusChanged) {
boolean nextApproverFound = false;
for (Approver approver : approval.getApprovers()) {
if (ApproverStatus.PENDING.equals(approver.getApproverStatus()) && !nextApproverFound) {
approver.setApproverStatus(ApproverStatus.CURRENT);
nextApproverFound = true;
}
}
}
updateApprovalStatus(approval);
logger.info("Approval {} approved by approver {}", approval.getId(), approverId);
} else {
logger.warn("Unauthorized approval attempt for approval {} by approver {}", approval.getId(), approverId);
throw new UnauthorizedApprovalException("Unauthorized approver");
}
}
private void handleDeny(Approval approval, Map<String, Object> options) {
String approverId = (String) options.getOrDefault(ApprovalConstants.APPROVER, "");
if (isAuthorizedApprover(approval, approverId, options)) {
approval.getApprovers().forEach(approver -> {
if (approver.getUser().getId().equals(approverId)) {
approver.setApproverStatus(ApproverStatus.DENIED);
approver.setApprovedDate(LocalDateTime.now());
String message = (String) options.getOrDefault("message", "");
approver.setApprovalMessage(message);
}
});
approval.setApprovalStatus(new DeniedState());
logger.info("Approval {} denied by approver {}", approval.getId(), approverId);
} else {
logger.warn("Unauthorized denial attempt for approval {} by approver {}", approval.getId(), approverId);
throw new UnauthorizedApprovalException("Unauthorized approver");
}
}
private void handleCancel(Approval approval, Map<String, Object> options) {
String approverId = (String) options.getOrDefault(ApprovalConstants.APPROVER, "");
if (isAuthorizedApprover(approval, approverId, options)) {
approval.getApprovers().forEach(approver -> {
if (approver.getUser().getId().equals(approverId)) {
approver.setApproverStatus(ApproverStatus.CANCELED);
approver.setApprovedDate(LocalDateTime.now());
}
});
approval.setApprovalDate(LocalDateTime.now());
getApprovalListener(options).rollback(approval);
approval.setApprovalStatus(new CanceledState());
logger.info("Approval {} cancelled by approver {}", approval.getId(), approverId);
} else {
logger.warn("Unauthorized denial attempt for approval {} by approver {}", approval.getId(), approverId);
throw new UnauthorizedApprovalException("Unauthorized approver");
}
}
@Override
public String getDescription() {
return ApprovalStatus.REQUESTED.getDescription();
}
@Override
public String toString() {
return ApprovalStatus.REQUESTED.toString();
}
}
@@ -0,0 +1,8 @@
package com.eactive.apim.portal.approval.statemachine;
public class UnauthorizedApprovalException extends RuntimeException {
public UnauthorizedApprovalException(String message) {
super(message);
}
}
@@ -0,0 +1,12 @@
package com.eactive.apim.portal.approval.statemachine.listener;
import com.eactive.apim.portal.approval.entity.Approval;
import com.eactive.apim.portal.approval.service.ApprovalDeployException;
import java.util.Map;
public interface ApprovalListener {
void execute(Approval approval, Map<String, Object> options) throws ApprovalDeployException;
void rollback(Approval approval);
}
@@ -0,0 +1,22 @@
package com.eactive.apim.portal.approval.statemachine.listener;
import com.eactive.apim.portal.approval.entity.Approval;
import com.eactive.apim.portal.approval.service.ApprovalDeployException;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DefaultApprovalListener implements ApprovalListener {
private static final Logger logger = LoggerFactory.getLogger(DefaultApprovalListener.class);
@Override
public void execute(Approval approval, Map<String, Object> options) throws ApprovalDeployException {
logger.info("Executing approval listener for approval {}", approval.getId());
}
@Override
public void rollback(Approval approval) {
logger.info("Rolling back approval {}", approval.getId());
}
}
@@ -0,0 +1,40 @@
package com.eactive.apim.portal.approvalline.entity;
import com.eactive.apim.portal.common.entity.Auditable;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.hibernate.annotations.Comment;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.util.List;
@Data
@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = true)
@Entity
@Table(name = "ptl_approval_line")
public class PortalApprovalLine extends Auditable {
private static final long serialVersionUID = 1L;
@Id
@Column(name = "id", length = 36, nullable = false)
@GenericGenerator(name = "uuid-gen", strategy = "uuid2")
@GeneratedValue(generator = "uuid-gen", strategy = GenerationType.IDENTITY)
private String id;
@Column(name = "approval_type", length = 20, nullable = false)
@Comment("승인타입")
private String approvalType;
@Column(name = "approval_name", length = 50, nullable = false)
@Comment("승인이름")
private String approvalName;
@OneToMany(mappedBy = "approvalLine", cascade = CascadeType.ALL, orphanRemoval = true)
@ToString.Exclude
@OrderBy("approvalOrder ASC")
private List<PortalApprovalLineUser> portalApprovalLineUsers;
}
@@ -0,0 +1,48 @@
package com.eactive.apim.portal.approvalline.entity;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.hibernate.annotations.Comment;
import com.eactive.apim.portal.user.entity.UserInfo;
import com.eactive.eai.data.entity.AbstractEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Data
@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = true)
@Entity
@Table(name = "ptl_approval_line_user")
public class PortalApprovalLineUser extends AbstractEntity<PortalApprovalLineUserId> implements Serializable {
private static final long serialVersionUID = 1L;
@EmbeddedId
private PortalApprovalLineUserId id;
@ManyToOne
@JoinColumn(name = "approval_line_id", insertable = false, updatable = false)
@Comment("승인라인ID")
@ToString.Exclude
private PortalApprovalLine approvalLine;
@ManyToOne
@JoinColumn(name = "user_id", insertable = false, updatable = false)
@Comment("사용자ID")
@ToString.Exclude
private UserInfo user;
@Column(name = "approval_order")
@Comment("승인순서")
private int approvalOrder;
}
@@ -0,0 +1,25 @@
package com.eactive.apim.portal.approvalline.entity;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Embeddable
public class PortalApprovalLineUserId implements Serializable {
private static final long serialVersionUID = 1L;
@Column(name = "approval_line_id", nullable = false)
private String approvalLineId;
@Column(name = "user_id", nullable = false)
private String userId;
}
@@ -0,0 +1,12 @@
package com.eactive.apim.portal.approvalline.repository;
import com.eactive.apim.portal.approvalline.entity.PortalApprovalLine;
import com.eactive.eai.data.jpa.BaseRepository;
import com.eactive.eai.rms.data.EMSDataSource;
import java.util.Optional;
@EMSDataSource
public interface PortalApprovalLineRepository extends BaseRepository<PortalApprovalLine, String> {
Optional<PortalApprovalLine> findPortalApprovalLineByApprovalType(String approvalType);
}
@@ -0,0 +1,11 @@
package com.eactive.apim.portal.approvalline.repository;
import com.eactive.apim.portal.approvalline.entity.PortalApprovalLineUser;
import com.eactive.apim.portal.approvalline.entity.PortalApprovalLineUserId;
import com.eactive.eai.data.jpa.BaseRepository;
import com.eactive.eai.rms.data.EMSDataSource;
@EMSDataSource
public interface PortalApprovalLineUserRepository extends BaseRepository<PortalApprovalLineUser, PortalApprovalLineUserId> {
}
@@ -0,0 +1,14 @@
package com.eactive.apim.portal.approvalline.service;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.eactive.apim.portal.approvalline.entity.PortalApprovalLine;
import com.eactive.apim.portal.approvalline.repository.PortalApprovalLineRepository;
import com.eactive.eai.data.jpa.AbstractDataService;
@Service
@Transactional(transactionManager = "transactionManagerForEMS")
public class PortalApprovalLineService extends AbstractDataService<PortalApprovalLine, String, PortalApprovalLineRepository> {
}
@@ -0,0 +1,15 @@
package com.eactive.apim.portal.approvalline.service;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.eactive.apim.portal.approvalline.entity.PortalApprovalLineUser;
import com.eactive.apim.portal.approvalline.entity.PortalApprovalLineUserId;
import com.eactive.apim.portal.approvalline.repository.PortalApprovalLineUserRepository;
import com.eactive.eai.data.jpa.AbstractDataService;
@Service
@Transactional(transactionManager = "transactionManagerForEMS")
public class PortalApprovalLineUserService extends AbstractDataService<PortalApprovalLineUser, PortalApprovalLineUserId, PortalApprovalLineUserRepository> {
}
@@ -0,0 +1,18 @@
package com.eactive.apim.portal.common.converter;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
@Converter
public class BooleanYNConverter implements AttributeConverter<Boolean, String> {
@Override
public String convertToDatabaseColumn(Boolean attribute) {
return attribute != null && attribute ? "Y" : "N";
}
@Override
public Boolean convertToEntityAttribute(String dbData) {
return "Y".equalsIgnoreCase(dbData);
}
}
@@ -0,0 +1,13 @@
package com.eactive.apim.portal.common.converter;
import com.eactive.apim.portal.common.entity.EnabledStatus;
import org.springframework.core.convert.converter.Converter;
public class EnabledStatusConverter implements Converter<Boolean, EnabledStatus> {
@Override
public EnabledStatus convert(Boolean source) {
if (source == null) return EnabledStatus.N;
return source ? EnabledStatus.Y : EnabledStatus.N;
}
}
@@ -0,0 +1,58 @@
package com.eactive.apim.portal.common.entity;
import com.eactive.eai.data.converter.LocalDateTimeToStringConverter14;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.Column;
import javax.persistence.Convert;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@Data
@EqualsAndHashCode(callSuper = false)
public abstract class Auditable {
/**
* 최초등록시점
*/
@Column(name = "CREATED_DATE")
@Convert(converter = LocalDateTimeToStringConverter14.class)
@CreatedDate
private LocalDateTime createdDate;
/**
* 최초등록자ID
*/
@Column(name = "created_by")
@CreatedBy
private String createdBy;
/**
* 최종수정시점
*/
@Column(name = "LAST_MODIFIED_DATE")
@Convert(converter = LocalDateTimeToStringConverter14.class)
@LastModifiedDate
private LocalDateTime lastModifiedDate;
/**
* 최종수정자ID
*/
@Column(name = "LAST_MODIFIED_BY")
@LastModifiedBy
private String lastModifiedBy;
}
@@ -0,0 +1,12 @@
package com.eactive.apim.portal.common.entity;
public enum EnabledStatus {
Y("사용"),
N("미사용");
private String description;
EnabledStatus(String description) {
this.description = description;
}
}
@@ -0,0 +1,52 @@
package com.eactive.apim.portal.common.event;
import com.eactive.apim.portal.template.service.MessageEventHandler;
import com.eactive.apim.portal.template.service.MessageRecipient;
import com.eactive.apim.portal.template.service.MessageSendEvent;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Component
public class UnknownErrorEventHandler implements MessageEventHandler {
public static final String KEY = "unknown_error";
@Override
public String getKey() {
return KEY;
}
@Override
public boolean allowAdditionalRecipients() {
return true;
}
@Override
public String getDisplayName() {
return "알수 없는 에러 발생";
}
@Override
public String getDescription() {
return "<div>사용 가능 변수: </div><br/>"
+ "<div> - userName 수신자 이름 </div><br/>"
+ "<div> - userId 수신자 ID (이메일) </div><br/>"
+ "<div> - errorMessage 에러 메시지 </div><br/>"
+ "<div> - stackTrace Stack Trace </div><br/>"
+ "<div> - requestURL URL </div><br/>"
+ "<div> - requestParams 파라미터 </div><br/>";
}
@Override
public MessageSendEvent createEvent(Object source, MessageRecipient recipient, Map<String, Object> params) {
Map<String, String> requestParams = new HashMap<>();
requestParams.put("errorMessage", (String) params.get("errorMessage"));
requestParams.put("stackTrace", (String) params.get("stackTrace"));
requestParams.put("requestURL", (String) params.get("requestURL"));
requestParams.put("requestParams", params.get("requestParams").toString());
return new MessageSendEvent(source, KEY, recipient, requestParams);
}
}
@@ -0,0 +1,7 @@
package com.eactive.apim.portal.common.exception;
public class IntegrationException extends RuntimeException {
public IntegrationException(String message) {
super(message);
}
}
@@ -0,0 +1,7 @@
package com.eactive.apim.portal.common.exception;
public class NotFoundException extends IllegalArgumentException {
public NotFoundException(String message) {
super(message);
}
}
@@ -0,0 +1,7 @@
package com.eactive.apim.portal.common.exception;
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}
@@ -0,0 +1,8 @@
package com.eactive.apim.portal.common.exception;
public class UserPasswordResetException extends RuntimeException {
public UserPasswordResetException(String message) {
super(message);
}
}
@@ -0,0 +1,59 @@
package com.eactive.apim.portal.file.entity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.persistence.*;
/**
* PT_FILE_DETAIL 파일상세정보
*
* @author Sungpil Hyun
* @version 1.0
*/
@Data
@EqualsAndHashCode
@Entity
@Table(name = "ptl_file_detail")
@IdClass(FileDetailId.class)
public class FileDetail {
/**
* 첨부 파일 ID
*/
@Id
@Column(name = "FILE_ID", nullable = false)
private String fileId;
/**
* 파일 순번
*/
@Id
@Column(name = "FILE_SN", nullable = false)
private Integer fileSn;
/**
* 파일 내용
*/
@Column(name = "FILE_CN", nullable = true)
private String fileContents;
/**
* 파일 확장자
*/
@Column(name = "FILE_EXTENSION", nullable = false)
private String fileExtension;
/**
* 파일 크기
*/
@Column(name = "FILE_SIZE", nullable = true)
private Integer fileSize;
/**
* 원파일 이름
*/
@Column(name = "ORIGINAL_FILE_NAME", nullable = true)
private String originalFileName;
}
@@ -0,0 +1,46 @@
package com.eactive.apim.portal.file.entity;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import java.io.Serializable;
import java.util.Objects;
@Embeddable
public class FileDetailId implements Serializable {
@Column(name = "FILE_ID", nullable = false)
private String fileId;
@Column(name = "FILE_SN", nullable = false)
private Integer fileSn;
public String getFileId() {
return fileId;
}
public void setFileId(String fileId) {
this.fileId = fileId;
}
public Integer getFileSn() {
return fileSn;
}
public void setFileSn(Integer fileSn) {
this.fileSn = fileSn;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FileDetailId that = (FileDetailId) o;
return Objects.equals(fileId, that.fileId) && Objects.equals(fileSn, that.fileSn);
}
@Override
public int hashCode() {
return Objects.hash(fileId, fileSn);
}
}
@@ -0,0 +1,49 @@
package com.eactive.apim.portal.file.entity;
import com.eactive.apim.portal.common.entity.Auditable;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.lang3.StringUtils;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
/**
* PT_FILE 파일속성
*
* @author Sungpil Hyun
* @version 1.0
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Entity
@Table(name = "ptl_file")
public class FileInfo extends Auditable {
/**
* 첨부파일ID
*/
@Id
@Column(name = "FILE_ID", nullable = false)
private String fileId;
/**
* 사용여부
*/
@Column(name = "USE_YN", nullable = true)
private String useYn;
@OneToMany(mappedBy = "fileId", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
private List<FileDetail> fileDetails = new ArrayList<>();
@Transient
public String getFileDetailIds() {
StringBuilder sb = new StringBuilder();
for (FileDetail fileDetail : fileDetails) {
sb.append(fileDetail.getFileSn()).append(",");
}
return StringUtils.removeEnd(sb.toString(), ",");
}
}
@@ -0,0 +1,7 @@
package com.eactive.apim.portal.file.exception;
public class InvalidFileException extends IllegalArgumentException {
public InvalidFileException(String message) {
super(message);
}
}
@@ -0,0 +1,20 @@
package com.eactive.apim.portal.file.repository;
import com.eactive.apim.portal.file.entity.FileDetail;
import com.eactive.apim.portal.file.entity.FileDetailId;
import com.eactive.eai.rms.data.EMSDataSource;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
@EMSDataSource
public interface FileDetailRepository extends JpaRepository<FileDetail, FileDetailId> {
List<FileDetail> findAllByFileId(String fileId);
Optional<FileDetail> findFirstByFileIdAndFileSn(String fileId, Integer fileSn);
}
@@ -0,0 +1,11 @@
package com.eactive.apim.portal.file.repository;
import com.eactive.apim.portal.file.entity.FileInfo;
import com.eactive.eai.rms.data.EMSDataSource;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
@EMSDataSource
public interface FileRepository extends JpaRepository<FileInfo, String> {
}
@@ -0,0 +1,23 @@
package com.eactive.apim.portal.file.service;
import java.io.IOException;
/**
* Interface for binary content storage
*/
public interface BinaryStorage {
/**
* Store binary data and return content identifier
*/
String store(byte[] content) throws IOException;
/**
* Retrieve binary data using content identifier
*/
byte[] retrieve(String contentId) throws IOException;
/**
* Delete binary data using content identifier
*/
void delete(String contentId) throws IOException;
}
@@ -0,0 +1,52 @@
package com.eactive.apim.portal.file.service;
import static com.eactive.apim.portal.file.service.StorageType.DATABASE;
import static com.eactive.apim.portal.file.service.StorageType.FILESYSTEM;
import com.eactive.apim.portal.portalproperty.service.PortalPropertyService;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import javax.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class BinaryStorageFactory {
private static final String PORTAL_PROPERTY = "Portal";
private final DatabaseBinaryStorage databaseStorage;
private final FileSystemBinaryStorage filesystemStorage;
private final PortalPropertyService portalPropertyService;
@PostConstruct
public void validateConfiguration() {
Map<String, String> properties = portalPropertyService.getPortalPropertiesAsMap(PORTAL_PROPERTY);
String storageType = properties.getOrDefault("portal.file_storage.type", "DATABASE");
if (StorageType.valueOf(storageType) == FILESYSTEM) {
String path = properties.getOrDefault("portal.file_storage.path", "./files");
Path basePath = Paths.get(path);
if (!Files.exists(basePath)) {
try {
Files.createDirectories(basePath);
} catch (IOException e) {
throw new IllegalStateException("Could not create storage directory: " + basePath, e);
}
}
}
}
public BinaryStorage getStorage() {
Map<String, String> properties = portalPropertyService.getPortalPropertiesAsMap(PORTAL_PROPERTY);
String storageType = properties.getOrDefault("portal.file_storage.type", "DATABASE");
if (StorageType.valueOf(storageType) == FILESYSTEM) {
return filesystemStorage;
} else {
return databaseStorage;
}
}
}
@@ -0,0 +1,23 @@
package com.eactive.apim.portal.file.service;
import java.io.IOException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class BinaryStorageService {
private final BinaryStorageFactory storageFactory;
public String store(byte[] content) throws IOException {
return storageFactory.getStorage().store(content);
}
public byte[] retrieve(String contentId) throws IOException {
return storageFactory.getStorage().retrieve(contentId);
}
public void delete(String contentId) throws IOException {
storageFactory.getStorage().delete(contentId);
}
}
@@ -0,0 +1,30 @@
package com.eactive.apim.portal.file.service;
import java.io.IOException;
import java.util.Base64;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class DatabaseBinaryStorage implements BinaryStorage {
@Override
public String store(byte[] content) {
return Base64.getEncoder().encodeToString(content);
}
@Override
public byte[] retrieve(String contentId) throws IOException {
try {
return Base64.getDecoder().decode(contentId);
} catch (IllegalArgumentException e) {
throw new IOException("Failed to decode content", e);
}
}
@Override
public void delete(String contentId) {
// No action needed - handled by database
}
}
@@ -0,0 +1,7 @@
package com.eactive.apim.portal.file.service;
public class FileNotFoundException extends RuntimeException {
public FileNotFoundException(String message) {
super(message);
}
}
@@ -0,0 +1,225 @@
package com.eactive.apim.portal.file.service;
import com.eactive.apim.portal.file.entity.FileDetail;
import com.eactive.apim.portal.file.entity.FileInfo;
import com.eactive.apim.portal.file.repository.FileDetailRepository;
import com.eactive.apim.portal.file.repository.FileRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import com.eactive.apim.portal.file.exception.InvalidFileException;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.*;
@Service("fileService")
@Transactional
@RequiredArgsConstructor
@Slf4j
public class FileService {
private final FileRepository fileRepository;
private final FileDetailRepository fileDetailRepository;
private final BinaryStorageService binaryStorageService;
private final FileTypeDetector fileTypeDetector;
/**
* 특정 파일 ID를 기반으로 파일 정보를 검색합니다.
*
* @param fileId 파일 ID
* @return 파일 정보
*/
public FileInfo findById(String fileId) {
if (StringUtils.isEmpty(fileId)) {
return null;
}
return fileRepository.findById(fileId).orElseThrow(() -> new FileNotFoundException("파일 정보가 없습니다."));
}
/**
* 특정 파일 ID와 일련 번호를 기반으로 파일 세부 정보를 얻습니다.
*
* @param fileId 파일 ID
* @param fileSn 파일 일련 번호
* @return 파일 세부 정보
*/
public FileDetail getFileDetailByIds(String fileId, int fileSn) {
return fileDetailRepository.findFirstByFileIdAndFileSn(fileId, fileSn).orElseThrow(() -> new FileNotFoundException("파일 정보가 없습니다."));
}
public byte[] retrieveFileContent(FileDetail fileDetail) throws IOException {
return binaryStorageService.retrieve(fileDetail.getFileContents());
}
private void processAttachments(List<MultipartFile> attachFiles, FileInfo fileInfo, int lastFileDetailSn) throws IOException {
for (MultipartFile file : attachFiles) {
if (file.getSize() > 0) {
lastFileDetailSn = addFileDetail(fileInfo, lastFileDetailSn, file);
}
}
}
private int addFileDetail(FileInfo fileInfo, int lastFileDetailSn, MultipartFile file) throws IOException {
String type = fileTypeDetector.detectFileType(file.getBytes());
log.debug("upload file type:{}", type);
if(type.equalsIgnoreCase("unknown")) {
throw new InvalidFileException("허용된 파일 형식이 아닙니다. <br>문서파일과 이미지 파일만 등록 가능합니다. <br>(pdf, doc, docx, xls, xlsx, ppt, pptx, hwp, gif, jpg, png)");
}
if (StringUtils.isNotBlank(file.getOriginalFilename()) && file.getSize() > 0) {
int fileSn = lastFileDetailSn + 1;
FileDetail fileDetail = new FileDetail();
fileDetail.setFileId(fileInfo.getFileId());
fileDetail.setFileSn(fileSn);
fileDetail.setFileSize((int) file.getSize());
fileDetail.setOriginalFileName(FilenameUtils.getBaseName(file.getOriginalFilename()));
fileDetail.setFileExtension(type);
String contentReference = binaryStorageService.store(file.getBytes());
fileDetail.setFileContents(contentReference);
fileInfo.getFileDetails().add(fileDetail);
return fileSn;
} else {
return lastFileDetailSn;
}
}
/**
* 다중 파일을 생성합니다.
*
* @param attachFiles 첨부파일 리스트
* @return 파일 정보
* @throws IOException 파일 처리 중 오류 발생 시
*/
public FileInfo createFile(List<MultipartFile> attachFiles) throws IOException {
FileInfo fileInfo = new FileInfo();
String id = UUID.randomUUID().toString();
fileInfo.setFileId(id);
fileInfo.setCreatedDate(LocalDateTime.now());
fileInfo.setUseYn("Y");
int lastFileDetailSn = 0;
processAttachments(attachFiles, fileInfo, lastFileDetailSn);
if (!fileInfo.getFileDetails().isEmpty()) {
return fileRepository.save(fileInfo);
} else {
return null;
}
}
/**
* 기존 파일을 업데이트합니다.
*
* @param fileId 파일 ID
* @param fileSn 파일 일련 번호 리스트
* @param attachFiles 첨부파일 리스트
* @return 파일 정보
* @throws IOException 파일 처리 중 오류 발생 시
*/
public FileInfo updateFile(String fileId, List<Integer> fileSn, List<MultipartFile> attachFiles) throws IOException {
FileInfo fileInfo = findById(fileId);
List<FileDetail> fileDetails = fileInfo.getFileDetails();
int lastFileDetailSn = fileDetails.isEmpty() ? 0 : fileDetails.get(fileDetails.size() - 1).getFileSn();
if (fileSn != null) {
fileDetails.removeIf(fileDetail -> !fileSn.contains(fileDetail.getFileSn()));
}
processAttachments(attachFiles, fileInfo, lastFileDetailSn);
if (!fileInfo.getFileDetails().isEmpty()) {
return fileRepository.save(fileInfo);
} else {
return null;
}
}
/**
* 특정 파일을 삭제합니다.
*
* @param fileId 파일 ID
*/
public void deleteFile(String fileId) {
fileRepository.findById(fileId).ifPresent(file -> {
file.getFileDetails().forEach(fileDetail -> {
try {
binaryStorageService.delete(fileDetail.getFileContents());
} catch (IOException e) {
throw new RuntimeException(e);
}
});
});
fileRepository.deleteById(fileId);
}
/**
* 단일 파일을 생성하거나 업데이트합니다.
*
* @param existingFileId 기존 파일 ID (존재할 경우)
* @param file 멀티파트 파일
* @param fileName 파일 이름
* @return 파일 정보
* @throws IOException 파일 처리 중 오류 발생 시
*/
public FileInfo createOrUpdateSingleFile(String existingFileId, MultipartFile file, String fileName) throws IOException {
// 신규 등록 또는 기존 파일 정보 조회
FileInfo fileInfo = Optional.ofNullable(existingFileId)
.filter(StringUtils::isNotBlank)
.map(this::findById)
.orElseGet(() -> {
FileInfo newFile = new FileInfo();
newFile.setFileId(UUID.randomUUID().toString());
newFile.setUseYn("Y");
return newFile;
});
// 파일 상세 정보 생성 또는 업데이트
FileDetail fileDetail = createFileDetail(fileInfo.getFileId(), 1, file, fileName);
// 기존 파일 상세 정보 제거 후 새로운 정보 추가
fileInfo.getFileDetails().clear();
fileInfo.getFileDetails().add(fileDetail);
return fileRepository.save(fileInfo);
}
/**
* 파일 세부 정보를 생성합니다.
*
* @param fileId 파일 ID
* @param fileSn 파일 일련 번호
* @param file 멀티파트 파일
* @param fileName 파일 이름
* @return 파일 세부 정보
* @throws IOException 파일 처리 중 오류 발생 시
*/
private FileDetail createFileDetail(String fileId, int fileSn, MultipartFile file, String fileName) throws IOException {
String type = fileTypeDetector.detectFileType(file.getBytes());
log.debug("upload file type:{}", type);
if(type.equalsIgnoreCase("unknown")) {
throw new InvalidFileException("허용된 파일 형식이 아닙니다. <br>문서파일과 이미지 파일만 등록 가능합니다. <br>(pdf, doc, docx, xls, xlsx, ppt, pptx, hwp, gif, jpg, png)");
}
FileDetail fileDetail = new FileDetail();
fileDetail.setFileId(fileId);
fileDetail.setFileSn(fileSn);
fileDetail.setFileSize((int) file.getSize());
fileDetail.setOriginalFileName(FilenameUtils.getBaseName(fileName));
fileDetail.setFileExtension(FilenameUtils.getExtension(fileName));
String contentReference = binaryStorageService.store(file.getBytes());
fileDetail.setFileContents(contentReference);
return fileDetail;
}
}
@@ -0,0 +1,84 @@
package com.eactive.apim.portal.file.service;
import static com.eactive.apim.portal.file.service.StorageType.FILESYSTEM;
import com.eactive.apim.portal.portalproperty.service.PortalPropertyService;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import javax.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class FileSystemBinaryStorage implements BinaryStorage {
private static final String PORTAL_PROPERTY = "Portal";
private final PortalPropertyService portalPropertyService;
@PostConstruct
public void init() throws IOException {
Map<String, String> properties = portalPropertyService.getPortalPropertiesAsMap(PORTAL_PROPERTY);
String storageType = properties.getOrDefault("portal.file_storage.type", "DATABASE");
if (StorageType.valueOf(storageType) == FILESYSTEM) {
Path basePath = getBasePath();
Files.createDirectories(basePath);
}
}
@Override
public String store(byte[] content) throws IOException {
String fileName = UUID.randomUUID().toString();
// Get fileType from ThreadLocal
String fileType = Optional.ofNullable(FileTypeContext.getFileType())
.filter(type -> !type.trim().isEmpty())
.orElse("apim");
// Create full path: basePath/fileType/fileName
Path filePath = getBasePath()
.resolve(fileType)
.resolve(fileName);
// Create directories if they don't exist
Files.createDirectories(filePath.getParent());
// Write the file
Files.write(filePath, content, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
return fileType + "/" + fileName;
}
@Override
public byte[] retrieve(String contentId) throws IOException {
Path filePath = getBasePath().resolve(contentId);
if (!Files.exists(filePath)) {
throw new FileNotFoundException("File not found: " + contentId);
}
return Files.readAllBytes(filePath);
}
@Override
public void delete(String contentId) throws IOException {
if (contentId != null) {
Path filePath = getBasePath().resolve(contentId);
Files.deleteIfExists(filePath);
}
}
private Path getBasePath() {
Map<String, String> properties = portalPropertyService.getPortalPropertiesAsMap(PORTAL_PROPERTY);
String path = properties.getOrDefault("portal.file_storage.path", "./files");
return Paths.get(path).toAbsolutePath();
}
}
@@ -0,0 +1,17 @@
package com.eactive.apim.portal.file.service;
public class FileTypeContext {
private static final ThreadLocal<String> fileTypeHolder = new ThreadLocal<>();
public static void setFileType(String fileType) {
fileTypeHolder.set(fileType);
}
public static String getFileType() {
return fileTypeHolder.get();
}
public static void clear() {
fileTypeHolder.remove();
}
}
@@ -0,0 +1,369 @@
package com.eactive.apim.portal.file.service;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.springframework.stereotype.Component;
/**
* 파일의 시그니처와 내용을 분석하여 파일 형식을 감지하는 클래스
* 지원하는 파일 형식:
* - 이미지: PNG, JPG, GIF(87a, 89a)
* - 문서: PDF, HWP, HWPX
* - Office: DOC, DOCX, XLS, XLSX, PPT, PPTX
*/
@Component
public class FileTypeDetector {
private static final int BUFFER_SIZE = 8192;
private static final int SMALL_BUFFER_SIZE = 4096;
private static final int OLE_HEADER_SIZE = 512;
private static final int MIN_FILE_SIZE = 8;
private static final byte[][] HWP_PATTERNS = {
"HwpSummaryInformation".getBytes(StandardCharsets.US_ASCII),
"FileHeader".getBytes(StandardCharsets.US_ASCII),
"DocInfo".getBytes(StandardCharsets.US_ASCII),
"BodyText".getBytes(StandardCharsets.US_ASCII),
{0x48, 0x57, 0x50, 0x20, 0x44, 0x6F, 0x63, 0x75, 0x6D, 0x65, 0x6E, 0x74}, // "HWP Document"
{0x48, 0x57, 0x50, 0x20, 0x46, 0x69, 0x6C, 0x65}, // "HWP File"
};
private static final byte[][] WORD_PATTERNS = {
"Word.Document".getBytes(StandardCharsets.US_ASCII),
"MSWordDoc".getBytes(StandardCharsets.US_ASCII),
{(byte) 0xEC, (byte) 0xA5, 0x40, (byte) 0xC0} // Word specific binary pattern
};
private static final byte[][] EXCEL_PATTERNS = {
"Excel.Sheet".getBytes(StandardCharsets.US_ASCII),
"Workbook".getBytes(StandardCharsets.US_ASCII),
{0x09, 0x08, 0x10, 0x00, 0x00, 0x06, 0x05, 0x00} // Excel specific binary pattern
};
private static final byte[][] POWERPOINT_PATTERNS = {
"PowerPoint".getBytes(StandardCharsets.US_ASCII),
"PP97_DUALSTORAGE".getBytes(StandardCharsets.US_ASCII),
{0x00, 0x6E, 0x1E, (byte) 0xF0} // PowerPoint specific binary pattern
};
// 파일 시그니처 정의
private static final Map<String, byte[]> FILE_SIGNATURES = new HashMap<>();
static {
FILE_SIGNATURES.put("ooxml", new byte[]{0x50, 0x4B, 0x03, 0x04});
FILE_SIGNATURES.put("ole2", new byte[]{(byte) 0xD0, (byte) 0xCF, 0x11, (byte) 0xE0, (byte) 0xA1, (byte) 0xB1, 0x1A, (byte) 0xE1});
FILE_SIGNATURES.put("pdf", new byte[]{0x25, 0x50, 0x44, 0x46});
FILE_SIGNATURES.put("png", new byte[]{(byte) 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A});
FILE_SIGNATURES.put("jpg", new byte[]{(byte) 0xFF, (byte) 0xD8, (byte) 0xFF});
FILE_SIGNATURES.put("gif_87a", new byte[]{0x47, 0x49, 0x46, 0x38, 0x37, 0x61});
FILE_SIGNATURES.put("gif_89a", new byte[]{0x47, 0x49, 0x46, 0x38, 0x39, 0x61});
FILE_SIGNATURES.put("hwp", new byte[]{(byte) 0xD0, (byte) 0xCF, 0x11, (byte) 0xE0, (byte) 0xA1, (byte) 0xB1, 0x1A, (byte) 0xE1});
FILE_SIGNATURES.put("hwpx", new byte[]{0x50, 0x4B, 0x03, 0x04});
}
/**
* 바이트 배열로부터 파일 형식을 감지합니다.
* @param bytes 검사할 파일의 바이트 배열
* @return 파일 형식을 나타내는 문자열 (unknown인 경우 파일 형식을 판별할 수 없음)
* @throws IllegalArgumentException bytes가 null이거나 너무 작은 경우
*/
public String detectFileType(byte[] bytes) {
if (bytes == null || bytes.length < MIN_FILE_SIZE) {
return "unknown";
}
try {
// GIF 버전 확인
if (matchSignature(bytes, FILE_SIGNATURES.get("gif_87a"))) {
if (isValidGif(bytes)) {
return "gif";
}
}
if (matchSignature(bytes, FILE_SIGNATURES.get("gif_89a"))) {
if (isValidGif(bytes)) {
return "gif";
}
}
// PNG 확인
if (matchSignature(bytes, FILE_SIGNATURES.get("png"))) {
if (isValidPng(bytes)) {
return "png";
}
}
// JPG 확인
if (matchSignature(bytes, FILE_SIGNATURES.get("jpg"))) {
if (isValidJpeg(bytes)) {
return "jpg";
}
}
// PDF 확인
if (matchSignature(bytes, FILE_SIGNATURES.get("pdf"))) {
return "pdf";
}
// HWPX 또는 OOXML 확인 (ZIP 기반)
if (matchSignature(bytes, FILE_SIGNATURES.get("hwpx"))) {
try {
String zipType = detectZipBasedType(bytes);
if (zipType != null) {
return zipType;
}
} catch (Exception e) {
return "unknown";
}
}
// HWP 또는 OLE2 확인
if (matchSignature(bytes, FILE_SIGNATURES.get("hwp"))) {
if (isHwpFile(bytes)) {
return "hwp";
}
return detectOLE2TypeByContent(bytes);
}
} catch (IOException e) {
return "unknown";
}
return "unknown";
}
private static boolean isValidGif(byte[] bytes) {
if (bytes.length < 13) {
return false;
}
// 너비와 높이 추출 (리틀 엔디안)
int width = (bytes[7] & 0xFF) << 8 | (bytes[6] & 0xFF);
int height = (bytes[9] & 0xFF) << 8 | (bytes[8] & 0xFF);
return width > 0 && height > 0;
}
private static boolean isValidPng(byte[] bytes) throws IOException {
if (bytes.length < 29) { // 8 (signature) + 4 (length) + 4 (IHDR) + 13 (IHDR data)
return false;
}
// IHDR 길이는 항상 13
int length = ((bytes[8] & 0xFF) << 24) |
((bytes[9] & 0xFF) << 16) |
((bytes[10] & 0xFF) << 8) |
(bytes[11] & 0xFF);
if (length != 13) {
return false;
}
// IHDR 청크 타입 확인
String ihdrType = new String(bytes, 12, 4, StandardCharsets.US_ASCII);
if (!ihdrType.equals("IHDR")) {
return false;
}
// 너비와 높이 추출
int width = ((bytes[16] & 0xFF) << 24) |
((bytes[17] & 0xFF) << 16) |
((bytes[18] & 0xFF) << 8) |
(bytes[19] & 0xFF);
int height = ((bytes[20] & 0xFF) << 24) |
((bytes[21] & 0xFF) << 16) |
((bytes[22] & 0xFF) << 8) |
(bytes[23] & 0xFF);
if (width <= 0 || height <= 0) {
return false;
}
// 비트 깊이 확인
int bitDepth = bytes[24] & 0xFF;
if (bitDepth != 1 && bitDepth != 2 && bitDepth != 4 &&
bitDepth != 8 && bitDepth != 16) {
return false;
}
// 색상 타입 확인
int colorType = bytes[25] & 0xFF;
if (colorType != 0 && colorType != 2 && colorType != 3 &&
colorType != 4 && colorType != 6) {
return false;
}
// 압축 방식 확인
if (bytes[26] != 0) {
return false;
}
// 필터 방식 확인
if (bytes[27] != 0) {
return false;
}
// 인터레이스 방식 확인
return bytes[28] == 0 || bytes[28] == 1;
}
private static boolean isValidJpeg(byte[] bytes) {
if (bytes.length < 2) {
return false;
}
// JPEG EOI 마커 확인 (0xFF, 0xD9)
return (bytes[bytes.length - 2] == (byte) 0xFF &&
bytes[bytes.length - 1] == (byte) 0xD9);
}
private static boolean isHwpFile(byte[] bytes) {
if (bytes.length < OLE_HEADER_SIZE) {
return false;
}
// OLE 헤더 건너뛰기
int offset = OLE_HEADER_SIZE;
int remainingLength = bytes.length - offset;
byte[] searchBuffer = new byte[Math.min(remainingLength, BUFFER_SIZE)];
System.arraycopy(bytes, offset, searchBuffer, 0, searchBuffer.length);
for (byte[] pattern : HWP_PATTERNS) {
if (findPattern(searchBuffer, searchBuffer.length, pattern)) {
return true;
}
}
return false;
}
private static String detectOLE2TypeByContent(byte[] bytes) {
if (bytes.length < OLE_HEADER_SIZE) {
return "ole2";
}
// OLE 헤더 건너뛰기
int offset = OLE_HEADER_SIZE;
int remainingLength = bytes.length - offset;
byte[] searchBuffer = new byte[Math.min(remainingLength, BUFFER_SIZE)];
System.arraycopy(bytes, offset, searchBuffer, 0, searchBuffer.length);
// Word 문서 검사
for (byte[] pattern : WORD_PATTERNS) {
if (findPattern(searchBuffer, searchBuffer.length, pattern)) {
return "doc";
}
}
// Excel 문서 검사
for (byte[] pattern : EXCEL_PATTERNS) {
if (findPattern(searchBuffer, searchBuffer.length, pattern)) {
return "xls";
}
}
// PowerPoint 문서 검사
for (byte[] pattern : POWERPOINT_PATTERNS) {
if (findPattern(searchBuffer, searchBuffer.length, pattern)) {
return "ppt";
}
}
return "ole2";
}
private static String detectZipBasedType(byte[] bytes) throws IOException {
try (ZipInputStream zis = new ZipInputStream(new java.io.ByteArrayInputStream(bytes))) {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
try {
String name = entry.getName().toLowerCase();
// HWPX 확인
if (name.equals("version.xml") || name.startsWith("contents/")) {
byte[] content = new byte[SMALL_BUFFER_SIZE];
int len = zis.read(content);
if (len > 0) {
String contentStr = new String(content, 0, len, StandardCharsets.UTF_8);
if (contentStr.contains("hwpml")) {
return "hwpx";
}
}
}
// OOXML 확인
if (name.equals("[content_types].xml")) {
byte[] content = new byte[SMALL_BUFFER_SIZE];
int len = zis.read(content);
String contentStr = new String(content, 0, len, StandardCharsets.UTF_8);
if (contentStr.contains("word/document.xml")) return "docx";
if (contentStr.contains("xl/workbook.xml")) return "xlsx";
if (contentStr.contains("ppt/presentation.xml")) return "pptx";
}
} finally {
zis.closeEntry();
}
}
}
return null;
}
private static boolean matchSignature(byte[] buffer, byte[] signature) {
if (buffer.length < signature.length) {
return false;
}
for (int i = 0; i < signature.length; i++) {
if (buffer[i] != signature[i]) {
return false;
}
}
return true;
}
/**
* KMP 알고리즘을 사용한 패턴 검색
*/
private static boolean findPattern(byte[] buffer, int bufferLength, byte[] pattern) {
if (bufferLength < pattern.length) {
return false;
}
int[] failureFunction = computeFailureFunction(pattern);
int j = 0; // pattern의 인덱스
for (int i = 0; i < bufferLength; i++) {
while (j > 0 && pattern[j] != buffer[i]) {
j = failureFunction[j - 1];
}
if (pattern[j] == buffer[i]) {
j++;
}
if (j == pattern.length) {
return true;
}
}
return false;
}
/**
* KMP 알고리즘의 실패 함수 계산
*/
private static int[] computeFailureFunction(byte[] pattern) {
int[] failure = new int[pattern.length];
int j = 0;
for (int i = 1; i < pattern.length; i++) {
while (j > 0 && pattern[j] != pattern[i]) {
j = failure[j - 1];
}
if (pattern[j] == pattern[i]) {
j++;
}
failure[i] = j;
}
return failure;
}
}
@@ -0,0 +1,6 @@
package com.eactive.apim.portal.file.service;
public enum StorageType {
DATABASE,
FILESYSTEM
}
@@ -0,0 +1,59 @@
package com.eactive.apim.portal.invitation.entity;
import com.eactive.apim.portal.invitation.entity.UserInvitationEnums.InvitationStatus;
import com.eactive.eai.data.converter.LocalDateTimeToStringConverter14;
import lombok.Data;
import org.hibernate.annotations.Comment;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.time.LocalDateTime;
@Data
@Entity
@Table(name = "PTL_USER_INVITATION")
public class UserInvitation {
@Id
@GeneratedValue(generator = "uuid")
@GenericGenerator(name = "uuid", strategy = "uuid2")
@Column(name = "ID", length = 36)
private String id;
@Column(name = "ADMIN_ID", length = 10, nullable = false)
@Comment("기관관리자ID")
private String adminId;
@Column(name = "ORG_ID", length = 50, nullable = false)
@Comment("기관ID")
private String orgId;
@Column(name = "INVITATION_EMAIL", length = 30, nullable = false)
@Comment("초대이메일")
private String invitationEmail;
@Column(name = "INVITATION_DATE", length = 14, nullable = false)
@Convert(converter = LocalDateTimeToStringConverter14.class)
@Comment("초대일시")
private LocalDateTime invitationDate;
@Column(name = "COMPLETE_DATE", length = 14)
@Convert(converter = LocalDateTimeToStringConverter14.class)
@Comment("가입완료일시")
private LocalDateTime completeDate;
@Column(name = "STATUS", length = 20, nullable = false)
@Enumerated(EnumType.STRING)
@Comment("상태")
private InvitationStatus status;
@Column(name = "TOKEN", length = 36, nullable = false)
private String token;
@Column(name = "EXPIRES_ON", length = 14, nullable = false)
@Convert(converter = LocalDateTimeToStringConverter14.class)
@Comment("만료일시")
private LocalDateTime expiresOn;
}
@@ -0,0 +1,22 @@
package com.eactive.apim.portal.invitation.entity;
public class UserInvitationEnums {
public enum InvitationStatus {
PENDING("준비"),
COMPLETED("완료"),
EXPIRED("만료"),
CANCELED("취소"),
REJECTED("반려");
private final String description;
InvitationStatus(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
}
@@ -0,0 +1,47 @@
package com.eactive.apim.portal.invitation.event;
import com.eactive.apim.portal.template.service.MessageEventHandler;
import com.eactive.apim.portal.template.service.MessageRecipient;
import com.eactive.apim.portal.template.service.MessageSendEvent;
import java.util.HashMap;
import java.util.Map;
import org.springframework.stereotype.Component;
@Component
public class UserInvitationEvent implements MessageEventHandler {
public static final String KEY = "user_invitation";
@Override
public boolean allowAdditionalRecipients() {
return false;
}
@Override
public MessageSendEvent createEvent(Object source, MessageRecipient recipient, Map<String, Object> params) {
Map<String, String> requestParams = new HashMap<>();
requestParams.put("company", params.get("company").toString());
requestParams.put("manager", params.get("manager").toString());
requestParams.put("url", params.get("url").toString());
requestParams.put("userName",params.get("company").toString() + " 이용자");
return new MessageSendEvent(source, KEY, recipient, requestParams);
}
@Override
public String getKey() {
return KEY;
}
@Override
public String getDisplayName() {
return "이메일 사용자 초대";
}
@Override
public String getDescription() {
return "<div>사용 가능 변수: </div><br/>"
+ "<div> - userId 수신자 ID (이메일) </div><br/>";
}
}
@@ -0,0 +1,21 @@
package com.eactive.apim.portal.invitation.repository;
import com.eactive.apim.portal.invitation.entity.UserInvitation;
import com.eactive.apim.portal.invitation.entity.UserInvitationEnums.InvitationStatus;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface UserInvitationRepository extends JpaRepository<UserInvitation, String> {
List<UserInvitation> findByOrgIdAndStatus(String orgId, InvitationStatus status);
Optional<UserInvitation> findByToken(String token);
List<UserInvitation> findByStatus(InvitationStatus status);
Optional<UserInvitation> findFirstByInvitationEmailAndStatus(String email, InvitationStatus status);
}
@@ -0,0 +1,111 @@
package com.eactive.apim.portal.monitoringcode.entity;
import java.io.Serializable;
import java.time.LocalDateTime;
import javax.persistence.Cacheable;
import javax.persistence.Column;
import javax.persistence.Convert;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Comment;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import com.eactive.eai.data.converter.LocalDateTimeToStringConverter14;
import com.eactive.eai.data.entity.AbstractEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Data
@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = true)
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@Entity
@Table(name = "tseairm28")
//@org.hibernate.annotations.Table(appliesTo = "tseairm28", comment = "monitoring 코드")
public class MonitoringCode extends AbstractEntity<MonitoringCodeId> implements Serializable {
private static final long serialVersionUID = 1L;
@EmbeddedId
private MonitoringCodeId id;
@Column(nullable = false, length = 100)
@Comment("코드명")
private String codeName;
@Column(length = 50)
@Comment("extra 1")
private String ext1;
@Column(length = 50)
@Comment("extra 2")
private String ext2;
@Column(length = 50)
@Comment("extra 3")
private String ext3;
@Column(length = 50)
@Comment("extra 4")
private String ext4;
@Column(length = 50)
@Comment("extra 5")
private String ext5;
@Column(length = 50)
@Comment("상위 코드")
private String parentCode;
@Column(length = 50)
@Comment("상위 코드 그룹")
private String parentCodeGroup;
@Column(nullable = false, precision = 10)
@Comment("정렬순서")
private Integer seq;
@Column(nullable = false, length = 8)
@Comment("생성자")
@CreatedBy
private String createBy;
@Column(nullable = false, length = 14)
@Comment("생성 일시")
@CreatedDate
@Convert(converter = LocalDateTimeToStringConverter14.class)
private LocalDateTime createOn;
@Column(nullable = false, length = 8)
@Comment("수정자")
@LastModifiedBy
private String updateBy;
@Column(nullable = false, length = 14)
@Comment("수정 일시")
@Convert(converter = LocalDateTimeToStringConverter14.class)
@LastModifiedDate
private LocalDateTime updateOn;
@Column(nullable = false, length = 1)
@Comment("사용여부")
private String useYn;
@ManyToOne
@JoinColumn(name = "codeGroup", insertable = false, updatable = false)
@ToString.Exclude
private MonitoringCodeGroup monitoringCodeGroup;
}
@@ -0,0 +1,57 @@
package com.eactive.apim.portal.monitoringcode.entity;
import java.io.Serializable;
import java.util.List;
import javax.persistence.Cacheable;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import javax.persistence.Table;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Comment;
import com.eactive.eai.data.entity.AbstractEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NonNull;
import lombok.ToString;
@Data
@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = true)
@Entity
@Table(name = "tseairm27")
//@org.hibernate.annotations.Table(appliesTo = "tseairm27", comment = "monitoring 코드 그룹")
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class MonitoringCodeGroup extends AbstractEntity<String> implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@Column(unique = true, nullable = false, length = 50)
@Comment("코드 그룹")
private String codeGroup;
@Column(nullable = false, length = 1000)
@Comment("그룹 설명")
private String description;
@Override
public @NonNull String getId() {
return codeGroup;
}
@OneToMany(mappedBy = "monitoringCodeGroup", cascade = CascadeType.ALL, orphanRemoval = true)
@ToString.Exclude
@OrderBy("id.code ASC")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private List<MonitoringCode> monitoringCodes;
}
@@ -0,0 +1,35 @@
package com.eactive.apim.portal.monitoringcode.entity;
import java.io.Serializable;
import javax.persistence.Cacheable;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Comment;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Embeddable
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class MonitoringCodeId implements Serializable {
private static final long serialVersionUID = 1L;
@Column(nullable = false, length = 50)
@Comment("코드 그룹")
private String codeGroup;
@Column(nullable = false, length = 50)
@Comment("코드")
private String code;
}
@@ -0,0 +1,36 @@
package com.eactive.apim.portal.partnershipapplication.entity;
import com.eactive.apim.portal.common.entity.Auditable;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Comment;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
@Data
@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = true)
@Entity
@Table(name = "ptl_partnership_application")
public class PartnershipApplication extends Auditable {
private static final long serialVersionUID = 1L;
@Id
@Column(name = "id", length = 38, nullable = false)
@GenericGenerator(name = "uuid-gen", strategy = "uuid2")
@GeneratedValue(generator = "uuid-gen", strategy = GenerationType.IDENTITY)
private String id;
@Column(name = "biz_subject", length = 255, nullable = false)
@Comment("비즈니스제목")
private String bizSubject;
@Column(name = "biz_detail", columnDefinition = "TEXT", nullable = false)
@Comment("비즈니스내용")
private String bizDetail;
@Column(name = "file_id")
@Comment("첨부파일ID")
private String fileId;
}
@@ -0,0 +1,50 @@
package com.eactive.apim.portal.portalNotice.entity;
import com.eactive.apim.portal.common.entity.Auditable;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Comment;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
@Data
@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = true)
@Entity
@Table(name = "ptl_notice")
public class PortalNotice extends Auditable {
@Id
@Column(name = "id", length = 36, nullable = false)
@GenericGenerator(name = "uuid-gen", strategy = "uuid2")
@GeneratedValue(generator = "uuid-gen", strategy = GenerationType.IDENTITY)
private String id;
@Column(name = "notice_subject", length = 255, nullable = false)
@Comment("공지제목")
private String noticeSubject;
@Column(name = "notice_detail", columnDefinition = "TEXT", nullable = false)
@Comment("공지내용")
private String noticeDetail;
@Column(name = "file_id", length = 38)
@Comment("파일ID")
private String fileId;
@Column(name = "read_count")
@Comment("조회횟수")
private Long readCount;
@Column(name = "use_yn", length = 1, nullable = false)
@Comment("사용여부")
private String useYn;
@Column(name = "inquirer_name", length = 50, nullable = false)
@Comment("작성자이름")
private String inquirerName;
}
@@ -0,0 +1,17 @@
package com.eactive.apim.portal.portalfaq.entity;
public enum FaqType {
API("API"),
PORTAL("포탈이용"),
PARTNERSHIP("제휴");
private String description;
FaqType(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
@@ -0,0 +1,47 @@
package com.eactive.apim.portal.portalfaq.entity;
import com.eactive.apim.portal.common.entity.Auditable;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Comment;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import static javax.persistence.EnumType.STRING;
@Data
@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = true)
@Entity
@Table(name = "ptl_faq")
public class PortalFaq extends Auditable {
@Id
@Column(name = "id", length = 38, nullable = false)
@GenericGenerator(name = "uuid-gen", strategy = "uuid2")
@GeneratedValue(generator = "uuid-gen", strategy = GenerationType.IDENTITY)
private String id;
@Column(name = "faq_question", length = 255, nullable = false)
@Comment("FAQ질문")
private String faqQuestion;
@Column(name = "faq_answer", columnDefinition = "TEXT", nullable = false)
@Comment("FAQ답변")
private String faqAnswer;
@Column(name = "use_yn", length = 1, nullable = false)
@Comment("사용여부")
private String useYn;
@Column(name = "file_id")
@Comment("첨부파일ID")
private String fileId;
@Enumerated(value = STRING)
@Column(name = "faq_type")
@Comment("FAQ타입")
private FaqType faqType;
}
@@ -0,0 +1,104 @@
package com.eactive.apim.portal.portalorg.entity;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.tuple.ValueGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class OrgCodeGenerator implements ValueGenerator<String> {
private static final Logger log = LoggerFactory.getLogger(OrgCodeGenerator.class);
private static final String TABLE_NAME = "PTL_ID";
private static final String SELECT_SQL =
"SELECT NEXT_ID FROM " + TABLE_NAME +
" WHERE TABLE_NAME = 'PTL_ORG' FOR UPDATE";
private static final String UPDATE_SQL =
"UPDATE " + TABLE_NAME +
" SET NEXT_ID = ? WHERE TABLE_NAME = 'PTL_ORG'";
private static final String CHECK_EXISTS_SQL =
"SELECT COUNT(*) FROM " + TABLE_NAME +
" WHERE TABLE_NAME = 'PTL_ORG'";
private static final String INSERT_SQL =
"INSERT INTO " + TABLE_NAME +
" (TABLE_NAME, NEXT_ID) VALUES ('PTL_ORG', ?)";
private static final long INITIAL_VALUE = 1000000000L;
@Override
public String generateValue(Session session, Object owner) {
if(owner instanceof PortalOrg){
PortalOrg portalOrg= (PortalOrg) owner;
if(portalOrg.getOrgCode() != null && !portalOrg.getOrgCode().isEmpty()){
return portalOrg.getOrgCode();
}
}
SessionImplementor sessionImplementor = (SessionImplementor) session;
try {
Connection connection = sessionImplementor.getJdbcConnectionAccess()
.obtainConnection();
try {
ensureTableInitialized(connection);
return generateOrgCode(connection);
} finally {
sessionImplementor.getJdbcConnectionAccess()
.releaseConnection(connection);
}
} catch (SQLException e) {
log.error("Failed to generate organization code", e);
throw new HibernateException("Could not generate organization code", e);
}
}
private void ensureTableInitialized(Connection connection) throws SQLException {
// Check if entry exists
try (PreparedStatement checkStmt = connection.prepareStatement(CHECK_EXISTS_SQL)) {
ResultSet rs = checkStmt.executeQuery();
if (rs.next() && rs.getLong(1) == 0) {
// Entry doesn't exist, insert initial value
try (PreparedStatement insertStmt = connection.prepareStatement(INSERT_SQL)) {
insertStmt.setLong(1, INITIAL_VALUE);
insertStmt.executeUpdate();
log.info("Initialized PTL_ID table with initial value {} for PTL_ORG", INITIAL_VALUE);
}
}
}
}
private String generateOrgCode(Connection connection) throws SQLException {
long nextVal = getAndIncrementValue(connection);
return String.format("%010d", nextVal);
}
private long getAndIncrementValue(Connection connection) throws SQLException {
try (PreparedStatement select = connection.prepareStatement(SELECT_SQL)) {
ResultSet rs = select.executeQuery();
if (!rs.next()) {
throw new HibernateException("Unable to get next organization code value");
}
long nextVal = rs.getLong(1);
validateValue(nextVal);
try (PreparedStatement update = connection.prepareStatement(UPDATE_SQL)) {
update.setLong(1, nextVal + 1);
update.executeUpdate();
}
return nextVal;
}
}
private void validateValue(long value) {
if (value > 9999999999L) {
throw new HibernateException("Organization code sequence has reached its maximum value");
}
}
}
@@ -0,0 +1,100 @@
package com.eactive.apim.portal.portalorg.entity;
import com.eactive.apim.portal.common.entity.Auditable;
import com.eactive.apim.portal.portalorg.entity.PortalOrgEnums.ApprovalStatus;
import com.eactive.apim.portal.portalorg.entity.PortalOrgEnums.OrgStatus;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Id;
import javax.persistence.Table;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Comment;
import org.hibernate.annotations.GenerationTime;
import org.hibernate.annotations.GeneratorType;
@Data
@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = true)
@Entity
@Table(name = "ptl_org")
public class PortalOrg extends Auditable implements Serializable, com.eactive.eai.data.Data {
private static final long serialVersionUID = 1L;
@Id
@Column(name = "id", length = 36, nullable = false)
private String id;
@Column(name = "corp_reg_no", length = 20)
@Comment("법인등록번호")
private String corpRegNo;
@Column(name = "comp_reg_no", length = 11)
@Comment("사업자등록번호")
private String compRegNo;
@Column(name = "org_name", nullable = false, length = 50)
@Comment("기관이름")
private String orgName;
@Column(name = "org_code")
@Comment("제휴기관코드")
@GeneratorType(type = OrgCodeGenerator.class, when = GenerationTime.INSERT)
private String orgCode;
@Column(name = "org_desc", length = 255)
@Comment("기관설명")
private String orgDesc;
@Enumerated(EnumType.STRING)
@Column(name = "org_status", length = 30)
@Comment("기관상태")
private OrgStatus orgStatus;
@Enumerated(EnumType.STRING)
@Column(name = "approval_status", length = 30)
@Comment("승인상태")
private ApprovalStatus approvalStatus;
@Column(name = "org_type", length = 10)
@Comment("기관타입")
private String orgType;
@Column(name = "ceo_name", length = 50)
@Comment("대표자성명")
private String ceoName;
@Column(name = "org_addr", length = 255)
@Comment("기관사업장소재지")
private String orgAddr;
@Column(name = "org_sectors", length = 50)
@Comment("기관업태")
private String orgSectors;
@Column(name = "org_industrytype", length = 50)
@Comment("기관업종")
private String orgIndustryType;
@Column(name = "org_phone_number", length = 20)
@Comment("기관전화번호")
private String orgPhoneNumber;
@Column(name = "ip_whitelist", length = 255)
@Comment("사용서버IP대역")
private String ipWhitelist;
@Column(name = "comp_reg_file", length = 36)
@Comment("사업자등록증")
private String compRegFile;
@Column(name = "service_name", length = 36)
@Comment("서비스명")
private String serviceName;
@Column(name = "sc_phone_number", length = 20)
@Comment("고객센터연락처")
private String scPhoneNumber;
}

Some files were not shown because too many files have changed in this diff Show More