1. CI 빌드 실패 수정
문제 상황
GitHub Actions CI 빌드가 계속 실패했다. 원인은 세 가지였다.
- 봇이 자동 생성한 DefaultSecurityConfigTest.java가 SecurityConfig가 없다는 전제로 작성되어 있었음
- CI 환경에는 MySQL과 .env 파일이 없어서 DB 연결 실패
- Redis 서비스가 CI 환경에 없어서 연결 실패
해결 방법
- DefaultSecurityConfigTest.java 삭제
- application-test.yml 생성 (H2 인메모리 DB로 대체)
- OneStopApplicationTests.java에 @ActiveProfiles("test") 추가
- ci.yml에 Redis 서비스 추가
왜 H2를 선택했나?
| 기술 | 장점 | 단점 | 사용하면 좋은 곳 |
| H2 인메모리 DB | 별도 설치 불필요, 빠름 | MySQL과 완전히 동일하지 않음 | CI/CD 테스트 환경 |
| TestContainers | 실제 MySQL 사용 가능 | 컨테이너 실행 시간 필요, 설정 복잡 | 통합 테스트, 실제 DB 동작 검증 필요 시 |
| MockBean | 의존성 없이 테스트 | 실제 DB 동작 검증 불가 | 단위 테스트 |
- CI 환경에서는 빠른 빌드가 중요하고 간단한 컨텍스트 로드 테스트만 필요해서 H2를 선택했다.
2. SUPER_ADMIN Role 접근 권한 추가
변경 내용
// 변경 전
.requestMatchers("/api/admin/**").hasRole("ADMIN")
// 변경 후
.requestMatchers("/api/admin/**").hasAnyRole("ADMIN", "SUPER_ADMIN")
- SUPER_ADMIN은 ADMIN 권한을 포함하는 최고 관리자 역할로 /api/admin/** 접근이 정책상 필요하다.
AI 리뷰 대응
- 봇이 "권한 과부여 위험"이라고 경고했지만 SUPER_ADMIN이 ADMIN보다 높은 권한을 가지는 것은 의도된 정책이므로 무시했다.
3. 판매자 승인/반려 API
구현 기능
- GET /api/admin/sellers?status=PENDING - 대기 중인 판매자 목록
- POST /api/admin/sellers/{sellerId}/approve - 판매자 승인
- POST /api/admin/sellers/{sellerId}/reject - 판매자 반려
AI 리뷰 보고 수정한 것
사업자등록번호 마스킹
- 처음에 단순 substring으로 처리했는데 AI 리뷰에서 null 체크와 형식 검증이 없다고 지적했다. 아래와 같이 개선했다.
private static String maskBusinessNumber(String businessNumber) {
if (businessNumber == null || businessNumber.isBlank()) {
log.warn("사업자등록번호가 비어있습니다.");
return MASKED_DEFAULT;
}
String digits = businessNumber.replaceAll("[^0-9]", "");
if (digits.length() != 10) {
log.warn("올바르지 않은 사업자등록번호 포맷입니다. 길이: {}", digits.length());
return MASKED_DEFAULT;
}
return digits.substring(0, 3) + "-****-" + digits.substring(8);
}
개선 포인트
- null/빈 문자열 체크
- 하이픈 제거 후 순수 숫자만 추출 (123-45-67890 형식도 대응)
- 10자리 아닌 경우 기본 마스킹값 반환
- @Slf4j는 record에서 지원 안 됨 → class로 변경
AI 리뷰 무시한 것
- 인증/인가 누락: SecurityConfig에서 /api/admin/** 전체를 이미 막고 있어 컨트롤러 레벨 @PreAuthorize는 이중 처리
- 낙관적 락: 관리자 기능이라 동시 요청 가능성 낮음, MVP 단계에서 불필요
4. 상품 승인/반려 + FORCE_INACTIVE API
구현 기능
- GET /api/admin/products - 승인 요청된 상품 목록 (페이징)
- POST /api/admin/products/{productId}/approve - 상품 승인
- POST /api/admin/products/{productId}/reject - 상품 반려
- POST /api/admin/products/{productId}/force-inactive - 상품 강제 비활성화
AI 리뷰 보고 수정한 것
1. 에러코드 구체화
- 처음에 forceInactive에서 ADMIN_003(이미 거절된 판매자)을 재사용하고 있었다. 전용 에러코드를 추가했다.
ADMIN_007 - 이미 강제 비활성화된 상품
ADMIN_008 - 이미 승인된 상품
ADMIN_009 - 이미 반려된 상품
2. 페이징 적용
- 대량 조회 시 성능 문제를 방지하기 위해 페이징을 적용했다.
@PageableDefault(size = 20, sort = "id", direction = Sort.Direction.DESC) Pageable pageable
3. 중복 코드 제거
- 상품 조회 + 예외처리가 3개 메서드에서 반복되어 공통 메서드로 추출했다.
private Product findProductOrThrow(Long productId) {
return productRepository.findById(productId)
.orElseThrow(() -> new CustomException(ErrorCode.PRODUCT_001));
}
5. 판매자 강제 비활성화 API
정책 결정 과정
- 처음에 SellerStatus에 FORCE_INACTIVE를 추가하려 했다. 하지만 이미 UserStatus.SUSPENDED가 있어서 이를 활용하는 게 더 깔끔하다고 판단했다.
판매자 강제 비활성화 = User.status → SUSPENDED
판매자 소속 상품 전체 → FORCE_INACTIVE
정지 해제 후에도 상품은 FORCE_INACTIVE 유지 (재판매 시 관리자 승인 필요)
AI 리뷰 보고 수정한 것: N+1 문제 해결
기존 코드 (N+1 문제)
productRepository.findAllBySellerId(seller.getId())
.forEach(Product::forceInactive);
- 상품이 100개면 UPDATE 쿼리가 100번 나간다.
개선 후 (배치 업데이트)
productRepository.updateStatusBySellerId(seller.getId(), ProductStatus.FORCE_INACTIVE);
@Modifying(clearAutomatically = true)
@Query("update Product p set p.status = :status where p.seller.id = :sellerId")
int updateStatusBySellerId(@Param("sellerId") Long sellerId, @Param("status") ProductStatus status);
- 쿼리 1번으로 모든 상품 상태를 일괄 변경한다.
N+1 문제란? 1번의 쿼리로 N개의 데이터를 가져온 뒤 각각 추가 쿼리가 N번 발생하는 문제다. @Modifying + JPQL 배치 업데이트로 쿼리 1번에 처리하도록 개선했다.
clearAutomatically = true란? 배치 업데이트 쿼리 실행 후 영속성 컨텍스트를 자동으로 클리어해서 이후 조회 시 DB의 최신 상태를 보장한다. 이 옵션이 없으면 업데이트 후에도 캐시된 이전 상태를 읽어올 수 있다.
'💻 PROJECT > Team Projects' 카테고리의 다른 글
| [ 🚚 ONESTOP ] Spring Boot 모니터링(Prometheus + Grafana) + Swagger JWT 인증 설정 (0) | 2026.05.21 |
|---|---|
| [ 🚚 ONESTOP ] Spring Data JPA 집계 쿼리 - @Query, JPQL, N+1 트레이드오프 (0) | 2026.05.19 |
| [ 🚚 ONESTOP ] 정책 설계 — 옵션 중복 방지와 상태 관리 (0) | 2026.05.15 |
| [ 🚚 ONESTOP ] Docker Compose + GitHub Actions로 팀 개발 환경 & CI 구축하기 (0) | 2026.05.14 |
| [ 🚚 ONESTOP ] Spring Boot 프로젝트 기술 선택 이유 정리 (0) | 2026.05.13 |