728x90
0.개요
쇼핑몰 프로젝트를 시작하며 우선 핵심이 되는 기능인 쇼핑몰 물품에 대한 모델을 만들기로 하였다.
모델의 관계도를 만들기 위해서 는 dbdiagram.io 를 사용하였다.
우선 눈에 보일때 필요한 부분만 생성하였다.
dbdiagram.io - Database Relationship Diagrams Design Tool
dbdiagram.io
1. 모델생성하기
1-1. 회원
회원은 고객과 판매자 2개로 나뉘어 진다. 때문에 회원(Member)을 기본으로 두고, 고객(Customer)과 판매자(Seller)를 분리한다.
Member 테이블은 모든 사용자의 공통 정보를 관리하고 사용자는 CUSTOMER, SELLER, 또는 두 역할을 동시에 가진 BOTH로 구분될 수 있다.
Customer와 SELLER은 Member 테이블과 1:1 관계를 가지고 추후 주문 이력이나 장바구니 등의 정보를 추가할예정이다.
Table Member {
id integer [primary key]
user_id string [unique, not null]
password string [not null]
nickname string [not null]
role enum('CUSTOMER', 'SELLER', 'BOTH') [not null]
}
Table Customer {
id integer [primary key]
member_id integer [ref: > Member.id, not null]
}
Table Seller {
id integer [primary key]
member_id integer [ref: > Member.id, not null]
}
이걸이제 스프링부트에서 Entity로 생성한다.
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String userId;
@Column(nullable = false)
private String password;
@Column(nullable = false)
private String nickname;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private Role role;
public enum Role {
CUSTOMER, SELLER, BOTH
}
}
public class Seller {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne
@JoinColumn(name = "member_id", referencedColumnName = "id", nullable = false)
private Member member;
}
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne
@JoinColumn(name = "member_id", referencedColumnName = "id", nullable = false)
private Member member;
}
1-2.상품
판매자가 등록한 상품 정보를 관리하는 테이블이다.
Product와 Seller는 1:N 관계로 설정해둬서 한명의 판매자는 여러개의 상품을 등록할수있다.
Table Product {
id integer [primary key]
name string [not null]
image string
description string
price decimal [not null]
stock integer [not null]
seller_id integer [ref: > Seller.id, not null]
}
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
private String image;
private String description;
@Column(nullable = false)
private int price;
@Column(nullable = false)
private int stock;
@OneToOne
@JoinColumn(name = "seller_id", referencedColumnName = "id", nullable = false)
private String sellerId;
}
2.엔드포인트 만들기
2-1.DTO
우선 엔드포인트에 전달할 내용을 DTO로 정의해두자
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ProductDTO {
private Long id;
private String name;
private String image;
private String description;
private int price;
private int stock;
private String sellerName;
}
2-2.Controller
Controller에서 /product 엔드포인트들을 Restcontroller로 제작하고 쿼리파라미터로 개수를 받아 제품들을 리턴해주는 엔드포인트를 만든다.
@RestController
@RequiredArgsConstructor
@RequestMapping("/product")
@Tag(name = "상품API", description = "/product")
public class ProductController {
private final ProductService productService;
@GetMapping("/")
public List<ProductDTO> getProductItems(@RequestParam(defaultValue = "10") int count) {
return productService.getProductItems(count);
}
}
2-3.Service
Service에서는 Repository를 통해 JPA메소드 findAll로 데이터를 불러온다음 ProductDTO로 매개변수로 받은 count만큼 반환해준다.
@Service
@RequiredArgsConstructor
public class ProductService {
private final ProductRepository productRepository;
public List<ProductDTO> getProductItems(int count){
List<Product> productList = productRepository.findAll();
if (productList.isEmpty()) {
return Collections.emptyList();
}
List<ProductDTO> productDTOList = productList.stream()
.limit(count)
.map(product -> ProductDTO.builder()
.id(product.getId())
.name(product.getName())
.description(product.getDescription())
.image("http://localhost:8080/images/"+product.getImage())
.price(product.getPrice())
.stock(product.getStock())
.sellerName(product.getSeller().getMember().getNickname())
.build())
.collect(Collectors.toList());
return productDTOList;
}
}
이미지의 경우 resources -> static 에 images폴더를 만들어서 이미지를 넣어줬기 때문에 다음과같이 엔드포인트로 넣어뒀다.
2-4.Repository
JPA를 상속받은 레포지토리로 각 모델별로 만들어뒀다.
package com.shopping.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.shopping.model.Product;
public interface ProductRepository extends JpaRepository<Product, Long> {
}
2-5.Config
로컬환경에서 테스트해야하기때문에 CORS 허용을 위한 WebConfig.java 파일을 만든다.
package com.shopping.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 모든 경로에 대해 CORS 허용
.allowedOrigins("http://localhost:5173") // 클라이언트가 실행되는 도메인
.allowedMethods("GET", "POST", "PUT", "DELETE") // 허용할 HTTP 메서드
.allowedHeaders("*"); // 모든 헤더 허용
}
}
3.데이터 생성하기
이제 다음과같은 테스트파일을 통해 데이터들을 생성해본다.
@SpringBootTest
class ShoppingmallBackendApplicationTests {
@Autowired
private ProductRepository productRepository;
@Autowired
private MemberRepository memberRepository;
@Autowired
private SellerRepository sellerRepository;
private Member member;
@BeforeEach
public void setUp() {
member = Member.builder()
.userId("TestMan123")
.password("password123")
.nickname("TestMan")
.role(Member.Role.SELLER)
.build();
memberRepository.save(member);
}
@Test
void ProductCreateTest() {
Seller seller = Seller.builder()
.member(member)
.build();
sellerRepository.save(seller);
Product product = Product.builder()
.name("Smartphone")
.image("smartphone.jpg")
.description("Latest model smartphone")
.price(500)
.stock(100)
.seller(seller)
.build();
productRepository.save(product);
assertNotNull(product.getId());
}
}
4.엔드포인트 Swagger로 확인
이제 Swagger를 통해 엔드포인트가 정상적으로 작동하는지 확인해본다.
내가 넣은 데이터2개가 DTO를 통해 원하는 형태로 나왔다.
5.FrontEnd 만들어서 테스트 해보기
FrontEnd 부분은 React + TS + Vite 를 통해 만들었다.
API는 Axois 를 이용해서 불러와서 사용했다.
자세한 코드는 github에 있다.
export const getProducts = async (
count:Number
): Promise<ProductInfo[]> => {
const url = `${BASE_URL}/product/?count=${count}`;
try {
const response = await axios.get(url, {
headers: {
'Content-Type': 'application/json'
}
});
const items = response.data;
if (!Array.isArray(items)) {
return [];
}
return items.map((item: ProductInfo) => ({
id: item.id,
name: item.name,
description: item.description,
price: item.price,
stock: item.stock,
sellerName: item.sellerName,
image:item.image,
}));
} catch (error) {
console.error('API 호출 에러:', error);
throw error;
}
};
function ProductContainer(){
const [products, setProducts] = useState<ProductInfo[]>([]);
useEffect(() => {
const fetchProducts = async () => {
try {
const data = await getProducts(2);
setProducts(data);
} catch (error) {
console.error('상품 데이터를 가져오는 중 오류 발생:', error);
}
};
fetchProducts();
console.log(products)
}, []);
return(
<div className="bg-white mt-10">
<span>너에게 추천하는 제품!!!!</span>
<div className="bg-white h-[250px] flex gap-10 p-5 ">
{products.map((product) => (
<ProductCard key={String(product.id)} product={product} />
))}
</div>
</div>
)
}
function ProductCard({ product }: ProductCardProps) {
return (
<div className="h-full bg-blue-100 w-[200px] flex flex-col gap-1">
<img className="h-3/4 w-full bg-yellow-100" src={product.image}></img>
<span>{product.name}</span>
<span>{product.sellerName}</span>
</div>
);
}
내가 생성한 엔드포인트가 정상적으로 작동하는것을 FrontEnd에서 확인할수있었다.
https://github.com/asa9874/shoppingmall-backend
GitHub - asa9874/shoppingmall-backend
Contribute to asa9874/shoppingmall-backend development by creating an account on GitHub.
github.com
https://github.com/asa9874/shoppingmall-frontend
GitHub - asa9874/shoppingmall-frontend
Contribute to asa9874/shoppingmall-frontend development by creating an account on GitHub.
github.com
728x90
'BackEnd > SpringBoot' 카테고리의 다른 글
[SpringBoot] 쇼핑몰 회원가입 기능 제작 (0) | 2025.02.21 |
---|---|
[SpringBoot] Test로 데이터 추가하기 경매장#7 (0) | 2024.11.05 |
[SpringBoot] RESTAPI CRUD 제약조건추가 경매장#6 (0) | 2024.11.04 |