-
Notifications
You must be signed in to change notification settings - Fork 121
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주차 과제(1단계 ~ 3단계) #204
base: main
Are you sure you want to change the base?
Changes from all commits
b30bca0
ced7912
8ff227f
46d42da
0e3f39f
54c8b31
9677627
248f66c
1c097d0
3d6b6a9
9c3474b
9233af6
3616f84
1bee1a0
5429dc4
c29de3d
b8c43f9
33dce60
7e19112
c73252b
47d94a1
d11f6c0
3bf2cba
0088049
0a42f99
c7f414f
3111479
8b8363c
84d0360
558cb4d
d3052e8
19e9c76
c7ef350
4febc53
8966964
1962d6d
0a86c31
b7c0b55
ab6871b
c0ecf75
eec33d2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,75 @@ | ||
# spring-gift-product | ||
# spring-gift-product | ||
## 기능 요구 사항 | ||
* 상품을 조회, 추가, 수정, 삭제할 수 있는 간단한 HTTP API를 구현한다. | ||
* HTTP 요청과 응답은 JSON 형식으로 주고받는다. | ||
* 현재는 별도의 데이터베이스가 없으므로 적절한 자바 컬렉션 프레임워크를 사용하여 메모리에 저장한다. | ||
|
||
**Request** | ||
``` | ||
GET /api/products HTTP/1.1 | ||
``` | ||
|
||
**Response** | ||
``` | ||
HTTP/1.1 200 | ||
Content-Type: application/json | ||
[ | ||
{ | ||
"id": 8146027, | ||
"name": "아이스 카페 아메리카노 T", | ||
"price": 4500, | ||
"imageUrl": "https://st.kakaocdn.net/product/gift/product/20231010111814_9a667f9eccc943648797925498bdd8a3.jpg" | ||
} | ||
] | ||
``` | ||
|
||
**상품 데이터 관리** | ||
```java | ||
public class ProductController { | ||
private final Map<Long, Product> products = new HashMap<>(); | ||
} | ||
|
||
public class Product { | ||
private Long id; | ||
private String name; | ||
private int quantity; | ||
private double price; | ||
// ... | ||
} | ||
``` | ||
|
||
### 1. get | ||
* 모든 product 조회 | ||
* /api/products | ||
* id로 product 선택 조회 | ||
* /api/products/:id | ||
* 없으면 error 발생 | ||
### 2. post | ||
* product 추가 | ||
* /api/products | ||
### 3. put | ||
* product 수정 | ||
* /api/products/:id | ||
* 없으면 error 발생 | ||
### 4. delete | ||
* product 삭제 | ||
* /api/products/:id | ||
* 없으면 error 발생 | ||
|
||
# step 2 | ||
### /api/products/admin | ||
상품 관리 | ||
### /api/products/admin/add | ||
상품 추가 | ||
### /api/products/admin/:id | ||
상품 편집 및 삭제 가능 | ||
|
||
|
||
# step 3 | ||
상품 정보에 옵션을 추가한다. 상품과 옵션 모델 간의 관계를 고려하여 설계하고 구현한다. | ||
* 상품에는 항상 하나 이상의 옵션이 있어야 한다. | ||
|
||
## 프로그래밍 요구 사항 | ||
* H2 데이터베이스를 사용하도록 변경한다. | ||
* 사용하는 테이블은 애플리케이션이 실행될 때 구축되어야 한다. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
drop table if exists product CASCADE; | ||
create table product | ||
( | ||
id bigint AUTO_INCREMENT PRIMARY KEY, | ||
name varchar(255), | ||
price int, | ||
imageUrl varchar(255) | ||
); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package gift; | ||
|
||
import gift.controller.AdminController; | ||
import gift.repository.JdbcProductRepository; | ||
import gift.repository.MemoryProductRepository; | ||
import gift.repository.ProductRepository; | ||
import jakarta.annotation.PostConstruct; | ||
import javax.sql.DataSource; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.core.io.ClassPathResource; | ||
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils; | ||
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; | ||
|
||
@Configuration | ||
public class SpringConfig { | ||
|
||
private final DataSource dataSource; | ||
|
||
@Autowired | ||
public SpringConfig(DataSource dataSource) { | ||
this.dataSource = dataSource; | ||
} | ||
|
||
@Bean | ||
public AdminController adminController() { | ||
return new AdminController(productRepository()); | ||
} | ||
|
||
@Bean | ||
public ProductRepository productRepository() { | ||
// JDBC를 이용한 데이터베이스 접근 방식 선택 | ||
return new JdbcProductRepository(dataSource); | ||
} | ||
|
||
@PostConstruct | ||
private void initializeDB() { | ||
// 데이터베이스 초기화를 위한 SQL 스크립트 실행 | ||
ResourceDatabasePopulator populator = new ResourceDatabasePopulator(); | ||
populator.addScript(new ClassPathResource("sql/ddl.sql")); // 데이터베이스 초기화 스크립트 경로 지정 | ||
DatabasePopulatorUtils.execute(populator, dataSource); // 데이터베이스 초기화 실행 | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package gift.controller; | ||
|
||
import gift.repository.ProductRepository; | ||
import org.springframework.stereotype.Controller; | ||
import org.springframework.ui.Model; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.PathVariable; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
|
||
@Controller | ||
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. 이미 해당 어노테이션을 통해 빈 등록이 되니 config에서는 지워주세요. |
||
@RequestMapping("/api/products/admin") | ||
public class AdminController { | ||
|
||
private final ProductRepository repository; | ||
|
||
public AdminController(ProductRepository repository) { | ||
this.repository = repository; | ||
} | ||
|
||
// 모든 제품을 표시하는 관리자 페이지로 매핑 | ||
@GetMapping("") | ||
public String adminPage(Model model) { | ||
model.addAttribute("products", repository.findAll()); | ||
return "admin"; | ||
} | ||
|
||
// 새 제품을 추가하는 페이지로 매핑 | ||
@GetMapping("/add") | ||
public String getAdminAddPage() { | ||
return "adminAdd"; | ||
} | ||
|
||
// 삭제된 제품을 보여주는 페이지로 매핑 | ||
@GetMapping("/deleted") | ||
public String getAdminDeletedPage() { | ||
return "adminDeleted"; | ||
} | ||
|
||
// 특정 ID의 제품 세부 정보를 보여주는 페이지로 매핑 | ||
@GetMapping("/{id}") | ||
public String getAdminProductDetailPage(@PathVariable Long id, Model model) { | ||
model.addAttribute("product", repository.findById(id).orElse(null)); // Null을 처리하기 위해 orElse(null) 사용 | ||
return "adminProductDetail"; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package gift.controller; | ||
|
||
import org.springframework.stereotype.Controller; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
|
||
@Controller | ||
public class ErrorController { | ||
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. 목적에 따른 클래스 분리 좋습니다. |
||
|
||
// 오류 페이지로 매핑 | ||
@GetMapping("/error") | ||
public String errorPage() { | ||
return "error"; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package gift.controller; | ||
|
||
import gift.model.Product; | ||
import gift.model.ProductForm; | ||
import gift.repository.ProductRepository; | ||
import java.util.List; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.*; | ||
|
||
@RestController | ||
@RequestMapping("/api/products") | ||
public class ProductController { | ||
|
||
private final ProductRepository repository; | ||
|
||
public ProductController(ProductRepository repository) { | ||
this.repository = repository; | ||
} | ||
|
||
// 모든 제품을 가져오는 엔드포인트 | ||
@GetMapping("") | ||
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. ("") 는 지워주셔도 좋습니다. |
||
public List<Product> getAllProducts() { | ||
return repository.findAll(); | ||
} | ||
|
||
// 특정 ID의 제품을 가져오는 엔드포인트 | ||
@GetMapping("/{id}") | ||
public ResponseEntity<Product> getProduct(@PathVariable Long id) { | ||
Product result = repository.findById(id).orElse(null); // Optional 처리 | ||
if (result != null) { | ||
return ResponseEntity.status(HttpStatus.OK).body(result); | ||
} | ||
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null); | ||
} | ||
|
||
// 새로운 제품을 생성하는 엔드포인트 | ||
@PostMapping(path = "", consumes = "application/json") | ||
public ResponseEntity<Product> postProduct(@RequestBody ProductForm form) { | ||
Product product = new Product(form); // ProductForm을 Product로 변환 | ||
Product result = repository.save(product); | ||
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. 컨트롤러 단에서 바로 로직이 실행되고 있습니다. |
||
if (result != null) { | ||
return ResponseEntity.status(HttpStatus.OK).body(result); | ||
} | ||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null); // 상태 코드를 BAD_REQUEST로 수정 | ||
} | ||
|
||
// 특정 ID의 제품을 수정하는 엔드포인트 | ||
@PutMapping(path = "/{id}", consumes = "application/json") | ||
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. consumes은 기본값을 확인해보고 생략해주셔도 좋습니다. |
||
public ResponseEntity<Product> putProduct(@RequestBody ProductForm form, @PathVariable Long id) { | ||
Product product = new Product(form); // ProductForm을 Product로 변환 | ||
Product result = repository.edit(id, product); | ||
if (result != null) { | ||
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. 에러 처리가 보일러플레이트처럼 반복되는 것으로 보입니다. |
||
return ResponseEntity.status(HttpStatus.OK).body(result); | ||
} | ||
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null); | ||
} | ||
|
||
// 특정 ID의 제품을 삭제하는 엔드포인트 | ||
@DeleteMapping(path = "/{id}") | ||
public ResponseEntity<String> deleteProduct(@PathVariable Long id) { | ||
boolean result = repository.delete(id); | ||
if (result) { | ||
return ResponseEntity.status(HttpStatus.OK).body("Successfully deleted"); | ||
} | ||
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Product not found"); // 메시지를 명확히 수정 | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
package gift.model; | ||
|
||
public class Product { | ||
|
||
private Long id; | ||
private String name; | ||
private int price; | ||
private String imageUrl; | ||
|
||
// 기본 생성자 추가 (JPA나 다른 프레임워크에서 필요할 수 있음) | ||
public Product() { | ||
} | ||
|
||
// 생성자 | ||
public Product(String name, int price, String imageUrl) { | ||
this.name = name; | ||
this.price = price; | ||
this.imageUrl = imageUrl; | ||
} | ||
|
||
// ProductForm을 Product로 변환하는 생성자 | ||
public Product(ProductForm form) { | ||
this.name = form.getName(); | ||
this.price = form.getPrice(); | ||
this.imageUrl = form.getImageUrl(); | ||
} | ||
|
||
// getter 메서드들 | ||
public Long getId() { | ||
return id; | ||
} | ||
|
||
public String getName() { | ||
return name; | ||
} | ||
|
||
public int getPrice() { | ||
return price; | ||
} | ||
|
||
public String getImageUrl() { | ||
return imageUrl; | ||
} | ||
|
||
// setter 메서드들 | ||
public void setId(Long id) { | ||
this.id = id; | ||
} | ||
|
||
public void setImageUrl(String imageUrl) { | ||
this.imageUrl = imageUrl; | ||
} | ||
|
||
// 주어진 Product 객체의 정보를 현재 객체에 업데이트하는 메서드 | ||
public void updateFrom(Product product) { | ||
this.name = product.getName(); | ||
this.price = product.getPrice(); | ||
this.imageUrl = product.getImageUrl(); | ||
} | ||
|
||
// 객체를 문자열로 표현하는 메서드 | ||
@Override | ||
public String toString() { | ||
return "Product{" + | ||
"id=" + id + | ||
", name='" + name + '\'' + | ||
", price=" + price + | ||
", imageUrl='" + imageUrl + '\'' + | ||
'}'; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package gift.model; | ||
|
||
public class ProductForm { | ||
|
||
private String name; | ||
private Integer price; | ||
private String imageUrl; | ||
|
||
// 생성자 | ||
public ProductForm(String name, Integer price, String imageUrl) { | ||
this.name = name; | ||
this.price = price; | ||
this.imageUrl = imageUrl; | ||
} | ||
|
||
// getter 메서드들 | ||
public String getName() { | ||
return name; | ||
} | ||
|
||
public Integer getPrice() { | ||
return price; | ||
} | ||
|
||
public String getImageUrl() { | ||
return imageUrl; | ||
} | ||
|
||
// setter 메서드들 | ||
public void setName(String name) { | ||
this.name = name; | ||
} | ||
|
||
public void setPrice(Integer price) { | ||
this.price = price; | ||
} | ||
|
||
public void setImageUrl(String imageUrl) { | ||
this.imageUrl = imageUrl; | ||
} | ||
|
||
// 객체를 문자열로 표현하는 메서드 | ||
@Override | ||
public String toString() { | ||
return "ProductForm{" + | ||
"name='" + name + '\'' + | ||
", price=" + price + | ||
", imageUrl='" + imageUrl + '\'' + | ||
'}'; | ||
} | ||
|
||
} |
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.
빈 등록을 config에서 중복적으로 하고 있는 것으로 보입니다.