# tdc114plus 프로필 이미지 관리 정책 및 단계별 진행안

작성일: 2026-07-14
최종 개정일: 2026-07-20
상태: v2.2
관련 Phase: `Phase 7-C`

## 1. 목적

신규앱의 직원 프로필 이미지를 어떤 우선순위와 어떤 식별 기준으로 운영할지 고정한다.

본 문서는 아래 사항을 최신 기준으로 정리한다.

- 프로필 이미지 노출 우선순위
- 네이버웍스와 Baron SSO 조직도 응답의 역할
- R2 버킷 파일명 정책
- 앱과 중계서버가 직접 하지 않아야 할 일
- 현재까지 확인된 검증 결과
- 다음 작업 순서

## 2. 현재 확정 정책

### 2-1. 이미지 노출 우선순위

신규앱의 프로필 이미지 우선순위는 아래처럼 고정한다.

1. 네이버웍스 프로필 사진
2. Baron SSO 조직도 `members[].id` 기준 UUID 파일명 이미지
3. 신규앱 기본 아바타

즉, R2 버킷 이미지는 유지하되, 더 이상 이메일 또는 해시 매핑 DB를 기본 경로로 사용하지 않는다.

### 2-2. 기본 식별자 정책

- 직원 프로필 이미지의 2순위 식별자는 Baron SSO 조직도 응답의 `members[].id`를 사용한다.
- 이 값은 `includeUsers=true`, `includeUserIds=true` 조건의 `org-context` 응답에서 확인되는 사용자 고정 식별자다.
- 신규앱은 `이메일 @앞부분.jpg`를 직접 만들지 않는다.
- 신규앱은 해시 파일명도 직접 계산하지 않는다.
- 신규앱은 직원별 Baron UUID를 확보한 뒤 `https://baroncs.co.kr/employee_img/{uuid}.jpg` 규칙으로만 2순위 이미지를 시도한다.

### 2-3. DB 사용 정책

- 현재 운영 기본안에서는 프로필 이미지 전용 매핑 DB를 두지 않는다.
- `tdc114plus-auth` 내부 PostgreSQL 기반 fallback 구현은 검토/검증 결과물로만 남기고, 현재 채택안의 기본 경로로 사용하지 않는다.
- 추후 Baron SSO UUID를 안정적으로 받을 수 없는 별도 화면이나 별도 운영 요구가 생기면 그때 보조안으로만 재검토한다.

## 3. 현재까지 확인된 사실

### 3-1. 네이버웍스 문서 및 실연동 검토 결과

2026-07-15 기준 네이버웍스 개발자 문서와 실제 서비스 계정 호출 기준으로 아래를 확인했다.

- 구성원 프로필 조회 endpoint: `GET /users/{userId}`
- 구성원 사진 조회 endpoint: `GET /users/{userId}/photo`
- `userId`에는 구성원 ID, 메일, 리소스 ID, `externalKey:{externalKey}` 형식 사용 가능
- 사진 조회 응답은 `HTTP 302`, `HTTP 400`, `HTTP 404` 규칙을 가진다

실제 1차 호출 결과:

- 서비스 계정 토큰 발급: `HTTP 200`
- `GET /users/{userId}` 샘플 1
  - 입력: `khkang@samaneng.com`
  - 결과: `HTTP 200`
- `GET /users/{userId}/photo` 샘플 1
  - 입력: `khkang@samaneng.com`
  - 결과: `HTTP 404`
- `GET /users/{userId}` 샘플 2
  - 입력: `thlee3@samaneng.com`
  - 결과: `HTTP 200`
- `GET /users/{userId}/photo` 샘플 2
  - 입력: `thlee3@samaneng.com`
  - 결과: `HTTP 302`
  - `Location` 헤더에 실제 이미지 접근 URL 반환 확인

현재 판단:

- 네이버웍스 사진 연동은 1순위 경로로 실제 성립한다.
- 현재까지의 1차 검증 기준으로는 `사진 있음 -> 302`, `사진 없음 -> 404` 흐름이 확인됐다.

### 3-2. R2 버킷과 공개 경로

현재 프로필 이미지 파일은 Cloudflare R2 Object Storage에 적재되어 있다.

