From 0ad5bffa4fde037ce051b771dfeab10e48efaf34 Mon Sep 17 00:00:00 2001 From: Rinjae Date: Tue, 11 Aug 2026 11:47:31 +0900 Subject: [PATCH] =?UTF-8?q?DJB=20=EC=BB=A4=EC=8A=A4=ED=85=80=20=EC=86=8C?= =?UTF-8?q?=EC=8A=A4=20=EB=82=A9=ED=92=88=20=EC=8A=A4=ED=81=AC=EB=A6=BD?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80:=20-=20Windows,=20Linux/macOS=20?= =?UTF-8?q?=EC=A7=80=EC=9B=90=20=EB=B0=B0=EC=B9=98/=EC=85=B8=20=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EA=B5=AC=ED=98=84=20-=20?= =?UTF-8?q?=EC=86=8C=EC=8A=A4=20=ED=95=84=ED=84=B0=20=EA=B7=9C=EC=B9=99=20?= =?UTF-8?q?=EB=B0=8F=20=EC=88=98=EC=A7=91=20=ED=8C=8C=EC=9D=BC/=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20=EB=A7=A4=EB=8B=88=ED=8E=98=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- script/djb-custom-export/README.md | 112 ++++++ script/djb-custom-export/export-custom.bat | 29 ++ script/djb-custom-export/export-custom.ps1 | 278 ++++++++++++++ script/djb-custom-export/export-custom.sh | 352 ++++++++++++++++++ script/djb-custom-export/export-filter.rules | 74 ++++ .../filelist-no-djb-marker.txt | 157 ++++++++ script/djb-custom-export/filelist.txt | 173 +++++++++ script/djb-custom-export/modules.conf | 26 ++ .../config/PortalConfigForwardedHeader.java | 91 +++++ .../apim/portal/djb/guide/GuideProperty.java | 45 +++ 10 files changed, 1337 insertions(+) create mode 100644 script/djb-custom-export/README.md create mode 100644 script/djb-custom-export/export-custom.bat create mode 100644 script/djb-custom-export/export-custom.ps1 create mode 100755 script/djb-custom-export/export-custom.sh create mode 100644 script/djb-custom-export/export-filter.rules create mode 100644 script/djb-custom-export/filelist-no-djb-marker.txt create mode 100644 script/djb-custom-export/filelist.txt create mode 100644 script/djb-custom-export/modules.conf create mode 100644 src/main/java/com/eactive/apim/portal/config/PortalConfigForwardedHeader.java create mode 100644 src/main/java/com/eactive/apim/portal/djb/guide/GuideProperty.java diff --git a/script/djb-custom-export/README.md b/script/djb-custom-export/README.md new file mode 100644 index 0000000..624ed95 --- /dev/null +++ b/script/djb-custom-export/README.md @@ -0,0 +1,112 @@ +# DJB 커스텀 소스 납품본 수집 + +발주사 납품용으로, 기준 시점 이후 **신규로 추가된 커스텀 소스 파일**만 모아 +디렉터리 구조를 유지한 채 한곳에 복사하고 `MANIFEST.csv` / `SUMMARY.md` / zip 을 만든다. + +## 파일 + +| 파일 | 용도 | +|---|---| +| `export-custom.sh` | macOS / Linux 실행 스크립트 | +| `export-custom.ps1` | Windows PowerShell 실행 스크립트 (실제 로직) | +| `export-custom.bat` | Windows cmd 래퍼 — 인자를 `.ps1` 로 그대로 전달 | +| `modules.conf` | 대상 모듈 / 소스 경로 / 수집 확장자 | +| `export-filter.rules` | 포함·제외 규칙 (`.gitignore` 유사 문법) | +| `filelist.txt` | 마지막으로 뽑은 수집 대상 목록 (검토용, 재생성 가능) | +| `filelist-no-djb-marker.txt` | 그중 `djb` / `custom` 마커가 **없는** 파일 — 업그레이드 유입분 검토 대상 | + +## 수집 방식 + +1. 모듈별로 **컷오프 날짜 직전의 마지막 커밋**을 base 로 잡는다. + `git rev-list -1 --before= HEAD` + 컷오프 이전 커밋이 아예 없으면 저장소 전체를 신규로 본다. +2. `git diff --name-only --diff-filter=A -M HEAD -- <소스경로>` 로 + **신규 추가(A)** 파일만 뽑는다. `-M` 이라 단순 rename 은 신규로 잡히지 않는다. +3. `modules.conf` 의 확장자 allowlist 로 거른다. (확장자 없는 파일은 제외) +4. `export-filter.rules` 로 포함·제외를 최종 결정한다. +5. 남은 파일을 `/src/<모듈>/<원래 경로>` 로 복사한다. + +## 사용법 + +```bash +# macOS / Linux +./export-custom.sh --dry-run > filelist.txt # 목록만 뽑기 (로그는 stderr) +./export-custom.sh --show-excluded # 규칙에 걸려 빠진 목록 + 걸린 규칙 +./export-custom.sh # 실제 수집 +./export-custom.sh --zip # 수집 + zip +./export-custom.sh -c 2026-05-01 -m eapim-portal +``` + +```powershell +# Windows +.\export-custom.ps1 -DryRun > filelist.txt +.\export-custom.ps1 -ShowExcluded +.\export-custom.ps1 -Zip +.\export-custom.ps1 -Cutoff 2026-05-01 -Module eapim-portal,eapim-admin + +# cmd 에서 +export-custom.bat -DryRun > filelist.txt +``` + +### 주요 옵션 + +| sh | ps1 | 설명 | +|---|---|---| +| `-c, --cutoff` | `-Cutoff` | 기준 날짜 (기본 `2026-05-01`) | +| `-r, --root` | `-Root` | 저장소 루트. 미지정 시 스크립트 위치 기준 자동 탐지 | +| `-o, --out` | `-Out` | 출력 디렉터리 (기본 `/build/djb-custom-export`) | +| `-m, --module` | `-Module` | 특정 모듈만 | +| `--zip` | `-Zip` | zip 생성 | +| `--include-untracked` | `-IncludeUntracked` | 미커밋 신규 파일도 포함 | +| `--dry-run` | `-DryRun` | 복사 없이 목록만 | +| `--show-excluded` | `-ShowExcluded` | 규칙에 걸려 빠진 목록 | + +## `export-filter.rules` 문법 + +`.gitignore` 와 비슷하되 더 단순하다. + +- 한 줄에 패턴 하나. `#` 주석, 빈 줄 무시 +- 기본은 **모두 포함**. 패턴에 걸리면 제외 +- `!` 로 시작하면 예외(다시 포함) +- **뒤에 오는 규칙이 앞 규칙을 덮는다** — 순서가 중요하다 +- 패턴에 `/` 가 있으면 `<모듈>/<경로>` 전체와 매칭, 없으면 파일명만 매칭 +- `/` 로 끝나면 그 디렉터리 하위 전체 +- `*` 는 `/` 도 포함해 매칭한다 (단순 glob, `**` 없음) + +매칭 대상 경로 예: + +``` +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/WebhookService.java +eapim-admin/WebContent/jsp/onl/apim/webhook/webhookList.jsp +``` + +규칙 파일은 세 구역으로 나뉜다. + +1. **업그레이드 유입분 제외** — `filelist-no-djb-marker.txt` 를 검토하며 채운다 +2. **`!*djb*` 예외** — djb 마커가 붙은 파일은 커스텀이므로 되살린다 +3. **무조건 제외** — 빌드 산출물·서드파티. 2)보다 뒤에 있어 최종 승리한다 + +## 신규지만 커스텀이 아닌 파일 걸러내기 + +신규 파일이라도 제품 업그레이드로 딸려 온 것일 수 있다. 판별 순서: + +```bash +# 1) 전체 목록 +./export-custom.sh --dry-run > filelist.txt + +# 2) djb / custom 마커가 없는 것 = 검토 대상 +grep -vi djb filelist.txt | grep -v '/custom/' > filelist-no-djb-marker.txt + +# 3) 검토 후 업그레이드 유입분을 export-filter.rules 의 1) 구역에 추가 + +# 4) 반영 확인 +./export-custom.sh --show-excluded +``` + +## 주의 + +- 컷오프 이전 커밋이 없으면 base 가 **빈 트리**가 되어 저장소 전체가 신규로 잡힌다. + `SUMMARY.md` 의 base 컬럼에 `(컷오프 이전 커밋 없음 …)` 으로 표시된다. +- 모듈에 컷오프 직전 커밋이 드문드문 있으면 base 가 컷오프보다 훨씬 이전으로 잡힐 수 있다. + 실행 로그의 `base:` 날짜를 항상 확인할 것. +- 추가된 뒤 삭제·이동된 파일은 현재 트리에 없으므로 건너뛰고, 건수를 `SUMMARY.md` 에 남긴다. diff --git a/script/djb-custom-export/export-custom.bat b/script/djb-custom-export/export-custom.bat new file mode 100644 index 0000000..5325663 --- /dev/null +++ b/script/djb-custom-export/export-custom.bat @@ -0,0 +1,29 @@ +@echo off +REM --------------------------------------------------------------------------- +REM DJB 커스텀 소스 납품본 수집 (Windows cmd 래퍼) +REM +REM 실제 로직은 export-custom.ps1 에 있다. 이 배치는 PowerShell 을 띄우고 +REM 전달받은 인자를 그대로 넘기기만 한다. +REM +REM 사용 예: +REM export-custom.bat +REM export-custom.bat -Cutoff 2026-05-01 -Zip +REM export-custom.bat -DryRun > filelist.txt +REM export-custom.bat -ShowExcluded +REM export-custom.bat -Module eapim-portal,eapim-admin +REM --------------------------------------------------------------------------- +setlocal + +REM 한글 경로/출력이 깨지지 않도록 코드페이지를 UTF-8 로 전환 +chcp 65001 >nul + +where powershell >nul 2>nul +if errorlevel 1 ( + echo [오류] PowerShell 을 찾을 수 없습니다. 1>&2 + exit /b 1 +) + +powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0export-custom.ps1" %* +set RC=%ERRORLEVEL% + +endlocal & exit /b %RC% diff --git a/script/djb-custom-export/export-custom.ps1 b/script/djb-custom-export/export-custom.ps1 new file mode 100644 index 0000000..7b859ec --- /dev/null +++ b/script/djb-custom-export/export-custom.ps1 @@ -0,0 +1,278 @@ +<# +.SYNOPSIS + DJB 커스텀 소스 납품본 수집 스크립트 (Windows / PowerShell) + +.DESCRIPTION + 기준 커밋(컷오프 직전 마지막 커밋) 이후 "신규로 추가된" 소스 파일만 모아 + 모듈별 디렉터리 구조를 유지한 채 출력 디렉터리에 복사하고, + MANIFEST.csv / SUMMARY.md / (선택) zip 아카이브를 생성한다. + +.EXAMPLE + .\export-custom.ps1 + .\export-custom.ps1 -Cutoff 2026-05-01 -Zip + .\export-custom.ps1 -DryRun + .\export-custom.ps1 -Module eapim-portal,eapim-admin +#> +[CmdletBinding()] +param( + [string] $Cutoff = '2026-05-01', + [string] $Root, + [string] $Out, + [string[]] $Module, + [switch] $Zip, + [switch] $IncludeUntracked, + [switch] $DryRun, + [switch] $ShowExcluded +) + +if ($ShowExcluded) { $DryRun = $true } + +$ErrorActionPreference = 'Stop' + +# git 출력의 한글이 깨지지 않도록 콘솔 인코딩을 UTF-8로 고정한다. +try { [Console]::OutputEncoding = [Text.Encoding]::UTF8 } catch { } +$OutputEncoding = [Text.Encoding]::UTF8 + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$ConfFile = Join-Path $ScriptDir 'modules.conf' +$RulesFile = Join-Path $ScriptDir 'export-filter.rules' + +# 저장소 루트: 스크립트는 \eapim-portal\script\djb-custom-export\ 에 있다. +if (-not $Root) { $Root = (Resolve-Path (Join-Path $ScriptDir '..\..\..')).Path } +if (-not (Test-Path -LiteralPath $Root -PathType Container)) { + throw "저장소 루트를 찾을 수 없다: $Root" +} +if (-not $Out) { $Out = Join-Path $Root 'build\djb-custom-export' } +if (-not (Test-Path -LiteralPath $ConfFile)) { throw "설정 파일 없음: $ConfFile" } + +# 빈 트리 해시 — base 커밋이 없을 때(= 모든 이력이 컷오프 이후) 사용한다. +$EmptyTree = '4b825dc642cb6eb9a060e54bf8d69288fbee4904' + +# ---- 설정 로드 ------------------------------------------------------------ +$Modules = @() +$AllowExt = @() + +foreach ($line in (Get-Content -LiteralPath $ConfFile -Encoding UTF8)) { + $t = $line.Trim() + if ($t -eq '' -or $t.StartsWith('#')) { continue } + $idx = $t.IndexOf('|') + if ($idx -lt 0) { continue } + $key = $t.Substring(0, $idx) + $rest = $t.Substring($idx + 1) + + switch ($key) { + 'MODULE' { + $i2 = $rest.IndexOf('|') + if ($i2 -lt 0) { continue } + $Modules += [pscustomobject]@{ + Name = $rest.Substring(0, $i2) + Paths = $rest.Substring($i2 + 1).Split(' ') | Where-Object { $_ -ne '' } + } + } + 'EXT' { $AllowExt = $rest.Split(',') | ForEach-Object { $_.Trim().ToLower() } | Where-Object { $_ -ne '' } } + } +} + +# ---- 포함/제외 규칙 로드 (.gitignore 유사) -------------------------------- +$Rules = @() +if (Test-Path -LiteralPath $RulesFile) { + foreach ($line in (Get-Content -LiteralPath $RulesFile -Encoding UTF8)) { + $t = $line.Trim() + if ($t -eq '' -or $t.StartsWith('#')) { continue } + $Rules += $t + } +} + +# 마지막으로 매칭된 규칙이 이긴다. 기본 포함, '!' 는 예외(다시 포함). +# 반환: @{ Keep = $true/$false; Rule = '<제외시킨 규칙>' } +function Test-RuleKeep([string]$RelPath) { + $leaf = Split-Path $RelPath -Leaf + $keep = $true + $matched = '' + foreach ($raw in $Rules) { + $rule = $raw + $neg = $false + if ($rule.StartsWith('!')) { $neg = $true; $rule = $rule.Substring(1) } + if ($rule.EndsWith('/')) { $rule = $rule + '*' } # 디렉터리 규칙 + $target = if ($rule.Contains('/')) { $RelPath } else { $leaf } + + if ($target -like $rule) { + if ($neg) { $keep = $true; $matched = '' } + else { $keep = $false; $matched = $rule } + } + } + return @{ Keep = $keep; Rule = $matched } +} + +function Test-AllowedExt([string]$RelPath) { + $leaf = Split-Path $RelPath -Leaf + if ($leaf -notmatch '\.') { return $false } # 확장자 없는 파일 제외 + $ext = $leaf.Substring($leaf.LastIndexOf('.') + 1).ToLower() + return $AllowExt -contains $ext +} + +function Invoke-Git([string]$Dir, [string[]]$GitArgs) { + $all = @('-C', $Dir, '-c', 'core.quotepath=false') + $GitArgs + $res = & git @all 2>$null + if ($LASTEXITCODE -ne 0) { return @() } + return @($res) +} + +# ---- 준비 ----------------------------------------------------------------- +$Manifest = Join-Path $Out 'MANIFEST.csv' +$Summary = Join-Path $Out 'SUMMARY.md' + +if (-not $DryRun) { + if (Test-Path -LiteralPath $Out) { Remove-Item -LiteralPath $Out -Recurse -Force } + New-Item -ItemType Directory -Path (Join-Path $Out 'src') -Force | Out-Null + # Excel 한글용 UTF-8 BOM + $utf8Bom = New-Object System.Text.UTF8Encoding($true) + [IO.File]::WriteAllText($Manifest, "module,path,ext,added_commit,added_date,author`r`n", $utf8Bom) +} + +Write-Host "저장소 루트 : $Root" +Write-Host "기준 날짜 : $Cutoff (이 날짜 직전 마지막 커밋이 base)" +Write-Host "출력 경로 : $Out" +if ($DryRun) { Write-Host '모드 : DRY-RUN (복사 안 함)' } +Write-Host '' + +$Rows = New-Object System.Collections.Generic.List[string] +$SummaryRs = New-Object System.Collections.Generic.List[object] +$Total = 0 +$Missing = 0 +$Excluded = 0 + +# ---- 모듈 순회 ------------------------------------------------------------ +foreach ($m in $Modules) { + if ($Module -and ($Module -notcontains $m.Name)) { continue } + + $modDir = Join-Path $Root $m.Name + if (-not (Test-Path -LiteralPath (Join-Path $modDir '.git'))) { + Write-Host "[건너뜀] $($m.Name) — git 저장소 아님 ($modDir)" + continue + } + + # base 커밋 결정 + $base = (Invoke-Git $modDir @('rev-list', '-1', "--before=$Cutoff", 'HEAD') | Select-Object -First 1) + if ([string]::IsNullOrWhiteSpace($base)) { + $base = $EmptyTree + $baseDesc = '(컷오프 이전 커밋 없음 → 전체를 신규로 간주)' + } else { + $d = (Invoke-Git $modDir @('log', '-1', '--format=%h %ad %s', '--date=short', $base) | Select-Object -First 1) + $baseDesc = if ($d.Length -gt 80) { $d.Substring(0, 80) } else { $d } + } + + # 실제 존재하는 소스 경로만 pathspec 으로 사용 + $spec = @() + foreach ($p in $m.Paths) { + if (Test-Path -LiteralPath (Join-Path $modDir ($p -replace '/', '\')) -PathType Container) { $spec += $p } + } + if ($spec.Count -eq 0) { + Write-Host "[건너뜀] $($m.Name) — 설정된 소스 경로가 존재하지 않음" + continue + } + + # 신규(Added) 파일만. -M 으로 rename 은 신규에서 제외한다. + $files = @(Invoke-Git $modDir (@('diff', '--name-only', '--diff-filter=A', '-M', $base, 'HEAD', '--') + $spec)) + + if ($IncludeUntracked) { + $files += @(Invoke-Git $modDir (@('ls-files', '--others', '--exclude-standard', '--') + $spec)) + } + + $files = $files | Where-Object { $_ -ne '' } | Sort-Object -Unique + + $count = 0 + $miss = 0 + foreach ($f in $files) { + if (-not (Test-AllowedExt $f)) { continue } + + # 규칙 매칭은 <모듈>/<경로> 전체 문자열 기준 + $rk = Test-RuleKeep "$($m.Name)/$f" + if (-not $rk.Keep) { + $Excluded++ + if ($ShowExcluded) { Write-Output "$($m.Name)/$f`t# $($rk.Rule)" } + continue + } + if ($ShowExcluded) { continue } + + $src = Join-Path $modDir ($f -replace '/', '\') + if (-not (Test-Path -LiteralPath $src -PathType Leaf)) { + # 추가된 뒤 삭제/이동된 파일 — 납품 대상 아님 + Write-Warning " [없음] $($m.Name)/$f" + $miss++ + continue + } + + $count++ + # 목록은 Write-Output(성공 스트림) 으로 — 리다이렉트하면 목록만 파일로 떨어진다. + if ($DryRun) { Write-Output "$($m.Name)/$f"; continue } + + $dst = Join-Path (Join-Path $Out 'src') (Join-Path $m.Name ($f -replace '/', '\')) + $dstDir = Split-Path -Parent $dst + if (-not (Test-Path -LiteralPath $dstDir)) { New-Item -ItemType Directory -Path $dstDir -Force | Out-Null } + Copy-Item -LiteralPath $src -Destination $dst -Force + + $meta = (Invoke-Git $modDir @('log', '-1', '--diff-filter=A', '--format=%h|%ad|%an', '--date=short', '--', $f) | Select-Object -First 1) + $cHash = ''; $cDate = ''; $cAuth = '' + if ($meta) { + $parts = $meta.Split('|') + if ($parts.Count -ge 3) { $cHash = $parts[0]; $cDate = $parts[1]; $cAuth = $parts[2] } + } + $leaf = Split-Path $f -Leaf + $ext = if ($leaf -match '\.') { $leaf.Substring($leaf.LastIndexOf('.') + 1) } else { '' } + + $esc = { param($s) '"' + ($s -replace '"', '""') + '"' } + $Rows.Add((@( + (& $esc $m.Name), (& $esc $f), (& $esc $ext), + (& $esc $cHash), (& $esc $cDate), (& $esc $cAuth) + ) -join ',')) + } + + $Total += $count + $Missing += $miss + $SummaryRs.Add([pscustomobject]@{ Module = $m.Name; Count = $count; Base = $baseDesc }) + Write-Host ("[수집] {0,-22} {1,5} 개 base: {2}" -f $m.Name, $count, $baseDesc) +} + +# ---- 출력 ------------------------------------------------------------------ +if (-not $DryRun) { + if ($Rows.Count -gt 0) { + [IO.File]::AppendAllText($Manifest, (($Rows -join "`r`n") + "`r`n"), (New-Object System.Text.UTF8Encoding($false))) + } + + $sb = New-Object System.Text.StringBuilder + [void]$sb.AppendLine('# DJB 커스텀 소스 납품본') + [void]$sb.AppendLine('') + [void]$sb.AppendLine("- 생성 일시: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')") + [void]$sb.AppendLine("- 기준 날짜: ``$Cutoff`` (이 날짜 직전 마지막 커밋을 base로 삼아, 이후 **신규 추가된** 파일만 수집)") + [void]$sb.AppendLine('- 수집 규칙: `git diff --diff-filter=A -M HEAD` / 확장자 allowlist / `export-filter.rules` 필터') + [void]$sb.AppendLine("- 대상 확장자: ``$($AllowExt -join ',')``") + [void]$sb.AppendLine("- 규칙 파일: ``$(Split-Path $RulesFile -Leaf)`` ($($Rules.Count) 개 규칙, 제외 $Excluded 건)") + [void]$sb.AppendLine('') + [void]$sb.AppendLine('## 모듈별 수집 결과') + [void]$sb.AppendLine('') + [void]$sb.AppendLine('| 모듈 | 파일 수 | base 커밋 |') + [void]$sb.AppendLine('|---|---:|---|') + foreach ($r in $SummaryRs) { [void]$sb.AppendLine("| $($r.Module) | $($r.Count) | $($r.Base) |") } + [void]$sb.AppendLine('') + [void]$sb.AppendLine("**합계: $Total 개**") + if ($Missing -gt 0) { [void]$sb.AppendLine("> 추가 후 삭제/이동되어 현재 트리에 없는 파일 $Missing 개는 제외됨.") } + [void]$sb.AppendLine('') + [void]$sb.AppendLine('## 파일 목록') + [void]$sb.AppendLine('') + [void]$sb.AppendLine('`MANIFEST.csv` 참조 (module, path, ext, 최초 추가 커밋/일자/작성자).') + + [IO.File]::WriteAllText($Summary, $sb.ToString(), (New-Object System.Text.UTF8Encoding($true))) + + Write-Host '' + Write-Host "MANIFEST : $Manifest" + Write-Host "SUMMARY : $Summary" + + if ($Zip) { + $zipName = "djb-custom-export-$(Get-Date -Format 'yyyyMMdd').zip" + $zipPath = Join-Path (Split-Path -Parent $Out) $zipName + if (Test-Path -LiteralPath $zipPath) { Remove-Item -LiteralPath $zipPath -Force } + Compress-Archive -Path (Join-Path $Out '*') -DestinationPath $zipPath + Write-Host "ZIP : $zipPath" + } +} diff --git a/script/djb-custom-export/export-custom.sh b/script/djb-custom-export/export-custom.sh new file mode 100755 index 0000000..1c5e30b --- /dev/null +++ b/script/djb-custom-export/export-custom.sh @@ -0,0 +1,352 @@ +#!/usr/bin/env bash +# +# DJB 커스텀 소스 납품본 수집 스크립트 (macOS / Linux) +# +# 기준 커밋(컷오프 직전 마지막 커밋) 이후 "신규로 추가된" 소스 파일만 모아 +# 모듈별 디렉터리 구조를 유지한 채 출력 디렉터리에 복사하고, +# MANIFEST.csv / SUMMARY.md / (선택) zip 아카이브를 생성한다. +# +# 사용법: +# ./export-custom.sh # 기본값으로 실행 +# ./export-custom.sh -c 2026-05-01 --zip +# ./export-custom.sh --dry-run +# ./export-custom.sh -m eapim-portal -m eapim-admin +# +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +CONF_FILE="$SCRIPT_DIR/modules.conf" +RULES_FILE="$SCRIPT_DIR/export-filter.rules" + +# ---- 기본값 --------------------------------------------------------------- +CUTOFF="2026-05-01" +REPO_ROOT="" +OUT_DIR="" +MAKE_ZIP=0 +DRY_RUN=0 +INCLUDE_UNTRACKED=0 +ONLY_MODULES="" +SHOW_EXCLUDED=0 + +usage() { + cat <<'EOF' +DJB 커스텀 소스 납품본 수집 + +옵션: + -c, --cutoff 기준 날짜. 이 날짜 직전 마지막 커밋을 base로 삼는다. + (기본: 2026-05-01) + -r, --root 저장소 루트(djb-eapim). 미지정 시 스크립트 위치에서 자동 탐지. + -o, --out 출력 디렉터리. (기본: /build/djb-custom-export) + -m, --module 특정 모듈만 수집. 여러 번 지정 가능. + --zip 수집 후 zip 아카이브 생성. + --include-untracked git에 아직 커밋되지 않은 신규 파일도 포함. + --dry-run 복사하지 않고 대상 목록만 stdout 으로 출력(로그는 stderr). + --show-excluded --dry-run 과 함께. 규칙에 걸려 제외된 목록을 대신 출력. + -h, --help 도움말. + +포함/제외 규칙은 export-filter.rules (.gitignore 유사 문법) 에서 관리한다. +EOF +} + +while [ $# -gt 0 ]; do + case "$1" in + -c|--cutoff) CUTOFF="$2"; shift 2 ;; + -r|--root) REPO_ROOT="$2"; shift 2 ;; + -o|--out) OUT_DIR="$2"; shift 2 ;; + -m|--module) ONLY_MODULES="$ONLY_MODULES $2"; shift 2 ;; + --zip) MAKE_ZIP=1; shift ;; + --include-untracked) INCLUDE_UNTRACKED=1; shift ;; + --dry-run) DRY_RUN=1; shift ;; + --show-excluded) SHOW_EXCLUDED=1; DRY_RUN=1; shift ;; + -h|--help) usage; exit 0 ;; + *) echo "알 수 없는 옵션: $1" >&2; usage >&2; exit 1 ;; + esac +done + +# ---- 저장소 루트 탐지 ----------------------------------------------------- +# 스크립트는 /eapim-portal/script/djb-custom-export/ 에 있다. +if [ -z "$REPO_ROOT" ]; then + REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" +fi +if [ ! -d "$REPO_ROOT" ]; then + echo "저장소 루트를 찾을 수 없다: $REPO_ROOT" >&2 + exit 1 +fi +[ -z "$OUT_DIR" ] && OUT_DIR="$REPO_ROOT/build/djb-custom-export" + +if [ ! -f "$CONF_FILE" ]; then + echo "설정 파일 없음: $CONF_FILE" >&2 + exit 1 +fi + +# ---- 설정 로드 ------------------------------------------------------------ +ALLOW_EXT="" +EXCLUDE_PAT="" +MODULE_LINES="" +while IFS= read -r line || [ -n "$line" ]; do + case "$line" in + ''|'#'*) continue ;; + esac + key="${line%%|*}" + rest="${line#*|}" + case "$key" in + MODULE) MODULE_LINES="$MODULE_LINES +$rest" ;; + EXT) ALLOW_EXT="$rest" ;; + EXCLUDE) EXCLUDE_PAT="$rest" ;; + esac +done < "$CONF_FILE" + +# 빈 트리 해시 — base 커밋이 없을 때(= 모든 이력이 컷오프 이후) 사용한다. +EMPTY_TREE="4b825dc642cb6eb9a060e54bf8d69288fbee4904" + +is_allowed_ext() { + _f="$1" + _base="${_f##*/}" + case "$_base" in + *.*) _ext="${_base##*.}" ;; + *) return 1 ;; # 확장자 없는 파일 제외 + esac + # 소문자 변환 (bash 3.2 호환) + _ext="$(printf '%s' "$_ext" | tr '[:upper:]' '[:lower:]')" + case ",$ALLOW_EXT," in + *",$_ext,"*) return 0 ;; + *) return 1 ;; + esac +} + +# ---- 포함/제외 규칙 (.gitignore 유사) -------------------------------------- +RULES=() +if [ -f "$RULES_FILE" ]; then + while IFS= read -r r || [ -n "$r" ]; do + # 앞뒤 공백 제거 + r="${r#"${r%%[![:space:]]*}"}" + r="${r%"${r##*[![:space:]]}"}" + case "$r" in + ''|'#'*) continue ;; + esac + RULES[${#RULES[@]}]="$r" + done < "$RULES_FILE" +fi + +# 경로 하나에 규칙을 순서대로 적용한다. 마지막으로 매칭된 규칙이 이긴다. +# 기본은 포함(0), '!' 규칙은 다시 포함, 일반 규칙은 제외(1). +# 반환: 0 = 포함, 1 = 제외. 제외시킨 규칙은 MATCHED_RULE 에 담긴다. +MATCHED_RULE="" +rule_keep() { + _path="$1" + _leaf="${_path##*/}" + _keep=0 + MATCHED_RULE="" + _i=0 + while [ $_i -lt ${#RULES[@]} ]; do + _rule="${RULES[$_i]}" + _i=$((_i + 1)) + + _neg=0 + case "$_rule" in + '!'*) _neg=1; _rule="${_rule#!}" ;; + esac + # 디렉터리 규칙: 하위 전체 + case "$_rule" in + */) _rule="${_rule}*" ;; + esac + # '/' 가 있으면 전체 경로, 없으면 파일명과 매칭 + case "$_rule" in + */*) _target="$_path" ;; + *) _target="$_leaf" ;; + esac + + # shellcheck disable=SC2254 # 의도적으로 unquoted — glob 패턴으로 쓴다 + case "$_target" in + $_rule) + if [ $_neg -eq 1 ]; then + _keep=0; MATCHED_RULE="" + else + _keep=1; MATCHED_RULE="$_rule" + fi + ;; + esac + done + return $_keep +} + +csv_escape() { + printf '"%s"' "$(printf '%s' "$1" | sed 's/"/""/g')" +} + +# ---- 준비 ----------------------------------------------------------------- +if [ "$DRY_RUN" -eq 0 ]; then + rm -rf "$OUT_DIR" + mkdir -p "$OUT_DIR/src" +fi + +MANIFEST="$OUT_DIR/MANIFEST.csv" +SUMMARY="$OUT_DIR/SUMMARY.md" +TMP_SUMMARY="$(mktemp)" +trap 'rm -f "$TMP_SUMMARY"' EXIT + +if [ "$DRY_RUN" -eq 0 ]; then + printf '\357\273\277' > "$MANIFEST" # Excel 한글용 UTF-8 BOM + echo 'module,path,ext,added_commit,added_date,author' >> "$MANIFEST" +fi + +# dry-run(목록 뽑기) 모드에서는 파일 목록만 stdout으로 내보내고 +# 진행 로그는 전부 stderr로 보낸다. 그래야 파이프/리다이렉트로 목록을 바로 가공할 수 있다. +if [ "$DRY_RUN" -eq 1 ]; then LOG=2; else LOG=1; fi + +{ + echo "저장소 루트 : $REPO_ROOT" + echo "기준 날짜 : $CUTOFF (이 날짜 직전 마지막 커밋이 base)" + echo "출력 경로 : $OUT_DIR" + [ "$DRY_RUN" -eq 1 ] && echo "모드 : DRY-RUN (목록만 출력, 복사 안 함)" + echo +} >&$LOG + +TOTAL=0 +MISSING=0 +EXCLUDED=0 + +# ---- 모듈 순회 ------------------------------------------------------------ +# 파이프 대신 process substitution 을 써서 집계 변수가 서브셸에 갇히지 않게 한다. +while IFS= read -r mline; do + [ -z "$mline" ] && continue + MOD="${mline%%|*}" + PATHS="${mline#*|}" + + if [ -n "$ONLY_MODULES" ]; then + case " $ONLY_MODULES " in + *" $MOD "*) : ;; + *) continue ;; + esac + fi + + MOD_DIR="$REPO_ROOT/$MOD" + if [ ! -d "$MOD_DIR/.git" ]; then + echo "[건너뜀] $MOD — git 저장소 아님 ($MOD_DIR)" >&$LOG + continue + fi + + # base 커밋 결정 + BASE="$(git -C "$MOD_DIR" -c core.quotepath=false rev-list -1 --before="$CUTOFF" HEAD 2>/dev/null || true)" + if [ -z "$BASE" ]; then + BASE="$EMPTY_TREE" + BASE_DESC="(컷오프 이전 커밋 없음 → 전체를 신규로 간주)" + else + BASE_DESC="$(git -C "$MOD_DIR" -c core.quotepath=false log -1 --format='%h %ad %s' --date=short "$BASE" | cut -c1-80)" + fi + + # 실제 존재하는 소스 경로만 pathspec으로 사용 + SPEC="" + for p in $PATHS; do + [ -d "$MOD_DIR/$p" ] && SPEC="$SPEC $p" + done + if [ -z "$SPEC" ]; then + echo "[건너뜀] $MOD — 설정된 소스 경로가 존재하지 않음" >&$LOG + continue + fi + + # 신규(Added) 파일만. -M 으로 rename은 신규에서 제외한다. + FILES="$(git -C "$MOD_DIR" -c core.quotepath=false diff --name-only --diff-filter=A -M "$BASE" HEAD -- $SPEC 2>/dev/null || true)" + + if [ "$INCLUDE_UNTRACKED" -eq 1 ]; then + UNTRACKED="$(git -C "$MOD_DIR" -c core.quotepath=false ls-files --others --exclude-standard -- $SPEC 2>/dev/null || true)" + FILES="$FILES +$UNTRACKED" + fi + + COUNT=0 + MISS=0 + while IFS= read -r f; do + [ -z "$f" ] && continue + is_allowed_ext "$f" || continue + + # 규칙 매칭은 <모듈>/<경로> 전체 문자열 기준 + if ! rule_keep "$MOD/$f"; then + EXCLUDED=$((EXCLUDED + 1)) + [ "$SHOW_EXCLUDED" -eq 1 ] && printf '%s\t# %s\n' "$MOD/$f" "$MATCHED_RULE" + continue + fi + [ "$SHOW_EXCLUDED" -eq 1 ] && continue + + SRC="$MOD_DIR/$f" + if [ ! -f "$SRC" ]; then + # 추가된 뒤 삭제/이동된 파일 — 납품 대상 아님 + echo " [없음] $MOD/$f" >&2 + MISS=$((MISS + 1)) + continue + fi + + COUNT=$((COUNT + 1)) + if [ "$DRY_RUN" -eq 1 ]; then + echo "$MOD/$f" # 목록은 stdout으로 (가공하기 쉽게 접두 공백 없음) + continue + fi + + DST="$OUT_DIR/src/$MOD/$f" + mkdir -p "$(dirname "$DST")" + cp -p "$SRC" "$DST" + + META="$(git -C "$MOD_DIR" -c core.quotepath=false log -1 --diff-filter=A --format='%h|%ad|%an' --date=short -- "$f" 2>/dev/null || true)" + C_HASH="${META%%|*}"; _r="${META#*|}" + C_DATE="${_r%%|*}" + C_AUTH="${_r#*|}" + BASE_F="${f##*/}" + case "$BASE_F" in *.*) EXT="${BASE_F##*.}" ;; *) EXT="" ;; esac + + { + csv_escape "$MOD"; printf ',' + csv_escape "$f"; printf ',' + csv_escape "$EXT"; printf ',' + csv_escape "$C_HASH"; printf ',' + csv_escape "$C_DATE"; printf ',' + csv_escape "$C_AUTH"; printf '\n' + } >> "$MANIFEST" + done < <(printf '%s\n' "$FILES" | sort -u) + + TOTAL=$((TOTAL + COUNT)) + MISSING=$((MISSING + MISS)) + printf '%s|%s|%s\n' "$MOD" "$COUNT" "$BASE_DESC" >> "$TMP_SUMMARY" + printf '[수집] %-22s %5s 개 base: %s\n' "$MOD" "$COUNT" "$BASE_DESC" >&$LOG +done < <(printf '%s\n' "$MODULE_LINES") + +# ---- 요약 ------------------------------------------------------------------ +if [ "$DRY_RUN" -eq 0 ]; then + { + echo "# DJB 커스텀 소스 납품본" + echo + echo "- 생성 일시: $(date '+%Y-%m-%d %H:%M:%S')" + echo "- 기준 날짜: \`$CUTOFF\` (이 날짜 직전 마지막 커밋을 base로 삼아, 이후 **신규 추가된** 파일만 수집)" + echo "- 수집 규칙: \`git diff --diff-filter=A -M HEAD\` / 확장자 allowlist / \`export-filter.rules\` 필터" + echo "- 대상 확장자: \`$ALLOW_EXT\`" + echo "- 규칙 파일: \`$(basename "$RULES_FILE")\` (${#RULES[@]} 개 규칙, 제외 ${EXCLUDED} 건)" + echo + echo "## 모듈별 수집 결과" + echo + echo "| 모듈 | 파일 수 | base 커밋 |" + echo "|---|---:|---|" + while IFS='|' read -r m n d; do + [ -z "$m" ] && continue + echo "| $m | $n | $d |" + done < "$TMP_SUMMARY" + echo + echo "**합계: ${TOTAL} 개**" + [ "$MISSING" -gt 0 ] && echo "> 추가 후 삭제/이동되어 현재 트리에 없는 파일 ${MISSING} 개는 제외됨." + echo + echo "## 파일 목록" + echo + echo "\`MANIFEST.csv\` 참조 (module, path, ext, 최초 추가 커밋/일자/작성자)." + } > "$SUMMARY" + + echo + echo "MANIFEST : $MANIFEST" + echo "SUMMARY : $SUMMARY" + + if [ "$MAKE_ZIP" -eq 1 ]; then + ZIP_NAME="djb-custom-export-$(date '+%Y%m%d').zip" + ZIP_PATH="$(dirname "$OUT_DIR")/$ZIP_NAME" + rm -f "$ZIP_PATH" + ( cd "$OUT_DIR" && zip -qr "$ZIP_PATH" . ) + echo "ZIP : $ZIP_PATH" + fi +fi diff --git a/script/djb-custom-export/export-filter.rules b/script/djb-custom-export/export-filter.rules new file mode 100644 index 0000000..3dba8bc --- /dev/null +++ b/script/djb-custom-export/export-filter.rules @@ -0,0 +1,74 @@ +# DJB 커스텀 소스 납품 - 포함/제외 규칙 +# +# .gitignore 와 비슷하게 동작한다. +# - 한 줄에 패턴 하나. '#' 주석, 빈 줄 무시. +# - 기본은 "모두 포함". 패턴에 걸리면 제외된다. +# - '!' 로 시작하면 예외(다시 포함). +# - 뒤에 오는 규칙이 앞 규칙을 덮는다 (마지막 매칭 승리). → 순서가 중요하다. +# - 패턴에 '/' 가 있으면 <모듈>/<경로> 전체와 glob 매칭, +# 없으면 파일명(basename)과만 매칭한다. +# - '/' 로 끝나면 그 디렉터리 하위 전체를 뜻한다. +# - '*' 는 '/' 도 포함해 매칭한다(단순 glob). +# +# 매칭 대상 경로 예: +# eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/WebhookService.java +# eapim-admin/WebContent/jsp/onl/apim/webhook/webhookList.jsp +# +# 확인: ./export-custom.sh --dry-run (남는 목록) +# ./export-custom.sh --dry-run --show-excluded (규칙에 걸려 빠진 목록) + +# =========================================================================== +# 1) 제품 업그레이드로 유입된 신규 파일 — 커스텀 아니므로 제외 +# filelist-no-djb-marker.txt(djb/custom 마커 없는 신규 파일)를 검토하며 채운다. +# =========================================================================== +# 예시) +# eapim-online/src/main/java/com/eactive/eai/agent/inflow/ +# eapim-admin/src/main/java/com/eactive/eai/rms/common/acl/sitemap/ + + +eapim-portal/src/main/java/com/eactive/apim/gateway/data/statistics/ +eapim-portal/src/main/java/com/eactive/apim/portal/apps/ +eapim-portal/src/main/java/com/eactive/apim/portal/apps/apis/ +eapim-portal/src/main/java/com/eactive/apim/portal/apps/app/ +eapim-portal/src/main/java/com/eactive/apim/portal/apps/auth/ +eapim-portal/src/main/java/com/eactive/apim/portal/apps/login/ +eapim-portal/src/main/java/com/eactive/apim/portal/apps/session/ +eapim-portal/src/main/java/com/eactive/apim/portal/config/ +eapim-portal/src/main/java/com/eactive/apim/portal/common/ +eapim-portal/src/main/resources/templates/views/fragment/popup/ +eapim-portal/src/test/ + +elink-portal-common/src/main/java/com/eactive/apim/portal/common/util/ +elink-portal-common/src/main/java/com/eactive/apim/portal/jpa/ +elink-portal-common/src/main/java/com/eactive/apim/portal/menu/ + + +# =========================================================================== +# 2) 위 제외에 걸렸더라도 되살릴 예외 +# djb 마커가 붙은 파일은 어떤 경우에도 커스텀이다. +# ※ 아래 3)의 빌드 산출물 제외보다 반드시 앞에 와야 한다. +# =========================================================================== +!*djb* +!*DJB* +!*Djb* + + +# =========================================================================== +# 3) 무조건 제외 (최종 승리) — 소스가 아니거나 서드파티 +# =========================================================================== +*/build/ +*/out/ +*/bin/ +*/.gradle/ +*/.metadata/ +*/node_modules/ +*/generated/ +*/design-backup/ +*/WebContent/plugins/ + +# SafeDB(damo) 벤더 API stub — 컴파일용 껍데기라 납품 대상 아님 +damo-manager/src/stub/ + +# 서드파티 폴더 안에 섞여 있는 우리 커스텀 파일은 되살린다 +# (예: WebContent/plugins/swaggerUI/djb-swagger-i18n.js) +!*/WebContent/plugins/*djb* diff --git a/script/djb-custom-export/filelist-no-djb-marker.txt b/script/djb-custom-export/filelist-no-djb-marker.txt new file mode 100644 index 0000000..1b12182 --- /dev/null +++ b/script/djb-custom-export/filelist-no-djb-marker.txt @@ -0,0 +1,157 @@ +eapim-portal/src/main/java/com/eactive/apim/gateway/data/statistics/entity/ApiStatsDay.java +eapim-portal/src/main/java/com/eactive/apim/gateway/data/statistics/entity/ApiStatsDayId.java +eapim-portal/src/main/java/com/eactive/apim/gateway/data/statistics/entity/ApiStatsHour.java +eapim-portal/src/main/java/com/eactive/apim/gateway/data/statistics/entity/ApiStatsHourId.java +eapim-portal/src/main/java/com/eactive/apim/gateway/data/statistics/entity/ApiStatsMonth.java +eapim-portal/src/main/java/com/eactive/apim/gateway/data/statistics/entity/ApiStatsMonthId.java +eapim-portal/src/main/java/com/eactive/apim/gateway/data/statistics/entity/GwApiStatus.java +eapim-portal/src/main/java/com/eactive/apim/gateway/data/statistics/entity/GwAuthClient.java +eapim-portal/src/main/java/com/eactive/apim/gateway/data/statistics/entity/GwSvcInfo.java +eapim-portal/src/main/java/com/eactive/apim/gateway/data/statistics/repository/ApiStatsDayRepository.java +eapim-portal/src/main/java/com/eactive/apim/gateway/data/statistics/repository/ApiStatsHourRepository.java +eapim-portal/src/main/java/com/eactive/apim/gateway/data/statistics/repository/ApiStatsMonthRepository.java +eapim-portal/src/main/java/com/eactive/apim/gateway/data/statistics/repository/GwApiStatusRepository.java +eapim-portal/src/main/java/com/eactive/apim/gateway/data/statistics/repository/GwAuthClientRepository.java +eapim-portal/src/main/java/com/eactive/apim/gateway/data/statistics/repository/GwSvcInfoRepository.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/apis/filter/ApiTesterAuditLogger.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/app/service/AdminGatewayClient.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/auth/AuthNoticeProperties.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/dto/TwoFactorChannel.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/dto/TwoFactorInfoResponse.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/dto/TwoFactorSendResponse.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/dto/TwoFactorVerifyResponse.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/StepUpAuthInterceptor.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/StepUpPasswordController.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/StepUpProtectedPaths.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/TwoFactorCleanupScheduler.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/TwoFactorContext.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/TwoFactorController.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/TwoFactorProperties.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/TwoFactorService.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/community/notice/dto/IncidentAffectedApiDTO.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/community/partnership/dto/PartnershipApplicationSummaryDTO.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/login/constants/LoginFailureReason.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/login/constants/LoginType.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/login/controller/DuplicateLoginController.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/login/service/DuplicateLoginService.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/login/service/LoginFinalizer.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/ReadinessController.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/session/controller/SessionApiController.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/session/entity/UserSession.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/session/filter/SessionValidationFilter.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/session/repository/UserSessionRepository.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/session/service/UserSessionService.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/statistics/dto/ApiStatisticsPeriodDto.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/statistics/repository/entity/JobInfo.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/statistics/repository/JobInfoRepository.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/user/service/UserRoleHistoryService.java +eapim-portal/src/main/java/com/eactive/apim/portal/common/exception/UserErrorMessageResolver.java +eapim-portal/src/main/java/com/eactive/apim/portal/common/migration/LegacyEncryptionMigrationController.java +eapim-portal/src/main/java/com/eactive/apim/portal/common/security/ClientGuardService.java +eapim-portal/src/main/java/com/eactive/apim/portal/common/security/LoginLockPolicy.java +eapim-portal/src/main/java/com/eactive/apim/portal/config/PasswordChangeEnforcementInterceptor.java +eapim-portal/src/main/java/com/eactive/apim/portal/config/PasswordEnforcementPolicy.java +eapim-portal/src/main/java/com/eactive/apim/portal/config/PortalPropertyDuplicateChecker.java +eapim-portal/src/main/java/com/eactive/apim/portal/config/StartupInfoPrinter.java +eapim-portal/src/main/resources/menu.yml +eapim-portal/src/main/resources/roles.yml +eapim-portal/src/main/resources/static/js/api-selector.js +eapim-portal/src/main/resources/static/js/password-policy.js +eapim-portal/src/main/resources/static/js/popup/two-factor-auth.js +eapim-portal/src/main/resources/static/sass/components/_board-common.scss +eapim-portal/src/main/resources/static/sass/components/_password-policy.scss +eapim-portal/src/main/resources/static/sass/components/_session-timer.scss +eapim-portal/src/main/resources/static/sass/components/_toast.scss +eapim-portal/src/main/resources/static/sass/components/_two-factor.scss +eapim-portal/src/main/resources/static/sass/pages/_api-status.scss +eapim-portal/src/main/resources/static/sass/pages/_webhook.scss +eapim-portal/src/main/resources/templates/views/apps/auth/stepupPassword.html +eapim-portal/src/main/resources/templates/views/apps/auth/twoFactorChallenge.html +eapim-portal/src/main/resources/templates/views/apps/community/mainFaqDetail.html +eapim-portal/src/main/resources/templates/views/apps/register/signupVerificationEmail.html +eapim-portal/src/main/resources/templates/views/apps/service/oauth2-guide.html +eapim-portal/src/main/resources/templates/views/apps/service/webhook-dev-guide.html +eapim-portal/src/main/resources/templates/views/apps/webhook/webhookEmpty.html +eapim-portal/src/main/resources/templates/views/apps/webhook/webhookList.html +eapim-portal/src/main/resources/templates/views/apps/webhook/webhookModifyStep1.html +eapim-portal/src/main/resources/templates/views/apps/webhook/webhookModifyStep2.html +eapim-portal/src/main/resources/templates/views/apps/webhook/webhookModifyStep3.html +eapim-portal/src/main/resources/templates/views/apps/webhook/webhookRegisterStep1.html +eapim-portal/src/main/resources/templates/views/apps/webhook/webhookRegisterStep2.html +eapim-portal/src/main/resources/templates/views/apps/webhook/webhookRegisterStep3.html +eapim-portal/src/main/resources/templates/views/fragment/api_selector.html +eapim-portal/src/main/resources/templates/views/fragment/popup/cancelInvitationPopup.html +eapim-portal/src/main/resources/templates/views/fragment/popup/terminateRequestPopup.html +eapim-portal/src/main/resources/templates/views/fragment/popup/twoFactorAuthPopup.html +eapim-portal/src/test/java/com/eactive/apim/portal/common/exception/UserErrorMessageResolverTest.java +eapim-portal/src/test/java/com/eactive/apim/portal/common/util/PhoneNumberUtilTest.java +eapim-portal/src/test/java/com/eactive/apim/portal/common/util/StringMaskingUtilTest.java +eapim-admin/src/main/java/com/eactive/eai/rms/common/acl/sitemap/SitemapController.java +eapim-admin/src/main/java/com/eactive/eai/rms/common/acl/sitemap/SitemapService.java +eapim-admin/src/main/java/com/eactive/eai/rms/common/acl/sitemap/SitemapServiceImpl.java +eapim-admin/src/main/java/com/eactive/eai/rms/common/acl/sitemap/ui/SitemapNode.java +eapim-admin/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/messagerequest/MessageRequestEmsRepository.java +eapim-admin/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/messagerequest/MessageRequestService.java +eapim-admin/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/messagerequest/MessageRequestUISearch.java +eapim-admin/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/portalinquiry/PortalInquiryCommentRepository.java +eapim-admin/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/portalinquiry/PortalInquiryCommentService.java +eapim-admin/src/main/java/com/eactive/eai/rms/data/entity/onl/security/CryptoModuleConfigDataService.java +eapim-admin/src/main/java/com/eactive/eai/rms/data/entity/onl/security/CryptoModuleConfigDataServiceImpl.java +eapim-admin/src/main/java/com/eactive/eai/rms/data/entity/onl/security/CryptoModuleConfigRepository.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/apim/authserver/ClientBlockApiController.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/apim/masking/MessagePatternMaskingUtils.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/apim/messagerequest/MessageRequestManController.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/apim/messagerequest/MessageRequestManService.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/apim/messagerequest/MessageRequestUI.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryClosingService.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryCommentUI.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryCommentUIMapper.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/apim/portalmenu/PortalMenuCacheClient.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/apim/portalmenu/PortalMenuManController.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/apim/portalmenu/PortalMenuManService.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/apim/portalmenu/PortalMenuUI.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/apim/portalmenu/PortalMenuUIMapper.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/apim/portalnotice/IncidentAffectedApiUI.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/apim/portalnotice/IncidentTimelineUI.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/manage/crypto/CryptoModuleConfigManController.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/manage/crypto/CryptoModuleConfigManService.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/manage/crypto/CryptoModuleConfigUI.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/manage/crypto/CryptoModuleConfigUiMapper.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/manage/inflow/inflow/InflowClientControlManController.java +eapim-admin/src/main/resources/apistatus-draft.yml +eapim-admin/src/main/resources/logback-rinjae_ma.xml +eapim-admin/src/test/java/com/eactive/eai/rms/onl/apim/masking/MaskingUtilsTest.java +eapim-admin/src/test/java/com/eactive/eai/rms/onl/apim/masking/MessagePatternMaskingUtilsTest.java +eapim-admin/WebContent/damo.jsp +eapim-admin/WebContent/jsp/common/acl/sitemap/sitemapMan.jsp +eapim-admin/WebContent/jsp/onl/admin/inflow/inflowClientControlMan.jsp +eapim-admin/WebContent/jsp/onl/admin/inflow/inflowClientControlManDetail.jsp +eapim-admin/WebContent/jsp/onl/admin/rule/transform2/transform2ManApiPopup.jsp +eapim-admin/WebContent/jsp/onl/admin/security/cryptoModuleMan.jsp +eapim-admin/WebContent/jsp/onl/admin/security/cryptoModuleManDetail.jsp +eapim-admin/WebContent/jsp/onl/apim/messagerequest/messageRequestMan.jsp +eapim-admin/WebContent/jsp/onl/apim/messagerequest/messageRequestManDetail.jsp +eapim-admin/WebContent/jsp/onl/apim/portalmenu/portalMenuMan.jsp +eapim-admin/WebContent/jsp/onl/apim/webhook/webhookSendLogMan.jsp +eapim-admin/WebContent/jsp/onl/apim/webhook/webhookSendLogManDetail.jsp +eapim-admin/WebContent/jsp/onl/kjb/statistics/apiUseStatsMan.jsp +elink-portal-common/src/main/java/com/eactive/apim/portal/apprequest/entity/GwAction.java +elink-portal-common/src/main/java/com/eactive/apim/portal/common/util/IpAddressMatcher.java +elink-portal-common/src/main/java/com/eactive/apim/portal/common/util/PhoneNumberUtil.java +elink-portal-common/src/main/java/com/eactive/apim/portal/jpa/PersonalDataEncryptConverter.java +elink-portal-common/src/main/java/com/eactive/apim/portal/jpa/UuidV7Generator.java +elink-portal-common/src/main/java/com/eactive/apim/portal/menu/entity/PortalMenuItem.java +elink-portal-common/src/main/java/com/eactive/apim/portal/menu/entity/PortalMenuPlacement.java +elink-portal-common/src/main/java/com/eactive/apim/portal/menu/entity/PortalRole.java +elink-portal-common/src/main/java/com/eactive/apim/portal/menu/entity/PortalRoleAuthority.java +elink-portal-common/src/main/java/com/eactive/apim/portal/menu/entity/PortalRoleAuthorityId.java +elink-portal-common/src/main/java/com/eactive/apim/portal/menu/repository/PortalMenuItemRepository.java +elink-portal-common/src/main/java/com/eactive/apim/portal/menu/repository/PortalMenuPlacementRepository.java +elink-portal-common/src/main/java/com/eactive/apim/portal/menu/repository/PortalRoleAuthorityRepository.java +elink-portal-common/src/main/java/com/eactive/apim/portal/menu/repository/PortalRoleRepository.java +elink-portal-common/src/main/java/com/eactive/apim/portal/menu/service/PortalMenuDataService.java +elink-portal-common/src/main/java/com/eactive/apim/portal/portaluser/entity/UserRoleHistory.java +elink-portal-common/src/main/java/com/eactive/apim/portal/portaluser/repository/UserRoleHistoryRepository.java +elink-portal-common/src/main/java/com/eactive/apim/portal/qna/entity/InquiryComment.java +elink-portal-common/src/main/java/com/eactive/apim/portal/qna/entity/VisibilityScope.java +elink-portal-common/src/test/java/com/eactive/apim/portal/common/util/IpAddressMatcherTest.java diff --git a/script/djb-custom-export/filelist.txt b/script/djb-custom-export/filelist.txt new file mode 100644 index 0000000..7254cdd --- /dev/null +++ b/script/djb-custom-export/filelist.txt @@ -0,0 +1,173 @@ +eapim-portal/src/main/java/com/eactive/apim/portal/custom/config/DjbPasswordEncoder.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/controller/ApiStatusController.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/ActiveIncidentDTO.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/AffectedApiDTO.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/ApiCurrentStatusDTO.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/ApiOptionDTO.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/DailyStatDTO.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/IssueDateEntryDTO.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/MaintenanceCardDTO.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/MyApiStatusDTO.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/PastIssueCardDTO.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/TimelineEntryDTO.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/repository/ApiStatusIncidentQueryRepository.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/service/ApiCurrentStatusService.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/service/ApiStatusAssembler.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/service/ApiStatusCatalogService.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/service/ApiStatusIssueHistoryService.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/service/ApiStatusQueryService.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/service/ApiStatusSupport.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/service/ApiStatusUptimeService.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/service/MyApiStatusQueryService.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/controller/InquiryCommentController.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/dto/InquiryCommentCreateRequest.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/dto/InquiryCommentDTO.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/repository/InquiryCommentRepository.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/repository/UserInfoRepository.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/service/InquiryCommentFacade.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/service/InquiryCommentFacadeImpl.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/service/InquiryCommentService.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/community/qna/constant/DjbInquiryStatus.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/community/qna/exception/InquiryClosedException.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/community/qna/exception/InquiryCommentNotOwnedException.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/community/qna/support/InquiryCommentPermissionChecker.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/footer/RelatedSite.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/footer/RelatedSiteService.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/menu/MenuAccessInterceptor.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/menu/MenuInternalController.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/menu/MenuModelAdvice.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/menu/MenuNode.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/menu/MenuSeeder.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/menu/MenuService.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/menu/MenuView.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/menu/PortalMenuYmlProperties.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/menu/PortalRolesProperties.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/swing/EmployeeIdPolicy.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/swing/SwingMessageWriter.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/swing/SwingNotifier.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/swing/SwingNotifyProperties.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/swing/config/SwingAsyncConfig.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/swing/repository/SwingStaffRepository.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/testbed/advice/DjbTestbedExceptionHandler.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/testbed/config/DjbTestbedGatewayProperty.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/testbed/controller/DjbTestbedAuthController.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/testbed/controller/DjbTestbedSpecController.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/testbed/dto/DjbCredentialOptionDto.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/testbed/dto/DjbCredentialSecretDto.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/testbed/dto/DjbTestbedContextDto.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/testbed/enums/DjbAuthType.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/testbed/enums/DjbGatewayMode.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/testbed/exception/DjbUnsupportedAuthTypeException.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/testbed/service/DjbSwaggerSpecEnricher.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/testbed/service/DjbTestbedAuthService.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/testbed/service/DjbTestbedSpecServerRewriter.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/controller/WebhookController.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/dto/WebhookCreatedResult.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/dto/WebhookDTO.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/dto/WebhookEventTypeDTO.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/dto/WebhookRegistrationDTO.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/exception/WebhookAlreadyExistsException.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/exception/WebhookNotFoundException.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/mapper/WebhookMapper.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/repository/WebhookRequestApiRepository.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/repository/WebhookRequestEventRepository.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/repository/WebhookRequestRepository.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/repository/entity/WebhookRequest.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/repository/entity/WebhookRequestApi.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/repository/entity/WebhookRequestApiId.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/repository/entity/WebhookRequestEvent.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/repository/entity/WebhookRequestEventId.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/service/WebhookEventTypeProvider.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/service/WebhookSecretGenerator.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/service/WebhookService.java +eapim-portal/src/main/resources/menu.yml +eapim-portal/src/main/resources/roles.yml +eapim-portal/src/main/resources/static/favicon.png +eapim-portal/src/main/resources/static/img/avatar1.svg +eapim-portal/src/main/resources/static/img/avatar2.svg +eapim-portal/src/main/resources/static/img/favicon/apple-touch-icon.png +eapim-portal/src/main/resources/static/img/favicon/favicon-16x16.png +eapim-portal/src/main/resources/static/img/favicon/favicon-180x180.png +eapim-portal/src/main/resources/static/img/favicon/favicon-192x192.png +eapim-portal/src/main/resources/static/img/favicon/favicon-32x32.png +eapim-portal/src/main/resources/static/img/favicon/favicon-48x48.png +eapim-portal/src/main/resources/static/img/favicon/favicon-512x512.png +eapim-portal/src/main/resources/static/img/favicon/favicon.png +eapim-portal/src/main/resources/static/img/icon/icon_check_green.png +eapim-portal/src/main/resources/static/img/icon/img_icon.png +eapim-portal/src/main/resources/static/img/keyimage/api_img.png +eapim-portal/src/main/resources/static/img/keyimage/api_img.svg +eapim-portal/src/main/resources/static/img/keyimage/faq_img.png +eapim-portal/src/main/resources/static/img/keyimage/faq_img.svg +eapim-portal/src/main/resources/static/img/keyimage/feedback_img.svg +eapim-portal/src/main/resources/static/img/keyimage/notice_img.png +eapim-portal/src/main/resources/static/img/keyimage/notice_img.svg +eapim-portal/src/main/resources/static/img/keyimage/q&a_img.png +eapim-portal/src/main/resources/static/img/keyimage/q&a_img.svg +eapim-portal/src/main/resources/static/img/keyimage/sigdnUp_img.svg +eapim-portal/src/main/resources/static/img/keyimage/signUp_img.png +eapim-portal/src/main/resources/static/img/logo/logo-djb.png +eapim-portal/src/main/resources/static/img/logo/logo-jjb.png +eapim-portal/src/main/resources/static/img/logo/logo-jjb_white.png +eapim-portal/src/main/resources/static/js/api-selector.js +eapim-portal/src/main/resources/static/js/djb/api-status-issues.js +eapim-portal/src/main/resources/static/js/djb/api-status.js +eapim-portal/src/main/resources/static/js/djb/client-guard.js +eapim-portal/src/main/resources/static/js/djb/inquiry-comments.js +eapim-portal/src/main/resources/static/js/djb/inquiry-view.js +eapim-portal/src/main/resources/static/js/djb/toast.js +eapim-portal/src/main/resources/static/js/password-policy.js +eapim-portal/src/main/resources/static/js/popup/two-factor-auth.js +eapim-portal/src/main/resources/static/plugins/swaggerUI/djb-swagger-i18n.js +eapim-portal/src/main/resources/static/plugins/swaggerUI/djb-swagger-response.js +eapim-portal/src/main/resources/static/plugins/swaggerUI/djb-swagger-snippet-panel.js +eapim-portal/src/main/resources/static/plugins/swaggerUI/djb-swagger-snippets.js +eapim-portal/src/main/resources/static/plugins/swaggerUI/djb-swagger-testbed.css +eapim-portal/src/main/resources/static/plugins/swaggerUI/djb-swagger-validator.js +eapim-portal/src/main/resources/static/sass/base/_djb-font.scss +eapim-portal/src/main/resources/static/sass/components/_board-common.scss +eapim-portal/src/main/resources/static/sass/components/_djb-inquiry-comments.scss +eapim-portal/src/main/resources/static/sass/components/_password-policy.scss +eapim-portal/src/main/resources/static/sass/components/_session-timer.scss +eapim-portal/src/main/resources/static/sass/components/_toast.scss +eapim-portal/src/main/resources/static/sass/components/_two-factor.scss +eapim-portal/src/main/resources/static/sass/pages/_api-status.scss +eapim-portal/src/main/resources/static/sass/pages/_webhook.scss +eapim-portal/src/main/resources/templates/views/apps/auth/stepupPassword.html +eapim-portal/src/main/resources/templates/views/apps/auth/twoFactorChallenge.html +eapim-portal/src/main/resources/templates/views/apps/community/djb/fragments-inquiry-comments.html +eapim-portal/src/main/resources/templates/views/apps/community/mainFaqDetail.html +eapim-portal/src/main/resources/templates/views/apps/register/signupVerificationEmail.html +eapim-portal/src/main/resources/templates/views/apps/service/oauth2-guide.html +eapim-portal/src/main/resources/templates/views/apps/service/webhook-dev-guide.html +eapim-portal/src/main/resources/templates/views/apps/webhook/webhookEmpty.html +eapim-portal/src/main/resources/templates/views/apps/webhook/webhookList.html +eapim-portal/src/main/resources/templates/views/apps/webhook/webhookModifyStep1.html +eapim-portal/src/main/resources/templates/views/apps/webhook/webhookModifyStep2.html +eapim-portal/src/main/resources/templates/views/apps/webhook/webhookModifyStep3.html +eapim-portal/src/main/resources/templates/views/apps/webhook/webhookRegisterStep1.html +eapim-portal/src/main/resources/templates/views/apps/webhook/webhookRegisterStep2.html +eapim-portal/src/main/resources/templates/views/apps/webhook/webhookRegisterStep3.html +eapim-portal/src/main/resources/templates/views/djb/apistatus/index.html +eapim-portal/src/main/resources/templates/views/djb/apistatus/issues.html +eapim-portal/src/main/resources/templates/views/fragment/api_selector.html +eapim-portal/src/main/resources/templates/views/fragment/djbank/footer.html +eapim-portal/src/main/resources/templates/views/fragment/djbank/header_container.html +eapim-portal/src/main/resources/templates/views/fragment/djbank/service_sidebar.html +eapim-portal/src/main/resources/templates/views/fragment/djbank/terms_agreements.html +elink-portal-common/src/main/java/com/eactive/apim/portal/apprequest/entity/GwAction.java +elink-portal-common/src/main/java/com/eactive/apim/portal/djb/apistatus/incident/entity/DjbApistatusIncident.java +elink-portal-common/src/main/java/com/eactive/apim/portal/djb/apistatus/incident/entity/DjbApistatusIncidentApi.java +elink-portal-common/src/main/java/com/eactive/apim/portal/djb/apistatus/incident/entity/DjbApistatusIncidentApiId.java +elink-portal-common/src/main/java/com/eactive/apim/portal/djb/apistatus/incident/entity/DjbApistatusIncidentTimeline.java +elink-portal-common/src/main/java/com/eactive/apim/portal/djb/apistatus/incident/entity/IncidentKind.java +elink-portal-common/src/main/java/com/eactive/apim/portal/djb/apistatus/incident/entity/IncidentState.java +elink-portal-common/src/main/java/com/eactive/apim/portal/djb/apistatus/incident/repository/DjbApistatusIncidentApiRepository.java +elink-portal-common/src/main/java/com/eactive/apim/portal/djb/apistatus/incident/repository/DjbApistatusIncidentRepository.java +elink-portal-common/src/main/java/com/eactive/apim/portal/djb/apistatus/incident/repository/DjbApistatusIncidentTimelineRepository.java +elink-portal-common/src/main/java/com/eactive/apim/portal/portaluser/entity/UserRoleHistory.java +elink-portal-common/src/main/java/com/eactive/apim/portal/portaluser/repository/UserRoleHistoryRepository.java +elink-portal-common/src/main/java/com/eactive/apim/portal/qna/entity/InquiryComment.java +elink-portal-common/src/main/java/com/eactive/apim/portal/qna/entity/VisibilityScope.java +damo-manager/src/main/java/com/eactive/ext/djb/DamoCli.java +damo-manager/src/main/java/com/eactive/ext/djb/DamoManager.java diff --git a/script/djb-custom-export/modules.conf b/script/djb-custom-export/modules.conf new file mode 100644 index 0000000..ebdb2c2 --- /dev/null +++ b/script/djb-custom-export/modules.conf @@ -0,0 +1,26 @@ +# DJB 커스텀 소스 납품 수집 설정 +# +# 형식: +# MODULE|<모듈 디렉터리>|<소스 경로들, 공백 구분> +# EXT|<수집 대상 확장자, 콤마 구분> +# +# - 모듈 디렉터리는 저장소 루트(djb-eapim) 기준 상대 경로. +# - 소스 경로가 실제로 없으면 조용히 건너뛴다. +# - '#'으로 시작하는 줄과 빈 줄은 무시된다. +# - 파일 단위 포함/제외는 export-filter.rules 에서 관리한다. + +MODULE|eapim-portal|src/main +#MODULE|eapim-admin|src WebContent +MODULE|elink-portal-common|src/main +MODULE|damo-manager|src + +# eapim-online 은 납품 범위에서 제외한다. +# Gateway 제품(elink-online) 본체라 신규 파일 중 무엇이 DJB 커스텀이고 +# 무엇이 제품 업그레이드분인지 포털 쪽에서 판정할 수 없다. +# 규칙 파일이 아니라 여기서 빼는 이유: export-filter.rules 의 '!*djb*' 예외가 +# 뒤에 있어 DJBEncrypt.java 같은 파일이 되살아나기 때문. +#MODULE|eapim-online|src elink-online-common/src elink-online-core/src elink-online-core-jpa/src elink-online-emsclient/src elink-online-transformer/src + +# 수집 대상 확장자 (확장자 없는 파일은 제외된다) +# EXT|java,xml,yml,yaml,properties,sql,html,jsp,jspf,tag,js,ts,css,scss,json,txt,md +EXT|java,xml,yml,yaml,properties,html,jsp,jspf,tag,js,ts,css,scss,jpg,jpeg,png,gif,svg,ico diff --git a/src/main/java/com/eactive/apim/portal/config/PortalConfigForwardedHeader.java b/src/main/java/com/eactive/apim/portal/config/PortalConfigForwardedHeader.java new file mode 100644 index 0000000..82e7e72 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/config/PortalConfigForwardedHeader.java @@ -0,0 +1,91 @@ +package com.eactive.apim.portal.config; + +import javax.servlet.DispatcherType; +import javax.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.web.filter.ForwardedHeaderFilter; + +/** + * 리버스프록시(OHS) 전달 헤더 처리 필터 등록. + * + *

