배경 및 목표
쿠폰 시스템 프로젝트의 확장성을 높이고, 카카오 쇼핑 서비스와의 연동한다. 이를 통해 사용자 인증과 쿠폰 발급을 하나의 시스템에서 관리할 수 있으며, 사용자 이메일을 기반으로 userID를 조회하는 기능을 추가해 정확한 쿠폰 발급과 데이터 일관성을 보장한다.
kakaohop-BE
구조 및 구현
kakaoshop-BE
├── src/
│ ├── main/
│ │ ├── java/com/example/kakao/
│ │ │ ├── coupon/
│ │ │ │ ├── CouponController.java
│ │ │ │ └── CouponRegisterRequest.java
│ │ │ └── user/
│ │ │ ├── UserJPARepository.java
│ │ │ ├── UserRestController.java
│ │ │ └── UserService.java
│ │ └── resources/
│ │ └── application-local.yml
│ └── build.gradle
Java
복사
파일명 | 내용 |
application-local.yml | 서버 포트와 MySQL 데이터베이스 설정, Hibernate JPA 설정, 로깅 설정 정의 |
CouponController.java | 쿠폰 발급 요청 처리 및 백엔드 쿠폰 서비스 호출 |
CouponRegisterRequest.java | 쿠폰 발급 요청 데이터를 담는 DTO 클래스 |
UserJPARepository.java | 이메일 기반 사용자 조회를 위한 JPA Repository |
UserRestController.java | 사용자 로그인, 회원가입 및 사용자 ID 조회를 처리하는 컨트롤러 |
UserService.java | 사용자 데이터 검증, ID 조회 및 비즈니스 로직 구현 |
소스코드 (쿠폰 서비스와 연결 된 부분 중심)
•
applicaion-local.yml
서버 포트, 데이터베이스 연결 설정 및 로깅 수준을 정의한다.
server:
servlet:
encoding:
charset: utf-8
force: true
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/coupon
driver-class-name: com.mysql.cj.jdbc.Driver
username: abcd
password: 1234
jpa:
hibernate:
ddl-auto: update
show-sql: true
logging:
level:
'[com.example.kakao]': DEBUG
'[org.hibernate.type]': TRACE
file:
path: ./images/
SQL
복사
설정 항목 | 설명 |
server.port | 서버가 실행될 포트 번호 (8080) |
datasource.url | MySQL 데이터베이스 URL 및 인코딩 설정 |
jpa.ddl-auto | 테이블 스키마 자동 업데이트 설정 |
logging.level | Hibernate SQL 및 애플리케이션 로그 출력 설정 |
•
CouponController.java
사용자 요청을 받아 쿠폰 백엔드 서비스로 전달한다.
package com.example.kakao.coupon;
import com.example.kakao._core.security.CustomUserDetails;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/coupon")
public class CouponController {
private final RestTemplate client;
private final String COUPON_BACKEND_URL = "http://localhost:8081"; // CHECK
@PostMapping("/v1/issue")
public ResponseEntity<?> registerCouponV1(@RequestBody CouponRegisterRequest request, @AuthenticationPrincipal CustomUserDetails userDetails) {
request.setUserId((long) userDetails.getUser().getId());
log.info("/v1/issue: request[{}]", request);
return ResponseEntity.ok(client.postForObject(COUPON_BACKEND_URL + "/v1/issue", request, Map.class));
}
@PostMapping("/v2/issue-async")
public ResponseEntity<?> registerCouponV3(@RequestBody CouponRegisterRequest request) {
log.info("/v2/issue-async: request[{}]", request);
return ResponseEntity.ok(client.postForObject(COUPON_BACKEND_URL + "/v2/issue-async", request, Map.class));
}
}
Java
복사
기능 | 설명 |
POST 요청 처리 | 쿠폰 발급 요청을 쿠폰 백엔드로 전달 |
RestTemplate 사용 | 외부 API 요청을 통해 쿠폰 발급을 처리 |
유저 ID 주입 | 사용자 인증 정보를 통해 userId를 설정 |
•
CouponRegisterRequest.java
쿠폰 발급 요청 데이터를 저장하는 DTO이다.
package com.example.kakao.coupon;
import lombok.Data;
@Data
public class CouponRegisterRequest {
private Long userId;
private Long couponId;
}
Java
복사
필드 | 설명 |
userId | 사용자 ID |
couponId | 발급할 쿠폰 ID |
•
UserJPARepository.java
이메일을 통해 사용자 정보를 조회하는 JPA Repository이다.
package com.example.kakao.user;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface UserJPARepository extends JpaRepository<User, Integer> {
Optional<User> findByEmail(String email); // 반환 타입이 Optional<User>
}
Java
복사
기능 | 설명 |
findByEmail | 이메일을 기반으로 사용자 정보를 조회 |
•
UserRestController.java
로그인, 회원가입, 사용자 ID 조회 기능을 제공한다.
package com.example.kakao.user;
import com.example.kakao._core.security.JWTProvider;
import com.example.kakao._core.utils.ApiUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import java.util.Map;
@RequiredArgsConstructor
@RestController
public class UserRestController {
private final UserService userService;
// (기능3) 이메일 중복체크
@PostMapping("/check")
public ResponseEntity<?> check(@RequestBody @Valid UserRequest.EmailCheckDTO emailCheckDTO, Errors errors) {
userService.sameCheckEmail(emailCheckDTO.getEmail());
return ResponseEntity.ok(ApiUtils.success(null));
}
// (기능4) 회원가입
@PostMapping("/join")
public ResponseEntity<?> join(@RequestBody @Valid UserRequest.JoinDTO requestDTO, Errors errors) {
userService.join(requestDTO);
return ResponseEntity.ok().body(ApiUtils.success(null));
}
// (기능5) 로그인
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody @Valid UserRequest.LoginDTO requestDTO, Errors errors) {
String jwt = userService.login(requestDTO);
return ResponseEntity.ok().header(JWTProvider.HEADER, jwt).body(ApiUtils.success(null));
}
// (추가)
@PostMapping("/find-id")
public ResponseEntity<?> findUserId(@RequestBody Map<String, String> request) {
String email = request.get("email");
if (email == null || email.isEmpty()) {
return ResponseEntity.badRequest().body("Email is required");
}
Integer userId = userService.findUserIdByEmail(email);
if (userId != null) {
return ResponseEntity.ok(Map.of("userId", userId));
} else {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("User not found");
}
}
// 로그아웃 사용안함 - 프론트에서 JWT 토큰을 브라우저의 localstorage에서 삭제하면 됨.
}
Java
복사
기능 | 설명 |
사용자 ID 조회 | 이메일 기반으로 사용자 ID를 반환 |
유효성 검증 | 이메일이 없을 경우 BAD_REQUEST 반환 |
•
UserService.java
사용자 데이터 검증 및 조회 로직을 처리한다.
package com.example.kakao.user;
import com.example.kakao._core.errors.exception.Exception400;
import com.example.kakao._core.errors.exception.Exception500;
import com.example.kakao._core.security.JWTProvider;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
@Transactional(readOnly = true)
@RequiredArgsConstructor
@Service
public class UserService {
private final PasswordEncoder passwordEncoder;
private final UserJPARepository userJPARepository;
@Transactional
public void join(UserRequest.JoinDTO requestDTO) {
sameCheckEmail(requestDTO.getEmail());
requestDTO.setPassword(passwordEncoder.encode(requestDTO.getPassword()));
try {
userJPARepository.save(requestDTO.toEntity());
} catch (Exception e) {
throw new Exception500("unknown server error");
}
}
public String login(UserRequest.LoginDTO requestDTO) {
User userPS = userJPARepository.findByEmail(requestDTO.getEmail()).orElseThrow(
() -> new Exception400("이메일을 찾을 수 없습니다 : "+requestDTO.getEmail())
);
if(!passwordEncoder.matches(requestDTO.getPassword(), userPS.getPassword())){
throw new Exception400("패스워드가 잘못입력되었습니다");
}
return JWTProvider.create(userPS);
}
public void sameCheckEmail(String email) {
Optional<User> userOP = userJPARepository.findByEmail(email);
if (userOP.isPresent()) {
throw new Exception400("동일한 이메일이 존재합니다 : " + email);
}
}
public Integer findUserIdByEmail(String email) {
return userJPARepository.findByEmail(email)
.map(User::getId) // 그대로 Integer로 반환
.orElse(null);
}
}
Java
복사
기능 | 설명 |
findUserIdByEmail | 이메일을 통해 사용자 ID를 조회 |
Optional 처리 | 조회 결과가 없을 경우 null 반환 처리 |
결론
쿠폰 발급 요청 및 사용자 ID 조회를 위한 기능을 추가하여 카카오 쇼핑 서비스와 쿠폰 백엔드를 효과적으로 연동하였다. 이를 통해 사용자 데이터 검증 및 쿠폰 처리의 신뢰성과 안정성을 확보한다.
Related Posts
Search