From be62219fb111f692794e15d124faefa1dc743322 Mon Sep 17 00:00:00 2001 From: thkim Date: Thu, 16 Oct 2025 11:15:40 +0900 Subject: [PATCH] Init --- .gitignore | 358 ++++++++++++++++++ .idea/compiler.xml | 26 ++ .idea/copyright/profiles_settings.xml | 3 + .idea/misc.xml | 57 +++ .idea/modules.xml | 10 + .idea/modules/wms_cache.iml | 12 + .idea/modules/wms_cache_main.iml | 49 +++ .idea/modules/wms_cache_test.iml | 51 +++ build.gradle | 54 +++ .../kr/co/dbnt/wms/cache/MainController.java | 41 ++ .../wms/cache/controller/WmsController.java | 59 +++ .../kr/co/dbnt/wms/cache/dao/TestDao.java | 43 +++ .../kr/co/dbnt/wms/cache/dao/WmsCacheDao.java | 39 ++ .../co/dbnt/wms/cache/entity/TestTable.java | 55 +++ .../kr/co/dbnt/wms/cache/entity/WmsCache.java | 102 +++++ .../dbnt/wms/cache/service/TestService.java | 49 +++ .../wms/cache/service/WmsCacheService.java | 152 ++++++++ src/main/resources/application.properties | 22 ++ src/main/webapp/WEB-INF/jsp/index.jsp | 10 + src/main/webapp/WEB-INF/jsp/test_db.jsp | 34 ++ .../WEB-INF/spring/applicationContext.xml | 49 +++ .../WEB-INF/spring/dispatcher-servlet.xml | 27 ++ src/main/webapp/WEB-INF/web.xml | 46 +++ 23 files changed, 1348 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/compiler.xml create mode 100644 .idea/copyright/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/modules/wms_cache.iml create mode 100644 .idea/modules/wms_cache_main.iml create mode 100644 .idea/modules/wms_cache_test.iml create mode 100644 build.gradle create mode 100644 src/main/java/kr/co/dbnt/wms/cache/MainController.java create mode 100644 src/main/java/kr/co/dbnt/wms/cache/controller/WmsController.java create mode 100644 src/main/java/kr/co/dbnt/wms/cache/dao/TestDao.java create mode 100644 src/main/java/kr/co/dbnt/wms/cache/dao/WmsCacheDao.java create mode 100644 src/main/java/kr/co/dbnt/wms/cache/entity/TestTable.java create mode 100644 src/main/java/kr/co/dbnt/wms/cache/entity/WmsCache.java create mode 100644 src/main/java/kr/co/dbnt/wms/cache/service/TestService.java create mode 100644 src/main/java/kr/co/dbnt/wms/cache/service/WmsCacheService.java create mode 100644 src/main/resources/application.properties create mode 100644 src/main/webapp/WEB-INF/jsp/index.jsp create mode 100644 src/main/webapp/WEB-INF/jsp/test_db.jsp create mode 100644 src/main/webapp/WEB-INF/spring/applicationContext.xml create mode 100644 src/main/webapp/WEB-INF/spring/dispatcher-servlet.xml create mode 100644 src/main/webapp/WEB-INF/web.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9922b8c --- /dev/null +++ b/.gitignore @@ -0,0 +1,358 @@ +# Created by https://www.toptal.com/developers/gitignore/api/windows,macos,git,java,maven,eclipse,intellij,gradle,netbeans,visualstudiocode +# Edit at https://www.toptal.com/developers/gitignore?templates=windows,macos,git,java,maven,eclipse,intellij,gradle,netbeans,visualstudiocode + +### Eclipse ### +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.recommenders + +# 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 + +### Eclipse Patch ### +# Spring Boot Tooling +.sts4-cache/ + +### Git ### +# Created by git for backups. To disable backups in Git: +# $ git config --global mergetool.keepBackup false +*.orig + +# Created by git when using merge tools for conflicts +*.BACKUP.* +*.BASE.* +*.LOCAL.* +*.REMOTE.* +*_BACKUP_*.txt +*_BASE_*.txt +*_LOCAL_*.txt +*_REMOTE_*.txt + +/src/main/generated + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# 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 + +# AWS User-specific +.idea/**/aws.xml + +# 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/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .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 + +# SonarLint plugin +.idea/sonarlint/ + +# 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 +# https://plugins.jetbrains.com/plugin/7973-sonarlint +.idea/**/sonarlint/ + +# SonarQube Plugin +# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator-enh.xml +.idea/**/markdown-navigator/ + +# Cache file creation bug +# See https://youtrack.jetbrains.com/issue/JBR-2257 +.idea/$CACHE_FILE$ + +# CodeStream plugin +# https://plugins.jetbrains.com/plugin/12206-codestream +.idea/codestream.xml + +# Azure Toolkit for IntelliJ plugin +# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij +.idea/**/azureSettings.xml + +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.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* +replay_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 + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Maven ### +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +# https://github.com/takari/maven-wrapper#usage-without-binary-jar +.mvn/wrapper/maven-wrapper.jar + +# Eclipse m2e generated files +# Eclipse Core +.project +# JDT-specific (Eclipse Java Development Tools) +.classpath + +### NetBeans ### +**/nbproject/private/ +**/nbproject/Makefile-*.mk +**/nbproject/Package-*.bash +build/ +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +### Gradle ### +.gradle +**/build/ +!src/**/build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Avoid ignore Gradle wrappper properties +!gradle-wrapper.properties + +# Cache of project +.gradletasknamecache + +# Eclipse Gradle plugin generated files +# Eclipse Core +# JDT-specific (Eclipse Java Development Tools) + +### Gradle Patch ### +# Java heap dump +*.hprof + +# End of https://www.toptal.com/developers/gitignore/api/windows,macos,git,java,maven,eclipse,intellij,gradle,netbeans,visualstudiocode \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..67fe8e1 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000..e7bedf3 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..e97b8a2 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..b00a362 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/wms_cache.iml b/.idea/modules/wms_cache.iml new file mode 100644 index 0000000..452a7d3 --- /dev/null +++ b/.idea/modules/wms_cache.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/wms_cache_main.iml b/.idea/modules/wms_cache_main.iml new file mode 100644 index 0000000..ec8f4b3 --- /dev/null +++ b/.idea/modules/wms_cache_main.iml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/wms_cache_test.iml b/.idea/modules/wms_cache_test.iml new file mode 100644 index 0000000..4fb33b2 --- /dev/null +++ b/.idea/modules/wms_cache_test.iml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..326d8c9 --- /dev/null +++ b/build.gradle @@ -0,0 +1,54 @@ +// Java와 웹 애플리케이션(WAR) 빌드를 위한 플러그인 설정 +apply plugin: 'java' +apply plugin: 'war' +// 내장 웹서버(jetty)를 사용하기 위한 플러그인 +apply plugin: 'jetty' + +// 소스 코드의 Java 버전을 1.7로 지정 +sourceCompatibility = 1.7 +targetCompatibility = 1.7 + +// UTF-8 인코딩 설정 +[compileJava, compileTestJava]*.options*.encoding = 'UTF-8' + +// 라이브러리를 다운로드할 저장소 지정 +repositories { + mavenCentral() +} + +// 프로젝트에 필요한 라이브러리(의존성) 목록 +dependencies { + // 1. Spring Framework 3.1.4 버전으로 변경 + compile 'org.springframework:spring-webmvc:3.1.4.RELEASE' + compile 'org.springframework:spring-orm:3.1.4.RELEASE' + + // 2. Hibernate + compile 'org.hibernate:hibernate-core:4.3.11.Final' + compile 'org.hibernate:hibernate-entitymanager:4.3.11.Final' + + // @Transactional을 위한 CGLIB 라이브러리 + compile 'cglib:cglib-nodep:2.2.2' + + // JSP와 Servlet API (실제 서버에 이미 있으므로 컴파일 시에만 필요) + providedCompile 'javax.servlet:servlet-api:2.5' + providedCompile 'javax.servlet.jsp:jsp-api:2.1' + + // JSTL (JSP에서 유용한 태그 라이브러리) + compile 'javax.servlet:jstl:1.2' + + // HTTP 요청을 위한 Apache HttpClient + compile 'org.apache.httpcomponents:httpclient:4.3.4' + + // SHA-256 해시 생성을 위한 Apache Commons Codec + compile 'commons-codec:commons-codec:1.9' + + // PostgreSQL 14+ 버전을 지원하는 최신 JDBC 드라이버로 변경 + compile 'org.postgresql:postgresql:42.7.3' + + // 데이터베이스 커넥션 풀을 위한 C3P0 + compile 'c3p0:c3p0:0.9.1.2' + // ---------------------------- +} + +// Jetty 서버 포트를 8080으로 설정 +httpPort = 8083 \ No newline at end of file diff --git a/src/main/java/kr/co/dbnt/wms/cache/MainController.java b/src/main/java/kr/co/dbnt/wms/cache/MainController.java new file mode 100644 index 0000000..328dc07 --- /dev/null +++ b/src/main/java/kr/co/dbnt/wms/cache/MainController.java @@ -0,0 +1,41 @@ +package kr.co.dbnt.wms.cache; + +import kr.co.dbnt.wms.cache.entity.TestTable; +import kr.co.dbnt.wms.cache.service.TestService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import java.util.List; + +/** + * Created by kimtheho on 25. 10. 15. + */ +@Controller +public class MainController { + + @Autowired + private TestService testService; // TestService 주입 + + // 웹 브라우저에서 최상위 주소("/")로 GET 요청이 오면 이 메서드를 실행 + @RequestMapping(value = "/", method = RequestMethod.GET) + public String index(Model model) { + return "index"; + } + + @RequestMapping(value = "/test", method = RequestMethod.GET) + public String testDatabase(Model model) { + // 1. Insert -> Select -> Update 실행 + testService.runCrudTest(); + + // 2. 전체 데이터 조회해서 View로 전달 + List testDataList = testService.getAllData(); + + model.addAttribute("message", "DB CRUD Test Completed! Check the Console Log."); + model.addAttribute("testDataList", testDataList); + + return "test_db"; // 결과를 hello.jsp에서 보여줌 + } +} diff --git a/src/main/java/kr/co/dbnt/wms/cache/controller/WmsController.java b/src/main/java/kr/co/dbnt/wms/cache/controller/WmsController.java new file mode 100644 index 0000000..4b4a98a --- /dev/null +++ b/src/main/java/kr/co/dbnt/wms/cache/controller/WmsController.java @@ -0,0 +1,59 @@ +package kr.co.dbnt.wms.cache.controller; + +import kr.co.dbnt.wms.cache.service.WmsCacheService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Created by kimtheho on 25. 10. 15. + */ +@Controller +public class WmsController { + + @Autowired + private WmsCacheService wmsCacheService; + + @RequestMapping(value = "/openapi/wms", method = RequestMethod.GET) + public void getWmsImage(HttpServletRequest request, HttpServletResponse response) { + try { + String queryString = request.getQueryString(); + if (queryString == null || queryString.isEmpty()) { + response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Query parameters are required."); + return; + } + + File imageFile = wmsCacheService.getImage(queryString); + + response.setContentType("image/png"); + response.setContentLength((int) imageFile.length()); + + // try-with-resources 구문으로 InputStream을 안전하게 엽니다. + try (InputStream inputStream = new FileInputStream(imageFile)) { + // InputStream의 내용을 OutputStream으로 직접 복사합니다. + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + response.getOutputStream().write(buffer, 0, bytesRead); + } + } + + } catch (IOException e) { + e.printStackTrace(); + try { + // 에러 발생 시 서버 에러 응답 + response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Failed to process WMS request."); + } catch (IOException ioException) { + ioException.printStackTrace(); + } + } + } +} diff --git a/src/main/java/kr/co/dbnt/wms/cache/dao/TestDao.java b/src/main/java/kr/co/dbnt/wms/cache/dao/TestDao.java new file mode 100644 index 0000000..6194730 --- /dev/null +++ b/src/main/java/kr/co/dbnt/wms/cache/dao/TestDao.java @@ -0,0 +1,43 @@ +package kr.co.dbnt.wms.cache.dao; + +import kr.co.dbnt.wms.cache.entity.TestTable; +import org.springframework.stereotype.Repository; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import java.util.List; + +/** + * Created by kimtheho on 25. 10. 15. + */ +@Repository +public class TestDao { + + @PersistenceContext + private EntityManager em; + + // 데이터 삽입 (Insert) + public void save(TestTable testTable) { + em.persist(testTable); + } + + // ID로 한 건 조회 (Select by ID) + public TestTable findById(Long id) { + return em.find(TestTable.class, id); + } + + // 전체 조회 (Select All) + public List findAll() { + return em.createQuery("SELECT t FROM TestTable t", TestTable.class) + .getResultList(); + } + + // 영속성 컨텍스트의 변경 내용을 데이터베이스에 강제로 반영합니다. + public void flush() { + em.flush(); + } + + // 데이터 수정 (Update) + // 참고: Service 레이어의 @Transactional에 의해 자동으로 DB에 반영됩니다. + // 별도의 update 메서드 없이, find로 가져온 객체의 값을 변경하면 됩니다. +} diff --git a/src/main/java/kr/co/dbnt/wms/cache/dao/WmsCacheDao.java b/src/main/java/kr/co/dbnt/wms/cache/dao/WmsCacheDao.java new file mode 100644 index 0000000..20bd087 --- /dev/null +++ b/src/main/java/kr/co/dbnt/wms/cache/dao/WmsCacheDao.java @@ -0,0 +1,39 @@ +package kr.co.dbnt.wms.cache.dao; + +import kr.co.dbnt.wms.cache.entity.WmsCache; +import org.springframework.stereotype.Repository; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.TypedQuery; +import java.util.List; + +/** + * Created by kimtheho on 25. 10. 15. + */ +@Repository +public class WmsCacheDao { + + @PersistenceContext + private EntityManager em; + + public void save(WmsCache wmsCache) { + em.persist(wmsCache); + } + + /** + * 요청 해시값으로 가장 최근 캐시 데이터를 1건 조회합니다. + * @param requestHash SHA-256으로 암호화된 요청 파라미터 + * @return WmsCache 객체 (없으면 null) + */ + public WmsCache findLatestByHash(String requestHash) { + // 'status'가 'SUCCESS'인 데이터만 조회하도록 조건을 추가합니다. + String jpql = "SELECT wc FROM WmsCache wc WHERE wc.requestHash = :hash AND wc.status = 'SUCCESS' ORDER BY wc.createdAt DESC"; + TypedQuery query = em.createQuery(jpql, WmsCache.class); + query.setParameter("hash", requestHash); + query.setMaxResults(1); // 가장 최신 1건만 가져옴 + + List results = query.getResultList(); + return results.isEmpty() ? null : results.get(0); + } +} \ No newline at end of file diff --git a/src/main/java/kr/co/dbnt/wms/cache/entity/TestTable.java b/src/main/java/kr/co/dbnt/wms/cache/entity/TestTable.java new file mode 100644 index 0000000..771a3d3 --- /dev/null +++ b/src/main/java/kr/co/dbnt/wms/cache/entity/TestTable.java @@ -0,0 +1,55 @@ +package kr.co.dbnt.wms.cache.entity; + +import javax.persistence.*; +/** + * Created by kimtheho on 25. 10. 15. + */ +@Entity +@Table(name = "test") +public class TestTable { + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "test_id_seq_generator") + @SequenceGenerator( + name = "test_id_seq_generator", + sequenceName = "test_id_seq", + allocationSize = 1 + ) + private Long id; + + @Column(name = "name", length = 100) + private String name; + + // JPA는 기본 생성자를 필요로 합니다. + public TestTable() { + } + + public TestTable(String name) { + this.name = name; + } + + // --- Getter and Setter --- + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return "TestTable{" + + "id=" + id + + ", name='" + name + '\'' + + '}'; + } +} \ No newline at end of file diff --git a/src/main/java/kr/co/dbnt/wms/cache/entity/WmsCache.java b/src/main/java/kr/co/dbnt/wms/cache/entity/WmsCache.java new file mode 100644 index 0000000..55ae485 --- /dev/null +++ b/src/main/java/kr/co/dbnt/wms/cache/entity/WmsCache.java @@ -0,0 +1,102 @@ +package kr.co.dbnt.wms.cache.entity; + +import javax.persistence.*; +import java.util.Date; + +/** + * Created by kimtheho on 25. 10. 15. + */ +@Entity +@Table(name = "wms_cache") +public class WmsCache { + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "wms_cache_id_seq_generator") + @SequenceGenerator( + name = "wms_cache_id_seq_generator", + sequenceName = "wms_cache_id_seq", // PostgreSQL이 자동으로 생성하는 시퀀스 이름 + allocationSize = 1 + ) + private Long id; + + @Column(name = "request_hash", length = 64, nullable = false) + private String requestHash; + + @Column(name = "request_params", columnDefinition = "TEXT") + private String requestParams; + + @Column(name = "file_path", length = 255, nullable = true) + private String filePath; + + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "created_at", nullable = false) + private Date createdAt; + + @Column(name = "status", length = 20) + private String status; + + + @Column(name = "error_message", columnDefinition = "TEXT") + private String errorMessage; + + // --- Constructors, Getters, Setters --- + + public WmsCache() { + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getRequestHash() { + return requestHash; + } + + public void setRequestHash(String requestHash) { + this.requestHash = requestHash; + } + + public String getRequestParams() { + return requestParams; + } + + public void setRequestParams(String requestParams) { + this.requestParams = requestParams; + } + + public String getFilePath() { + return filePath; + } + + public void setFilePath(String filePath) { + this.filePath = filePath; + } + + public Date getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Date createdAt) { + this.createdAt = createdAt; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } +} \ No newline at end of file diff --git a/src/main/java/kr/co/dbnt/wms/cache/service/TestService.java b/src/main/java/kr/co/dbnt/wms/cache/service/TestService.java new file mode 100644 index 0000000..d129628 --- /dev/null +++ b/src/main/java/kr/co/dbnt/wms/cache/service/TestService.java @@ -0,0 +1,49 @@ +package kr.co.dbnt.wms.cache.service; + +import kr.co.dbnt.wms.cache.dao.TestDao; +import kr.co.dbnt.wms.cache.entity.TestTable; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +/** + * Created by kimtheho on 25. 10. 15. + */ +@Service +@Transactional // 이 클래스의 모든 public 메서드는 하나의 트랜잭션으로 묶입니다. +public class TestService { + + @Autowired + private TestDao testDao; + + public void runCrudTest() { + // 1. Insert + System.out.println("--- Inserting new data ---"); + TestTable newData = new TestTable("First Data"); + testDao.save(newData); + + testDao.flush(); // DB에 즉시 반영 + + System.out.println("Saved data: " + newData); + + // 2. Select (방금 넣은 데이터 확인) + System.out.println("\n--- Finding data by ID ---"); + TestTable foundData = testDao.findById(newData.getId()); + System.out.println("Found data: " + foundData); + + // 3. Update + System.out.println("\n--- Updating data ---"); + foundData.setName("Updated Data"); + System.out.println("Data updated in memory. Transaction commit will save it to DB."); + } + + @Transactional(readOnly = true) // 조회 전용 트랜잭션 (성능에 유리) + public List getAllData() { + System.out.println("\n--- Finding all data ---"); + List allData = testDao.findAll(); + System.out.println("All data in DB: " + allData); + return allData; + } +} \ No newline at end of file diff --git a/src/main/java/kr/co/dbnt/wms/cache/service/WmsCacheService.java b/src/main/java/kr/co/dbnt/wms/cache/service/WmsCacheService.java new file mode 100644 index 0000000..a27067b --- /dev/null +++ b/src/main/java/kr/co/dbnt/wms/cache/service/WmsCacheService.java @@ -0,0 +1,152 @@ +package kr.co.dbnt.wms.cache.service; + +import kr.co.dbnt.wms.cache.dao.WmsCacheDao; +import kr.co.dbnt.wms.cache.entity.WmsCache; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.http.HttpEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Date; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import org.springframework.beans.factory.annotation.Value; + +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.conn.ssl.SSLContexts; +import org.apache.http.conn.ssl.TrustStrategy; +import javax.net.ssl.SSLContext; +import java.security.cert.X509Certificate; + +/** + * Created by kimtheho on 25. 10. 15. + */ +@Service +@Transactional +public class WmsCacheService { + + @Autowired + private WmsCacheDao wmsCacheDao; + + @Value("${wms.cache.directory}") + private String cacheDir; + + @Value("${wms.upstream.url}") + private String upstreamWmsUrl; + + @Value("${wms.upstream.apikey}") + private String upstreamApiKey; + + /** + * WMS 이미지를 가져오는 메인 메서드 + * @param queryString 사용자의 요청 파라미터 문자열 (API 키 제외) + * @return 이미지 파일 객체 + * @throws IOException + */ + public File getImage(String queryString) throws IOException { + String requestHash = DigestUtils.sha256Hex(queryString); + WmsCache latestCache = wmsCacheDao.findLatestByHash(requestHash); + + // 1. 유효한 캐시가 있는지 확인 (100일 이내) + if (latestCache != null) { + long diff = new Date().getTime() - latestCache.getCreatedAt().getTime(); + long days = TimeUnit.MILLISECONDS.toDays(diff); + if (days <= 100) { + System.out.println(">>> Cache HIT! Returning file: " + latestCache.getFilePath()); + File cachedFile = new File(latestCache.getFilePath()); + if (cachedFile.exists()) { + return cachedFile; + } + } + } + + // 2. 유효한 캐시가 없으면 새로 요청하여 저장 (Cache Miss) + System.out.println(">>> Cache MISS! Fetching new image..."); + return fetchAndCacheImage(queryString, requestHash); + } + + /** + * 외부 WMS 서버에서 이미지를 가져와 파일과 DB에 저장합니다. + */ + private File fetchAndCacheImage(String queryString, String requestHash) throws IOException { + // --- 수정된 부분 --- + // 외부 서버로 요청할 URL을 생성할 때, 원본 queryString에 API 키를 추가합니다. + // WMS 표준에서는 파라미터 순서가 중요하지 않으므로, 가장 간단하고 안전하게 맨 뒤에 추가합니다. + String queryStringWithKey = queryString + "&key=" + upstreamApiKey; + String requestUrl = upstreamWmsUrl + "?" + queryStringWithKey; + + System.out.println(">>> Requesting to upstream: " + requestUrl); + // ------------------ + + try { + // SSL 인증서를 무시하는 SSLContext 생성 + SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, new TrustStrategy() { + @Override + public boolean isTrusted(X509Certificate[] chain, String authType) { + return true; // 모든 인증서를 신뢰함 + } + }).build(); + + SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext); + + // SSL 설정을 적용한 HttpClient 생성 + try (CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build(); + CloseableHttpResponse response = httpClient.execute(new HttpGet(requestUrl))) { + + HttpEntity entity = response.getEntity(); + if (response.getStatusLine().getStatusCode() != 200 || entity == null) { + // HTTP 에러 발생 시 예외를 던짐 + throw new IOException("Failed to fetch image from upstream server: " + response.getStatusLine()); + } + + String fileName = UUID.randomUUID().toString() + ".png"; + File imageFile = new File(cacheDir, fileName); + imageFile.getParentFile().mkdirs(); + + try (InputStream inputStream = entity.getContent(); + FileOutputStream outputStream = new FileOutputStream(imageFile)) { + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + } + } + + // 성공 정보를 DB에 저장 (이때는 API 키가 없는 원본 queryString을 저장) + WmsCache newCache = new WmsCache(); + newCache.setStatus("SUCCESS"); // 성공 상태 기록 + newCache.setRequestHash(requestHash); + newCache.setRequestParams(queryString); + newCache.setFilePath(imageFile.getAbsolutePath()); + newCache.setCreatedAt(new Date()); + wmsCacheDao.save(newCache); + + System.out.println(">>> New image cached at: " + imageFile.getAbsolutePath()); + return imageFile; + } + } catch (Exception e) { + System.err.println("!!! Failed to fetch image. Logging error to DB. Reason: " + e.getMessage()); + + // 실패 정보를 DB에 저장 (이때도 API 키가 없는 원본 queryString을 저장) + WmsCache errorCache = new WmsCache(); + errorCache.setStatus("ERROR"); // 에러 상태 기록 + errorCache.setRequestHash(requestHash); + errorCache.setRequestParams(queryString); + errorCache.setErrorMessage(e.toString()); // 에러 메시지 기록 + errorCache.setCreatedAt(new Date()); + wmsCacheDao.save(errorCache); + + // 원래 발생했던 예외를 다시 던져서 Controller가 알 수 있도록 함 + throw new IOException("Failed to create SSL context or execute HTTP request", e); + } + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..561b7c3 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,22 @@ +db.driver=org.postgresql.Driver +db.url=jdbc:postgresql://localhost:5432/wms_cache +db.username=postgres +db.password=geoinfo + +# Hibernate +hibernate.dialect=org.hibernate.dialect.PostgreSQL9Dialect +hibernate.show_sql=true +hibernate.format_sql=true +#hibernate.hbm2ddl.auto=create ɼ ø̼ ۵ ̺ ϰ ̺ Ͱ ϴ. +# ܰ: ̺ ٲ ſ մϴ. +# ܰ: Ͱ ǹǷ create update ϸ ˴ϴ.  ÿ validate Ǵ none ؾ մϴ. +hibernate.hbm2ddl.auto=update + + +# WMS Cache Server Settings +# ܺ WMS ⺻ URL +wms.upstream.url=https://data.kigam.re.kr/openapi/wms +# ܺ WMS API Ű( https://data.kigam.re.kr/my-openapi/request/2213 ) +wms.upstream.apikey=4n4YdSOGgluWg8KEqxPYQfsxQB8Iqo +# ij ̹ +wms.cache.directory=/opt/wms_cache/wms_cache_images/ \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/index.jsp b/src/main/webapp/WEB-INF/jsp/index.jsp new file mode 100644 index 0000000..0aa225b --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/index.jsp @@ -0,0 +1,10 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + + + Spring 3.1 Test + + +

