Skip to content

refactor: 관리자 대학 이미지 업로드 경로 식별자 개선#782

Merged
Hexeong merged 2 commits into
developfrom
refactor/774-admin-university-image-path-identifier
Jun 22, 2026
Merged

refactor: 관리자 대학 이미지 업로드 경로 식별자 개선#782
Hexeong merged 2 commits into
developfrom
refactor/774-admin-university-image-path-identifier

Conversation

@lsy1307

@lsy1307 lsy1307 commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

관련 이슈

작업 내용

관리자 대학 이미지 업로드를 대학 생성/수정 API에 통합

  • 관리자 대학 생성 API가 multipart/form-data로 대학 정보 JSON(request)과 로고/배경 이미지 파일을 함께 받도록 변경했습니다.
  • 관리자 대학 수정 API도 multipart/form-data로 변경하고, 로고/배경 이미지 파일은 선택 입력으로 처리했습니다.
  • 기존 분리형 관리자 대학 이미지 업로드 API(/file/admin/university/logo, /file/admin/university/background)는 제거했습니다.

이유

  • 이미지 업로드 API와 대학 CRUD API가 분리되어 있으면 프론트에서 호출 순서를 관리해야 하고, 업로드 성공 후 대학 생성/수정 실패 시 S3 파일이 남을 수 있습니다.
  • 대학 이미지 업로드는 대학 생성/수정 유스케이스에 종속된 작업이므로, 서버에서 하나의 작업 흐름으로 관리하는 편이 실패 처리와 데이터 정합성 측면에서 안전합니다.

이미지 URL 요청 필드 제거

  • AdminHostUniversityCreateRequest, AdminHostUniversityUpdateRequest에서 logoImageUrl, backgroundImageUrl을 제거했습니다.
  • 서버가 S3 업로드 결과로 받은 URL을 HostUniversity 이미지 필드에 저장하도록 변경했습니다.

이유

  • 클라이언트가 임의 URL을 전달하는 구조에서는 업로드 실패, 비이미지 파일 URL 저장, 잘못된 경로 저장을 서버가 충분히 제어하기 어렵습니다.
  • 이미지 파일 검증과 URL 생성 책임을 서버로 모아 관리자 대학 이미지 필드의 무결성을 높였습니다.

대학별 S3 경로 식별자 개선

  • UploadDirectoryName.fromUniversityNames(englishName, koreanName)을 추가했습니다.
  • 디렉토리 prefix는 영문명을 slug로 변환하고, suffix hash는 unique 제약이 있는 한글명 기준으로 생성하도록 변경했습니다.

이유

  • HostUniversity.englishName은 unique 제약이 없어 서로 다른 대학이 같은 영문명을 가질 수 있습니다.
  • 영문명 정규화는 공백/특수문자 제거 등 손실적 변환이 포함되어 같은 slug로 충돌할 수 있습니다.
  • 한글명은 unique 제약이 있으므로, 같은 영문명이어도 한글명이 다르면 서로 다른 S3 경로를 만들 수 있습니다.

업로드 실패 보상 삭제 처리

  • 대학 생성/수정 중 S3 업로드 또는 DB 저장이 실패하면, 이미 업로드된 새 이미지를 삭제하도록 보상 로직을 추가했습니다.
  • 로고 업로드 성공 후 배경 업로드가 실패하는 경우에도 로고 파일이 남지 않도록 업로드 단계 전체를 보상 삭제 범위에 포함했습니다.
  • 보상 삭제 자체가 실패해도 원래 실패 원인이 덮이지 않도록 warn 로그만 남기고 원래 예외를 유지했습니다.

이유

  • S3는 DB 트랜잭션에 포함되지 않기 때문에 DB rollback만으로 업로드된 파일을 되돌릴 수 없습니다.
  • 중간 실패 시 orphan 파일이 남지 않도록 서버에서 보상 삭제를 수행해야 합니다.
  • 보상 삭제 실패가 원래 예외를 덮으면 실제 원인 파악이 어려워지므로 원래 예외를 보존했습니다.

S3 삭제 API 캡슐화

  • S3Service.deleteFile(String)은 내부 구현으로 유지하고, 외부에서는 deleteUploadedFile(UploadedFileUrlResponse)를 사용하도록 변경했습니다.
  • UploadedFileUrlResponse에 내부 삭제용 deletionKey를 추가하고, API 응답에는 노출되지 않도록 @JsonIgnore를 적용했습니다.

이유

  • 리사이즈 대상 이미지는 클라이언트/DB에 저장되는 반환 URL과 실제 업로드 key가 다를 수 있습니다.
  • 삭제 호출부가 S3 key 정책을 직접 알지 않도록 삭제 책임을 S3Service 내부로 캡슐화했습니다.

테스트 보강

  • 관리자 대학 생성/수정 시 이미지 업로드 URL 저장 동작을 검증했습니다.
  • 수정 시 이미지 파일을 전달하지 않으면 기존 이미지 URL을 유지하는지 검증했습니다.
  • 같은 영문명이어도 한글명이 다르면 서로 다른 S3 경로가 생성되는지 검증했습니다.
  • 배경 이미지 업로드 실패 시 이미 업로드된 로고 이미지가 보상 삭제되는지 검증했습니다.
  • 보상 삭제가 실패해도 원래 예외가 유지되는지 검증했습니다.
  • deleteUploadedFilefileUrl이 아니라 deletionKey로 삭제 요청을 보내는지 검증했습니다.

