[Spring Boot] 8. Spring Boot Project (Store v2)_11.Authentication logic 추가한 최종 Code
Mar 31, 2025
User
- User
package com.metacoding.storev2.user;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.sql.Timestamp;
@NoArgsConstructor
@Getter
@Table(name = "user_tb")
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(unique = true, nullable = false, length = 12)
private String username;
@Column(nullable = false, length = 12)
private String password;
@Column(nullable = false)
private String fullname;
private Timestamp createdAt;
}
- UserController
package com.metacoding.storev2.user;
import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@RequiredArgsConstructor
@Controller
public class UserController {
private final UserService userService;
private final HttpSession session;
@GetMapping("/")
public String home() {
return "home";
}
@GetMapping("/login-form")
public String loginForm() {
return "user/login-form";
}
@GetMapping("/logout")
public String logout() {
session.invalidate();
return "redirect:/";
}
@PostMapping("/login")
public String login(UserRequest.LoginDTO loginDTO) {
User sessionUser = userService.로그인(loginDTO);
session.setAttribute("sessionUser", sessionUser);
return "redirect:/";
}
@GetMapping("/join-form")
public String joinForm() {
return "user/join-form";
}
@PostMapping("/join")
public String join(UserRequest.JoinDTO joinDTO) {
userService.회원가입(joinDTO);
return "redirect:/login-form";
}
}
- UserService
package com.metacoding.storev2.user;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@RequiredArgsConstructor
@Service
public class UserService {
private final UserRepository userRepository;
@Transactional
public void 회원가입(UserRequest.JoinDTO joinDTO) {
// 동일 회원 있는지 검사
User user = userRepository.findByUsername(joinDTO.getUsername());
if (user != null) {
throw new RuntimeException("동일한 username이 존재합니다.");
}
// 회원가입
userRepository.save(joinDTO.getUsername(), joinDTO.getPassword(), joinDTO.getFullname());
}
public User 로그인(UserRequest.LoginDTO loginDTO) {
// User 존재 확인
User user = userRepository.findByUsername(loginDTO.getUsername());
if (user == null) {
throw new RuntimeException("해당 username이 없습니다.");
}
if (!(user.getPassword().equals(loginDTO.getPassword()))) {
throw new RuntimeException("해당 password가 틀렸습니다.");
}
// 로그인
return user;
}
}
- UserRepository
package com.metacoding.storev2.user;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
@RequiredArgsConstructor
@Repository
public class UserRepository {
private final EntityManager em;
public User findByUsername(String username) {
Query query = em.createNativeQuery("select * from user_tb where username = ?", User.class);
query.setParameter(1, username);
try {
return (User) query.getSingleResult();
} catch (RuntimeException e) {
return null;
}
}
public void save(String username, String password, String fullname) {
Query query = em.createNativeQuery("insert into user_tb(username,password,fullname,created_at) values(?,?,?,now())");
query.setParameter(1, username);
query.setParameter(2, password);
query.setParameter(3, fullname);
query.executeUpdate();
}
}
- UserRequest
package com.metacoding.storev2.user;
import lombok.Data;
public class UserRequest {
@Data
public static class JoinDTO {
private String username;
private String password;
public String fullname;
}
@Data
public static class LoginDTO {
private String username;
private String password;
}
}
Store
- Store
package com.metacoding.storev2.store;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
@NoArgsConstructor
@Getter
@Table(name = "store_tb")
@Entity
public class Store {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private Integer stock;
private Integer price;
public void 재고감소(int qty){
this.stock = this.stock - qty;
}
}
- StoreController
package com.metacoding.storev2.store;
import com.metacoding.storev2.user.User;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
@RequiredArgsConstructor
@Controller
public class StoreController {
private final StoreService storeService;
private final HttpSession session;
@GetMapping("/store/save-form")
public String saveForm() {
return "store/save-form";
}
@PostMapping("/store/save")
public String save(StoreRequest.SaveDTO saveDTO) {
// 인증체크 (반복되는 공통부가로직)
User sessionUser = (User) session.getAttribute("sessionUser");
if (sessionUser == null) throw new RuntimeException("로그인 후 사용해주세요");
System.out.println("name: " + saveDTO.getName());
System.out.println("stock: " + saveDTO.getStock());
System.out.println("price: " + saveDTO.getPrice());
storeService.상품등록(saveDTO);
return "redirect:/store/list";
}
@GetMapping("/store/list")
public String list(HttpServletRequest request) {
List<Store> storeList = storeService.상품목록();
request.setAttribute("models", storeList);
return "store/list";
}
@GetMapping("/store/{id}")
public String detail(@PathVariable("id") int id, HttpServletRequest request) {
Store store = storeService.상세보기(id);
request.setAttribute("model", store);
return "store/detail";
}
@PostMapping("/store/{id}/delete")
public String delete(@PathVariable("id") int id) {
// 인증체크 (반복되는 공통부가로직)
User sessionUser = (User) session.getAttribute("sessionUser");
if (sessionUser == null) throw new RuntimeException("로그인 후 사용해주세요");
storeService.상품삭제(id);
return "redirect:/store/list";
}
@GetMapping("/store/{id}/update-form")
public String updateForm(@PathVariable("id") int id, HttpServletRequest request) {
// 인증체크 (반복되는 공통부가로직)
User sessionUser = (User) session.getAttribute("sessionUser");
if (sessionUser == null) throw new RuntimeException("로그인 후 사용해주세요");
Store store = storeService.상세보기(id);
request.setAttribute("model", store);
return "store/update-form";
}
@PostMapping("/store/{id}/update")
public String update(@PathVariable("id") int id, @RequestParam("name")String name,@RequestParam("stock") int stock,@RequestParam("price") int price) {
// 인증체크 (반복되는 공통부가로직)
User sessionUser = (User) session.getAttribute("sessionUser");
if (sessionUser == null) throw new RuntimeException("로그인 후 사용해주세요");
storeService.상품수정(id,name, stock, price);
return "redirect:/store/" + id;
}
}
- StoreService
package com.metacoding.storev2.store;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@RequiredArgsConstructor
@Service
public class StoreService {
private final StoreRepository storeRepository;
@Transactional
public void 상품등록(StoreRequest.SaveDTO saveDTO) {
storeRepository.save(saveDTO.getName(), saveDTO.getStock(), saveDTO.getPrice());
}
public List<Store> 상품목록() {
List<Store> storeList = storeRepository.findAll();
return storeList;
}
public Store 상세보기(int id) {
Store store = storeRepository.findById(id);
return store;
}
@Transactional
public void 상품삭제(int id) {
// 상품존재확인
Store store = storeRepository.findById(id);
if (store == null) {
throw new RuntimeException("삭제할 상품이 없습니다.");
}
storeRepository.deleteById(id);
}
@Transactional
public void 상품수정(int id, String name, int stock, int price) {
// 상품존재확인
Store store = storeRepository.findById(id);
if (store == null) {
throw new RuntimeException("수정할 상품이 없습니다.");
}
storeRepository.update(id,name, stock, price);
}
}
- StorerRepository
package com.metacoding.storev2.store;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.List;
@RequiredArgsConstructor
@Repository
public class StoreRepository {
private final EntityManager em;
public void save(String name, int stock, int price) {
Query query = em.createNativeQuery("insert into store_tb (name, stock, price) values (?, ?, ?)");
query.setParameter(1, name);
query.setParameter(2, stock);
query.setParameter(3, price);
query.executeUpdate();
}
public List<Store> findAll() {
Query query = em.createNativeQuery("select * from store_tb order by id desc", Store.class);
return query.getResultList();
}
public Store findById(int id) {
Query query = em.createNativeQuery("select * from store_tb where id = ?", Store.class);
query.setParameter(1, id);
return (Store) query.getSingleResult();
}
public void deleteById(int id) {
Query query = em.createNativeQuery("delete from store_tb where id = ?");
query.setParameter(1, id);
query.executeUpdate();
}
public void update(@PathVariable("id") int id, String name, int stock, int price) {
Query query = em.createNativeQuery("update store_tb set name = ?, stock = ?, price = ? where id = ?");
query.setParameter(1, name);
query.setParameter(2, stock);
query.setParameter(3, price);
query.setParameter(4, id);
query.executeUpdate();
}
}
- StoreRequest
package com.metacoding.storev2.store;
import lombok.Data;
public class StoreRequest {
@Data
public static class SaveDTO {
private String name;
private Integer stock;
private Integer price;
}
@Data
public static class UpdateDTO {
private String name;
private Integer stock;
private Integer price;
}
}
Order
- Order
package com.metacoding.storev2.order;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
@NoArgsConstructor
@Getter
@Table(name = "order_tb")
@Entity
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private Integer storeId;
private Integer userId;
private Integer qty;
private Integer totalPrice;
}
- OrderController
package com.metacoding.storev2.order;
import com.metacoding.storev2.user.User;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
@RequiredArgsConstructor
@Controller
public class OrderController {
private final OrderService orderService;
private final HttpSession session;
@GetMapping("/order/list")
public String orderList(HttpServletRequest request) {
// 인증체크 (반복되는 공통부가로직)
User sessionUser = (User) session.getAttribute("sessionUser");
if (sessionUser == null) throw new RuntimeException("로그인 후 사용해주세요");
List<OrderResponse.OrderListPage> OrderListPage = orderService.구매목록(sessionUser.getId());
request.setAttribute("models", OrderListPage);
return "order/list";
}
@PostMapping("order/save")
public String save(@RequestParam("storeId") int storeId, @RequestParam("qty") int qty) {
// 인증체크 (반복되는 공통부가로직)
User sessionUser = (User) session.getAttribute("sessionUser");
if (sessionUser == null) throw new RuntimeException("로그인 후 사용해주세요");
orderService.구매(storeId,qty,sessionUser.getId());
return "redirect:/order/list";
}
}
- OrderService
package com.metacoding.storev2.order;
import com.metacoding.storev2.store.Store;
import com.metacoding.storev2.store.StoreRepository;
import com.metacoding.storev2.store.StoreRequest;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@RequiredArgsConstructor
@Service
public class OrderService {
private final OrderRepository orderRepository;
private final StoreRepository storeRepository;
public List<OrderResponse.OrderListPage> 구매목록(Integer userId) {
return orderRepository.findAllJoinStore(userId);
}
@Transactional
public void 구매(int storeId, int qty, int userId) {
// 재고조회
Store store = storeRepository.findById(storeId);
// 업데이트
store.재고감소(qty);
storeRepository.update(store.getId(),store.getName(),store.getStock(),store.getPrice());
// 구매기록
orderRepository.save(storeId,qty,qty*store.getPrice(),userId);
}
}
- OrderRepository
package com.metacoding.storev2.order;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import java.util.ArrayList;
import java.util.List;
@RequiredArgsConstructor
@Repository
public class OrderRepository {
private final EntityManager em;
public List<OrderResponse.OrderListPage> findAllJoinStore(Integer userId) {
Query query = em.createNativeQuery("select ot.id, st.name, ot.qty, ot.total_price, ot.user_id from order_tb ot inner join store_tb st on ot.store_id=st.id where user_id= ?order by ot.id desc");
query.setParameter(1,userId);
List<Object[]> objList = (List<Object[]>) query.getResultList();
List<OrderResponse.OrderListPage> orderListPages = new ArrayList<>();
for (Object[] obj : objList) {
OrderResponse.OrderListPage olp = new OrderResponse.OrderListPage(
(Integer) obj[0],
(String) obj[1],
(Integer) obj[2],
(Integer) obj[3],
(Integer) obj[4]
);
orderListPages.add(olp);
}
return orderListPages;
}
public void save(int storeId, int qty, int totalPrice,int userId) {
Query query = em.createNativeQuery("insert into order_tb(store_id,qty,total_price,user_id) values(?,?,?,?)");
query.setParameter(1, storeId);
query.setParameter(2, qty);
query.setParameter(3, totalPrice);
query.setParameter(4, userId);
query.executeUpdate();
}
}
- OrderRequest
package com.metacoding.storev2.order;
import lombok.Data;
public class OrderRequest {
@Data
public static class SaveDTO{
Integer storeId;
Integer qty;
}
}
- OrderResponse
package com.metacoding.storev2.order;
import lombok.Data;
public class OrderResponse {
@Data
public static class OrderListPage {
private Integer id;
private String name;
private Integer qty;
private Integer totalPrice;
private Integer userId;
public OrderListPage(Integer id, String name, Integer qty, Integer totalPrice, Integer userId) {
this.id = id;
this.name = name;
this.qty = qty;
this.totalPrice = totalPrice;
this.userId = userId;
}
}
}
Muchache
Layout
- header
<!DOCTYPE html>
<html lang="en">
<head>
<title>Store</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</head>
<body>
<nav class="navbar navbar-expand-sm bg-dark navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="/">Home</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#collapsibleNavbar">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="collapsibleNavbar">
<ul class="navbar-nav">
{{^sessionUser}}
<li class="nav-item">
<a class="nav-link" href="/join-form">회원가입</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/login-form">로그인</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/store/list">상품목록</a>
</li>
{{/sessionUser}}
{{#sessionUser}}
<li class="nav-item">
<li class="nav-item">
<a class="nav-link" href="/store/list">상품목록</a>
</li>
<a class="nav-link" href="/store/save-form">상품등록</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/order/list">구매목록</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/logout">로그아웃</a>
</li>
{{/sessionUser}}
</ul>
</div>
</div>
</nav>
<!--네브바종료-->
Order
- list
{{> layout/header}}
<div class="container mt-2">
<div class="mt-4 p-5 bg-light text-dark rounded-4">
<h1>구매목록 페이지</h1>
<hr>
<table class="mt-4 table table-hover">
<thead>
<tr>
<th>주문번호</th>
<th>상품명</th>
<th>구매개수</th>
<th>총 가격</th>
</tr>
</thead>
<tbody>
{{#models}}
<tr>
<td>{{id}}</td>
<td>{{name}}</td>
<td>{{qty}}개</td>
<td>{{totalPrice}}원</td>
</tr>
{{/models}}
</tbody>
</table>
</div>
</div>
</body>
</html>
Store
- list
{{> layout/header}}
<div class="container mt-2">
<table class="table table-hover">
<thead>
<tr>
<th>번호</th>
<th>상품명</th>
<th>상세보기</th>
</tr>
</thead>
<tbody>
{{#models}}
<tr>
<td>{{id}}</td>
<td>{{name}}</td>
<td><a href="/store/{{id}}">상세보기</a></td>
</tr>
{{/models}}
</tbody>
</table>
</div>
</body>
</html>
- detail
{{> layout/header}}
<div class="container mt-2">
<div class="mt-4 p-5 bg-light text-dark rounded-4">
<p>번호 : {{model.id}}</p>
<p>상품명 : {{model.name}}</p>
<p>상품가격 : {{model.price}}</p>
<p>상품재고 : {{model.stock}}</p>
</div>
<div class="mt-3 mb-3">
<a href="/store/{{model.id}}/update-form" class="btn btn-outline-primary">수정</a>
<form action="/store/{{model.id}}/delete" method="POST" class="d-inline">
<button type="submit" class="btn btn-outline-primary">삭제</button>
</form>
</div>
<div class="mt-4 p-5 bg-light text-dark rounded-4">
<form action="/order/save" method="post">
<input type="hidden" value="{{model.id}}" name="storeId">
<input type="text" placeholder="개수를 입력하세요" name="qty">
<button type="submit" class="btn btn-outline-primary">구매</button>
</form>
</div>
</div>
</body>
</html>
- save-form
{{> layout/header}}
<div class="container mt-2">
<div class="mt-4 p-5 bg-light text-dark rounded-4">
<h1>상품등록 페이지</h1>
<form action="/store/save" method="post">
<div class="mb-3 mt-3">
<input type="text" class="form-control" placeholder="상품명을 입력하세요" name="name">
</div>
<div class="mb-3">
<input type="text" class="form-control" placeholder="재고를 입력하세요" name="stock">
</div>
<div class="mb-3 mt-3">
<input type="text" class="form-control" placeholder="가격을 입력하세요" name="price">
</div>
<button type="submit" class="btn btn-primary">상품등록</button>
</form>
</div>
</div>
</body>
</html>
- update-form
{{> layout/header}}
<div class="container mt-2">
<div class="mt-4 p-5 bg-light text-dark rounded-4">
<h1>상품수정 페이지</h1>
<form action="/store/{{model.id}}/update" method="post">
<div class="mb-3 mt-3">
<input type="text" class="form-control" name="name" value="딸기">
</div>
<div class="mb-3">
<input type="text" class="form-control" name="stock" value=45>
</div>
<div class="mb-3 mt-3">
<input type="text" class="form-control" name="price" value=2000>
</div>
<button type="submit" class="btn btn-primary">상품수정</button>
</form>
</div>
</div>
</body>
</html>
User
- join-form
{{> layout/header}}
<div class="container mt-2">
<div class="mt-4 p-5 bg-light text-dark rounded-4">
<h1>회원가입 페이지</h1>
<form action="/join" method="post">
<div class="mb-3 mt-3">
<input type="text" class="form-control" placeholder="Enter username" name="username">
</div>
<div class="mb-3">
<input type="password" class="form-control" placeholder="Enter password" name="password">
</div>
<div class="mb-3">
<input type="text" class="form-control" placeholder="Enter fullname" name="fullname">
</div>
<button type="submit" class="btn btn-primary">회원가입</button>
</form>
</div>
</div>
</body>
</html>
- login-form
{{> layout/header}}
<div class="container mt-2">
<div class="mt-4 p-5 bg-light text-dark rounded-4">
<h1>로그인 페이지</h1>
<form action="/login" method="post">
<div class="mb-3 mt-3">
<input type="text" class="form-control" placeholder="Enter username" name="username">
</div>
<div class="mb-3">
<input type="password" class="form-control" placeholder="Enter password" name="password">
</div>
<button type="submit" class="btn btn-primary">로그인</button>
</form>
</div>
</div>
</body>
</html>
home
{{> layout/header}}
<div class="container mt-2">
<div class="mt-4 p-5 bg-light text-dark rounded-4">
<h1>가게에 오신것을 환영합니다.</h1>
<h3>
{{#sessionUser}}
{{sessionUser.fullname}}님
{{/sessionUser}}
</h3>
</div>
</div>
</body>
</html>
Share article