Post

[Day33] 쇼핑몰 실습 - 장바구니, Restful

[Day33] 쇼핑몰 실습 - 장바구니, Restful

📌 장바구니 기능 (로그인 인증이 필요한 기능)

1. DB 테이블 생성

1
2
3
4
5
6
CREATE TABLE cart (
  email VARCHAR(50),
  prodcode INT,
  quantity INT,
  PRIMARY KEY (email, prodcode)
);
  • 같은 사용자가 동일한 상품을 여러 번 담을 경우, 수량이 증가

2. Cart.java (DTO)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.shop.cafe.dto;

public class Cart {
	private String email;
	private int prodcode, quantity;
	public String getEmail() {
		return email;
	}

    public Cart(String email, int prodcode, int quantity) {
        this.email = email;
        this.prodcode = prodcode;
        this.quantity = quantity;
    }

	public Cart() {
		super();
	}
    // Getter & Setter & toString() 생략
}

3. CartnDao.java (MyBatis)

👉 장바구니 추가 기능을 DB에 연결.

1
2
3
4
@Mapper
public interface CartDao {
    void addToCart(Cart cart) throws Exception;
}

4. CartService.java (Service Layer)

👉 장바구니 추가 기능을 비즈니스 로직에 연결.

1
2
3
4
5
6
7
8
9
@Service
public class CartService {
    @Autowired
    CartDao cartDao;

    public void addToCart(Cart cart) throws Exception {
        cartDao.addToCart(cart);
    }
}

5. CartController.js (API 엔드포인트 + 인증 검사 포함)

👉 로그인된 사용자만 장바구니 추가 가능

  • JWT 토큰을 이용한 로그인 인증 (authorization 헤더 활용)
  • 30분 이내 활동한 경우에만 장바구니 담기 허용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@RestController
@CrossOrigin("http://127.0.0.1:5500/")
public class CartController {

    @Autowired
    MemberService memberService;
    
    @Autowired
    CartService cartService;
    