- 공개 도메인 prefix: `https://baroncs.co.kr/employee_img/`
- 현재부터는 UUID 파일명 기준으로 관리한다.
- 목표 URL 규칙: `https://baroncs.co.kr/employee_img/{uuid}.jpg`

2026-07-15 기준 UUID 파일명 샘플 공개 URL 검증 결과:

- `https://baroncs.co.kr/employee_img/bebb832c-052e-493d-b5a9-732518d67685.jpg`
  - `HTTP 200`
  - `content-type: image/jpeg`
- `https://baroncs.co.kr/employee_img/cc2db80c-5a18-4439-8026-22dd06d6452f.jpg`
  - `HTTP 200`
  - `content-type: image/jpeg`

현재 판단:

- UUID 파일명으로 변경된 2순위 이미지가 실제 공개 경로에서 응답하는 것을 확인했다.
- 따라서 2순위 경로는 문서상 계획이 아니라 실배치/실응답까지 확인된 상태로 본다.

### 3-3. Baron SSO 조직도 UUID 실검증 결과

2026-07-15 기준 `org-context` 실조회로 아래 사실을 확인했다.

- 조회 대상: `tenantSlug=hanmac-family`
- 옵션: `includeUsers=true`, `includeUserIds=true`
- `members[].id`가 실제 응답에 포함된다
- 해당 값은 예시 문자열이 아니라 UUID 형식이다
- 예:
  - `c6ac492a-d4f3-4fff-8409-b50e317ca793`
  - `73f80ef0-62ec-49b2-a8eb-1f4de52966b7`
  - `bebb832c-052e-493d-b5a9-732518d67685`

현재 판단:

- `members[].id`는 2순위 프로필 이미지 파일명 기준으로 사용할 수 있다.
- 이메일 local-part보다 보안성과 변경 내성이 높다.

### 3-4. 가족사 전인원 UUID 매핑 확인 결과

2026-07-15 기준 가족사 전체 `org-context` 응답과 기존 파일 매핑 CSV를 대조해 아래를 확인했다.

- 기준 원본: `docs/references/file_rename_hash_results.csv`
- 기존 원본 행 수: `2457`
- `org-context` 고유 이메일 수: `2610`
- UUID 매핑 성공 행 수: `2457`
- 미매핑 행 수: `0`

생성한 참고 산출물:

- `docs/references/profile_image_uuid_rename_candidates.csv`

이 CSV에는 아래 컬럼을 포함했다.

- `comp`
- `employee_name`
- `employee_email`
- `employee_email_local_part`
- `legacy_photo_file_name`
- `hashed_photo_file_name`
- `baron_user_uuid`
- `target_uuid_file_name`
- `rename_ready`

현재 판단:

- 기존 사진 파일을 UUID 파일명으로 재정리하는 작업은 충분히 진행 가능하다.
- 현재 확보된 CSV만으로 `기존 파일명 -> UUID 파일명` 변경 작업 계획을 잡을 수 있다.

## 4. 채택안 기준 연동 구조

### 4-1. 신규앱 기본 흐름

```text
직원 DTO 수신
-> 앱이 직접 네이버웍스와 외부 이미지 서버를 각각 판단하지 않음
-> 중계서버 /api/v1/profile-image 호출
-> 중계서버가 네이버웍스 사진 조회 시도
-> 실패 시 Baron UUID 기준 https://baroncs.co.kr/employee_img/{uuid}.jpg 판단
-> 둘 다 없으면 found=false 반환
-> 앱은 기본 아바타 표시
```

현재 시점의 가장 효율적인 구현안은, 앱이 직접 1순위/2순위를 분기하지 않고 기존 `tdc114plus-auth`의 `GET /api/v1/profile-image` endpoint를 유지한 채 내부 우선순위만 `네이버웍스 -> UUID 이미지 -> 기본 미발견`으로 바꾸는 방식이다.

2026-07-20 기준 네이버웍스 1순위 이미지는 외부 `Location` URL을 앱에 그대로 전달하지 않는다.

이유:

- 네이버웍스 `GET /users/{userId}/photo`는 사진이 있으면 `302 Location`을 반환한다.
- 일부 `Location` URL은 앱이나 일반 HTTP 클라이언트가 인증 없이 직접 열면 `400 Authentication failed`가 발생한다.
- 따라서 앱이 네이버웍스 원본 URL을 직접 표시하는 방식은 안정적이지 않다.