It works

+ + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/test_db.jsp b/src/main/webapp/WEB-INF/jsp/test_db.jsp new file mode 100644 index 0000000..52a91ec --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/test_db.jsp @@ -0,0 +1,34 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + + + Spring 3.1 Test + + +

${message}

+ +
+

Data in 'test' table:

+ + + + + + + + + + + + + + + + + + + + +
IDName
${item.id}${item.name}
No data found.
+ + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/spring/applicationContext.xml b/src/main/webapp/WEB-INF/spring/applicationContext.xml new file mode 100644 index 0000000..c6f9520 --- /dev/null +++ b/src/main/webapp/WEB-INF/spring/applicationContext.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + ${hibernate.dialect} + ${hibernate.show_sql} + ${hibernate.format_sql} + ${hibernate.hbm2ddl.auto} + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/spring/dispatcher-servlet.xml b/src/main/webapp/WEB-INF/spring/dispatcher-servlet.xml new file mode 100644 index 0000000..86987fa --- /dev/null +++ b/src/main/webapp/WEB-INF/spring/dispatcher-servlet.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..cd20182 --- /dev/null +++ b/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,46 @@ + + + + MySpringProject + + + contextConfigLocation + /WEB-INF/spring/applicationContext.xml + + + + org.springframework.web.context.ContextLoaderListener + + + + dispatcher + org.springframework.web.servlet.DispatcherServlet + + contextConfigLocation + /WEB-INF/spring/dispatcher-servlet.xml + + 1 + + + + dispatcher + / + + + + encodingFilter + org.springframework.web.filter.CharacterEncodingFilter + + encoding + UTF-8 + + + + encodingFilter + /* + + + \ No newline at end of file