Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
a19ca70
chore: 타임리프, 웹 의존성 추가
Yiseull Jul 12, 2023
e40dca5
feat: Thymeleaf를 이용한 바우처 관리 기능 추가
Yiseull Jul 12, 2023
20ff8a8
feat: Thymeleaf를 이용한 바우처 관리 기능 추가
Yiseull Jul 12, 2023
eb7ebfb
chore: Validation 의존성 추가
Yiseull Jul 12, 2023
b6155e1
feat: amount 최소 입력값 검증 추가
Yiseull Jul 12, 2023
354c57b
fix: 바우처 수정 폼에서 Type select로 변경
Yiseull Jul 12, 2023
deeecc7
refactor: 바우처 서비스에서 바우처 생성 후 반환 타입 변경
Yiseull Jul 12, 2023
c10a43b
feat: REST API 컨트롤러 추가
Yiseull Jul 12, 2023
dd1bbc2
feat: 예외 처리
Yiseull Jul 12, 2023
baf8637
feat: 바우처가 존재하지 않을 때 예외 처리
Yiseull Jul 13, 2023
888ec87
fix: VoucherUpdateRequest에서 id 제거
Yiseull Jul 17, 2023
a45d6c3
test: RestApiVoucherController 테스트 추가
Yiseull Jul 17, 2023
ca8d1f7
fix: API root url 변경
Yiseull Jul 17, 2023
153bbea
refactor: 요청값 logging
Yiseull Jul 17, 2023
3e706b6
refactor: 응답 타입을 객체로 변경
Yiseull Jul 18, 2023
a4fbe98
test: 컨트롤러 테스트 수정
Yiseull Jul 18, 2023
6d00c0f
refactor: 정적 팩토리 메서드 패턴 적용
Yiseull Jul 18, 2023
326d12e
refactor: DTO에서 Entity 변환 처리
Yiseull Jul 18, 2023
08aa082
refactor: ErrorCode 한 곳에서 관리, 커스텀 코드 추가
Yiseull Jul 18, 2023
a2ee4d7
refactor: 커스텀 예외 코드 추가, Exception 핸들러 추가
Yiseull Jul 18, 2023
8ea7329
docs: 주차별 요구사항 정리
Yiseull Jul 18, 2023
763b5fe
test: 바우처 정적 팩토리 메서드 패턴 적용
Yiseull Jul 18, 2023
046f9df
test: 바우처 예외로 통일
Yiseull Jul 18, 2023
7ab0235
refactor: 바우처 api 변경
Yiseull Jul 24, 2023
584d09a
chore: aop 의존성 추가
Yiseull Jul 25, 2023
86ec575
feat: aop를 사용해서 log 찍기
Yiseull Jul 25, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,30 @@
# SpringBoot Basic Weekly Mission
스프링부트 basic 위클리미션을 코드리뷰하는 Repository입니다.

## 1주차 요구 사항
- 바우처 관리 Command-line Application을 만든다.
- create / list 커맨드를 지원한다.
- create 커맨드를 통해 바우처를 생성할수 있다.
- list 커맨드를 통해 만들어진 바우처를 조회할 수 있다.

## 2주차 요구사항
- 단위 테스트를 작성한다.
- 고객을 적용한다.
- customers 테이블 정의 및 추가한다.
- CustomerRepository 추가 및 JdbcTemplate을 사용해서 구현한다.
- 바우처 정보를 DB로 관리한다.
- 바우처에 엔터티에 해당하는 vouchers 테이블을 정의한다.
- JdbcTemplate을 사용해서 바우처 레포지토리를 만든다.
- vouchers 테이블을 통해서 CRUD를 할 수 있다.

## 3주차 요구사항
- 커맨드로 지원했던 기능을 thymeleaf를 이용해서 다음 기능을 지원할 수 있다.
- 조회페이지
- 상세페이지
- 입력페이지
- 삭제기능
- Spring MVC를 적용해서 JSON을 지원하는 REST API를 개발한다.
- 조회페이지
- 상세페이지
- 입력페이지
- 삭제기능
6 changes: 6 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ repositories {
}

