Skip to content
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

경북대 BE_김동현_1주차 과제(3단계) #201

Open
wants to merge 45 commits into
base: donghyuun
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
902b824
chore: build setting modification
donghyuun Jun 26, 2024
4e8d336
docs: post README.md
donghyuun Jun 26, 2024
b8a1e9b
feat: create Product class
donghyuun Jun 26, 2024
1bb950d
feat: create Product DTO
donghyuun Jun 26, 2024
ddd17ee
refactor: Product
donghyuun Jun 26, 2024
5cdd9db
feat: add product
donghyuun Jun 26, 2024
9e860bf
feat: get product(s)
donghyuun Jun 26, 2024
d6fcb53
refactor: 상품의 id 정적 변수 -> 지역 변수 사용으로 변경
donghyuun Jun 26, 2024
5e3b595
feat: modify product
donghyuun Jun 26, 2024
22320ce
feat: delete product
donghyuun Jun 26, 2024
b260e4a
refactor: 모든 요청 방식 form-data -> raw-json 변경
donghyuun Jun 26, 2024
c937c17
refactor: controller -> controller + service
donghyuun Jun 26, 2024
ceb052a
feat: post, get 테스트코드 작성
donghyuun Jun 26, 2024
29d8beb
fix: @PathVariable 에 경로 변수 이름 명시
donghyuun Jun 26, 2024
7d6874f
feat: update, delete 테스트코드 작성
donghyuun Jun 26, 2024
5a6d0d3
fix: javadoc 의 설명 부분 한칸 띄움
donghyuun Jun 26, 2024
35718a5
chore: java 버전 변경 17 -> 21
donghyuun Jun 26, 2024
93d2b81
fix: API 수정 "/products" -> "/api/products"
donghyuun Jun 26, 2024
43d6adc
docs(README): add step2 README
donghyuun Jun 28, 2024
e104fac
feat: 상품 조회 기능(홈 페이지의 일부 기능)
donghyuun Jun 28, 2024
b99a57c
refactor: controller 내 상품 저장소 -> Repository 클래스 생성
donghyuun Jun 28, 2024
19f0229
feat: 상품 추가 기능
donghyuun Jun 28, 2024
0eafb22
feat: 상품 삭제, 수정 기능
donghyuun Jun 28, 2024
60171d0
docs(JavaDoc): javadoc 수정
donghyuun Jun 28, 2024
a446935
refactor: 파일명 대문자로 수정
donghyuun Jun 28, 2024
304c9db
feat: JdbcTemplate 설정 및 Product 테이블 생성
donghyuun Jun 28, 2024
0c195c7
feat: 상품 조회 기능
donghyuun Jun 28, 2024
e1710be
feat: 상품 추가 기능
donghyuun Jun 28, 2024
fd7f6a5
feat: 상품 수정 기능
donghyuun Jun 28, 2024
9f832a2
feat: 체크 박스로 선택된 상품들 제거
donghyuun Jun 28, 2024
f3c8fc5
refactor: 불필요한 Mapping 및 메서드 삭제
donghyuun Jun 28, 2024
0997aa1
refactor: 데이터베이스 초기화 설정
donghyuun Jun 28, 2024
7af869e
remove: 기존 클래스 내 저장소 파일 제거
donghyuun Jun 28, 2024
8012427
fix: 직접 ID 생성, 증가시키는 코드 제거
donghyuun Jun 28, 2024
37ff02d
refactor: 주석 정리
donghyuun Jun 28, 2024
adb5221
remove: 코드로 db 초기화하는 파일 제거(불필요)
donghyuun Jun 28, 2024
f1ba685
refactor: @Controller + @ResponseBody -> @RestController
donghyuun Jun 28, 2024
d67b2a9
chore: spring-boot-starter-validation 의존성 추가
donghyuun Jun 30, 2024
ca31aa2
fix: 응답 메시지 정상 출력되도록
donghyuun Jun 30, 2024
fb8bbb8
feat: 예외 처리 & 응답 body 에 상황 별 ResponseDTO 사용
donghyuun Jun 30, 2024
96a9416
refactor: 폴더 이름 소문자로 변경
donghyuun Jul 3, 2024
c633d3e
refactor: @RequestMapping value 속성명 생략, javadoc 설명 추상화
donghyuun Jul 3, 2024
5a61b96
refactor: 응답(에러포함) 생성 방법 간소화, Error/Result ENUM 클래스 사용 X
donghyuun Jul 3, 2024
682473e
refactor: 유효성 검사 방식 변경, @Valid 어노테이션 사용
donghyuun Jul 3, 2024
9e8fa45
remove: Result/Errorcode Enum 클래스 삭제
donghyuun Jul 3, 2024
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
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
# spring-gift-product
# spring-gift-product

