From 982187298c810b5eea9edf07724ea5abf0d399d3 Mon Sep 17 00:00:00 2001 From: Rinjae Date: Mon, 7 Sep 2026 10:55:43 +0900 Subject: [PATCH] =?UTF-8?q?=EC=A0=95=EC=A0=81=20=EB=A6=AC=EC=86=8C?= =?UTF-8?q?=EC=8A=A4=20=EC=84=9C=EB=B9=99=20=EB=B0=8F=20=EB=B9=8C=EB=93=9C?= =?UTF-8?q?=20=ED=94=84=EB=A1=9C=EC=84=B8=EC=8A=A4=20=EA=B0=9C=EC=84=A0:?= =?UTF-8?q?=20-=20OHS=20=ED=99=98=EA=B2=BD=20=EB=8C=80=EC=9D=91=20?= =?UTF-8?q?=EC=A0=95=EC=A0=81=EC=9E=90=EC=9B=90=20=ED=95=B4=EC=8B=9C=20?= =?UTF-8?q?=EB=B2=84=EC=A0=84=EB=8B=9D=20OFF=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EB=B0=98=EC=98=81=20-=20eapim-portal-static.zip=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EB=B0=8F=20CI/CD=20=EC=B6=94=EA=B0=80=20-=20OHS=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EC=83=98=ED=94=8C=20=EA=B0=80=EC=9D=B4?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Jenkinsfile.deploy | 3 +- Jenkinsfile.test-build | 3 +- build.gradle | 12 ++++ djb-docs/OHS-StaticResource-설정.md | 64 +++++++++++++++++++ .../PortalConfigWebDispatcherServlet.java | 35 ++++++---- src/main/resources/application-prod.yml | 10 +-- 6 files changed, 110 insertions(+), 17 deletions(-) create mode 100644 djb-docs/OHS-StaticResource-설정.md diff --git a/Jenkinsfile.deploy b/Jenkinsfile.deploy index 930f2ca..552586a 100644 --- a/Jenkinsfile.deploy +++ b/Jenkinsfile.deploy @@ -74,8 +74,9 @@ pipeline { set -eu cd build/libs sha256sum eapim-portal.war > eapim-portal.war.sha256 + sha256sum eapim-portal-static.zip > eapim-portal-static.zip.sha256 ''' - archiveArtifacts artifacts: 'build/libs/eapim-portal.war,build/libs/eapim-portal.war.sha256', fingerprint: true + archiveArtifacts artifacts: 'build/libs/eapim-portal.war,build/libs/eapim-portal.war.sha256,build/libs/eapim-portal-static.zip,build/libs/eapim-portal-static.zip.sha256', fingerprint: true stash name: 'war', includes: 'build/libs/eapim-portal.war' } } diff --git a/Jenkinsfile.test-build b/Jenkinsfile.test-build index 2935c2e..a57fc43 100644 --- a/Jenkinsfile.test-build +++ b/Jenkinsfile.test-build @@ -96,8 +96,9 @@ pipeline { sha256sum "$f" > "$f.sha256" md5sum "$f" > "$f.md5" done + sha256sum eapim-portal-static.zip > eapim-portal-static.zip.sha256 ''' - archiveArtifacts artifacts: 'build/libs/eapim-portal.war,build/libs/eapim-portal-boot.war,build/libs/eapim-portal.war.sha1,build/libs/eapim-portal.war.sha256,build/libs/eapim-portal.war.md5,build/libs/eapim-portal-boot.war.sha1,build/libs/eapim-portal-boot.war.sha256,build/libs/eapim-portal-boot.war.md5', fingerprint: true + archiveArtifacts artifacts: 'build/libs/eapim-portal.war,build/libs/eapim-portal-boot.war,build/libs/eapim-portal.war.sha1,build/libs/eapim-portal.war.sha256,build/libs/eapim-portal.war.md5,build/libs/eapim-portal-boot.war.sha1,build/libs/eapim-portal-boot.war.sha256,build/libs/eapim-portal-boot.war.md5,build/libs/eapim-portal-static.zip,build/libs/eapim-portal-static.zip.sha256', fingerprint: true } } } diff --git a/build.gradle b/build.gradle index a021c9c..e367af4 100644 --- a/build.gradle +++ b/build.gradle @@ -315,6 +315,18 @@ war { classpath = excludeLocalOnlyLibs(classpath) } +// 정적 리소스만 별도 zip (OHS 추가 배포용). war/bootWar 는 그대로 정적 리소스 포함 유지 +// (WAS 단독 접속 인원 지원). sass/ 는 컴파일 소스이며 실제 서빙 경로에 없어 제외 +// (PortalConfigWebDispatcherServlet#addResourceHandlers 기준). +task staticResourcesZip(type: Zip) { + archiveFileName = "eapim-portal-static.zip" + destinationDirectory = file("$buildDir/libs") + from('src/main/resources/static') { + exclude 'sass/**' + } +} +assemble.dependsOn staticResourcesZip + task printSourceSets { doLast { sourceSets.each { srcSet -> diff --git a/djb-docs/OHS-StaticResource-설정.md b/djb-docs/OHS-StaticResource-설정.md new file mode 100644 index 0000000..9a2abcc --- /dev/null +++ b/djb-docs/OHS-StaticResource-설정.md @@ -0,0 +1,64 @@ +## OHS Static Resource 설정 예시 + +```apacheconf + + ServerName weblogic-djb.rinjae.kr + + + SSLEngine on + SSLVerifyClient None + SSLProtocol TLSv1.2 TLSv1.3 + SSLHonorCipherOrder on + SSLCipherSuite TLS_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384,TLS_CHACHA20_POLY1305_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + SSLWallet "${ORACLE_INSTANCE}/config/fmwconfig/components/${COMPONENT_TYPE}/instances/${COMPONENT_NAME}/keystores/default" + + + SSLOptions +StdEnvVars + + + + SSLOptions +StdEnvVars + + + BrowserMatch "MSIE [2-5]" \ + nokeepalive ssl-unclean-shutdown \ + downgrade-1.0 force-response-1.0 + + + Header always set Strict-Transport-Security "max-age=63072000; preload; includeSubDomains" + + + + Alias /css /ohs/static/eapim-portal/css + Alias /js /ohs/static/eapim-portal/js + Alias /img /ohs/static/eapim-portal/img + Alias /images /ohs/static/eapim-portal/images + Alias /webfonts /ohs/static/eapim-portal/webfonts + Alias /font /ohs/static/eapim-portal/font + Alias /html /ohs/static/eapim-portal/html + Alias /plugins /ohs/static/eapim-portal/plugins + + + Require all granted + Options -Indexes + + + + Header set Cache-Control "no-cache" + + + + + DirectoryIndex disabled + SetHandler weblogic-handler + WebLogicHost 172.30.1.100 + WebLogicPort 39130 + DynamicServerList OFF + ConnectTimeoutSecs 10 + ConnectRetrySecs 2 + WLProxySSL ON + + + + +``` \ No newline at end of file diff --git a/src/main/java/com/eactive/apim/portal/config/PortalConfigWebDispatcherServlet.java b/src/main/java/com/eactive/apim/portal/config/PortalConfigWebDispatcherServlet.java index 6a965f0..ae091ea 100644 --- a/src/main/java/com/eactive/apim/portal/config/PortalConfigWebDispatcherServlet.java +++ b/src/main/java/com/eactive/apim/portal/config/PortalConfigWebDispatcherServlet.java @@ -175,31 +175,44 @@ public class PortalConfigWebDispatcherServlet implements WebMvcConfigurer { * {@code /css/main.css} 요청을 내용 해시가 포함된 {@code /css/main-.css} 로 매핑한다. * 내용이 바뀌면 URL 이 바뀌므로 강제 새로고침 없이 브라우저 캐시가 무효화된다. Thymeleaf 의 * {@code @{/css/main.css}} 링크는 {@link #resourceUrlEncodingFilter()} 가 해시 URL 로 치환한다.