현재 확정 처리:

- `tdc114plus-auth`가 네이버웍스 사진 존재 여부를 확인한다.
- 네이버웍스 사진이 있으면 앱에는 `tdc114plus-auth`의 프록시 URL을 반환한다.
- 앱은 이 프록시 URL을 일반 이미지 URL처럼 표시한다.
- 프록시는 내부에서 네이버웍스 Access Token을 사용해 실제 이미지 바이너리를 받아 앱에 `image/jpeg`로 내려준다.
- 네이버웍스 사진이 없거나 프록시 확인이 실패하면 UUID 이미지 2순위로 내려간다.

이 방식의 장점은 아래와 같다.

- 앱 계약을 크게 흔들지 않는다.
- 네이버웍스 서비스 계정/토큰 처리 로직을 앱에 넣지 않아도 된다.
- 추후 우선순위가 바뀌어도 서버만 수정하면 된다.
- UUID 이미지 경로 변경, 캐시 정책, timeout 정책을 서버에서 통제할 수 있다.

### 4-2. 신규앱이 직접 하지 않아야 하는 일

- `이메일 @앞부분.jpg` 직접 조합
- 해시 파일명 직접 계산
- 별도 프로필 이미지 매핑 DB 직접 조회
- 버킷 내부 경로 추론

### 4-3. 중계서버 역할

현재 채택안 기준 중계서버의 역할은 아래처럼 정리한다.

- Baron SSO 로그인/세션 유지
- 필요 시 조직도 `org-context` proxy 제공
- `GET /api/v1/profile-image` 단일 endpoint 제공
- 네이버웍스 1순위 경로 처리
- 네이버웍스 사진 원본 URL을 앱에 직접 노출하지 않고 `GET /api/v1/profile-image/naver-photo` 프록시로 이미지 바이너리 제공
- Baron UUID 기준 2순위 이미지 경로 판단
- 앱에는 최종 `found/source/imageUrl` 결과만 반환

현재 채택안 기준으로는 중계서버가 프로필 이미지 전용 매핑 DB를 운영 기본 구조로 갖지 않는다. 다만 기존 `tdc114plus-auth`의 `/api/v1/profile-image` endpoint는 유지하고, 내부 로직만 새 우선순위로 재정리하는 것이 가장 효율적이다.

## 5. PostgreSQL 검토 결과 정리

2026-07-15 기준 `tdc114plus-auth`에 PostgreSQL 기반 fallback 검토를 이미 진행했다.

확인된 내용:

- 로컬 PostgreSQL 컨테이너 구성
- `profile_image_mapping` 스키마 초안 작성
- CSV `2457`건 적재 검증
- `GET /api/v1/profile-image?comp=...&email=...` 샘플 검증

현재 정책 판단:

- 위 결과는 기술 검증 자료로는 유효하다.
- 하지만 운영 기본안은 아니다.
- 현재 기준 주 경로는 `네이버웍스 -> UUID 파일명 이미지 -> 기본 아바타`다.
- 따라서 PostgreSQL 경로는 `보류된 대안`으로만 기록한다.
- 실제 구현은 기존 `tdc114plus-auth` endpoint를 재사용하되, PostgreSQL 분기는 기본 비활성 또는 최후 예비안으로만 남기는 편이 적절하다.

## 6. 단계별 진행작업 타임테이블