## 기능 목록
- 상품 추가 기능
- 상품 조회 기능
- 상품 수정 기능
- 상품 삭제 기능
- 상품 테스트 코드 작성
- 리팩토링
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ dependencies {
runtimeOnly 'com.h2database:h2'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

implementation 'org.springframework.boot:spring-boot-starter-validation'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 validation 기능을 활용하셨군요 좋습니당! 👍 저도 좋아합니다!


}

tasks.named('test') {
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/gift/Application.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}


}
36 changes: 36 additions & 0 deletions src/main/java/gift/controller/BasicController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package gift.controller;

import gift.service.ProductService;
import gift.model.Product;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping( "/api")
public class BasicController {

private final ProductService productService;

@Autowired
public BasicController(ProductService productService) {
this.productService = productService;
}

/**
* 홈 화면 렌더링 (thymeleaf)
*
* @param model
* @return 홈 화면 html 명
*/
@GetMapping
String homePage(Model model) {
// 현재 상품 목록 조회
List<Product> products = productService.getProducts();
model.addAttribute("products", products);
return "index";
}
}
101 changes: 101 additions & 0 deletions src/main/java/gift/controller/ProductController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package gift.controller;

import gift.global.response.ResponseMaker;
import gift.global.response.ResultResponseDto;
import gift.global.response.SimpleResultResponseDto;
import gift.service.ProductService;
import gift.dto.ProductDTO;
import gift.model.Product;
import jakarta.validation.Valid;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/products")
public class ProductController {

private final ProductService productService;

@Autowired
public ProductController(ProductService productService) {
this.productService = productService;
}

/**
* 상품 추가
*
* @param productDTO
* @return 결과 메시지
*/
@PostMapping
public ResponseEntity<SimpleResultResponseDto> createProduct(
@Valid @ModelAttribute ProductDTO productDTO) {
productService.createProduct(productDTO);
return ResponseMaker.createSimpleResponse(HttpStatus.CREATED, "상품이 추가되었습니다.");
}

/**
* 전체 상품 목록 조회
*
* @return 결과 메시지, products (상품 목록)
*/
@GetMapping
public ResponseEntity<ResultResponseDto<List<Product>>> getProducts() {
List<Product> products = productService.getProducts();
// 성공 시
return ResponseMaker.createResponse(HttpStatus.OK, "전체 목록 상품을 조회했습니다.", products);
}


/**
* 상품 수정
*
* @param id
* @param productDTO
* @return 결과 메시지
*/
@PutMapping("/{id}")
public ResponseEntity<SimpleResultResponseDto> updateProduct(@PathVariable("id") Long id,
@Valid @RequestBody ProductDTO productDTO) {
productService.updateProduct(id, productDTO);
return ResponseMaker.createSimpleResponse(HttpStatus.OK, "상품을 수정했습니다.");
}


/**
* 해당 ID 리스트에 속한 상품 삭제
*
* @param productIds
* @return 결과 메시지
*/
@DeleteMapping
public ResponseEntity<?> deleteSelectedProducts(@RequestBody List<Long> productIds) {
productService.deleteProductsByIds(productIds);
return ResponseMaker.createSimpleResponse(HttpStatus.OK, "선택된 상품들을 삭제했습니다.");
}

/**
* 해당 ID 를 가진 상품 삭제
*
* @param id
* @return 결과 메시지
*/
@DeleteMapping("/{id}")
public ResponseEntity<?> deleteProduct(@PathVariable("id") Long id) {
productService.deleteProduct(id);
return ResponseMaker.createSimpleResponse(HttpStatus.OK, "상품이 삭제되었습니다.");
}


}
50 changes: 50 additions & 0 deletions src/main/java/gift/dto/ProductDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package gift.dto;

import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

public class ProductDTO {

@NotBlank
private String name;

@Min(0)
private int price;

@NotBlank
private String imageUrl;

public ProductDTO() {
}

public ProductDTO(String name, int price, String imageUrl) {
this.name = name;
this.price = price;
this.imageUrl = imageUrl;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getPrice() {
return price;
}

public void setPrice(int price) {
this.price = price;
}

public String getImageUrl() {
return imageUrl;
}

public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}
}
24 changes: 24 additions & 0 deletions src/main/java/gift/global/exception/BusinessException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package gift.global.exception;