+ * + *

버전닝이 꺼져 있으면(prod 포함 — {@link #isResourceVersioningEnabled()} 참고) URL 이 + * 고정이라 장기 {@code max-age} 캐시를 쓰면 배포 후에도 브라우저가 옛 내용을 계속 쓸 위험이 있다. + * 이 경우 {@code no-cache}(매 요청 {@code If-Modified-Since} 조건부 재검증, 변경 없으면 304)로 + * 신선도를 보장한다 — OHS 가 정적 리소스를 직접 서빙해도 Apache 가 파일 mtime 기준으로 동일하게 + * Last-Modified/조건부 GET 을 처리하므로 WAS 와 동작이 어긋나지 않는다 + * (배경: djb-obsidian-docs/3000-테스트/15-Static리소스분리).

*/ private void addStaticResourceHandler(ResourceHandlerRegistry registry, String pattern, String... locations) { - // 캐싱 ON(prod) 이면 브라우저에 1일 캐시를, OFF(dev/local) 면 no-store 를 내려보낸다. - // no-store 는 브라우저가 아예 저장하지 않으므로 강제 새로고침 없이 sass/JS 변경이 바로 보인다. - CacheControl cacheControl = isResourceCachingEnabled() - ? CacheControl.maxAge(1, TimeUnit.DAYS) - : CacheControl.noStore(); + boolean versioningOn = isResourceVersioningEnabled(); + CacheControl cacheControl; + if (!isResourceCachingEnabled()) { + // no-store: 브라우저가 아예 저장하지 않으므로 강제 새로고침 없이 sass/JS 변경이 바로 보인다(dev/local). + cacheControl = CacheControl.noStore(); + } else if (versioningOn) { + cacheControl = CacheControl.maxAge(1, TimeUnit.DAYS); + } else { + cacheControl = CacheControl.noCache(); + } ResourceChainRegistration chain = registry.addResourceHandler(pattern) .addResourceLocations(locations) .setCacheControl(cacheControl) .resourceChain(isResourceCachingEnabled()); - if (isResourceVersioningEnabled()) { + if (versioningOn) { chain.addResolver(new VersionResourceResolver().addContentVersionStrategy("/**")); } chain.addResolver(new PathResourceResolver()); } /** - * 정적자원 해시 버전닝 활성 여부. prod 프로파일은 토글과 무관하게 항상 ON, - * 그 외 프로파일은 {@code app.resource-versioning.enabled} 값을 따른다. + * 정적자원 해시 버전닝 활성 여부. {@code app.resource-versioning.enabled} 값을 그대로 따른다. + * + *

과거 prod 프로파일은 이 토글과 무관하게 항상 ON 이었으나, OHS 가 정적 리소스를 직접 + * 서빙하는 구조로 바뀌면서 OFF 로 전환했다 — 해시가 붙은 URL(main-<hash>.css)이 OHS + * 디스크의 무해시 원본과 어긋나 404 가 날 수 있어서다(prod.yml 에서 명시적으로 false 설정). + * 대신 캐시 신선도는 {@link #addStaticResourceHandler} 의 no-cache 조건부 재검증으로 보장한다.

*/ private boolean isResourceVersioningEnabled() { - if (environment.acceptsProfiles(Profiles.of("prod"))) { - return true; - } return resourceVersioningEnabled; } diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 58703ce..fb510a3 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -9,9 +9,11 @@ spring: cachecontrol: max-age: 86400 public: true - # 정적자원 콘텐츠 해시 버전닝은 PortalConfigWebDispatcherServlet 가 prod 프로파일에서 - # 항상 ON 으로 수행한다(app.resource-versioning.enabled 토글 무시). - # 해시 URL + 장기 캐시(max-age 86400)로 배포 시 자동 캐시 무효화. + # 정적자원 콘텐츠 해시 버전닝: OHS 가 정적 리소스(css/js/img 등)를 직접 서빙하는 구조에서는 + # 해시 URL(main-.css)이 OHS 디스크의 무해시 원본과 어긋나 404 위험이 있어 OFF 로 전환했다 + # (아래 app.resource-versioning.enabled: false, PortalConfigWebDispatcherServlet 참고). + # 캐시 무효화는 대신 no-cache(매 요청 If-Modified-Since 조건부 재검증)로 보장한다. + # 배경: djb-obsidian-docs/3000-테스트/15-Static리소스분리 jpa: properties: hibernate: @@ -28,7 +30,7 @@ spring: app: resource-versioning: - enabled: true + enabled: false resource-caching: enabled: true