이유

  • 이번 변경은 API 요청 형식, S3 경로 생성, 실패 보상 로직이 함께 바뀌므로 정상 흐름뿐 아니라 중간 실패 흐름까지 회귀 테스트가 필요합니다.

특이 사항

  • 관리자 대학 생성/수정 API 요청 형식이 JSON body에서 multipart/form-data로 변경됩니다.
  • 생성 요청은 request, logoFile, backgroundFile part가 필요합니다.
  • 수정 요청은 request part가 필요하고, logoFile, backgroundFile part는 선택입니다.
  • 이미지 파일 없이 대학명 등 텍스트 정보만 수정하는 요청은 허용하며, 이 경우 기존 이미지 URL을 유지합니다.
  • S3 경로는 현재 대학명을 항상 반영하는 값이 아니라 이미지 업로드 시점의 저장 경로로 취급합니다. 따라서 이름만 변경하면 기존 이미지 경로가 이전 이름 기반 디렉터리를 가리킬 수 있습니다.
  • 이름 변경에 맞춰 기존 S3 객체를 자동 이동하지 않습니다. S3 rename은 copy/delete 작업이 필요하고 DB 커밋 이후 처리되어야 하므로, 필요 시 별도 마이그레이션/정리 작업 또는 이미지 재업로드 정책으로 다룹니다.
  • 기존 이미지 교체 성공 후 old image 삭제는 이번 PR 범위에 포함하지 않았습니다. 커밋 성공 이후 삭제되어야 하며, 프로필/뉴스/게시글 등 기존 이미지 삭제 정책도 함께 정리해야 하므로 별도 후속 작업으로 다루는 것이 안전합니다.
  • S3 업로드가 비동기로 처리되는 구조상, 실제 S3 반영 실패까지 DB 트랜잭션과 완전히 묶는 문제는 별도 개선 포인트로 남아 있습니다.

리뷰 요구사항 (선택)

  • AdminHostUniversityService가 S3 업로드까지 담당하도록 변경한 결합도가 현재 유스케이스 기준에서 적절한지 확인 부탁드립니다.
  • 한글명 unique 제약을 기반으로 S3 경로 hash suffix를 만드는 방향이 도메인 정책과 맞는지 확인 부탁드립니다.
  • 업로드 실패 보상 삭제 범위와 예외 보존 방식이 충분한지 확인 부탁드립니다.

lsy1307 added 2 commits June 21, 2026 09:57
- 관리자 대학 생성/수정 API에서 multipart로 대학 정보와 이미지 파일을 함께 받도록 변경

- 이미지 URL을 요청 DTO에서 제거하고 서버에서 S3 업로드 결과를 저장하도록 변경

- 영문명 slug와 한글명 hash를 조합해 중복 영문명 간 S3 경로 충돌을 방지

- 업로드 또는 DB 저장 실패 시 새로 업로드된 이미지가 남지 않도록 보상 삭제 처리
- 대학 생성/수정 시 이미지 업로드 URL 저장과 기존 이미지 유지 동작 검증

- 한글명 기반 S3 경로 식별자와 중복 영문명 충돌 방지 검증

- 업로드 실패 보상 삭제와 삭제용 key 사용 여부 검증
@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 '관리자 대학 이미지 업로드 경로 식별자 개선'으로 주요 변경사항(S3 경로 식별자 개선)을 명확하게 요약하고 있습니다.
Linked Issues check ✅ Passed PR이 #774의 모든 핵심 목표를 구현합니다: (1) koreanName 기반 hash suffix 추가 [#774], (2) 영문명 slug + 한글명 hash 조합 방식 [#774], (3) 중복 englishName 환경에서 경로 분리 [#774], (4) 멀티파트 API 통합 [#774], (5) 테스트 강화 [#774].
Out of Scope Changes check ✅ Passed 모든 변경이 S3 경로 식별자 개선, API 통합, 실패 보상 처리, 삭제 캡슐화라는 명시된 목표에 직결됩니다. 기존 이미지 삭제, 비동기 처리 개선 등은 의도적으로 후속 작업으로 배제했습니다.
Description check ✅ Passed PR 설명이 템플릿의 모든 필수 섹션을 포함하며, 작업 내용과 변경 사항을 명확하고 상세하게 기술했습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/774-admin-university-image-path-identifier

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@lsy1307 lsy1307 self-assigned this Jun 22, 2026
@whqtker whqtker added the 진행 중 자유롭게 merge 가능 label Jun 22, 2026
@lsy1307 lsy1307 marked this pull request as ready for review June 22, 2026 01:18

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b029f6fae4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +52 to +53
@RequestPart("logoFile") MultipartFile logoFile,
@RequestPart("backgroundFile") MultipartFile backgroundFile

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Raise multipart request limit for paired image uploads