dependencies {
// AOP
implementation 'org.springframework.boot:spring-boot-starter-aop'

// JDBC
implementation 'org.springframework.boot:spring-boot-starter-jdbc'

Expand All @@ -29,6 +32,9 @@ dependencies {
compileOnly 'org.projectlombok:lombok:1.18.26'
annotationProcessor 'org.projectlombok:lombok:1.18.26'

implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,13 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Controller;

import java.util.List;
import java.util.UUID;

@Profile("!test")
@Slf4j
@Controller
//@Controller
@RequiredArgsConstructor
public class CommandLineController implements CommandLineRunner {

Expand Down Expand Up @@ -60,8 +59,8 @@ private boolean isRunning() {
UUID id = InputView.inputVoucherUpdate(vouchers);
OutputView.showDiscountType();
VoucherCreationRequest updateVoucherInfo = InputView.inputVoucherInfo();
VoucherUpdateRequest request = new VoucherUpdateRequest(id, updateVoucherInfo.type(), updateVoucherInfo.amount());
voucherController.updateVoucher(request);
VoucherUpdateRequest request = new VoucherUpdateRequest(updateVoucherInfo.type(), updateVoucherInfo.amount());
voucherController.updateVoucher(id, request);
}
case DELETE -> {
List<VoucherResponse> vouchers = voucherController.getVouchers();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.programmers.vouchermanagement.customer.domain;

import com.programmers.vouchermanagement.customer.exception.InvalidCustomerTypeException;
import com.programmers.vouchermanagement.global.exception.ErrorCode;
import com.programmers.vouchermanagement.voucher.exception.VoucherException;

public enum CustomerType {
BLACK, WHITE;
Expand All @@ -9,7 +10,7 @@ public static CustomerType from(String type) {
try {
return valueOf(type.toUpperCase());
} catch (IllegalArgumentException e) {
throw new InvalidCustomerTypeException("잘못된 고객 타입입니다.");
throw new VoucherException(ErrorCode.INVALID_CUSTOMER_TYPE);
}
}
}

This file was deleted.

21 changes: 0 additions & 21 deletions src/main/java/com/programmers/vouchermanagement/docs/README.md

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.programmers.vouchermanagement.global.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Slf4j
@Aspect
@Component
public class ControllerLogAop {

@Before("bean(*Controller)")
public void beforeParameterLog(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
log.info("### method name: {}", method.getName());

Object[] args = joinPoint.getArgs();
for (Object arg : args) {
log.info("### parameter type: {}", arg.getClass().getSimpleName());
log.info("### parameter value: {}", arg);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.programmers.vouchermanagement.global.exception;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public class BaseException extends RuntimeException {
private final ErrorCode errorCode;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.programmers.vouchermanagement.global.exception;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;

@Getter
@RequiredArgsConstructor
public enum ErrorCode {

// Voucher
INVALID_DISCOUNT_TYPE(HttpStatus.BAD_REQUEST, "V001", "유효하지 않은 할인 타입입니다."),
VOUCHER_NOT_FOUND(HttpStatus.NOT_FOUND, "V002", "바우처를 찾을 수 없습니다."),
INVALID_FIX_AMOUNT(HttpStatus.BAD_REQUEST, "V003", "고정할인금액은 최소 1원 이상이여야 합니다."),
INVALID_PERCENT(HttpStatus.BAD_REQUEST, "V004", "할인률은 1에서 100 사이여야 합니다."),

// Customer
INVALID_CUSTOMER_TYPE(HttpStatus.BAD_REQUEST, "C001", "유효하지 않은 고객 타입입니다."),

// Common
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "S999", "Internal Server Error");

private final HttpStatus httpStatus;
private final String code;
private final String message;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.programmers.vouchermanagement.global.exception;

import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.RequiredArgsConstructor;


@Getter
@Builder
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
public class ErrorResponse {

private final String error;
private final String code;
private final String message;

public static ErrorResponse of(ErrorCode errorCode) {
return ErrorResponse.builder()
.error(errorCode.name())
.code(errorCode.getCode())
.message(errorCode.getMessage())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.programmers.vouchermanagement.global.exception;

import com.programmers.vouchermanagement.voucher.exception.VoucherException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleException(Exception e) {
log.error("Unexpected Exception", e);
ErrorCode errorCode = ErrorCode.INTERNAL_SERVER_ERROR;
ErrorResponse response = ErrorResponse.of(errorCode);
return new ResponseEntity<>(response, errorCode.getHttpStatus());
}

@ExceptionHandler(VoucherException.class)
public ResponseEntity<ErrorResponse> handleVoucherException(VoucherException e) {
log.error("Voucher Exception", e);
ErrorCode errorCode = e.getErrorCode();
ErrorResponse response = ErrorResponse.of(errorCode);
return new ResponseEntity<>(response, errorCode.getHttpStatus());
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package com.programmers.vouchermanagement.voucher.application;

import com.programmers.vouchermanagement.voucher.domain.DiscountPolicy;
import com.programmers.vouchermanagement.voucher.domain.DiscountType;
import com.programmers.vouchermanagement.global.exception.ErrorCode;
import com.programmers.vouchermanagement.voucher.domain.Voucher;
import com.programmers.vouchermanagement.voucher.domain.VoucherRepository;
import com.programmers.vouchermanagement.voucher.dto.request.VoucherCreationRequest;
import com.programmers.vouchermanagement.voucher.dto.request.VoucherUpdateRequest;
import com.programmers.vouchermanagement.voucher.dto.response.VoucherResponse;
import com.programmers.vouchermanagement.voucher.exception.VoucherException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
Expand All @@ -22,12 +22,10 @@ public class VoucherService {

private final VoucherRepository voucherRepository;

public Voucher createVoucher(VoucherCreationRequest request) {
DiscountType discountType = request.type();
int amount = request.amount();
DiscountPolicy discountPolicy = discountType.createDiscountPolicy(amount);
Voucher voucher = new Voucher(discountPolicy);
return voucherRepository.save(voucher);
public VoucherResponse createVoucher(VoucherCreationRequest request) {
Voucher voucher = request.toEntity();
Voucher savedVoucher = voucherRepository.save(voucher);
return new VoucherResponse(savedVoucher);
}

public List<VoucherResponse> getVouchers() {
Expand All @@ -37,16 +35,26 @@ public List<VoucherResponse> getVouchers() {
.collect(Collectors.toList());
}

public void updateVoucher(VoucherUpdateRequest request) {
UUID id = request.id();
DiscountType discountType = request.type();
int amount = request.amount();
DiscountPolicy discountPolicy = discountType.createDiscountPolicy(amount);
Voucher voucher = new Voucher(id, discountPolicy);
public VoucherResponse getVoucher(UUID id) {
Voucher voucher = voucherRepository.findById(id)
.orElseThrow(() -> new VoucherException(ErrorCode.VOUCHER_NOT_FOUND));
return new VoucherResponse(voucher);
}

public void updateVoucher(UUID id, VoucherUpdateRequest request) {
existsVoucher(id);
Voucher voucher = request.toEntity(id);
voucherRepository.update(voucher);
}

public void deleteVoucher(UUID id) {
existsVoucher(id);
voucherRepository.deleteById(id);
}

private void existsVoucher(UUID id) {
if (!voucherRepository.existById(id)) {
throw new VoucherException(ErrorCode.VOUCHER_NOT_FOUND);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.programmers.vouchermanagement.voucher.domain;

import com.programmers.vouchermanagement.voucher.exception.InvalidDiscountTypeException;
import com.programmers.vouchermanagement.global.exception.ErrorCode;
import com.programmers.vouchermanagement.voucher.exception.VoucherException;
import lombok.Getter;

import java.util.Collections;
Expand Down Expand Up @@ -35,7 +36,7 @@ public static DiscountType from(String number) {
if (DISCOUNT_TYPE_MAP.containsKey(number)) {
return DISCOUNT_TYPE_MAP.get(number);
}
throw new InvalidDiscountTypeException("존재하지 않는 할인 유형 입니다.");
throw new VoucherException(ErrorCode.INVALID_DISCOUNT_TYPE);
}

public DiscountPolicy createDiscountPolicy(int amount) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.programmers.vouchermanagement.voucher.domain;

import com.programmers.vouchermanagement.voucher.exception.InvalidDiscountAmountException;
import com.programmers.vouchermanagement.global.exception.ErrorCode;
import com.programmers.vouchermanagement.voucher.exception.VoucherException;

public class FixedAmountDiscountPolicy extends DiscountPolicy {

Expand All @@ -13,7 +14,7 @@ public FixedAmountDiscountPolicy(int amount) {
@Override
void validateAmount(int amount) {
if (amount < MIN_AMOUNT) {
throw new InvalidDiscountAmountException("고정할인금액은 최소 1원 이상이여야 합니다.");
throw new VoucherException(ErrorCode.INVALID_FIX_AMOUNT);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.programmers.vouchermanagement.voucher.domain;

import com.programmers.vouchermanagement.voucher.exception.InvalidDiscountAmountException;
import com.programmers.vouchermanagement.global.exception.ErrorCode;
import com.programmers.vouchermanagement.voucher.exception.VoucherException;

import java.math.BigDecimal;

Expand All @@ -16,7 +17,7 @@ public PercentDiscountPolicy(int amount) {
@Override
void validateAmount(int amount) {
if (MIN_PERCENT > amount || amount > MAX_PERCENT) {
throw new InvalidDiscountAmountException("할인률은 1에서 100 사이여야 합니다.");
throw new VoucherException(ErrorCode.INVALID_PERCENT);
}
}

Expand Down
Loading