DJB 커스텀 소스 납품 스크립트 추가:
- Windows, Linux/macOS 지원 배치/셸 스크립트 구현 - 소스 필터 규칙 및 수집 파일/모듈 매니페스트 작성
This commit is contained in:
@@ -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=<CUTOFF> HEAD`
|
||||||
|
컷오프 이전 커밋이 아예 없으면 저장소 전체를 신규로 본다.
|
||||||
|
2. `git diff --name-only --diff-filter=A -M <base> HEAD -- <소스경로>` 로
|
||||||
|
**신규 추가(A)** 파일만 뽑는다. `-M` 이라 단순 rename 은 신규로 잡히지 않는다.
|
||||||
|
3. `modules.conf` 의 확장자 allowlist 로 거른다. (확장자 없는 파일은 제외)
|
||||||
|
4. `export-filter.rules` 로 포함·제외를 최종 결정한다.
|
||||||
|
5. 남은 파일을 `<out>/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` | 출력 디렉터리 (기본 `<root>/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` 에 남긴다.
|
||||||
@@ -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%
|
||||||
@@ -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'
|
||||||
|
|
||||||
|
# 저장소 루트: 스크립트는 <root>\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 <base> 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+352
@@ -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 <YYYY-MM-DD> 기준 날짜. 이 날짜 직전 마지막 커밋을 base로 삼는다.
|
||||||
|
(기본: 2026-05-01)
|
||||||
|
-r, --root <DIR> 저장소 루트(djb-eapim). 미지정 시 스크립트 위치에서 자동 탐지.
|
||||||
|
-o, --out <DIR> 출력 디렉터리. (기본: <root>/build/djb-custom-export)
|
||||||
|
-m, --module <NAME> 특정 모듈만 수집. 여러 번 지정 가능.
|
||||||
|
--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
|
||||||
|
|
||||||
|
# ---- 저장소 루트 탐지 -----------------------------------------------------
|
||||||
|
# 스크립트는 <root>/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 <base> 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
|
||||||
@@ -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*
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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) 전달 헤더 처리 필터 등록.
|
||||||
|
*
|
||||||
|
* <p>{@code server.forward-headers-strategy=framework} 일 때만 활성화된다. Spring Boot 오토컨피그
|
||||||
|
* ({@code ServletWebServerFactoryAutoConfiguration.ForwardedHeaderFilterConfiguration}) 가 동일 조건에서
|
||||||
|
* 기본 {@link ForwardedHeaderFilter} 를 등록하지만, 여기서 직접 등록해 <b>{@code /internal/**} 를 필터 대상에서
|
||||||
|
* 제외</b>한다. 오토컨피그는 해당 필터 빈이 이미 있으면 물러난다.
|
||||||
|
*
|
||||||
|
* <h3>왜 native 가 아니라 framework 인가</h3>
|
||||||
|
* {@code native} 는 내장 Tomcat 의 {@code RemoteIpValve} 에 위임하는 전략이라 WAR 로 WebLogic 에 배포하면
|
||||||
|
* 아무 동작도 하지 않는다({@link com.eactive.apim.portal.PortalApplication} 은 {@code SpringBootServletInitializer}
|
||||||
|
* 상속). {@code framework} 는 평범한 서블릿 필터라 배포 형태와 무관하게 동작한다.
|
||||||
|
*
|
||||||
|
* <h3>왜 {@code /internal/**} 를 제외하는가</h3>
|
||||||
|
* {@link ForwardedHeaderFilter} 는 (1) {@code getRemoteAddr()} 을 {@code X-Forwarded-For} 값으로 치환하고
|
||||||
|
* (2) 처리한 forwarded 헤더를 요청에서 제거한다. 내부 API 는 이 두 값으로 접근을 통제하므로 그대로 두면 가드가 뒤집힌다.
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* 공격자: X-Forwarded-For: 127.0.0.1
|
||||||
|
* → getRemoteAddr() = "127.0.0.1" (위조값이 허용 IP 검사를 통과)
|
||||||
|
* → getHeader("X-Forwarded-For") = null (필터가 제거 → viaProxy 판정이 false)
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* 즉 {@code MenuInternalController#isAllowed} / {@code LegacyEncryptionMigrationController#assertAllowedIp}
|
||||||
|
* 의 "프록시 경유 거부 + loopback 허용" 가드가 무력화된다. 필터를 제외해 두면 이 경로만은 <b>원 소켓 IP</b> 로
|
||||||
|
* 검사되므로 기존 가드가 설계대로 동작한다.
|
||||||
|
*
|
||||||
|
* <p><b>전제</b>: {@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<ForwardedHeaderFilter> forwardedHeaderFilterRegistration() {
|
||||||
|
FilterRegistrationBean<ForwardedHeaderFilter> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.})
|
||||||
|
* 에서 읽는 얇은 접근자.
|
||||||
|
*
|
||||||
|
* <p>안내페이지에 표기되는 게이트웨이 주소는 서비스 환경(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user