import org.springframework.http.HttpStatus;

/**
* RuntimeException 을 상속받는 커스텀 에러
* 개발자가 직접 날리는 에러
*/
public class BusinessException extends RuntimeException{
HttpStatus status;

public BusinessException(HttpStatus status, String message) {
super(message);
this.status = status;
}

public HttpStatus getStatus() {
return status;
}

public void setStatus(HttpStatus status) {
this.status = status;
}
}
38 changes: 38 additions & 0 deletions src/main/java/gift/global/handler/GlobalExceptionHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package gift.global.handler;

import gift.global.exception.BusinessException;
import gift.global.response.ErrorResponseDto;
import gift.global.response.ResponseMaker;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {

/**
* RuntimeException 을 상속받는 커스텀 에러 핸들러
* 개발자가 직접 날리는 에러
* @param e
* @return
*/
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ErrorResponseDto> handleBusinessException(BusinessException e) {
return ResponseMaker.createErrorResponse(e.getStatus(), e.getMessage());
}


/**
* MethodArgumentNotValidException 에러 핸들러
* 매개변수 인자가 유효하지 않은 경우 발생
* @param e
* @return
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponseDto> MethodArgumentNotValidException(
MethodArgumentNotValidException e) {
return ResponseMaker.createErrorResponse(HttpStatus.BAD_REQUEST, "입력값이 유효하지 않습니다.");
}
}
5 changes: 5 additions & 0 deletions src/main/java/gift/global/response/ErrorResponseDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package gift.global.response;

public record ErrorResponseDto(String message) {

}
47 changes: 47 additions & 0 deletions src/main/java/gift/global/response/ResponseMaker.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package gift.global.response;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

/**
* 응답 객체를 생성하는 클래스
*/
public class ResponseMaker {

/**
* BODY 에 성공 메시지와 데이터를 보냄
* @param message
* @param data
* @return
* @param <T>
*/
public static <T> ResponseEntity<ResultResponseDto<T>> createResponse(HttpStatus status, String message, T data) {
ResultResponseDto<T> resultResponseDto = new ResultResponseDto<>(message, data);

return ResponseEntity.status(status).body(resultResponseDto);
}

/**
* BODY 에 성공 메시지만 보냄 (데이터 X)
* @param message
* @return
*/
public static ResponseEntity<SimpleResultResponseDto> createSimpleResponse(HttpStatus status, String message) {
System.out.println("message = " + message);
SimpleResultResponseDto simpleResultResponseDto = new SimpleResultResponseDto(message);
System.out.println("simpleResultResponseDto = " + simpleResultResponseDto);
return ResponseEntity.status(status).body(simpleResultResponseDto);
}

/**
* BODY 에 에러 메시지만 보냄 (데이터 X)
* @param message
* @return
*/
public static ResponseEntity<ErrorResponseDto> createErrorResponse(HttpStatus status, String message) {
ErrorResponseDto errorResponseDto = new ErrorResponseDto(message);
return ResponseEntity.status(status)
.body(errorResponseDto);
}

}
12 changes: 12 additions & 0 deletions src/main/java/gift/global/response/ResultResponseDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package gift.global.response;

/**
* 메시지, "데이터" 전달
*
* @param message
* @param data
* @param <T>
*/
public record ResultResponseDto<T>(String message, T data) {

}
10 changes: 10 additions & 0 deletions src/main/java/gift/global/response/SimpleResultResponseDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package gift.global.response;

/**
* 메시지 전달 (데이터 X)
*
* @param message
*/
public record SimpleResultResponseDto(String message) {

}
21 changes: 21 additions & 0 deletions src/main/java/gift/global/validation/validator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package gift.global.validation;

import gift.dto.ProductDTO;
import gift.global.exception.BusinessException;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

/**
* 유효성 검증 클래스
*/
@Component
public class validator {

public void validateProduct(ProductDTO productDTO) {
if (productDTO.getName() == ""
|| productDTO.getPrice() == 0
|| productDTO.getImageUrl() == "") {
throw new BusinessException(HttpStatus.BAD_REQUEST, "입력값이 유효하지 않습니다.");
}
}
}
Loading