-
Notifications
You must be signed in to change notification settings - Fork 44
[volume-1] 회원가입, 내 정보 조회, 비밀번호 변경 기능 구현 #30
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
base: APapeIsName
Are you sure you want to change the base?
Changes from all commits
22046d4
fde25e8
3f5d00d
c2726b6
5ae82cc
d4a79a8
f1e2ef3
0b4411d
83ca5c9
a5cac95
d9a06ef
e94314b
fe9ae05
531d2a6
ea90623
ab01a98
de31138
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,3 +38,6 @@ out/ | |
|
|
||
| ### Kotlin ### | ||
| .kotlin | ||
|
|
||
| /.claude | ||
| CLAUDE.md | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| package com.loopers.application.service; | ||
|
|
||
| import com.loopers.application.service.dto.MemberRegisterRequest; | ||
| import com.loopers.application.service.dto.MyMemberInfoResponse; | ||
| import com.loopers.domain.member.Member; | ||
| import com.loopers.domain.member.MemberExceptionMessage; | ||
| import com.loopers.infrastructure.member.MemberRepository; | ||
| import com.loopers.support.error.CoreException; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class MemberService { | ||
|
|
||
| private final MemberRepository memberRepository; | ||
|
|
||
| @Transactional | ||
| public void register(MemberRegisterRequest request) { | ||
| boolean isLoginIdAlreadyExists = memberRepository.existsByLoginId(request.loginId()); | ||
|
|
||
| if (isLoginIdAlreadyExists) { | ||
| throw new IllegalArgumentException(MemberExceptionMessage.LoginId.DUPLICATE_ID_EXISTS.message()); | ||
| } | ||
|
|
||
| memberRepository.save(Member.register(request.loginId(), request.password(), request.name(), request.birthdate(), request.email())); | ||
| } | ||
|
|
||
| @Transactional(readOnly = true) | ||
| public MyMemberInfoResponse getMyInfo(String userId, String password) { | ||
| // 1. 회원 조회 (없으면 예외 발생 - MemberExceptionMessage.Common.NOT_FOUND 사용) | ||
| Member member = memberRepository.findByLoginId(userId) | ||
| .orElseThrow(() -> new IllegalArgumentException(MemberExceptionMessage.ExistsMember.CANNOT_LOGIN.message())); | ||
|
|
||
| // 2. 비밀번호 일치 여부 확인 (도메인 모델의 isSamePassword 활용) | ||
| if (!member.isSamePassword(password)) { | ||
| // 비밀번호 불일치 시 예외 발생 (인증 관련 메시지 사용) | ||
| throw new IllegalArgumentException(MemberExceptionMessage.ExistsMember.CANNOT_LOGIN.message()); | ||
| } | ||
|
Comment on lines
+31
to
+40
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. IllegalArgumentException 사용으로 오류 포맷과 원인 보존이 일관되지 않다 Also applies to: 55-60 🤖 Prompt for AI Agents |
||
|
|
||
| // 3. DTO 변환 및 이름 마스킹 처리 | ||
| return new MyMemberInfoResponse( | ||
| member.getLoginId(), | ||
| maskName(member.getName()), // 마스킹 로직 적용 | ||
| member.getBirthDate(), | ||
| member.getEmail() | ||
| ); | ||
| } | ||
|
|
||
| @Transactional | ||
| public void updatePassword(String userId, String currentPassword, String newPassword) { | ||
| // 1. 회원 조회 | ||
| Member member = memberRepository.findByLoginId(userId) | ||
| .orElseThrow(() -> new IllegalArgumentException(MemberExceptionMessage.ExistsMember.CANNOT_LOGIN.message())); | ||
|
|
||
| // 2. 본인 확인 (기존 비밀번호 일치 여부) | ||
| if (!member.isSamePassword(currentPassword)) { | ||
| throw new IllegalArgumentException(MemberExceptionMessage.Password.PASSWORD_INCORRECT.message()); // 적절한 메시지로 변경 가능 | ||
| } | ||
|
Comment on lines
+54
to
+60
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. 비밀번호 오류 메시지 차이로 계정 존재 여부가 노출될 수 있다 🤖 Prompt for AI Agents |
||
|
|
||
| // 4. 도메인 정책 검증 및 수정 (생년월일 포함 여부 등은 도메인 내 로직에서 처리) | ||
| member.updatePassword(newPassword); | ||
| } | ||
|
|
||
| /** | ||
| * 이름의 마지막 글자를 *로 마스킹 처리 | ||
| */ | ||
| private String maskName(String name) { | ||
| if (name == null || name.isEmpty()) { | ||
| return ""; | ||
| } | ||
| if (name.length() == 1) { | ||
| return "*"; | ||
| } | ||
| return name.substring(0, name.length() - 1) + "*"; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package com.loopers.application.service.dto; | ||
|
|
||
| import lombok.Builder; | ||
|
|
||
| import java.time.LocalDate; | ||
|
|
||
| @Builder | ||
| public record MemberRegisterRequest ( | ||
| String loginId, | ||
| String password, | ||
| String name, | ||
| LocalDate birthdate, | ||
| String email | ||
| ) { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package com.loopers.application.service.dto; | ||
|
|
||
| import java.time.LocalDate; | ||
|
|
||
| public record MyMemberInfoResponse( | ||
| String loginId, | ||
| String name, | ||
| LocalDate birthdate, | ||
| String email | ||
| ) { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| package com.loopers.application.service.dto; | ||
|
|
||
| public record PasswordUpdateRequest( | ||
| String newPassword | ||
| ) { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| package com.loopers.controller; | ||
|
|
||
| import com.loopers.application.service.MemberService; | ||
| import com.loopers.application.service.dto.MemberRegisterRequest; | ||
| import com.loopers.application.service.dto.MyMemberInfoResponse; | ||
| import com.loopers.application.service.dto.PasswordUpdateRequest; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.web.bind.annotation.*; | ||
|
|
||
| @RestController | ||
| @RequestMapping("/api/members") | ||
| @RequiredArgsConstructor | ||
| public class MemberController { | ||
|
|
||
| private final MemberService memberService; | ||
|
|
||
| /** | ||
| * 회원가입 | ||
| */ | ||
| @PostMapping("/register") | ||
| @ResponseStatus(HttpStatus.CREATED) | ||
| public void register(@RequestBody MemberRegisterRequest request) { | ||
| memberService.register(request); | ||
| } | ||
|
|
||
| /** | ||
| * 내 정보 조회 | ||
| * 헤더 인증 (ID, PW) 기반 | ||
| */ | ||
| @GetMapping("/me") | ||
| public MyMemberInfoResponse getMyInfo( | ||
| @RequestHeader("X-Loopers-LoginId") String loginId, | ||
| @RequestHeader("X-Loopers-LoginPw") String password | ||
| ) { | ||
| return memberService.getMyInfo(loginId, password); | ||
| } | ||
|
|
||
| /** | ||
| * 비밀번호 수정 | ||
| * 헤더 인증 (ID, 기존 PW) + 바디 (새 PW) | ||
| */ | ||
| @PatchMapping("/password") | ||
| @ResponseStatus(HttpStatus.NO_CONTENT) | ||
| public void updatePassword( | ||
| @RequestHeader("X-Loopers-LoginId") String loginId, | ||
| @RequestHeader("X-Loopers-LoginPw") String currentPassword, | ||
| @RequestBody PasswordUpdateRequest request | ||
|
Comment on lines
+31
to
+48
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. 🧩 Analysis chain🏁 Script executed: # 대상 파일의 전체 내용 확인
cat -n apps/commerce-api/src/main/java/com/loopers/controller/MemberController.javaRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 2029 🏁 Script executed: # 로그 필터 또는 마스킹 설정 파일 검색
find . -type f \( -name "*Log*" -o -name "*Filter*" \) -path "*/commerce-api/*" | head -20Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 67 🏁 Script executed: # 보안/인증 관련 설정 파일 검색
find . -type f \( -name "*Security*" -o -name "*Auth*" -o -name "*Config*" \) -path "*/commerce-api/*" | head -20Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 67 🏁 Script executed: # 헤더 마스킹이나 로깅 관련 설정 검색
rg -i "mask|header|password|sensitive" --type java -A 2 apps/commerce-api/src/main/java/Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 6587 🏁 Script executed: # MemberService.java 전체 내용 확인
cat -n apps/commerce-api/src/main/java/com/loopers/application/service/MemberService.javaRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 3628 🏁 Script executed: # 테스트 코드 확인
find . -type f -name "*MemberControllerTest*" -o -name "*MemberServiceTest*" | head -10Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 165 🏁 Script executed: # 로깅 설정 파일 검색
find . -type f \( -name "logback.xml" -o -name "logback-spring.xml" -o -name "log4j*.xml" -o -name "application*.yml" -o -name "application*.yaml" \) -path "*/commerce-api/*"Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 139 🏁 Script executed: # 헤더 로깅 관련 필터 구현 여부 확인
rg -i "filter|interceptor|logging" --type java -l apps/commerce-api/src/main/java/ | head -20Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 67 🏁 Script executed: # application.yml 로깅 설정 확인
cat -n apps/commerce-api/src/main/resources/application.ymlRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 1450 🏁 Script executed: # MemberServiceTest.java 확인
cat -n apps/commerce-api/src/test/java/com/loopers/application/MemberServiceTest.javaRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 5710 🏁 Script executed: # 전체 프로젝트의 Filter/Interceptor 구현 검색
find . -type f -name "*Filter*.java" -o -name "*Interceptor*.java" | grep -v node_modules | head -20Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 67 🏁 Script executed: # 헤더 로그 마스킹 관련 구현 확인
rg "X-Loopers" --type java -B 2 -A 2Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 4666 비밀번호를 커스텀 헤더로 전달하면 운영 환경에서 심각한 정보 유출 위험이 있다 비밀번호를 X-Loopers-LoginPw 헤더로 전달하면 접근 로그, APM, 프록시, 네트워크 모니터링 도구에서 헤더가 그대로 수집되어 보안 사고로 이어질 수 있다. 현재 구조에서는 비밀번호 마스킹을 위한 로그 필터가 구현되지 않았다. 표준 Authorization 헤더(예: Bearer 토큰, Basic 인증) 또는 세션/쿠키 기반 인증으로 전환해야 한다. 불가피하게 헤더 기반 인증을 유지하려면 다음을 필수로 적용해야 한다:
인증 실패/성공 시나리오와 민감 헤더가 로그에 기록되지 않음을 검증하는 보안 테스트를 추가해야 한다. 🤖 Prompt for AI Agents |
||
| ) { | ||
| memberService.updatePassword(loginId, currentPassword, request.newPassword()); | ||
|
Comment on lines
+21
to
+50
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. 🧩 Analysis chain🏁 Script executed: find apps/commerce-api/src/main -name "*Controller.java" -type f | head -5Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 253 🏁 Script executed: find apps/commerce-api/src/main -name "*ControllerAdvice.java" -o -name "*ExceptionHandler.java" | head -5Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 168 🏁 Script executed: find apps -name "CoreException.java" -o -name "*Exception.java" | grep -i core | head -5Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 161 🏁 Script executed: cat -n apps/commerce-api/src/main/java/com/loopers/interfaces/api/ApiControllerAdvice.javaRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 7255 🏁 Script executed: cat -n apps/commerce-api/src/main/java/com/loopers/support/error/CoreException.javaRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 741 🏁 Script executed: cat -n apps/commerce-api/src/main/java/com/loopers/controller/MemberController.javaRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 2029 필수 헤더 누락 시 표준 에러 포맷이 깨질 수 있다
LoginId, LoginPw 헤더가 필수인 경우, required=false로 설정한 후 null/blank를 수동으로 검증하고 CoreException으로 변환해야 한다. 이렇게 하면 ApiControllerAdvice의 CoreException 핸들러를 거쳐 일관된 BAD_REQUEST 응답이 반환된다. <details>
<summary>예시</summary>
```java
`@GetMapping`("/me")
public MyMemberInfoResponse getMyInfo(
`@RequestHeader`(value = "X-Loopers-LoginId", required = false) String loginId,
`@RequestHeader`(value = "X-Loopers-LoginPw", required = false) String password
) {
if (loginId == null || loginId.isBlank()) {
throw new CoreException(ErrorType.BAD_REQUEST, "필수 헤더 'X-Loopers-LoginId'가 누락되었습니다.");
}
if (password == null || password.isBlank()) {
throw new CoreException(ErrorType.BAD_REQUEST, "필수 헤더 'X-Loopers-LoginPw'가 누락되었습니다.");
}
return memberService.getMyInfo(loginId, password);
}헤더/바디 누락 케이스에서 BAD_REQUEST 상태코드와 표준 에러 포맷이 반환되는지 단위 테스트와 E2E 테스트를 추가한다. 🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package com.loopers.infrastructure.member; | ||
|
|
||
| import com.loopers.domain.member.Member; | ||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
|
|
||
| import java.util.Optional; | ||
|
|
||
| public interface MemberRepository extends JpaRepository<Member, Long> { | ||
|
|
||
| boolean existsByLoginId(String inputId); | ||
|
|
||
| Optional<Member> findByLoginId(String loginId); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -113,6 +113,14 @@ public ResponseEntity<ApiResponse<?>> handle(Throwable e) { | |
| return failureResponse(ErrorType.INTERNAL_ERROR, null); | ||
| } | ||
|
|
||
| // TODO: 예외처리 변경하는 게 좋을 것 같음 | ||
| @ExceptionHandler(IllegalArgumentException.class) | ||
| public ResponseEntity<ApiResponse<?>> handleIllegalArgumentException(IllegalArgumentException e) { | ||
| log.warn("IllegalArgumentException : {}", e.getMessage()); | ||
| return ResponseEntity.status(401) | ||
| .body(ApiResponse.fail("UNAUTHORIZED", e.getMessage())); | ||
| } | ||
|
Comment on lines
+116
to
+122
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. HTTP 상태 코드 시맨틱 위반: 모든
TODO 주석에서 인지하고 있듯이, 예외 타입을 세분화하거나 수정안:
수정 제안 예시- // TODO: 예외처리 변경하는 게 좋을 것 같음
- `@ExceptionHandler`(IllegalArgumentException.class)
- public ResponseEntity<ApiResponse<?>> handleIllegalArgumentException(IllegalArgumentException e) {
- log.warn("IllegalArgumentException : {}", e.getMessage());
- return ResponseEntity.status(401)
- .body(ApiResponse.fail("UNAUTHORIZED", e.getMessage()));
- }
+ // IllegalArgumentException 대신 CoreException을 사용하여
+ // 각 예외 상황에 맞는 ErrorType을 지정하도록 도메인/서비스 레이어 수정 필요추가 테스트: 아이디 중복, 형식 오류, 비밀번호 불일치 각각에 대해 올바른 HTTP 상태 코드가 반환되는지 E2E 테스트를 보강해야 한다. Based on learnings: "enforce unified error handling by routing errors through CoreException to ApiControllerAdvice to ensure a consistent response format." 🤖 Prompt for AI Agents |
||
|
|
||
| private String extractMissingParameter(String message) { | ||
| Pattern pattern = Pattern.compile("'(.+?)'"); | ||
| Matcher matcher = pattern.matcher(message); | ||
|
|
||
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:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 169
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 67
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 250
🏁 Script executed:
fd -t f "MemberService" --extension javaRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 336
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 3628
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 404
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 67
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 2535
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 4192
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 69
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 8912
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 967
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 99
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 67
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 67
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 163
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 67
🏁 Script executed:
rg "DataIntegrityViolationException" --type javaRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 67
🏁 Script executed:
rg "@Column.*unique" --type javaRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 67
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 67
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 741
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 67
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 67
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 67
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 2903
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 256
🏁 Script executed:
cat -n ./apps/commerce-api/src/main/resources/application.yml | head -50Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 1297
🏁 Script executed:
rg "hibernate\.ddl-auto|spring\.jpa\.hibernate" --type yamlRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 67
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 174
🏁 Script executed:
rg "catch.*DataIntegrityViolationException" --type java -A 3Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 67
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 683
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 672
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 524
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 67
🏁 Script executed:
rg "^CREATE TABLE.*member" -A 20 --type sqlRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 233
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 67
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 5710
🏁 Script executed:
rg "@Column.*unique\|@UniqueConstraint" --type javaRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 67
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 67
🏁 Script executed:
grep -n "loginId" ./modules/jpa/src/main/java/com/loopers/domain/member/Member.javaRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 234
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 773
🏁 Script executed:
rg "IllegalArgumentException" ./apps/commerce-api/src/main/java/com/loopers/application/service/ --type javaRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 1107
🏁 Script executed:
rg "UNIQUE.*login_id\|login_id.*UNIQUE" --type sql --type javaRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 67
🏁 Script executed:
cat -n ./modules/jpa/src/main/java/com/loopers/domain/member/Member.java | head -35Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 1002
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 67
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 182
🏁 Script executed:
rg "throw new.*Exception" ./apps/commerce-api/src/main/java/com/loopers/application/service/MemberService.javaRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 430
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 67
🏁 Script executed:
rg "@Column.*loginId" ./modules/jpa/src/main/java/com/loopers/domain/member/Member.javaRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 67
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 1102
🏁 Script executed:
rg "catch.*DataIntegrityViolationException\|catch.*ConstraintViolation" --type javaRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 67
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 69
🏁 Script executed:
rg "synchronized\|Lock\|ReentrantLock" --type javaRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 67
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 2535
🏁 Script executed:
rg "CoreException" ./apps/commerce-api/src/main/java/com/loopers/application/service/ --type java -B 2 -A 2Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 759
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 159
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 67
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 67
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 949
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 750
🏁 Script executed:
grep -n "import.*CoreException\|import.*ErrorType" ./apps/commerce-api/src/main/java/com/loopers/application/service/MemberService.javaRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 134
🏁 Script executed:
rg "findByLoginId\|existsByLoginId" ./apps/commerce-api/src/main/java/com/loopers/ --type javaRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 67
🏁 Script executed:
cat -n ./apps/commerce-api/src/main/java/com/loopers/application/service/MemberService.java | head -30Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 1457
동시 가입 요청 시 중복 로그인 ID 검증이 원자적이지 않아 경쟁 조건이 발생한다
existsByLoginId() 조회 후 save() 사이에 다른 요청이 동일 ID로 가입하면 중복 레코드가 생성되거나 DataIntegrityViolationException으로 500 오류가 발생해 운영에서 간헐 장애로 보일 수 있다.
현재 Member 엔티티에는 login_id 컬럼에 유니크 제약이 없으므로 다음을 함께 수행해야 한다:
동시 가입 요청을 병렬로 실행하고 하나만 성공, 나머지는 중복 오류로 떨어지는지 검증하는 통합 테스트를 추가한다.
🔧 수정 제안
📝 Committable suggestion
🤖 Prompt for AI Agents