Conversation
Walkthrough사용자 SMS 인증을 통한 이메일 찾기 기능을 구현합니다. CoolSMS API를 통해 휴대폰 번호로 6자리 인증 코드를 전송하고 Redis에 일시 저장하며, 코드 검증 후 사용자 이메일을 반환하는 플로우를 추가합니다. Changes
Sequence DiagramsequenceDiagram
participant Client
participant UserController
participant SmsService
participant UserRepository
participant Redis as Redis<br/>(Code Storage)
participant CoolSMS as CoolSMS API
Note over Client,CoolSMS: SMS 인증 흐름
Client->>UserController: POST /sms-send<br/>{phoneNumber}
UserController->>SmsService: sendSms(phoneNumber)
SmsService->>UserRepository: findUserByPhoneNumber(phoneNumber)
alt 사용자 없음
SmsService-->>UserController: USER_NOT_FOUND_BY_PHONE 예외
UserController-->>Client: 404 Error
else 사용자 존재
SmsService->>SmsService: generateCode() → 6자리
SmsService->>CoolSMS: 인증코드 SMS 발송<br/>(API credentials 사용)
alt SMS 발송 실패
SmsService-->>UserController: SMS_SEND_FAILED 예외
UserController-->>Client: 500 Error
else SMS 발송 성공
SmsService->>Redis: 코드 저장<br/>(TTL: 180초)
SmsService-->>UserController: SmsSentResponse<br/>(message, phoneNumber, expireIn)
UserController-->>Client: 200 OK
end
end
Note over Client,CoolSMS: 코드 검증 흐름
Client->>UserController: POST /sms-verify<br/>{phoneNumber, verificationCode}
UserController->>SmsService: isPhoneVerified(phoneNumber, verificationCode)
SmsService->>Redis: 저장된 코드 조회
alt 코드 불일치 또는 만료
SmsService-->>UserController: USER_SMS_NOT_VERIFIED 예외
UserController-->>Client: 401 Error
else 코드 일치
SmsService->>Redis: 코드 삭제
SmsService->>UserRepository: findUserByPhoneNumber(phoneNumber)
SmsService-->>UserController: 사용자 이메일 반환
UserController-->>Client: 200 OK<br/>SmsVerifiedResponse<br/>(isVerified: true, email)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45분 복잡도 요인:
주의 깊게 살펴볼 부분:
칭찬할 점: ✅ 명확한 DTO 설계 - SmsSendRequest와SmsVerifyRequest를 구분하여 각 API의 책임을 명확히 함 Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Fix all issues with AI agents
In @.env.example:
- Around line 31-36: The SMS env entries contain extra spaces around the equals
signs which can cause parsers to read variable names like "SMS_API_KEY " instead
of "SMS_API_KEY"; remove spaces so each line uses the exact key=value form for
SMS_API_KEY, SMS_SECRET_KEY, and SMS_SENDER_NUMBER and ensure the file ends with
a single newline character; verify keys are exactly "SMS_API_KEY",
"SMS_SECRET_KEY", and "SMS_SENDER_NUMBER" with no trailing spaces.
In `@build.gradle`:
- Around line 62-67: Replace the incompatible dotenv and update the SMS SDK:
change the dependency "me.paulschwarz:spring-dotenv:4.0.0" to the Spring Boot 3
compatible artifact "me.paulschwarz:springboot3-dotenv:5.1.0" and bump
"net.nurigo:sdk:4.3.0" to "net.nurigo:sdk:4.3.2" in the build.gradle
dependencies so the project uses the recommended springboot3-dotenv for Spring
Boot 3.5 and the latest Nurigo SDK.
In
`@src/main/java/com/whereyouad/WhereYouAd/domains/user/application/dto/request/SmsRequest.java`:
- Around line 7-15: SmsVerifyRequest currently lacks the same format constraints
as SmsSendRequest, allowing phone strings like "010-1234-5678" and
variable-length codes which breaks Redis key matching; update the
SmsVerifyRequest record to apply the same `@Pattern`(regexp = "^\\d{10,11}$",
message = "...") annotation to phoneNumber as used in SmsSendRequest and add a
`@Pattern`(regexp = "^\\d{6}$", message = "인증코드는 6자리 숫자만 입력해주세요.") (and keep
`@NotBlank`) on verificationCode so both request DTOs use identical phone
validation and enforce a 6-digit code.
In
`@src/main/java/com/whereyouad/WhereYouAd/domains/user/domain/service/SmsService.java`:
- Around line 38-58: Before sending SMS in SmsService.sendSms, check the phone
is registered and enforce rate limiting: call
UserRepository.existsByPhoneNumber(phoneNumber) and throw
UserHandler(UserErrorCode.PHONE_NOT_FOUND) if false; then enforce a short
TTL-based rate limit using redisUtil (e.g., set/get a "sms:sent:{phoneNumber}"
key or use redisUtil.getData to check an existing flag) and if a recent send
exists throw UserHandler(UserErrorCode.TOO_MANY_REQUESTS); only after those
checks proceed to generateCode(), redisUtil.setDataExpire(phoneNumber,
verificationCode, 180) and call defaultMessageService.sendOne(new
SingleMessageSendingRequest(message)). Ensure error paths use the same
UserErrorCode semantics and do not call sendOne if checks fail.
- Around line 47-55: The Redis-stored verification code is set before sending
the SMS (redisUtil.setDataExpire), leaving a valid code if send fails; change
the flow in SmsService so you call defaultMessageService.sendOne(new
SingleMessageSendingRequest(message)) first and only on success persist the code
with redisUtil.setDataExpire(phoneNumber, verificationCode, 180); alternatively,
if you must keep the current order, catch exceptions from
defaultMessageService.sendOne and remove the stored key (use redisUtil.delete or
equivalent) inside the catch before throwing
UserHandler(UserErrorCode.SMS_SEND_FAILED) to ensure no orphaned codes remain.
In
`@src/main/java/com/whereyouad/WhereYouAd/domains/user/persistence/repository/UserRepository.java`:
- Around line 14-15: 쿼리의 네임드 파라미터 불일치로 바인딩 오류가 발생합니다; UserRepository의 메서드
findUserByPhoneNumber에서 `@Query가` :phoneNum을 사용하고 있으니 `@Param`("phoneNum")으로 이름을
맞추거나 쿼리 파라미터를 :phoneNumber로 변경해 두 식별자(쿼리의 :phoneNum과 `@Param의` 값)가 일치하도록 수정하세요.
🧹 Nitpick comments (5)
src/main/java/com/whereyouad/WhereYouAd/domains/user/domain/service/SmsService.java (3)
60-65: 보안 강화:Random대신SecureRandom사용을 권장합니다.
java.util.Random은 예측 가능한 난수를 생성하므로, 인증 코드와 같이 보안이 중요한 경우에는SecureRandom을 사용하는 것이 좋습니다. 공격자가 시드 값을 추측하면 다음 인증 코드를 예측할 수 있는 이론적 위험이 있습니다.🔒 SecureRandom 적용 예시
+import java.security.SecureRandom; + // 랜덤 인증 번호 생성 public String generateCode() { - Random random = new Random(); + SecureRandom random = new SecureRandom(); int code = 100000 + random.nextInt(900000); // 6자리 인증 번호 return Integer.toString(code); }
19-21: 불필요한@Transactional어노테이션입니다.이 서비스는 JPA 엔티티 수정 없이 Redis 작업과 외부 API 호출만 수행합니다. 클래스 레벨의
@Transactional은 불필요한 트랜잭션 오버헤드를 발생시킵니다.만약
isPhoneVerified에서userRepository.findUserByPhoneNumber가 읽기 전용이라면, 해당 메서드에만@Transactional(readOnly = true)를 적용하는 것이 더 적절합니다.♻️ 수정 제안
`@Service` -@Transactional public class SmsService { // ... + `@Transactional`(readOnly = true) public String isPhoneVerified(String phoneNumber, String insertedNum) { // ... } }
79-87: 메서드 이름이 반환 타입과 맞지 않습니다.
isPhoneVerified라는 이름은boolean을 반환할 것 같지만 실제로는String(email)을 반환합니다. 코드 가독성을 위해 메서드 이름을 변경하는 것을 권장합니다.💡 네이밍 개선 제안
- public String isPhoneVerified(String phoneNumber, String insertedNum) { + public String findEmailByVerifiedPhone(String phoneNumber, String insertedNum) { if (verifyCode(phoneNumber, insertedNum)) { User user = userRepository.findUserByPhoneNumber(phoneNumber) .orElseThrow(() -> new UserHandler(UserErrorCode.USER_NOT_FOUND)); return user.getEmail(); } else throw new UserHandler(UserErrorCode.USER_SMS_NOT_VERIFIED); }src/main/java/com/whereyouad/WhereYouAd/domains/user/presentation/UserController.java (1)
53-65: SMS 관련 엔드포인트 구현이 깔끔합니다!
@Valid를 통한 입력 검증과 서비스 위임 패턴이 잘 적용되어 있습니다.SmsRequest에 정의된@Pattern검증으로 전화번호 형식도 잘 처리됩니다.한 가지 작은 개선점으로, Line 64에서
SmsVerifiedResponse생성을 서비스 레이어로 이동하면 다른 엔드포인트들과 일관성이 높아집니다. 현재sendSms는 서비스에서 응답 객체를 생성하지만,verifySms는 컨트롤러에서 생성하고 있습니다.💡 일관성 개선 제안
SmsService.java에서 응답 생성:public SmsResponse.SmsVerifiedResponse verifyAndFindEmail(String phoneNumber, String insertedNum) { if (verifyCode(phoneNumber, insertedNum)) { User user = userRepository.findUserByPhoneNumber(phoneNumber) .orElseThrow(() -> new UserHandler(UserErrorCode.USER_NOT_FOUND)); return new SmsResponse.SmsVerifiedResponse(true, "이메일 찾기 성공", user.getEmail()); } throw new UserHandler(UserErrorCode.USER_SMS_NOT_VERIFIED); }
UserController.java:`@PostMapping`("/sms-verify") public ResponseEntity<DataResponse<SmsResponse.SmsVerifiedResponse>> verifySms( `@RequestBody` `@Valid` SmsRequest.SmsVerifyRequest request) { - String email = smsService.isPhoneVerified(request.phoneNumber(), request.verificationCode()); - return ResponseEntity.ok(DataResponse.from(new SmsResponse.SmsVerifiedResponse(true, "이메일 찾기 성공", email))); + SmsResponse.SmsVerifiedResponse response = smsService.verifyAndFindEmail(request.phoneNumber(), request.verificationCode()); + return ResponseEntity.ok(DataResponse.from(response)); }src/main/java/com/whereyouad/WhereYouAd/domains/user/presentation/docs/UserControllerDocs.java (1)
40-54: API 문서에 구체적인 에러 코드를 추가하면 좋겠습니다.이메일 관련 API들은
"400_2","400_3","400_4"등 구체적인 에러 코드와 설명을 제공하지만, SMS 관련 API들은 단순히"400"만 명시되어 있습니다. 클라이언트 개발자가 에러 상황을 구분하기 어려울 수 있습니다.📝 Swagger 문서 개선 제안
`@Operation`(summary = "이메일 찾기 - SMS 인증 번호 전송 API", description = "입력받은 전화번호로 인증 번호를 전송합니다.") `@ApiResponses`({ `@ApiResponse`(responseCode = "200", description = "성공"), - `@ApiResponse`(responseCode = "400", description = "실패") + `@ApiResponse`(responseCode = "404", description = "등록되지 않은 전화번호"), + `@ApiResponse`(responseCode = "500", description = "SMS 전송 실패") }) public ResponseEntity<DataResponse<SmsResponse.SmsSentResponse>> sendSms(...); `@Operation`(summary = "이메일 찾기 - SMS 인증 번호 확인 API", description = "전화번호와 인증 코드를 받아 맞는지 검증합니다.") `@ApiResponses`({ `@ApiResponse`(responseCode = "200", description = "성공"), - `@ApiResponse`(responseCode = "400", description = "실패") + `@ApiResponse`(responseCode = "401", description = "인증 코드 불일치"), + `@ApiResponse`(responseCode = "404", description = "등록되지 않은 전화번호") }) public ResponseEntity<DataResponse<SmsResponse.SmsVerifiedResponse>> verifySms(...);
| public record SmsSendRequest( | ||
| @NotBlank(message = "전화번호는 필수입니다.") | ||
| @Pattern(regexp = "^\\d{10,11}$", message = "전화번호는 하이픈 없이 10~11자리 숫자만 입력해주세요.") String phoneNumber) { | ||
| } | ||
|
|
||
| public record SmsVerifyRequest( | ||
| @NotBlank(message = "전화번호는 필수입니다.") String phoneNumber, | ||
| @NotBlank(message = "인증코드는 필수입니다.") String verificationCode) { | ||
| } |
There was a problem hiding this comment.
검증 요청에도 전화번호/인증코드 형식 검증을 맞춰주세요
발송 요청은 10~11자리만 허용하지만 검증 요청은 제약이 없어 '010-1234-5678' 같은 값이 통과할 수 있습니다. 이렇게 되면 Redis 키가 달라져 항상 실패할 수 있어요. 검증 요청에도 동일한 패턴과 6자리 코드 제약을 넣는 걸 권장합니다.
✅ 수정 제안
public record SmsVerifyRequest(
- `@NotBlank`(message = "전화번호는 필수입니다.") String phoneNumber,
- `@NotBlank`(message = "인증코드는 필수입니다.") String verificationCode) {
+ `@NotBlank`(message = "전화번호는 필수입니다.")
+ `@Pattern`(regexp = "^\\d{10,11}$", message = "전화번호는 하이픈 없이 10~11자리 숫자만 입력해주세요.") String phoneNumber,
+ `@NotBlank`(message = "인증코드는 필수입니다.")
+ `@Pattern`(regexp = "^\\d{6}$", message = "인증코드는 6자리 숫자만 입력해주세요.") String verificationCode) {
}🤖 Prompt for AI Agents
In
`@src/main/java/com/whereyouad/WhereYouAd/domains/user/application/dto/request/SmsRequest.java`
around lines 7 - 15, SmsVerifyRequest currently lacks the same format
constraints as SmsSendRequest, allowing phone strings like "010-1234-5678" and
variable-length codes which breaks Redis key matching; update the
SmsVerifyRequest record to apply the same `@Pattern`(regexp = "^\\d{10,11}$",
message = "...") annotation to phoneNumber as used in SmsSendRequest and add a
`@Pattern`(regexp = "^\\d{6}$", message = "인증코드는 6자리 숫자만 입력해주세요.") (and keep
`@NotBlank`) on verificationCode so both request DTOs use identical phone
validation and enforce a 6-digit code.
| // redis 저장 | ||
| redisUtil.setDataExpire(phoneNumber, verificationCode, 180); // 유효 기간 3분 | ||
|
|
||
| try { | ||
| this.defaultMessageService.sendOne(new SingleMessageSendingRequest(message)); | ||
| } catch (Exception e) { | ||
| e.printStackTrace(); | ||
| throw new UserHandler(UserErrorCode.SMS_SEND_FAILED); | ||
| } |
There was a problem hiding this comment.
SMS 전송 실패 시 Redis 데이터가 남아있는 문제가 있습니다.
현재 코드는 Redis에 인증 코드를 저장한 후 SMS를 전송합니다. 만약 SMS 전송이 실패하면 Redis에 저장된 데이터는 그대로 남아있게 되어, 사용자가 받지 못한 인증 코드가 3분간 유효한 상태로 남습니다.
예를 들어:
- 사용자 A가 인증 요청 → Redis에 코드 저장 → SMS 전송 실패
- 공격자가 해당 번호로 무작위 코드 대입 시도 가능
🛡️ 수정 제안: SMS 전송 성공 후 Redis 저장
public SmsResponse.SmsSentResponse sendSms(String phoneNumber) {
Message message = new Message();
String verificationCode = generateCode();
message.setFrom(senderNumber); // 발신 번호
message.setTo(phoneNumber);
message.setText("[Where You Ad] 이메일 찾기\n 휴대폰 인증 번호는 [" + verificationCode + "] 입니다.");
- // redis 저장
- redisUtil.setDataExpire(phoneNumber, verificationCode, 180); // 유효 기간 3분
-
try {
this.defaultMessageService.sendOne(new SingleMessageSendingRequest(message));
+ // SMS 전송 성공 후 redis 저장
+ redisUtil.setDataExpire(phoneNumber, verificationCode, 180); // 유효 기간 3분
} catch (Exception e) {
- e.printStackTrace();
+ log.error("SMS 전송 실패 - phoneNumber: {}", phoneNumber, e);
throw new UserHandler(UserErrorCode.SMS_SEND_FAILED);
}
return new SmsResponse.SmsSentResponse("인증번호가 전송되었습니다.", phoneNumber, 180L);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // redis 저장 | |
| redisUtil.setDataExpire(phoneNumber, verificationCode, 180); // 유효 기간 3분 | |
| try { | |
| this.defaultMessageService.sendOne(new SingleMessageSendingRequest(message)); | |
| } catch (Exception e) { | |
| e.printStackTrace(); | |
| throw new UserHandler(UserErrorCode.SMS_SEND_FAILED); | |
| } | |
| try { | |
| this.defaultMessageService.sendOne(new SingleMessageSendingRequest(message)); | |
| // SMS 전송 성공 후 redis 저장 | |
| redisUtil.setDataExpire(phoneNumber, verificationCode, 180); // 유효 기간 3분 | |
| } catch (Exception e) { | |
| log.error("SMS 전송 실패 - phoneNumber: {}", phoneNumber, e); | |
| throw new UserHandler(UserErrorCode.SMS_SEND_FAILED); | |
| } |
🤖 Prompt for AI Agents
In
`@src/main/java/com/whereyouad/WhereYouAd/domains/user/domain/service/SmsService.java`
around lines 47 - 55, The Redis-stored verification code is set before sending
the SMS (redisUtil.setDataExpire), leaving a valid code if send fails; change
the flow in SmsService so you call defaultMessageService.sendOne(new
SingleMessageSendingRequest(message)) first and only on success persist the code
with redisUtil.setDataExpire(phoneNumber, verificationCode, 180); alternatively,
if you must keep the current order, catch exceptions from
defaultMessageService.sendOne and remove the stored key (use redisUtil.delete or
equivalent) inside the catch before throwing
UserHandler(UserErrorCode.SMS_SEND_FAILED) to ensure no orphaned codes remain.
src/main/java/com/whereyouad/WhereYouAd/domains/user/persistence/repository/UserRepository.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In
`@src/main/java/com/whereyouad/WhereYouAd/domains/user/domain/service/SmsService.java`:
- Around line 84-92: The isPhoneVerified method throws UserHandler with
UserErrorCode.USER_NOT_FOUND when userRepository.findUserByPhoneNumber fails;
change that to throw new UserHandler(UserErrorCode.USER_NOT_FOUND_BY_PHONE) so
the error matches phone-based lookup. Locate isPhoneVerified (which calls
verifyCode and userRepository.findUserByPhoneNumber) and replace the
UserErrorCode.USER_NOT_FOUND constant with UserErrorCode.USER_NOT_FOUND_BY_PHONE
in the exception construction.
🧹 Nitpick comments (2)
src/main/java/com/whereyouad/WhereYouAd/domains/user/domain/service/SmsService.java (2)
57-59:e.printStackTrace()대신 로거를 사용해주세요.
e.printStackTrace()는 표준 에러 스트림으로 출력되어 로그 관리가 어렵습니다. SLF4J 로거를 사용하면 로그 레벨 조정, 파일 저장, 로그 모니터링 등이 가능해집니다.♻️ 수정 제안
클래스 상단에 로거 선언 추가:
import lombok.extern.slf4j.Slf4j; `@Slf4j` `@Service` `@Transactional` public class SmsService {catch 블록 수정:
} catch (Exception e) { - e.printStackTrace(); + log.error("SMS 전송 실패 - phoneNumber: {}", phoneNumber, e); throw new UserHandler(UserErrorCode.SMS_SEND_FAILED); }
65-70: 보안 강화를 위해SecureRandom사용을 권장드립니다.
Random은 예측 가능한 의사 난수를 생성합니다. 인증 코드는 보안에 민감하므로SecureRandom을 사용하면 더 안전합니다.참고로
EmailService.createCode()도 동일한 패턴을 사용 중인데, 추후 공통 유틸로 추출하는 것도 좋을 것 같습니다.♻️ 수정 제안
+import java.security.SecureRandom; -import java.util.Random; // 랜덤 인증 번호 생성 public String generateCode() { - Random random = new Random(); + SecureRandom random = new SecureRandom(); int code = 100000 + random.nextInt(900000); // 6자리 인증 번호 return Integer.toString(code); }
src/main/java/com/whereyouad/WhereYouAd/domains/user/domain/service/SmsService.java
Show resolved
Hide resolved
kingmingyu
left a comment
There was a problem hiding this comment.
P4: 고생하셨습니다! 이메일 찾기 기능 잘 구현하신 것 같습니다! 코드 보면서 생각난건데 혹시 회원가입 시에 저희가 전화번호 인증은 따로 안받는데 회원가입 시에 전화번호를 잘못 입력한 경우에 대해서는 당장 고칠 부분은 아니지만 생각 좀 해봐야 할 것 같습니다!
| @ApiResponse(responseCode = "400_2", description = "이메일 중복 회원 존재") | ||
| }) | ||
| public ResponseEntity<DataResponse<SignUpResponse>> signUp(@RequestBody @Valid SignUpRequest request); | ||
| @Operation(summary = "단순 회원가입 API", description = "이메일, 비밀번호, 이름, 전화번호를 받아 회원가입을 진행합니다(먼저 이메일 인증이 진행되어야 회원가입 가능)") |
There was a problem hiding this comment.
P4: 저 혹시 들여쓰기 8칸으로 수정하신 이유가 있을까요??
There was a problem hiding this comment.
엇 딱히 이유는 없습니다!! 수정하다보니 들여쓰기가 고쳐진 것 같습니다(?)...
| try { | ||
| this.defaultMessageService.sendOne(new SingleMessageSendingRequest(message)); | ||
| } catch (Exception e) { | ||
| e.printStackTrace(); |
There was a problem hiding this comment.
P4: log.error("SMS 발송 실패: {}", e.getMessage()) 이런식으로 에러 로그 찍는건 어떤가요??
There was a problem hiding this comment.
넵! Slf4j 쓰는 게 낫겠네요
ojy0903
left a comment
There was a problem hiding this comment.
고생하셨습니다! 제 비밀번호 찾기 PR 에서 지민님 하신것처럼 UserException -> UserHandler 로 통일해서 수정했는데, 제 PR 머지하고 나면 충돌이 생길수도 있을거 같아요. 코드 구조는 동일해서 만약 conflict 난다면 지민님이 하신 코드로 반영하면 될거 같아요. 전체적으로 로직은 깔끔해서 이대로 진행해도 될듯합니다!!
|
conflict 는 import 문이랑 제가 작성한 Docs 관련 차이인거 같아서 제 비밀번호 찾기 PR 내용 추가만 해주시면 될거 같아요! |
📌 관련 이슈
🚀 개요
이메일 찾기 기능을 위한 SMS 인증을 구현하였습니다.
📄 작업 내용
SmsService): Redis를 활용한 인증번호 저장(3분 만료) 및 검증 구현1. sms 전송 `POST /api/sms/sms-send`
{ "phoneNumber": "01012345678" }sendSms: 미가입자(유저가 아닐 시)는 전송 차단 (USER_404_2), 유저일 시 전송generateCode: 6자리 랜덤 인증 번호 생성POST /api/users/sms-verify{ "phoneNumber": "01012345678", "verificationCode": "123456" }verifyCode: 요청 내 인증 번호와 Redis에 저장된 해당 번호(key)의 인증번호(value)가 일치하는지 여부 확인isPhoneVerified: 검증 성공 시 해당 유저의 이메일 반환, 인증 성공 시 Redis 데이터 삭제(delete)로 재사용 방지📸 스크린샷 / 테스트 결과 (선택)
✅ 체크리스트
🔍 리뷰 포인트 (Review Points) + 고려할 사항
Summary by CodeRabbit
릴리스 노트
New Features
Chores
✏️ Tip: You can customize this high-level summary in your review settings.