generated from Loopers-dev-lab/loop-pack-be-l2-vol2-java
-
Notifications
You must be signed in to change notification settings - Fork 44
[VOLUME-1] 회원가입, 내 정보조회, 비밀번호 수정 구현 #15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
letter333
wants to merge
34
commits into
Loopers-dev-lab:letter333
Choose a base branch
from
letter333:main
base: letter333
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
34 commits
Select commit
Hold shift + click to select a range
8f91639
Add CLAUDE.md for project documentation
letter333 f0a5513
Add member signup design documents
letter333 68815c0
Merge branch 'Loopers-dev-lab:main' into main
letter333 d7cd9ba
Merge branch 'Loopers-dev-lab:main' into feature/member-signup
letter333 050dc5b
feat: add Member domain entity with field validation
letter333 1270fe7
Merge remote-tracking branch 'origin/feature/member-signup' into feat…
letter333 74ff057
feat: add Member infrastructure layer with repository and persistence…
letter333 65fffca
feat: 회원가입 비즈니스 로직 구현 (MemberService, PasswordEncoderConfig)
letter333 fd22458
feat: 회원가입 Application 레이어 구현 (MemberFacade, MemberInfo)
letter333 cf164da
feat: 회원가입 API 엔드포인트 구현 (Controller, DTO, ApiSpec, E2E 테스트)
letter333 4b80ed3
fix: 회원가입 API birthday null-safety 보완 및 .http 파일 추가
letter333 3ba45ed
docs: 내 정보 조회 설계 문서 추가 및 회원가입 시퀀스 다이어그램 보완
letter333 d2dcf7a
feat: 회원 조회를 위한 findByLoginId Repository 메서드 추가
letter333 5284c21
feat: 회원 인증 로직 구현 (authenticate, ErrorType.UNAUTHORIZED)
letter333 c429856
feat: 내 정보 조회 API 구현 및 통합 테스트 생성자 주입 통일
letter333 98e979e
test: Member 도메인 엔티티 필드 검증 경계값 테스트 추가
letter333 56fe6cd
Merge pull request #1 from letter333/feature/member-profile-lookup
letter333 c81b9d3
feat: Member 도메인 비밀번호 변경 로직 구현 및 단위 테스트 추가
letter333 5adb112
feat: 비밀번호 변경 서비스 로직 및 Repository 업데이트 구현
letter333 65b637e
feat: 비밀번호 변경 API 엔드포인트 및 E2E 테스트 구현
letter333 7b50186
docs: CLAUDE.md 프로젝트 규칙 보완
letter333 b6e6b42
Merge pull request #2 from letter333/feature/member-password-update
letter333 8e8d396
refactor: 비밀번호 변경 검증 책임을 서비스 레이어로 분리
letter333 04a22cf
Merge pull request #3 from letter333/feature/member-password-update
letter333 d5d4041
chore: .gitignore에 docs/study/ 학습 폴더 제외 추가
letter333 fa330aa
Merge branch 'feature/member-signup' of https://github.com/letter333/…
letter333 ff6be56
Merge pull request #4 from letter333/feature/member-signup
letter333 e622564
feat: 내 정보 조회 시 이름 마지막 글자 마스킹 처리
letter333 8879f6f
Merge pull request #5 from letter333/feature/member-profile-lookup
letter333 7da2806
feat: 로그인 ID 영문/숫자만 허용하는 검증 규칙 추가
letter333 767b39d
Merge pull request #6 from letter333/feature/member-profile-lookup
letter333 d2aa7cf
refactor: DTO에 Bean Validation 적용 및 도메인 검증 책임 분리
letter333 234ef00
merge: main 브랜치 병합 및 비밀번호 변경 Bean Validation 적용
letter333 7d34ed0
Merge pull request #7 from letter333/feature/member-profile-lookup
letter333 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,3 +38,6 @@ out/ | |
|
|
||
| ### Kotlin ### | ||
| .kotlin | ||
|
|
||
| ### Study ### | ||
| docs/study/ | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,180 @@ | ||
| # CLAUDE.md | ||
|
|
||
| ## 프로젝트 개요 | ||
|
|
||
| Spring Boot 기반 커머스 멀티모듈 템플릿 프로젝트 (`loopers-java-spring-template`). | ||
| REST API, 배치 처리, 이벤트 스트리밍을 위한 마이크로서비스 아키텍처 패턴을 제공한다. | ||
|
|
||
| ## 기술 스택 및 버전 | ||
|
|
||
| | 구분 | 기술 | 버전 | | ||
| |------|------|------| | ||
| | Language | Java | 21 | | ||
| | Language | Kotlin | 2.0.20 | | ||
| | Framework | Spring Boot | 3.4.4 | | ||
| | Framework | Spring Cloud | 2024.0.1 | | ||
| | Dependency Mgmt | spring-dependency-management | 1.1.7 | | ||
| | Database | MySQL | 8.0 | | ||
| | ORM | Spring Data JPA + QueryDSL | (Spring 관리) | | ||
| | Cache | Redis (Master-Replica) | 7.0 | | ||
| | Messaging | Apache Kafka (KRaft) | 3.5.1 | | ||
| | API Docs | SpringDoc OpenAPI | 2.7.0 | | ||
| | Monitoring | Micrometer + Prometheus | (Spring 관리) | | ||
| | Tracing | Micrometer Brave | (Spring 관리) | | ||
| | Logging | Logback + Slack Appender | 1.6.1 | | ||
| | Testing | JUnit 5, Mockito 5.14.0, SpringMockK 4.0.2, Instancio 5.0.2 | | ||
| | Testing Infra | TestContainers (MySQL, Redis, Kafka) | (Spring 관리) | | ||
| | Code Coverage | JaCoCo | (Gradle 관리) | | ||
| | Build Tool | Gradle (Kotlin DSL) | Wrapper 포함 | | ||
|
|
||
| ## 모듈 구조 | ||
|
|
||
| ``` | ||
| root | ||
| ├── apps/ # 실행 가능한 Spring Boot 애플리케이션 | ||
| │ ├── commerce-api # REST API 서버 (Tomcat) | ||
| │ ├── commerce-batch # Spring Batch 배치 처리 | ||
| │ └── commerce-streamer # Kafka Consumer 스트리밍 서비스 | ||
| ├── modules/ # 재사용 가능한 인프라 모듈 (java-library) | ||
| │ ├── jpa # JPA + MySQL + QueryDSL + HikariCP | ||
| │ ├── redis # Redis Master-Replica (Lettuce) | ||
| │ └── kafka # Kafka Producer/Consumer 설정 | ||
| ├── supports/ # 횡단 관심사 모듈 | ||
| │ ├── jackson # Jackson ObjectMapper 커스터마이징 | ||
| │ ├── logging # 구조화 로깅 + Slack 연동 | ||
| │ └── monitoring # Prometheus 메트릭 + Health Probe | ||
| └── docker/ | ||
| ├── infra-compose.yml # MySQL, Redis, Kafka, Kafka UI | ||
| └── monitoring-compose.yml # Prometheus, Grafana | ||
| ``` | ||
|
|
||
| ## 아키텍처 레이어 (commerce-api 기준) | ||
|
|
||
| ``` | ||
| interfaces/api/ → REST Controller, DTO, ApiSpec | ||
| application/ → Facade (유즈케이스 오케스트레이션), Info DTO | ||
| domain/ → Entity, Repository 인터페이스, Service (비즈니스 로직) | ||
| infrastructure/ → Repository 구현체 | ||
| support/error/ → CoreException, ErrorType | ||
| ``` | ||
|
|
||
| ## 빌드 및 실행 | ||
|
|
||
| ```bash | ||
| # 인프라 구동 | ||
| docker compose -f docker/infra-compose.yml up -d | ||
|
|
||
| # 모니터링 스택 (Grafana: localhost:3000, admin/admin) | ||
| docker compose -f docker/monitoring-compose.yml up -d | ||
|
|
||
| # 빌드 | ||
| ./gradlew clean build | ||
|
|
||
| # 실행 | ||
| ./gradlew :apps:commerce-api:bootRun | ||
| ./gradlew :apps:commerce-batch:bootRun --args='--job.name=demoJob' | ||
| ./gradlew :apps:commerce-streamer:bootRun | ||
|
|
||
| # 테스트 | ||
| ./gradlew test | ||
|
|
||
| # 코드 커버리지 | ||
| ./gradlew test jacocoTestReport | ||
| ``` | ||
|
|
||
| ## 테스트 설정 | ||
|
|
||
| - JUnit 5 기반, 테스트 프로파일: `test`, 타임존: `Asia/Seoul` | ||
| - TestContainers로 MySQL/Redis/Kafka 통합 테스트 | ||
| - 모듈별 `testFixtures`로 테스트 유틸리티 공유 (`DatabaseCleanUp`, `RedisCleanUp` 등) | ||
| - 테스트 병렬 실행 없음 (`maxParallelForks = 1`) | ||
|
|
||
| ## 프로파일 | ||
|
|
||
| `local`, `test`, `dev`, `qa`, `prd` — 환경별 설정은 각 모듈의 yml 파일에서 프로파일 그룹으로 관리. | ||
| 운영 환경은 환경변수로 주입: `MYSQL_HOST`, `REDIS_MASTER_HOST`, `BOOTSTRAP_SERVERS` 등. | ||
|
|
||
| ## 주요 패턴 | ||
|
|
||
| - **BaseEntity**: ID 자동생성, `createdAt`/`updatedAt` 감사, `deletedAt` 소프트 삭제 | ||
| - **ApiResponse**: 통일된 응답 래퍼 (`meta.result`, `meta.errorCode`, `data`) | ||
| - **CoreException + ErrorType**: 타입 기반 에러 처리 (400, 404, 409, 500) | ||
| - **별도 관리 포트**: 메트릭/헬스체크는 8081 포트로 분리 | ||
| - **Kafka 배치 소비**: 3000건 배치, 수동 커밋 (Manual ACK) | ||
| - **Redis 읽기 분산**: Master 쓰기, Replica 읽기 분리 | ||
|
|
||
| ## API 응답 규칙 | ||
| - 모든 응답은 `ApiResponse<T>`로 래핑 | ||
| - 성공: `ApiResponse.success(data)` 반환 | ||
| - 실패: `CoreException(ErrorType)` throw → GlobalExceptionHandler에서 처리 | ||
| - 생성 API: `@ResponseStatus(HttpStatus.CREATED)` | ||
|
|
||
| ## 의존성 방향 (외부 → 내부) | ||
| ``` | ||
| interfaces → application → domain ← infrastructure | ||
| ``` | ||
| - domain 계층은 다른 계층에 의존하지 않음 | ||
| - infrastructure는 domain의 Repository 인터페이스를 구현 | ||
|
|
||
| ## 문서 작성 | ||
| ### 다이어그램 작성 | ||
| - ERD, 시퀀스 다이어그램, 클래스 다이어그램 등 작성 시 mermaid를 이용한 마크다운으로 작성. | ||
|
|
||
| ## 개발 규칙 | ||
| ### 진행 Workflow - 증강 코딩 | ||
| - **대원칙** : 방향성 및 주요 의사 결정은 개발자에게 제안만 할 수 있으며, 최종 승인된 사항을 기반으로 작업을 수행. | ||
| - **중간 결과 보고** : AI 가 반복적인 동작을 하거나, 요청하지 않은 기능을 구현, 테스트 삭제를 임의로 진행할 경우 개발자가 개입. | ||
| - **설계 주도권 유지** : AI 가 임의판단을 하지 않고, 방향성에 대한 제안 등을 진행할 수 있으나 개발자의 승인을 받은 후 수행. | ||
| - 구현은 한 단계씩 순서대로 진행 및 단계가 끝날 때 마다 핵심 개념/키워드 설명. | ||
| - API는 RESTFul API로 구현 | ||
| ### 인증 요청 | ||
| - 유저 정보가 필요한 모든 요청은 아래 헤더를 통해 요청 | ||
| * X-Loopers-LoginId : 로그인 ID | ||
| * X-Loopers-LoginPw : 비밀번호 | ||
|
|
||
| ### 개발 Workflow - TDD (Red > Green > Refactor) | ||
| - 모든 테스트는 3A 원칙으로 작성할 것 (Arrange - Act - Assert) | ||
| #### 1. Red Phase : 실패하는 테스트 먼저 작성 | ||
| - 요구사항을 만족하는 기능 테스트 케이스 작성 | ||
| - 테스트 예시 | ||
| #### 2. Green Phase : 테스트를 통과하는 코드 작성 | ||
| - Red Phase 의 테스트가 모두 통과할 수 있는 코드 작성 | ||
| - 오버엔지니어링 금지 | ||
| #### 3. Refactor Phase : 불필요한 코드 제거 및 품질 개선 | ||
| - 불필요한 private 함수 지양, 객체지향적 코드 작성 | ||
| - unused import 제거 | ||
| - 성능 최적화 | ||
| - 모든 테스트 케이스가 통과해야 함 | ||
|
|
||
| ## 주의사항 | ||
| ### 1. Never Do | ||
| - 실제 동작하지 않는 코드, 불필요한 Mock 데이터를 이용한 구현을 하지 말 것 | ||
| - null-safety 하지 않게 코드 작성하지 말 것 (Java 의 경우, Optional 을 활용할 것) | ||
| - println 코드 남기지 말 것 | ||
| - 객체지향 5원칙을 어기지 말 것 | ||
|
|
||
| ### 2. Recommendation | ||
| - 실제 API 를 호출해 확인하는 E2E 테스트 코드 작성 | ||
| - 재사용 가능한 객체 설계 | ||
| - 성능 최적화에 대한 대안 및 제안 | ||
| - 개발 완료된 API 의 경우, `.http/**.http` 에 분류해 작성 | ||
| - Domain Entity와 Persistence Entity는 구분하여 구현 | ||
| - 필요한 의존성은 적절히 관리하여 최소화 | ||
| - 통합 테스트는 테스트 컨테이너를 이용해 진행 | ||
| - 테스트 코드 작성 시 MIN, MAX, EDGE 케이스를 고려하여 작성 | ||
|
|
||
| ### 3. Priority | ||
| 1. 실제 동작하는 해결책만 고려 | ||
| 2. null-safety, thread-safety 고려 | ||
| 3. 테스트 가능한 구조로 설계 | ||
| 4. 기존 코드 패턴 분석 후 일관성 유지 | ||
|
|
||
| ## 깃 커밋 컨벤션 | ||
| - feat: 새로운 기능 추가 | ||
| - fix: 버그 수정 | ||
| - docs: 문서만 수정 (예: README, 주석은 아님) | ||
| - style: 코드 포맷팅 (공백, 세미콜론 등 기능 변화 없음) | ||
| - refactor: 기능 변화 없이 코드 개선 | ||
| - test: 테스트 코드 추가/수정 | ||
| - chore: 빌드/패키지 설정 등 기능과 직접 관련 없는 작업 | ||
| - 커밋 메세지는 한국어로 작성할 것 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
29 changes: 29 additions & 0 deletions
29
apps/commerce-api/src/main/java/com/loopers/application/member/MemberFacade.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| package com.loopers.application.member; | ||
|
|
||
| import com.loopers.domain.member.Member; | ||
| import com.loopers.domain.member.MemberService; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| import java.time.LocalDate; | ||
|
|
||
| @RequiredArgsConstructor | ||
| @Component | ||
| public class MemberFacade { | ||
|
|
||
| private final MemberService memberService; | ||
|
|
||
| public MemberInfo signUp(String loginId, String password, String name, LocalDate birthday, String email) { | ||
| Member member = memberService.signUp(loginId, password, name, birthday, email); | ||
| return MemberInfo.from(member); | ||
| } | ||
|
|
||
| public MemberInfo getMyInfo(String loginId, String password) { | ||
| Member member = memberService.authenticate(loginId, password); | ||
| return MemberInfo.from(member).withMaskedName(); | ||
| } | ||
|
|
||
| public void updatePassword(String loginId, String currentPassword, String newPassword) { | ||
| memberService.updatePassword(loginId, currentPassword, newPassword); | ||
| } | ||
| } |
22 changes: 22 additions & 0 deletions
22
apps/commerce-api/src/main/java/com/loopers/application/member/MemberInfo.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| package com.loopers.application.member; | ||
|
|
||
| import com.loopers.domain.member.Member; | ||
|
|
||
| import java.time.LocalDate; | ||
|
|
||
| public record MemberInfo(Long id, String loginId, String name, LocalDate birthday, String email) { | ||
| public static MemberInfo from(Member member) { | ||
| return new MemberInfo( | ||
| member.getId(), | ||
| member.getLoginId(), | ||
| member.getName(), | ||
| member.getBirthday(), | ||
| member.getEmail() | ||
| ); | ||
| } | ||
|
|
||
| public MemberInfo withMaskedName() { | ||
| String maskedName = name.substring(0, name.length() - 1) + "*"; | ||
| return new MemberInfo(id, loginId, maskedName, birthday, email); | ||
| } | ||
| } | ||
87 changes: 87 additions & 0 deletions
87
apps/commerce-api/src/main/java/com/loopers/domain/member/Member.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| package com.loopers.domain.member; | ||
|
|
||
| import com.loopers.support.error.CoreException; | ||
| import com.loopers.support.error.ErrorType; | ||
|
|
||
| import java.time.LocalDate; | ||
| import java.time.format.DateTimeFormatter; | ||
|
|
||
| public class Member { | ||
|
|
||
| private static final DateTimeFormatter BIRTHDAY_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd"); | ||
|
|
||
| private Long id; | ||
| private String loginId; | ||
| private String password; | ||
| private String name; | ||
| private LocalDate birthday; | ||
| private String email; | ||
|
|
||
| public Member(String loginId, String password, String name, LocalDate birthday, String email) { | ||
| validateBirthday(birthday); | ||
| validatePasswordNotContainsBirthday(password, birthday); | ||
|
|
||
| this.loginId = loginId; | ||
| this.password = password; | ||
| this.name = name; | ||
| this.birthday = birthday; | ||
| this.email = email; | ||
| } | ||
|
|
||
| public Member(Long id, String loginId, String password, String name, LocalDate birthday, String email) { | ||
| this.id = id; | ||
| this.loginId = loginId; | ||
| this.password = password; | ||
| this.name = name; | ||
| this.birthday = birthday; | ||
| this.email = email; | ||
| } | ||
|
Comment on lines
+31
to
+38
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. DB 재구성용 생성자가 검증을 건너뛰어 도메인 무결성이 깨질 수 있다. 운영 관점 문제: 이 생성자가 DB 재구성 외 다른 용도로 사용되면 생년월일 미래 날짜, 비밀번호 내 생년월일 포함 등 무효한 상태의 Member 객체가 생성될 수 있다. 수정안: 재구성용 생성자임을 명확히 하고, 의도치 않은 사용을 방지한다. ♻️ 정적 팩토리 메서드로 명확화- public Member(Long id, String loginId, String password, String name, LocalDate birthday, String email) {
- this.id = id;
- this.loginId = loginId;
- this.password = password;
- this.name = name;
- this.birthday = birthday;
- this.email = email;
- }
+ /**
+ * 영속성 계층에서 기존 데이터 재구성 시에만 사용한다.
+ * 새 Member 생성 시에는 5개 인자 생성자를 사용한다.
+ */
+ public static Member reconstitute(Long id, String loginId, String password, String name, LocalDate birthday, String email) {
+ Member member = new Member(loginId, password, name, birthday, email);
+ member.id = id;
+ return member;
+ }추가 테스트: 재구성 메서드 사용 시에도 검증이 적용되는지 테스트를 추가한다. 🤖 Prompt for AI Agents |
||
|
|
||
| public void encryptPassword(String encodedPassword) { | ||
| this.password = encodedPassword; | ||
| } | ||
|
|
||
| public void changePassword(String newRawPassword, String newEncodedPassword) { | ||
| validatePasswordNotContainsBirthday(newRawPassword, this.birthday); | ||
| this.password = newEncodedPassword; | ||
| } | ||
|
|
||
| private void validateBirthday(LocalDate birthday) { | ||
| if (birthday != null && birthday.isAfter(LocalDate.now())) { | ||
| throw new CoreException(ErrorType.BAD_REQUEST, "생년월일은 미래 날짜일 수 없습니다."); | ||
| } | ||
| } | ||
|
|
||
| private void validatePasswordNotContainsBirthday(String password, LocalDate birthday) { | ||
| if (password != null && birthday != null) { | ||
| String birthdayStr = birthday.format(BIRTHDAY_FORMATTER); | ||
| if (password.contains(birthdayStr)) { | ||
| throw new CoreException(ErrorType.BAD_REQUEST, "비밀번호에 생년월일을 포함할 수 없습니다."); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| public Long getId() { | ||
| return id; | ||
| } | ||
|
|
||
| public String getLoginId() { | ||
| return loginId; | ||
| } | ||
|
|
||
| public String getPassword() { | ||
| return password; | ||
| } | ||
|
|
||
| public String getName() { | ||
| return name; | ||
| } | ||
|
|
||
| public LocalDate getBirthday() { | ||
| return birthday; | ||
| } | ||
|
|
||
| public String getEmail() { | ||
| return email; | ||
| } | ||
| } | ||
11 changes: 11 additions & 0 deletions
11
apps/commerce-api/src/main/java/com/loopers/domain/member/MemberRepository.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package com.loopers.domain.member; | ||
|
|
||
| import java.util.Optional; | ||
|
|
||
| public interface MemberRepository { | ||
| Member save(Member member); | ||
| boolean existsByLoginId(String loginId); | ||
| boolean existsByEmail(String email); | ||
| Optional<Member> findByLoginId(String loginId); | ||
| void updatePassword(String loginId, String encodedPassword); | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
fd -a "MemberInfo.java" --type fRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 182
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 173
🏁 Script executed:
fd -a "MemberV1Dto.java" --type fRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 186
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 898
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 3138
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 2897
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 810
🏁 Script executed:
rg -n "withMaskedName" --type javaRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 630
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 2928
🏁 Script executed:
cat -n /home/jailuser/git/apps/commerce-api/src/main/java/com/loopers/application/member/MemberFacade.java | head -30Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 1274
withMaskedName()에서 이름 필드의 null/빈 문자열 방어 로직이 필요하다
현재 코드는 name.substring(0, name.length() - 1)을 검증 없이 호출한다. Member 엔티티의 생성자는 name 필드를 검증하지 않으므로, 데이터베이스에서 로드된 데이터나 다른 코드 경로에서 null 또는 빈 문자열을 가질 수 있다. 이 경우 NullPointerException 또는 StringIndexOutOfBoundsException이 발생하여 /members/me 조회가 500으로 실패한다.
다음과 같은 방어 로직을 추가해야 한다:
추가로 null, 빈 문자열, 1자 이름에 대한 테스트 케이스를 MemberInfoTest에 보강해야 한다.
수정안 예시
public MemberInfo withMaskedName() { + if (name == null || name.isEmpty()) { + return this; + } + if (name.length() == 1) { + return new MemberInfo(id, loginId, "*", birthday, email); + } String maskedName = name.substring(0, name.length() - 1) + "*"; return new MemberInfo(id, loginId, maskedName, birthday, email); }🤖 Prompt for AI Agents