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
+
버전닝이 꺼져 있으면(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-