# 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`