diff --git a/.gitignore b/.gitignore
index 549e00a..70596f3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,3 +31,12 @@ build/
### VS Code ###
.vscode/
+
+### Gradle ###
+.gradle
+
+### Eclipse output ###
+bin/
+
+### Logs ###
+logs/
diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar
deleted file mode 100644
index c1dd12f..0000000
Binary files a/.mvn/wrapper/maven-wrapper.jar and /dev/null differ
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
deleted file mode 100644
index b74bf7f..0000000
--- a/.mvn/wrapper/maven-wrapper.properties
+++ /dev/null
@@ -1,2 +0,0 @@
-distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip
-wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..3b741c9
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,55 @@
+plugins {
+ id 'java'
+ id 'org.springframework.boot' version '3.1.4'
+ id 'io.spring.dependency-management' version '1.1.4'
+}
+
+group = 'com.goi'
+version = '0.0.1-SNAPSHOT'
+description = 'System Configuration REST Api'
+
+java {
+ toolchain {
+ languageVersion = JavaLanguageVersion.of(21)
+ }
+}
+
+repositories {
+ mavenCentral()
+ // Required to resolve the local SNAPSHOT dependency
+ // 'template:layered-architecture-template:1.0.0-SNAPSHOT'
+ mavenLocal()
+}
+
+dependencies {
+ // Spring Boot starters (versions managed by the Spring Boot BOM)
+ implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
+ implementation 'org.springframework.boot:spring-boot-starter-security'
+ implementation 'org.springframework.boot:spring-boot-starter-web'
+ implementation 'org.springframework.boot:spring-boot-starter-validation'
+
+ // OpenAPI / Swagger UI
+ implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0'
+
+ // JWT (jjwt) — api at compile time, impl + jackson at runtime
+ implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
+ runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
+ runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
+
+ // PostgreSQL driver
+ runtimeOnly 'org.postgresql:postgresql'
+
+ // Lombok (compile-only + annotation processor; excluded from the boot jar)
+ compileOnly 'org.projectlombok:lombok'
+ annotationProcessor 'org.projectlombok:lombok'
+ testCompileOnly 'org.projectlombok:lombok'
+ testAnnotationProcessor 'org.projectlombok:lombok'
+
+ // Test
+ testImplementation 'org.springframework.boot:spring-boot-starter-test'
+ testImplementation 'org.springframework.security:spring-security-test'
+}
+
+tasks.named('test') {
+ useJUnitPlatform()
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..8c5fc5c
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,3 @@
+org.gradle.caching=true
+org.gradle.parallel=true
+org.gradle.configuration-cache=true
diff --git a/gradle/wrapper/READ_ME_wrapper_jar.txt b/gradle/wrapper/READ_ME_wrapper_jar.txt
new file mode 100644
index 0000000..6c7a96c
--- /dev/null
+++ b/gradle/wrapper/READ_ME_wrapper_jar.txt
@@ -0,0 +1,15 @@
+이 폴더에는 원래 `gradle-wrapper.jar` 바이너리가 함께 있어야 합니다.
+변환 작업이 네트워크가 차단된 환경에서 진행되어 바이너리를 내려받을 수 없어
+이 한 개 파일만 빠져 있습니다. 아래 중 한 가지 방법으로 추가하면 됩니다.
+
+방법 A) Gradle 이 설치되어 있는 경우 (가장 깔끔함)
+ 프로젝트 루트에서:
+ gradle wrapper --gradle-version 8.5 --distribution-type bin
+ -> gradlew, gradlew.bat, gradle-wrapper.jar, gradle-wrapper.properties 가
+ 모두 올바르게 생성/갱신됩니다.
+
+방법 B) jar 만 직접 내려받기
+ curl -L -o gradle/wrapper/gradle-wrapper.jar \
+ https://raw.githubusercontent.com/gradle/gradle/v8.5.0/gradle/wrapper/gradle-wrapper.jar
+
+추가한 뒤에는 이 안내 파일을 삭제해도 됩니다.
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..d64cd49
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..1af9e09
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..1aa94a4
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,249 @@
+#!/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/HEAD/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
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
+
+# 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
+ if ! command -v java >/dev/null 2>&1
+ then
+ 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
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ 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
+
+
+# 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"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# 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" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..93e3f59
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,92 @@
+@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=.
+@rem This is normally unused
+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% equ 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% equ 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!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/mvnw b/mvnw
deleted file mode 100644
index 8a8fb22..0000000
--- a/mvnw
+++ /dev/null
@@ -1,316 +0,0 @@
-#!/bin/sh
-# ----------------------------------------------------------------------------
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you 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.
-# ----------------------------------------------------------------------------
-
-# ----------------------------------------------------------------------------
-# Maven Start Up Batch script
-#
-# Required ENV vars:
-# ------------------
-# JAVA_HOME - location of a JDK home dir
-#
-# Optional ENV vars
-# -----------------
-# M2_HOME - location of maven2's installed home dir
-# MAVEN_OPTS - parameters passed to the Java VM when running Maven
-# e.g. to debug Maven itself, use
-# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
-# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
-# ----------------------------------------------------------------------------
-
-if [ -z "$MAVEN_SKIP_RC" ] ; then
-
- if [ -f /usr/local/etc/mavenrc ] ; then
- . /usr/local/etc/mavenrc
- fi
-
- if [ -f /etc/mavenrc ] ; then
- . /etc/mavenrc
- fi
-
- if [ -f "$HOME/.mavenrc" ] ; then
- . "$HOME/.mavenrc"
- fi
-
-fi
-
-# OS specific support. $var _must_ be set to either true or false.
-cygwin=false;
-darwin=false;
-mingw=false
-case "`uname`" in
- CYGWIN*) cygwin=true ;;
- MINGW*) mingw=true;;
- Darwin*) darwin=true
- # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
- # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
- if [ -z "$JAVA_HOME" ]; then
- if [ -x "/usr/libexec/java_home" ]; then
- export JAVA_HOME="`/usr/libexec/java_home`"
- else
- export JAVA_HOME="/Library/Java/Home"
- fi
- fi
- ;;
-esac
-
-if [ -z "$JAVA_HOME" ] ; then
- if [ -r /etc/gentoo-release ] ; then
- JAVA_HOME=`java-config --jre-home`
- fi
-fi
-
-if [ -z "$M2_HOME" ] ; then
- ## resolve links - $0 may be a link to maven's home
- PRG="$0"
-
- # need this for relative symlinks
- while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG="`dirname "$PRG"`/$link"
- fi
- done
-
- saveddir=`pwd`
-
- M2_HOME=`dirname "$PRG"`/..
-
- # make it fully qualified
- M2_HOME=`cd "$M2_HOME" && pwd`
-
- cd "$saveddir"
- # echo Using m2 at $M2_HOME
-fi
-
-# For Cygwin, ensure paths are in UNIX format before anything is touched
-if $cygwin ; then
- [ -n "$M2_HOME" ] &&
- M2_HOME=`cygpath --unix "$M2_HOME"`
- [ -n "$JAVA_HOME" ] &&
- JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
- [ -n "$CLASSPATH" ] &&
- CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
-fi
-
-# For Mingw, ensure paths are in UNIX format before anything is touched
-if $mingw ; then
- [ -n "$M2_HOME" ] &&
- M2_HOME="`(cd "$M2_HOME"; pwd)`"
- [ -n "$JAVA_HOME" ] &&
- JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
-fi
-
-if [ -z "$JAVA_HOME" ]; then
- javaExecutable="`which javac`"
- if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
- # readlink(1) is not available as standard on Solaris 10.
- readLink=`which readlink`
- if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
- if $darwin ; then
- javaHome="`dirname \"$javaExecutable\"`"
- javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
- else
- javaExecutable="`readlink -f \"$javaExecutable\"`"
- fi
- javaHome="`dirname \"$javaExecutable\"`"
- javaHome=`expr "$javaHome" : '\(.*\)/bin'`
- JAVA_HOME="$javaHome"
- export JAVA_HOME
- fi
- fi
-fi
-
-if [ -z "$JAVACMD" ] ; then
- 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
- else
- JAVACMD="`\\unset -f command; \\command -v java`"
- fi
-fi
-
-if [ ! -x "$JAVACMD" ] ; then
- echo "Error: JAVA_HOME is not defined correctly." >&2
- echo " We cannot execute $JAVACMD" >&2
- exit 1
-fi
-
-if [ -z "$JAVA_HOME" ] ; then
- echo "Warning: JAVA_HOME environment variable is not set."
-fi
-
-CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
-
-# traverses directory structure from process work directory to filesystem root
-# first directory with .mvn subdirectory is considered project base directory
-find_maven_basedir() {
-
- if [ -z "$1" ]
- then
- echo "Path not specified to find_maven_basedir"
- return 1
- fi
-
- basedir="$1"
- wdir="$1"
- while [ "$wdir" != '/' ] ; do
- if [ -d "$wdir"/.mvn ] ; then
- basedir=$wdir
- break
- fi
- # workaround for JBEAP-8937 (on Solaris 10/Sparc)
- if [ -d "${wdir}" ]; then
- wdir=`cd "$wdir/.."; pwd`
- fi
- # end of workaround
- done
- echo "${basedir}"
-}
-
-# concatenates all lines of a file
-concat_lines() {
- if [ -f "$1" ]; then
- echo "$(tr -s '\n' ' ' < "$1")"
- fi
-}
-
-BASE_DIR=`find_maven_basedir "$(pwd)"`
-if [ -z "$BASE_DIR" ]; then
- exit 1;
-fi
-
-##########################################################################################
-# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
-# This allows using the maven wrapper in projects that prohibit checking in binary data.
-##########################################################################################
-if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Found .mvn/wrapper/maven-wrapper.jar"
- fi
-else
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
- fi
- if [ -n "$MVNW_REPOURL" ]; then
- jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
- else
- jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
- fi
- while IFS="=" read key value; do
- case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
- esac
- done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Downloading from: $jarUrl"
- fi
- wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
- if $cygwin; then
- wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
- fi
-
- if command -v wget > /dev/null; then
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Found wget ... using wget"
- fi
- if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
- wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
- else
- wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
- fi
- elif command -v curl > /dev/null; then
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Found curl ... using curl"
- fi
- if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
- curl -o "$wrapperJarPath" "$jarUrl" -f
- else
- curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
- fi
-
- else
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Falling back to using Java to download"
- fi
- javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
- # For Cygwin, switch paths to Windows format before running javac
- if $cygwin; then
- javaClass=`cygpath --path --windows "$javaClass"`
- fi
- if [ -e "$javaClass" ]; then
- if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
- if [ "$MVNW_VERBOSE" = true ]; then
- echo " - Compiling MavenWrapperDownloader.java ..."
- fi
- # Compiling the Java class
- ("$JAVA_HOME/bin/javac" "$javaClass")
- fi
- if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
- # Running the downloader
- if [ "$MVNW_VERBOSE" = true ]; then
- echo " - Running MavenWrapperDownloader.java ..."
- fi
- ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
- fi
- fi
- fi
-fi
-##########################################################################################
-# End of extension
-##########################################################################################
-
-export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
-if [ "$MVNW_VERBOSE" = true ]; then
- echo $MAVEN_PROJECTBASEDIR
-fi
-MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
-
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin; then
- [ -n "$M2_HOME" ] &&
- M2_HOME=`cygpath --path --windows "$M2_HOME"`
- [ -n "$JAVA_HOME" ] &&
- JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
- [ -n "$CLASSPATH" ] &&
- CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
- [ -n "$MAVEN_PROJECTBASEDIR" ] &&
- MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
-fi
-
-# Provide a "standardized" way to retrieve the CLI args that will
-# work with both Windows and non-Windows executions.
-MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
-export MAVEN_CMD_LINE_ARGS
-
-WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
-
-exec "$JAVACMD" \
- $MAVEN_OPTS \
- $MAVEN_DEBUG_OPTS \
- -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
- "-Dmaven.home=${M2_HOME}" \
- "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
- ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/mvnw.cmd b/mvnw.cmd
deleted file mode 100644
index 1d8ab01..0000000
--- a/mvnw.cmd
+++ /dev/null
@@ -1,188 +0,0 @@
-@REM ----------------------------------------------------------------------------
-@REM Licensed to the Apache Software Foundation (ASF) under one
-@REM or more contributor license agreements. See the NOTICE file
-@REM distributed with this work for additional information
-@REM regarding copyright ownership. The ASF licenses this file
-@REM to you under the Apache License, Version 2.0 (the
-@REM "License"); you may not use this file except in compliance
-@REM with the License. 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,
-@REM software distributed under the License is distributed on an
-@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-@REM KIND, either express or implied. See the License for the
-@REM specific language governing permissions and limitations
-@REM under the License.
-@REM ----------------------------------------------------------------------------
-
-@REM ----------------------------------------------------------------------------
-@REM Maven Start Up Batch script
-@REM
-@REM Required ENV vars:
-@REM JAVA_HOME - location of a JDK home dir
-@REM
-@REM Optional ENV vars
-@REM M2_HOME - location of maven2's installed home dir
-@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
-@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
-@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
-@REM e.g. to debug Maven itself, use
-@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
-@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
-@REM ----------------------------------------------------------------------------
-
-@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
-@echo off
-@REM set title of command window
-title %0
-@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
-@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
-
-@REM set %HOME% to equivalent of $HOME
-if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
-
-@REM Execute a user defined script before this one
-if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
-@REM check for pre script, once with legacy .bat ending and once with .cmd ending
-if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
-if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
-:skipRcPre
-
-@setlocal
-
-set ERROR_CODE=0
-
-@REM To isolate internal variables from possible post scripts, we use another setlocal
-@setlocal
-
-@REM ==== START VALIDATION ====
-if not "%JAVA_HOME%" == "" goto OkJHome
-
-echo.
-echo Error: JAVA_HOME not found in your environment. >&2
-echo Please set the JAVA_HOME variable in your environment to match the >&2
-echo location of your Java installation. >&2
-echo.
-goto error
-
-:OkJHome
-if exist "%JAVA_HOME%\bin\java.exe" goto init
-
-echo.
-echo Error: JAVA_HOME is set to an invalid directory. >&2
-echo JAVA_HOME = "%JAVA_HOME%" >&2
-echo Please set the JAVA_HOME variable in your environment to match the >&2
-echo location of your Java installation. >&2
-echo.
-goto error
-
-@REM ==== END VALIDATION ====
-
-:init
-
-@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
-@REM Fallback to current working directory if not found.
-
-set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
-IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
-
-set EXEC_DIR=%CD%
-set WDIR=%EXEC_DIR%
-:findBaseDir
-IF EXIST "%WDIR%"\.mvn goto baseDirFound
-cd ..
-IF "%WDIR%"=="%CD%" goto baseDirNotFound
-set WDIR=%CD%
-goto findBaseDir
-
-:baseDirFound
-set MAVEN_PROJECTBASEDIR=%WDIR%
-cd "%EXEC_DIR%"
-goto endDetectBaseDir
-
-:baseDirNotFound
-set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
-cd "%EXEC_DIR%"
-
-:endDetectBaseDir
-
-IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
-
-@setlocal EnableExtensions EnableDelayedExpansion
-for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
-@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
-
-:endReadAdditionalConfig
-
-SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
-set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
-set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
-
-set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
-
-FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
- IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
-)
-
-@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
-@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
-if exist %WRAPPER_JAR% (
- if "%MVNW_VERBOSE%" == "true" (
- echo Found %WRAPPER_JAR%
- )
-) else (
- if not "%MVNW_REPOURL%" == "" (
- SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
- )
- if "%MVNW_VERBOSE%" == "true" (
- echo Couldn't find %WRAPPER_JAR%, downloading it ...
- echo Downloading from: %DOWNLOAD_URL%
- )
-
- powershell -Command "&{"^
- "$webclient = new-object System.Net.WebClient;"^
- "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
- "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
- "}"^
- "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
- "}"
- if "%MVNW_VERBOSE%" == "true" (
- echo Finished downloading %WRAPPER_JAR%
- )
-)
-@REM End of extension
-
-@REM Provide a "standardized" way to retrieve the CLI args that will
-@REM work with both Windows and non-Windows executions.
-set MAVEN_CMD_LINE_ARGS=%*
-
-%MAVEN_JAVA_EXE% ^
- %JVM_CONFIG_MAVEN_PROPS% ^
- %MAVEN_OPTS% ^
- %MAVEN_DEBUG_OPTS% ^
- -classpath %WRAPPER_JAR% ^
- "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
- %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
-if ERRORLEVEL 1 goto error
-goto end
-
-:error
-set ERROR_CODE=1
-
-:end
-@endlocal & set ERROR_CODE=%ERROR_CODE%
-
-if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
-@REM check for post script, once with legacy .bat ending and once with .cmd ending
-if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
-if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
-:skipRcPost
-
-@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
-if "%MAVEN_BATCH_PAUSE%"=="on" pause
-
-if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
-
-cmd /C exit /B %ERROR_CODE%
diff --git a/pom.xml b/pom.xml
deleted file mode 100644
index ca97d0f..0000000
--- a/pom.xml
+++ /dev/null
@@ -1,102 +0,0 @@
-
-
- 4.0.0
-
- org.springframework.boot
- spring-boot-starter-parent
- 3.1.4
-
-
- com.goi
- sys-rest-api
- 0.0.1-SNAPSHOT
- System
- System Configuration REST Api
-
- 17
-
-
-
- org.springframework.boot
- spring-boot-starter-data-jpa
-
-
- org.springframework.boot
- spring-boot-starter-security
-
-
- org.springframework.boot
- spring-boot-starter-web
-
-
-
- org.postgresql
- postgresql
- runtime
-
-
- org.projectlombok
- lombok
- true
-
-
- io.jsonwebtoken
- jjwt-api
- 0.11.5
-
-
- io.jsonwebtoken
- jjwt-impl
- 0.11.5
-
-
- io.jsonwebtoken
- jjwt-jackson
- 0.11.5
-
-
- org.springdoc
- springdoc-openapi-starter-webmvc-ui
- 2.1.0
-
-
- org.springframework.boot
- spring-boot-starter-validation
-
-
-
- org.springframework.boot
- spring-boot-starter-test
- test
-
-
- org.springframework.security
- spring-security-test
- test
-
-
- template
- layered-architecture-template
- 1.0.0-SNAPSHOT
-
-
-
-
-
-
- org.springframework.boot
- spring-boot-maven-plugin
-
-
-
- org.projectlombok
- lombok
-
-
-
-
-
-
-
-
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..814af8e
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'sys-rest-api'
diff --git a/src/main/java/com/goi/erp/controller/SchedulerAdminController.java b/src/main/java/com/goi/erp/controller/SchedulerAdminController.java
new file mode 100644
index 0000000..14d5787
--- /dev/null
+++ b/src/main/java/com/goi/erp/controller/SchedulerAdminController.java
@@ -0,0 +1,31 @@
+package com.goi.erp.controller;
+
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.goi.erp.service.SchedulerCoreService;
+
+import lombok.RequiredArgsConstructor;
+
+
+@RestController
+@RequestMapping("/internal/scheduler")
+@RequiredArgsConstructor
+public class SchedulerAdminController {
+
+ private final SchedulerCoreService schedulerCoreService;
+
+ @PostMapping("/cycle")
+ public String runCycleOnce() {
+ schedulerCoreService.runOneCycle();
+ return "SCHEDULER_CYCLE_OK";
+ }
+
+ @PostMapping("/jobs/{jobCode}/force-run")
+ public String forceRun(@PathVariable String jobCode) {
+ schedulerCoreService.forceRunJob(jobCode);
+ return "FORCE_RUN_OK jobCode=" + jobCode;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/goi/erp/entity/Config.java b/src/main/java/com/goi/erp/entity/Config.java
index a3b4925..ff89635 100644
--- a/src/main/java/com/goi/erp/entity/Config.java
+++ b/src/main/java/com/goi/erp/entity/Config.java
@@ -23,6 +23,7 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener;
@Entity
@Table(
+ schema="sys",
name = "config",
uniqueConstraints = {
@UniqueConstraint(
diff --git a/src/main/java/com/goi/erp/entity/ConfigChangeLog.java b/src/main/java/com/goi/erp/entity/ConfigChangeLog.java
index d82b983..e56c7d2 100644
--- a/src/main/java/com/goi/erp/entity/ConfigChangeLog.java
+++ b/src/main/java/com/goi/erp/entity/ConfigChangeLog.java
@@ -20,7 +20,7 @@ import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
@Entity
-@Table(name = "config_change_log")
+@Table(schema="sys", name = "config_change_log")
@Data
@NoArgsConstructor
@AllArgsConstructor
diff --git a/src/main/java/com/goi/erp/entity/ScheduleJobConfig.java b/src/main/java/com/goi/erp/entity/ScheduleJobConfig.java
index 786df6f..7d36fa8 100644
--- a/src/main/java/com/goi/erp/entity/ScheduleJobConfig.java
+++ b/src/main/java/com/goi/erp/entity/ScheduleJobConfig.java
@@ -26,7 +26,8 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener;
@Entity
@Table(
- name = "schedule_job_config",
+ schema="sys",
+ name = "schedule_job_config",
uniqueConstraints = {
@UniqueConstraint(
name = "uk_schedule_job_config_job_code",
diff --git a/src/main/java/com/goi/erp/entity/ScheduleJobLog.java b/src/main/java/com/goi/erp/entity/ScheduleJobLog.java
index c39e3f5..5035efc 100644
--- a/src/main/java/com/goi/erp/entity/ScheduleJobLog.java
+++ b/src/main/java/com/goi/erp/entity/ScheduleJobLog.java
@@ -15,7 +15,7 @@ import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Entity
-@Table(name = "schedule_job_log")
+@Table(schema="sys", name = "schedule_job_log")
@Data
@NoArgsConstructor
@AllArgsConstructor
diff --git a/src/main/java/com/goi/erp/service/SchedulerCoreService.java b/src/main/java/com/goi/erp/service/SchedulerCoreService.java
index da4d224..65bd23d 100644
--- a/src/main/java/com/goi/erp/service/SchedulerCoreService.java
+++ b/src/main/java/com/goi/erp/service/SchedulerCoreService.java
@@ -19,6 +19,7 @@ import org.springframework.web.client.RestTemplate;
import java.time.LocalDateTime;
import java.time.ZoneId;
+import java.time.ZonedDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -33,13 +34,11 @@ public class SchedulerCoreService {
private final ScheduleJobLogService logService;
private final RestTemplate restTemplate;
private final ConfigService configService;
-
+
private final Map lastTickMap = new ConcurrentHashMap<>();
/**
- * Scheduler Tick
- * - 1분마다 실행
- * - 실제 cron 판단은 내부에서 수행
+ * 1분마다 tick
*/
@Scheduled(fixedDelay = 60_000)
public void tick() {
@@ -55,50 +54,46 @@ public class SchedulerCoreService {
}
}
- /* =========================
- * core logic
- * ========================= */
-
private void evaluateAndRun(ScheduleJobConfig job) {
- ZoneId zoneId = ZoneId.of(
+ ZoneId jobZone = ZoneId.of(
job.getSjcTimezone() != null ? job.getSjcTimezone() : "UTC"
);
- LocalDateTime now = LocalDateTime.now(zoneId);
+ LocalDateTime nowLocal = LocalDateTime.now(jobZone);
- // 1. cron 판단
- if (!shouldRun(job.getSjcId(), job.getSjcCronExpression(), now)) {
+ // 1. cron 판단 (job timezone 기준)
+ if (!shouldRun(job.getSjcId(), job.getSjcCronExpression(), nowLocal)) {
return;
}
// 2. 중복 실행 방지
- if (Boolean.TRUE.equals(job.getSjcEnabled())
- && logService.isRunning(job.getSjcId())) {
+ if (logService.isRunning(job.getSjcId())) {
log.info("Job already running. jobCode={}", job.getSjcJobCode());
return;
}
- // 3. 실행 범위 계산
- LocalDateTime from = now
+ // 3. 실행 범위 계산 (job timezone 기준)
+ LocalDateTime fromLocal = nowLocal
.minusHours(job.getSjcLookbackHours() != null ? job.getSjcLookbackHours() : 0)
.minusMinutes(job.getSjcOverlapMinutes() != null ? job.getSjcOverlapMinutes() : 0);
- LocalDateTime to = now;
+ // 4. UTC 변환 (⭐ 핵심)
+ LocalDateTime fromUtc = toUtc(fromLocal, jobZone);
+ LocalDateTime toUtc = toUtc(nowLocal, jobZone);
- // 4. RUNNING 로그 생성
+ // 5. RUNNING 로그
ScheduleJobLog runningLog = logService.createRunningLog(
job.getSjcId(),
- now,
+ nowLocal,
job.getSjcLookbackHours(),
job.getSjcOverlapMinutes(),
job.getSjcMaxRecords(),
"scheduler"
);
- // 5. worker 실행
try {
- executeWorker(job, from, to, runningLog);
+ executeWorker(job, fromUtc, toUtc, runningLog);
} catch (Exception e) {
logService.markFailed(
runningLog.getSjlId(),
@@ -109,35 +104,28 @@ public class SchedulerCoreService {
}
}
- /* =========================
- * worker execution
- * ========================= */
-
private void executeWorker(
ScheduleJobConfig job,
- LocalDateTime from,
- LocalDateTime to,
+ LocalDateTime fromUtc,
+ LocalDateTime toUtc,
ScheduleJobLog log
) {
-
- Map> configSnapshot = buildConfigSnapshot(job);
+
+ Map> configSnapshot = buildConfigSnapshot(job);
ScheduleWorkerRequestDto request = ScheduleWorkerRequestDto.builder()
.jobCode(job.getSjcJobCode())
- .from(from)
- .to(to)
+ .from(fromUtc) // ⭐ UTC
+ .to(toUtc) // ⭐ UTC
.maxRecords(job.getSjcMaxRecords())
.config(configSnapshot)
.build();
HttpMethod method = HttpMethod.valueOf(
- job.getSjcWorkerMethod() != null
- ? job.getSjcWorkerMethod()
- : "POST"
+ job.getSjcWorkerMethod() != null ? job.getSjcWorkerMethod() : "POST"
);
- HttpEntity entity =
- new HttpEntity<>(request);
+ HttpEntity entity = new HttpEntity<>(request);
ResponseEntity response =
restTemplate.exchange(
@@ -146,7 +134,7 @@ public class SchedulerCoreService {
entity,
ScheduleWorkerResponseDto.class
);
-
+
ScheduleWorkerResponseDto body = response.getBody();
if (body == null) {
throw new IllegalStateException("Worker response is null");
@@ -159,9 +147,6 @@ public class SchedulerCoreService {
body.getSuccessCount(),
body.getFailCount()
);
-
- // 연관된 다음 작업 바로 수행
- triggerNextJobIfExists(job, LocalDateTime.now());
} else {
logService.markFailed(
log.getSjlId(),
@@ -171,17 +156,13 @@ public class SchedulerCoreService {
}
}
- /* =========================
- * cron 판단
- * ========================= */
-
private boolean shouldRun(Long jobId, String cronExpression, LocalDateTime now) {
CronExpression cron = CronExpression.parse(cronExpression);
LocalDateTime last = lastTickMap.get(jobId);
if (last == null) {
lastTickMap.put(jobId, now);
- return true; // 최초 1회 허용 (원하면 false로 바꿔도 됨)
+ return true;
}
LocalDateTime next = cron.next(last);
@@ -190,10 +171,9 @@ public class SchedulerCoreService {
lastTickMap.put(jobId, now);
return shouldRun;
}
-
- private Map> buildConfigSnapshot(
- ScheduleJobConfig job
- ) {
+
+ private Map> buildConfigSnapshot(ScheduleJobConfig job) {
+
if (job.getSjcRequiredConfigs() == null ||
job.getSjcRequiredConfigs().isEmpty()) {
return Map.of();
@@ -204,68 +184,80 @@ public class SchedulerCoreService {
job.getSjcRequiredConfigs().forEach((module, keys) -> {
Map values = new HashMap<>();
for (String key : keys) {
- values.put(
- key,
- configService.getRawValue(module, key)
- );
+ values.put(key, configService.getRawValue(module, key));
}
snapshot.put(module, values);
});
return snapshot;
}
-
- private void triggerNextJobIfExists(ScheduleJobConfig job, LocalDateTime now) {
- if (job.getSjcNextJobCode() == null) {
- return;
- }
-
- ScheduleJobConfig nextJob =
- configRepository.findBySjcJobCode(job.getSjcNextJobCode())
- .orElse(null);
-
- if (nextJob == null || !Boolean.TRUE.equals(nextJob.getSjcEnabled())) {
- return;
- }
-
- log.info(
- "Trigger chained job. from={} to={}",
- job.getSjcJobCode(),
- nextJob.getSjcJobCode()
- );
-
- // cron 판단 없이 바로 실행
- evaluateAndRunChained(nextJob, now);
+ private LocalDateTime toUtc(LocalDateTime local, ZoneId zone) {
+ return ZonedDateTime.of(local, zone)
+ .withZoneSameInstant(ZoneId.of("UTC"))
+ .toLocalDateTime();
}
- private void evaluateAndRunChained(
- ScheduleJobConfig job,
- LocalDateTime now
- ) {
+ /**
+ * 강제 1회 사이클 실행
+ */
+ public void runOneCycle() {
+ List jobs = configRepository.findBySjcEnabledTrue();
+
+ for (ScheduleJobConfig job : jobs) {
+ try {
+ evaluateAndRun(job);
+ } catch (Exception e) {
+ log.error("Scheduler force-cycle error. jobCode={}", job.getSjcJobCode(), e);
+ }
+ }
+ }
+
+ /**
+ * 특정 jobCode를 cron 무시하고 즉시 실행
+ * - 중복 실행 방지는 유지
+ * - from/to는 "지금 시각 기준 lookback/overlap"으로 계산
+ */
+ public void forceRunJob(String jobCode) {
+
+ ScheduleJobConfig job = configRepository.findBySjcJobCode(jobCode)
+ .orElseThrow(() -> new IllegalArgumentException("Job not found. jobCode=" + jobCode));
+
+ if (!Boolean.TRUE.equals(job.getSjcEnabled())) {
+ throw new IllegalStateException("Job is disabled. jobCode=" + jobCode);
+ }
+
+ ZoneId jobZone = ZoneId.of(job.getSjcTimezone() != null ? job.getSjcTimezone() : "UTC");
+ LocalDateTime nowLocal = LocalDateTime.now(jobZone);
+
// 중복 실행 방지
if (logService.isRunning(job.getSjcId())) {
- log.info("Chained job already running. jobCode={}", job.getSjcJobCode());
+ log.info("Force-run skipped. Job already running. jobCode={}", jobCode);
return;
}
- LocalDateTime from = now
+ LocalDateTime fromLocal = nowLocal
.minusHours(job.getSjcLookbackHours() != null ? job.getSjcLookbackHours() : 0)
.minusMinutes(job.getSjcOverlapMinutes() != null ? job.getSjcOverlapMinutes() : 0);
+ LocalDateTime fromUtc = toUtc(fromLocal, jobZone);
+ LocalDateTime toUtc = toUtc(nowLocal, jobZone);
+
ScheduleJobLog runningLog = logService.createRunningLog(
job.getSjcId(),
- now,
+ nowLocal,
job.getSjcLookbackHours(),
job.getSjcOverlapMinutes(),
job.getSjcMaxRecords(),
- "scheduler-chain"
+ "manual"
);
- executeWorker(job, from, now, runningLog);
+ try {
+ executeWorker(job, fromUtc, toUtc, runningLog);
+ } catch (Exception e) {
+ logService.markFailed(runningLog.getSjlId(), "WORKER_ERROR", e.getMessage());
+ throw e;
+ }
}
-
-
-
}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 57d7565..0aec799 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -18,7 +18,7 @@ spring:
application:
security:
jwt:
- secret-key: ${SECRET_KEY}
+ secret-key: ${JWT_SECRET_KEY}
expiration: 86400000 # a day
refresh-token:
expiration: 604800000 # 7 days