| 단계 | 상태 | 작업 구분 | 작업 내용 | 완료 기준 |
| --- | --- | --- | --- | --- |
| Step 1 | 완료 | 문서/정책 | 프로필 이미지 우선순위와 기본 검토 구조를 정리했다 | 초기 문서 기준선 확보 |
| Step 2 | 완료 | 네이버웍스 문서 검토 | `GET /users/{userId}`, `GET /users/{userId}/photo`, scope, 302/404 구조를 확인했다 | 1순위 경로 성립 가능 조건 확인 |
| Step 3 | 완료 | R2 자산 점검 | 공개 prefix `https://baroncs.co.kr/employee_img/` 와 공개 응답을 확인했다 | 2순위 파일 배치 경로 확인 |
| Step 4 | 완료 | Baron UUID 실검증 | `org-context` 실응답에서 `members[].id`가 UUID 형식으로 내려오는 것을 확인했다 | 2순위 식별자 확정 |
| Step 5 | 완료 | 가족사 전인원 매핑 검증 | 기존 CSV `2457`건을 Baron UUID와 대조해 전건 매핑 성공을 확인했다 | `profile_image_uuid_rename_candidates.csv` 생성 |
| Step 6 | 완료 | 외부 파일 재배치 | 기존 사진 파일명을 `[uuid].jpg`로 변경하고 `employee_img/` 하위에 재배치했다 | 샘플 URL 2건 `HTTP 200 image/jpeg` 검증 완료 |
| Step 7 | 완료 | 앱 공통 resolver 정리 | 앱은 `GET /api/v1/profile-image`를 우선 호출하고, 응답 실패 또는 미발견 시에는 `profileImageUrl -> UUID 파일명 -> 기본 아바타` 순서의 보조 fallback을 사용하도록 정리했다 | 앱 공통 resolver와 보조 fallback 반영 완료 |
| Step 8 | 완료 | 네이버웍스 실연동 검증 | 개발자 계정 기준으로 사진 조회를 실제 호출해 `302`와 `404` 규칙을 확인했다 | 1순위 경로 실응답 규칙 1차 확정 |
| Step 9 | 완료 이력 | 서버 구현 및 실검증 | 2026-07-15 시점에는 `tdc114plus-auth GET /api/v1/profile-image`가 `NAVER WORKS -> Baron UUID 이미지 -> DEFAULT` 순서로 동작하는 구현과 샘플 응답을 확인했다 | 2026-07-15 기준 실검증 이력 |
| Step 10 | 완료 | 현재 기동 정합성 검증 | 2026-07-20 기준 `tdc114plus-auth`에 `/api/v1/profile-image`와 `/api/v1/profile-image/naver-photo` 프록시 route를 복구/보강했다 | `go test ./cmd/server` 통과, 5001 health 정상 |
| Step 11 | 완료 | 네이버웍스 프록시 검증 | 네이버웍스 302 Location을 앱에 직접 주지 않고 중계서버 프록시가 `image/jpeg` 바이너리로 내려주는 것을 확인했다 | `한치영`, `이태훈` 프록시 `200 OK image/jpeg` 확인 |
| Step 12 | 진행 중 | 실기기 화면 확대 검증 | 직원검색 목록에서 NAVER_WORKS와 BARON_UUID_R2가 동시에 정상 표시되는 것을 확인했다. DEFAULT 기본 아바타 케이스는 추가 샘플로 계속 확인한다 | 1순위/2순위 화면 확인 완료, 3순위 확대 검증 필요 |

## 7. 다음 작업 순서

1. DEFAULT 기본 아바타 케이스를 명시 샘플로 추가 확인한다.
2. 직원 상세/조직도/즐겨찾기 화면에서도 동일 이미지 규칙이 유지되는지 확인한다.
3. 화면 전환 후 이미지 캐시가 잘못된 null 상태를 유지하지 않는지 확인한다.
4. `adb reverse 5001/5000`이 끊겼을 때 로그인 실패처럼 보일 수 있으므로, 실기기 검증 전 reverse 상태를 먼저 확인한다.
5. 검증 결과를 본 문서와 타임테이블 문서에 즉시 반영한다.

## 8. 현재 시점 결론

- 프로필 이미지 1순위는 네이버웍스다.
- 2순위는 Baron SSO 조직도 `members[].id`를 파일명으로 사용하는 UUID 이미지다.
- 현재 운영 기본안에서는 프로필 이미지 전용 DB를 두지 않는다.
- 기존 PostgreSQL fallback 검토는 예비안으로만 남긴다.
- 2026-07-20 기준 `tdc114plus-auth`의 `/api/v1/profile-image` route와 네이버웍스 프록시 route는 복구/보강됐다.
- 네이버웍스 사진 원본 URL은 앱에 직접 주지 않고 `tdc114plus-auth` 프록시 URL로 제공한다.
- 현재 가장 중요한 다음 단계는 실기기 기준으로 `DEFAULT` 3순위와 직원 상세/조직도/즐겨찾기 화면의 동일 규칙 유지 여부를 확대 검증하는 것이다.