{@code server.forward-headers-strategy=framework} 일 때만 활성화된다. Spring Boot 오토컨피그 + * ({@code ServletWebServerFactoryAutoConfiguration.ForwardedHeaderFilterConfiguration}) 가 동일 조건에서 + * 기본 {@link ForwardedHeaderFilter} 를 등록하지만, 여기서 직접 등록해 {@code /internal/**} 를 필터 대상에서 + * 제외한다. 오토컨피그는 해당 필터 빈이 이미 있으면 물러난다. + * + *

왜 native 가 아니라 framework 인가

+ * {@code native} 는 내장 Tomcat 의 {@code RemoteIpValve} 에 위임하는 전략이라 WAR 로 WebLogic 에 배포하면 + * 아무 동작도 하지 않는다({@link com.eactive.apim.portal.PortalApplication} 은 {@code SpringBootServletInitializer} + * 상속). {@code framework} 는 평범한 서블릿 필터라 배포 형태와 무관하게 동작한다. + * + *

왜 {@code /internal/**} 를 제외하는가

+ * {@link ForwardedHeaderFilter} 는 (1) {@code getRemoteAddr()} 을 {@code X-Forwarded-For} 값으로 치환하고 + * (2) 처리한 forwarded 헤더를 요청에서 제거한다. 내부 API 는 이 두 값으로 접근을 통제하므로 그대로 두면 가드가 뒤집힌다. + * + *
+ *   공격자: X-Forwarded-For: 127.0.0.1
+ *     → getRemoteAddr()              = "127.0.0.1"  (위조값이 허용 IP 검사를 통과)
+ *     → getHeader("X-Forwarded-For") = null         (필터가 제거 → viaProxy 판정이 false)
+ * 
+ * + * 즉 {@code MenuInternalController#isAllowed} / {@code LegacyEncryptionMigrationController#assertAllowedIp} + * 의 "프록시 경유 거부 + loopback 허용" 가드가 무력화된다. 필터를 제외해 두면 이 경로만은 원 소켓 IP 로 + * 검사되므로 기존 가드가 설계대로 동작한다. + * + *

전제: {@code framework} 는 신뢰 프록시 목록이 없어 헤더를 무조건 신뢰한다. 앞단(OHS)에서 인바운드 + * {@code X-Forwarded-*} / {@code Forwarded} 를 제거한 뒤 재설정해야 하며, WAS 포트로의 직접 접근 경로도 차단해야 한다. + * + * @see com.eactive.apim.portal.djb.menu.MenuInternalController + * @see com.eactive.apim.portal.common.migration.LegacyEncryptionMigrationController + */ +@Slf4j +@Configuration +@ConditionalOnProperty(name = "server.forward-headers-strategy", havingValue = "framework") +public class PortalConfigForwardedHeader { + + /** 프록시 헤더를 신뢰하지 않고 원 소켓 IP 로 판정해야 하는 경로. */ + private static final String INTERNAL_PREFIX = "/internal/"; + + @Bean + public FilterRegistrationBean forwardedHeaderFilterRegistration() { + FilterRegistrationBean registration = + new FilterRegistrationBean<>(new InternalExcludingForwardedHeaderFilter()); + // 다른 필터(XSS, Security 등)가 요청을 읽기 전에 scheme/host/port/remoteAddr 이 교정되어야 한다. + registration.setOrder(Ordered.HIGHEST_PRECEDENCE); + registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC, DispatcherType.ERROR); + log.info("ForwardedHeaderFilter 등록 - X-Forwarded-* 반영, 제외 경로: {}**", INTERNAL_PREFIX); + return registration; + } + + /** {@code /internal/**} 요청은 원본 요청 그대로 통과시키는 {@link ForwardedHeaderFilter}. */ + static class InternalExcludingForwardedHeaderFilter extends ForwardedHeaderFilter { + + /** + * {@code super} 구현은 "forwarded 계열 헤더가 하나도 없으면 필터링하지 않는다"이다. + * 그 최적화를 유지해야 프록시를 거치지 않는 환경(로컬·내부 직접 접속)의 동작이 기존과 동일하게 남으므로 + * 내부 경로 판정과 반드시 OR 로 결합한다. + */ + @Override + protected boolean shouldNotFilter(HttpServletRequest request) { + return isInternal(request) || super.shouldNotFilter(request); + } + + private boolean isInternal(HttpServletRequest request) { + String uri = request.getRequestURI(); + if (uri == null) { + return false; + } + // context-path 는 현재 "/" 이지만, 변경되더라도 경로 판정이 어긋나지 않게 접두를 걷어낸다. + String contextPath = request.getContextPath(); + if (contextPath != null && contextPath.length() > 1 && uri.startsWith(contextPath)) { + uri = uri.substring(contextPath.length()); + } + return uri.startsWith(INTERNAL_PREFIX); + } + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/guide/GuideProperty.java b/src/main/java/com/eactive/apim/portal/djb/guide/GuideProperty.java new file mode 100644 index 0000000..b6d70e8 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/guide/GuideProperty.java @@ -0,0 +1,45 @@ +package com.eactive.apim.portal.djb.guide; + +import com.eactive.apim.portal.portalproperty.service.PortalPropertyService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +/** + * 개발가이드 안내페이지에 노출할 값을 {@code PTL_PROPERTY}(그룹 {@code Portal}, key prefix {@code djb.guide.}) + * 에서 읽는 얇은 접근자. + * + *

안내페이지에 표기되는 게이트웨이 주소는 서비스 환경(dev/stage/prod)마다 달라지므로 템플릿에 하드코딩하지 않고 + * DB 프로퍼티로 분리한다. 값 미존재 시 {@link PortalPropertyService#getOrCreateProperty} 가 기본값으로 INSERT 하며, + * 공백 값은 {@link #resolve} 에서 기본값으로 폴백한다({@code getOrCreateProperty} 는 공백 보정을 하지 않음). + */ +@Component +@RequiredArgsConstructor +public class GuideProperty { + + private final PortalPropertyService portalPropertyService; + + private static final String GROUP = "Portal"; + + public static final String KEY_GW_URL = "djb.guide.gw-url"; + + public static final String DEFAULT_GW_URL = "https://openapi.jejubank.co.kr"; + + /** 안내페이지(OAuth2 개발가이드 등)에 표기할 게이트웨이 base URL. 끝 슬래시는 제거한다. */ + public String gatewayUrl() { + return stripTrailingSlash(resolve(KEY_GW_URL, DEFAULT_GW_URL, + "개발가이드 안내페이지에 표기할 게이트웨이 base URL (예: https://openapi.jejubank.co.kr)")); + } + + private String resolve(String key, String defaultValue, String description) { + String value = portalPropertyService.getOrCreateProperty(GROUP, key, defaultValue, description); + return StringUtils.hasText(value) ? value.trim() : defaultValue; + } + + private static String stripTrailingSlash(String s) { + if (s == null) { + return ""; + } + return s.endsWith("/") ? s.substring(0, s.length() - 1) : s; + } +}