    @PostMapping("addToCart")
    public String addToCart(@RequestHeader String authorization, @RequestBody Cart cart) {
        try {
            Login loginInfo = memberService.checkToken(authorization);
            
            if (loginInfo != null && loginInfo.getLoginTime() != null) {
                long now = System.currentTimeMillis(); // 현재 시간 (밀리초) 
                long lastRequestTime = loginInfo.getLoginTime().getTime(); // DB에서 가져온 로그인 시간	 
                long interval = now - lastRequestTime; 

                if (interval <= 1800000) { // 30분 이내 로그인 유지
                    cart.setEmail(loginInfo.getEmail());
                    cart.setQuantity(1);
                    cartService.addToCart(cart);
                    return "ok";
                }
            }
            return null;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

6. cart.xml (MyBatis 설정)

  • ON DUPLICATE KEY UPDATE:
    같은 상품을 장바구니에 추가하면 수량 증가하도록 처리.
1
2
3
4
5
6
7
<mapper namespace="com.shop.cafe.dao.CartDao">
  <insert id="addToCart" parameterType="com.shop.cafe.dto.Cart">
    INSERT INTO cart (email, prodcode, quantity)
    VALUES (#{email}, #{prodcode}, #{quantity})
    ON DUPLICATE KEY UPDATE quantity = quantity + VALUES(quantity);
  </insert>  
</mapper>

7. 로그인 인증 -> MemberService.java

  • 로그인 시 토큰을 발급하고 DB에 저장.
  • 로그인 시간도 저장하여 세션 유지 시간(30분) 적용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Service
public class MemberService {
    @Autowired
    MemberDao memberDao;

    @Autowired
    LoginDao loginDao;

    public Login checkToken(String authorization) throws Exception {
        return loginDao.checkToken(authorization);
    }

    public Login tokenLogin(Member m) throws Exception {
        m = memberDao.login(m);
        if (m != null) {
            String nickname = m.getNickname();
            if (nickname != null && !nickname.trim().equals("")) {
                String email = m.getEmail();
                String salt = UUID.randomUUID().toString();
                byte[] originalHash = OpenCrypt.getSHA256(email, salt);
                String myToken = OpenCrypt.byteArrayToHex(originalHash);

                Login loginInfo = new Login(email, myToken, nickname, new Date());
                loginDao.insertToken(loginInfo);
                return loginInfo;
            }
        }
        return null;    
    }
}

8. LoginDao.java (로그인 인증)

  • 토큰을 저장, 삭제, 검증하는 역할.
1
2
3
4
5
6
@Mapper
public interface LoginDao {    
    void insertToken(Login login) throws Exception;
    void deleteToken(String token) throws Exception;
    Login checkToken(String authorization) throws Exception;
}

9. login.xml (로그인 인증)

  • 로그인 시 토큰을 DB에 저장
  • 로그인 시간이 자동으로 NOW()로 기록됨.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<mapper namespace="com.shop.cafe.dao.LoginDao">

  <select id="checkToken" parameterType="String" resultType="Login">
    SELECT * FROM login WHERE token=#{token}
  </select>
  
  <insert id="insertToken" parameterType="Login">
    INSERT INTO login(email, token, loginTime) VALUES(#{email}, #{token}, NOW())
  </insert>
  
  <delete id="deleteToken" parameterType="String">
    DELETE FROM login WHERE token=#{token}
  </delete>
  
</mapper>

10. secu.properties에서 세계표준 시를 제거

1
2
3
4
DB_DRIVER=com.mysql.cj.jdbc.Driver
DB_URL=jdbc:mysql://localhost:3306/ureca
DB_USER=ureca
DB_PW=ureca

11. MemberService.java에서 tokenLogin시에 로그인 시간을 직접 입력

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public Login tokenLogin(Member m) throws Exception {
    m=memberDao.login(m);
    if(m!=null) {
        String nickname=m.getNickname();
        if(nickname!=null && !nickname.trim().equals("")) {
            String email=m.getEmail();
            String salt=UUID.randomUUID().toString();
            System.out.println("salt:"+salt);
            byte[] originalHash=OpenCrypt.getSHA256(email, salt);
            String myToken=OpenCrypt.byteArrayToHex(originalHash);
            System.out.println("myToken : "+myToken);
            
            // 로그인 시간을 직접 입력
            Login loginInfo=new Login(email, myToken, nickname, new Date());
            loginDao.insertToken(loginInfo);
            return loginInfo;
        }
    }
    return null;		 
}

cart.js - 프론트엔드 요청

  • localStorage.getItem("token"): 저장된 로그인 토큰을 가져와 Authorization 헤더에 포함.
  • 장바구니 추가 요청을 보냄.
1
2
3
4
5
6
7
8
async function addToCart(prodcode) {
    const cartResponse = await axios.post(
        "http://localhost:8080/addToCart",
        { prodcode },
        { headers: { Authorization: localStorage.getItem("token") } } // 토큰 포함
    );
    console.log(cartResponse.data);
}

장바구니 담기 클릭 시, 테이블 모습


📌 RESTful API 란?

RESTfu에 대한 개념이 많아 따로 포스팅해보았다. RESTful API란?

✅ RESTful API 디자인 가이드

API URI는 직관적이고 일관성 있게 설계해야 함

✅ URI Naming Sample

HTTP MethodURI 예시설명
GET/products전체 상품 조회
GET/products/{id}특정 상품 조회
POST/cart장바구니에 상품 추가
PUT/cart/{id}장바구니 상품 수정
DELETE/cart/{id}장바구니 상품 삭제

✅ RESTful API Controller 예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@RestController
@CrossOrigin("http://127.0.0.1:5500/")
@RequestMapping("/api/members") // 기본 URI를 설정
public class RestMemberController {
    
    @Autowired
    MemberService memberService;    

    @DeleteMapping("/{id}") // 회원 삭제
    public String deleteMember(@PathVariable Long id) {
        System.out.println("Deleting member with ID: " + id);
        try {
            memberService.deleteMemberById(id);
            return "ok";
        } catch (Exception e) {
            e.printStackTrace();
            return "회원 삭제 실패";
        }
    }
}

📌 Swagger 사용하기

1. pom.xml 의존성 추가**

1
2
3
4
5
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.2.0</version>
</dependency>

2. Swagger UI 접속

Swagger UI를 통해 API 문서 자동 생성 및 테스트 가능

1
http://localhost:8080/swagger-ui/index.html


END

This post is licensed under CC BY 4.0 by the author.