## 9. 2026-07-20 검증 및 보강 메모

2026-07-20 기준 아래를 확인했다.

- `tdc114plus-auth GET /api/v1/profile-image` route 복구 및 유지
- `tdc114plus-auth GET /api/v1/profile-image/naver-photo` 프록시 route 추가
- `cyhan@samaneng.com`, `thlee3@samaneng.com` 네이버웍스 사진 존재 확인
- 네이버웍스 `Location` 원본 URL 직접 접근 시 `400 Authentication failed`가 발생할 수 있음 확인
- 프록시 route에서 두 사용자 모두 `200 OK`, `Content-Type: image/jpeg` 확인
- 실기기 직원검색 화면에서 기존에 기본 이니셜로 떨어지던 인원의 네이버웍스 사진 표시 정상화 확인
- `BARON_UUID_R2` source 로그도 동시에 확인되어 2순위 경로가 유지됨을 확인

검증한 자동 테스트:

- `tdc114plus-auth`: `GOCACHE=/tmp/go-build-cache go test ./cmd/server`
- `tdc114plus`: `./scripts/flutter-docker.sh test test/directory/profile_image_api_client_test.dart`

주의:

- 로컬 실기기 검증 중 `adb reverse tcp:5001 tcp:5001`, `adb reverse tcp:5000 tcp:5000`이 끊기면 앱 화면에는 `인증 서버에 연결하지 못했습니다`처럼 보일 수 있다.
- 이 경우 프로필 사진 코드 문제가 아니라 실기기와 로컬 중계서버 연결 문제일 수 있으므로, 먼저 `adb reverse --list`를 확인한다.

## 10. 2026-07-15 실기기 확인 메모

2026-07-15 기준 Android 실기기 직원검색 화면에서 실제 프로필 사진 노출을 확인했다.

확인 내용:

- 로그인 후 직원검색 목록 진입 확인
- 원형 기본 이니셜 아바타 대신 실제 사진 노출 확인
- 확인 화면에는 여러 직원의 사진이 동시에 표시됐다
- 현재 앱은 `tdc114plus-auth /api/v1/profile-image`를 우선 호출하고, 응답 실패 또는 미발견 시 UUID 공개 경로 fallback을 사용한다

현재 판단:

- 정책 문서상 구조가 아니라, 실기기 화면 기준으로도 프로필 사진 표시가 동작하는 상태다
- 다만 어떤 직원이 `NAVER_WORKS`로 해석됐는지, 어떤 직원이 `BARON_UUID_R2`로 해석됐는지는 추가 로그/샘플 검증으로 더 구분해 둘 필요가 있다

## 9. 2026-07-15 자격값 재검증 메모

2026-07-15 오후 기준, 갱신된 네이버웍스 서비스 계정 자격값을 다시 반영한 뒤 실제 호출을 재검증했다.

확인 결과:

- 서비스 계정 토큰 발급: `HTTP 200`
- 샘플 사용자 `thlee3@samaneng.com` 프로필 조회: `HTTP 200`
- 샘플 사용자 `thlee3@samaneng.com` 사진 조회: `HTTP 302`
- `Location` 헤더로 실제 이미지 접근 URL 반환 확인

현재 판단:

- 최신 자격 세트는 유효하다.
- `tdc114plus-auth` 서버가 이 자격 세트를 사용해 1순위 경로를 실제 구현할 수 있는 준비가 됐다.
- 이후 로컬 auth 서버 실기동 검증에서도 아래를 확인했다.
  - `GET /api/v1/profile-image?email=thlee3@samaneng.com`
    - `found=true`
    - `source=NAVER_WORKS`
  - `GET /api/v1/profile-image?email=khkang@samaneng.com`
    - `found=true`
    - `source=BARON_UUID_R2`

## 10. 관련 문서

- `docs/00_guide_tdc114plus_work_progress_timetable_2026-07-02.md`
- `docs/00_guide_tdc114plus_org_context_mapping_2026-07-08.md`
- `docs/00_contract_tdc114plus_api_2026-07-02.md`