This create endpoint now requires logoFile and backgroundFile in the same multipart request, but application.yml still caps spring.servlet.multipart.max-request-size at 10MB while each individual file is allowed up to 10MB. A logo and background that are each valid on their own, e.g. two ~6MB images, will be rejected by the servlet container before reaching this controller, so admins cannot create a university with two otherwise acceptable images unless the aggregate request limit is raised or uploads remain separate.

Useful? React with 👍 / 👎.

if (uploadedFile == null) {
return;
}
deleteFile(uploadedFile.deletionKey());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Delete the resized object during compensation

For images at or above the resize threshold, uploadFile stores deletionKey as original/... but returns fileUrl as resize/...webp; this compensation path deletes only the original key. If a later background upload or DB flush fails after the resize Lambda has already produced the resized logo/background, AdminHostUniversityService.deleteUploadedImages leaves the resize/... object orphaned even though the university was not saved. Delete both keys when they differ, or otherwise remove the generated resize key too.

Useful? React with 👍 / 👎.

@Hexeong Hexeong left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하십니다! 리뷰 남겨드려요!

);
hostUniversityRepository.flush();
evictUnivApplyInfoDetailCaches(id);
return AdminHostUniversityDetailResponse.from(hostUniversity);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이미지 교체 성공 시 기존 S3 객체는 삭제하지 않는 걸로 보입니다!
실패 보상 삭제는 잘 들어갔지만, 수정 API에서 새 로고/배경 업로드가 성공하면 DB URL만 교체하고 이전 이미지는 남습니다. 기존 정책이 “고아 파일 허용”이면 괜찮지만, 비용/정리 관점에서는 누수입니다.
이 부분 확인 부탁드립니다!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인했습니다. 기존 이미지 교체 성공 후 old S3 객체 삭제는 현재 대학 이미지에는 적용되어 있지 않습니다.

다만 기존 로직을 확인해보니 프로필, 뉴스, 게시글 쪽은 old image 삭제를 수행하고 있지만, 모두 DB 트랜잭션 커밋 전 삭제하고 있습니다. 이 경우 DB 업데이트가 rollback되면 DB는 기존 URL을 유지하는데 S3 객체는 이미 삭제되는 문제가 생길 수 있습니다.

따라서 이 부분은 대학 이미지에만 즉시 추가하기보다, S3 객체 삭제 정책을 공통으로 정리하는 별도 PR에서 처리하는 것이 맞다고 판단했습니다. 후속 작업에서는 기존 이미지 삭제를 AFTER_COMMIT 기준으로 수행하도록 프로필/뉴스/게시글/대학 이미지 교체 로직을 함께 정리하겠습니다.

backgroundFile,
UploadPath.ADMIN_UNIVERSITY_BACKGROUND,
directoryName
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이름 변경만 하고 이미지를 안 올리면 URL 경로와 새 이름 기반 디렉터리가 불일치할 수 있을 것 같습니다!
수정 시 이미지가 없으면 기존 URL을 유지합니다. 기능적으로는 자연스럽지만, 이번 PR 제목처럼 “경로 식별자 개선”이 목적이면 운영 데이터에서 같은 대학의 이름과 이미지 경로 식별자가 엇갈릴 수 있을 것으로 보이네요.. 확인 부탁드립니다!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인했습니다. 현재 경로 식별자는 이미지 업로드 시점의 저장 경로를 안정적으로 분리하기 위한 값이고 대학명 변경 시 기존 이미지를 자동 이동하지는 않습니다. 이름만 수정하는 경우 기존 이미지 URL을 유지하는 것이 의도된 동작입니다. S3 객체 rename은 실제로 copy+delete 작업이고 DB 커밋 이후 처리해야 하므로 수정 API에 암묵적으로 포함하면 실패 지점이 커질 수 있습니다. 이미지 경로를 새 이름 기준으로 맞추려면 새 이미지를 함께 업로드하도록 운영 정책을 두거나 별도 마이그레이션/정리 작업으로 다루는 편이 안전하다고 봅니다.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인했습니다! 그럼 단순 이름 변경에 대해서는 해당 API를 요청하지 않도록 방어로직을 짤 필요는 없는 걸까요? 정책으로 다룬다면 해당 부분에 대해서 명확히 FE 쪽과 협의가 필요해 보이긴 합니다!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

말씀하신 것처럼 FE/운영 정책은 명확히 맞춰야 할 것 같습니다. FE에서는 이름만 변경하는 경우 이미지 파일 없이 수정 API를 호출해도 되고 이미지까지 교체하려는 경우에만 새 파일을 함께 전달하는 방향으로 협의하겠습니다. PR 특이사항에도 이 정책을 명시하겠습니다.

@Hexeong Hexeong left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다! 추가 작업 내용은 까먹지 않게 이슈로 만들어주세요!

@Hexeong Hexeong merged commit f14b384 into develop Jun 22, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

리팩터링 진행 중 자유롭게 merge 가능

Projects

None yet

Development

Successfully merging this pull request may close these issues.

관리자 대학 이미지 업로드 경로 식별자 개선

3 participants