- 지난 포스팅 :
JWT
방식을 사용하여, 등록된User
의 인식 시,Secret Key
로 복호화가 가능한Token
을 클라이언트에게 제공하여, 다음 작업의 요청 시 요청 데이터와, 토큰을 같이Header
에 전송하여 서버에서 검증단계를 거쳐 로그인의 상태에서 할 수 있는 작업을 할 수 있게 했다.
- 오늘은 이렇게 검증된 회원이 담은 데이터를 html에서 보여줄 때, 1, 2, 3이런식으로 페이징 처리를 할 수 있게한다.
- 최저가, 이름순 , 오름차순, 내림차순등으로 필터링 정렬을 가능하게 한다.
- 사용자 별로 폴더를 생성할 수 있으며, 폴더에 따른 상품을 담을 수 있다.
-> 폴더의 생성시 중복을 제거 해야하며, 한 상품은 같은 폴더에 중복으로 담길 수 없다.
-> 어떤 유저의 폴더인지 구별하여 폴더 마다의 상품을 보여줄 수 있어야한다.
page
: 조회할 페이지 번호( 1번부터 )size
: 한 페이지에 보여줄 컨텐츠 개수
sortBy ( 정렬 항목 )
1)id
: Product 테이블의id
2)title
: 상품명
3)lprice
: 최저가
isAsc( 오름차순 ? )
1)true
: 오름차순(asc)
2)false
: 내림차순(desc)
number
: 조회된 페이지 번호( 0부터 시작 )
content
: 조회된 컨텐츠 정보size
: 한 페이지에서 보여줄 컨텐츠 개수NumberOfElements
: 실제 조회된 컨텐츠의 개수totalElements
: 전체 상품의 개수( 회원이 등록한 모든 상품의 개수 )totalPage
: 전체 페이지의 개수first
: 첫번째 페이지인가?(boolean)
last
: 마지막 페이지인가?(boolean)
totalPages = totalElement / size 결과를 소수점 올림
1 / 10 = 0.1 => 총 1 페이지
9 / 10 = 0.9 => 총 1페이지
10 / 10 = 1 => 총 1페이지
11 / 10 => 1.1 => 총 2페이지
기능 | Method | URL | Request |
---|---|---|---|
메인 페이지 | GET | /api/shop | -- |
키워드로 상품 검색하고 그 결과를 목록으로 보여주기 | GET | /api/search?query=검색어 | -- |
관심 상품 등록하기 | POST | /api/products | Header Authorization : Bearer <JWT> { "title" : String, ”image” : String, "link" : String, "lprice" : int } |
관심 상품 조회하기 | GET | /api/products?sortBy=String&isAsc=boolean&size=int&page=int | Header Authorization : Bearer <JWT> |
관심 상품 최저가 등록하기 | PUT | /api/products/{id} | Header Authorization : Bearer <JWT> {"myprice" : int } |
// 관심 상품 조회하기
@GetMapping("/products")
public Page<Product> getProducts(
@RequestParam("page") int page,
@RequestParam("size") int size,
@RequestParam("sortBy") String sortBy,
@RequestParam("isAsc") boolean isAsc,
HttpServletRequest request
) {
// 응답 보내기
return productService.getProducts(request, page-1, size, sortBy, isAsc);
}
@Transactional(readOnly = true)
public Page<Product> getProducts(HttpServletRequest request,
int page, int size, String sortBy, boolean isAsc) {
// 페이징 처리
Sort.Direction direction = isAsc ? Sort.Direction.ASC : Sort.Direction.DESC;
Sort sort = Sort.by(direction, sortBy);
Pageable pageable = PageRequest.of(page, size, sort);
// Request에서 Token 가져오기
String token = jwtUtil.resolveToken(request);
Claims claims;
// 토큰이 있는 경우에만 관심상품 조회 가능
if (token != null) {
// Token 검증
if (jwtUtil.validateToken(token)) {
// 토큰에서 사용자 정보 가져오기
claims = jwtUtil.getUserInfoFromToken(token);
} else {
throw new IllegalArgumentException("Token Error");
}
// 토큰에서 가져온 사용자 정보를 사용하여 DB 조회
User user = userRepository.findByUsername(claims.getSubject()).orElseThrow(
() -> new IllegalArgumentException("사용자가 존재하지 않습니다.")
);
// 사용자 권한 가져와서 ADMIN 이면 전체 조회, USER 면 본인이 추가한 부분 조회
UserRoleEnum userRoleEnum = user.getRole();
System.out.println("role = " + userRoleEnum);
Page<Product> products;
if (userRoleEnum == UserRoleEnum.USER) {
// 사용자 권한이 USER일 경우
products = productRepository.findAllByUserId(user.getId(), pageable);
} else {
products = productRepository.findAll(pageable);
}
return products;
} else {
return null;
}
}
Page<Product> findAllByUserId(Long userId, Pageable pageable);
Page<Product> findAll(Pageable pageable);
GET/api/products?sortBy=String&isAsc=boolean&size=int&page=int
-> 이런 요청을Pageable
객체로 담아JPA Repository
에게 보낸다.
->JPA Repository
는 원하는 페이지 만큼의 product의 정보를 보내주는 것.
-> 참고 : https://tecoble.techcourse.co.kr/post/2021-08-15-pageable/
- 회원의 폴더는 그 회원에게만 보여야한다.
회원 - 폴더
의 관계는회원 - 상품
의 관계와 같다.
@Entity
public class Folder {
@GeneratedValue(strategy = GenerationType.AUTO)
@Id
private Long id;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private Long userId;
}
Folder
가 가지고 있는userId
가 유저의id
라는 것은 개발을 진행한 사람만 알 수 있음!!- 객체와 DB는 서로의 관계를 파악할 수 없음
- 회원 -> 폴더 조회시 문제점
// 1. 로그인한 회원 (user1) 의 id 를 조회
Long userId = user1.getId();
// 2. userId 로 저장된 모든 folder 조회
List<Folder> folders = folderRepository.findAllByUserId(userId);
public interface FolderRepository extends JpaRepository<Folder, Long> {
List<Folder> findAllByUserId(Long userId);
}
- 폴더 -> 회원 조회시 문제점
// 1. folder1 의 userId 를 조회
Long userId = folder1.getUserId();
// 2. userId 로 저장된 회원 조회
User user = userRepository.findById(userId);
@OneToMany
public class User { @OneToMany private List<Folder> folders; }
- 이렇게 저장 시 회원의 폴더를 조회한다면
List<Folder> folders = user.getFolders();
- 기존의 방식은
User.getId()
를 해와서Repository
에findById(UserId)
를 해줘야 했음.
@OneToMany
public class Folder{ @ManyToOne private User user; }
- 폴더를 소유한 유저를 조회
folder.getUser();
- 폴더를 소유한 UserId가 아닌 User객체를 저장
- 외래키를 통한 관계 설정
@ManyToOne
@JoinColumn(name = "USER_ID", nullable = false)
private User user;
@JoinColumn
1)name
: 외래키 명
2)nullable
:false(default) -> 예) 폴더는 회원에 의해서만 만들어짐. user 값이 필수, true -> 예) 공용폴더의 경우, 폴더의 user 객체를 null 로 설정하기로 함
기능 | Method | URL | Request | Response |
---|---|---|---|---|
회원의 폴더 생성 | POST | /api/folders | { folderNames: [String, …] } | -- |
회원의 폴더 조회 | GET | /api/user-folder | -- | index.htmlmodel 추가 → folders |
@OneToMany
List<Folder> folders = new ArrayList<>();
package com.sparta.myselectshop.entity;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.persistence.*;
@Getter
@Entity
@NoArgsConstructor
public class Folder {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(nullable = false)
private String name;
@ManyToOne
@JoinColumn(name = "USER_ID", nullable = false)
private User user;
public Folder(String name, User user) {
this.name = name;
this.user = user;
}
}
package com.sparta.myselectshop.dto;
import lombok.Getter;
import java.util.List;
@Getter
public class FolderRequestDto {
List<String> folderNames;
}
package com.sparta.myselectshop.controller;
import com.sparta.myselectshop.dto.FolderRequestDto;
import com.sparta.myselectshop.entity.Folder;
import com.sparta.myselectshop.service.FolderService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
@RestController
@RequestMapping("/api")
@RequiredArgsConstructor
public class FolderController {
private final FolderService folderService;
@PostMapping("/folders")
public List<Folder> addFolders(
@RequestBody FolderRequestDto folderRequestDto,
HttpServletRequest request
) {
List<String> folderNames = folderRequestDto.getFolderNames();
return folderService.addFolders(folderNames, request);
}
}
package com.sparta.myselectshop.service;
import com.sparta.myselectshop.entity.Folder;
import com.sparta.myselectshop.entity.User;
import com.sparta.myselectshop.jwt.JwtUtil;
import com.sparta.myselectshop.repository.FolderRepository;
import com.sparta.myselectshop.repository.UserRepository;
import io.jsonwebtoken.Claims;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
@Service
@RequiredArgsConstructor
public class FolderService {
private final FolderRepository folderRepository;
private final UserRepository userRepository;
private final JwtUtil jwtUtil;
// 로그인한 회원에 폴더들 등록
@Transactional
public List<Folder> addFolders(List<String> folderNames, HttpServletRequest request) {
// Request에서 Token 가져오기
String token = jwtUtil.resolveToken(request);
Claims claims;
// 토큰이 있는 경우에만 관심상품 조회 가능
if (token != null) {
// Token 검증
if (jwtUtil.validateToken(token)) {
// 토큰에서 사용자 정보 가져오기
claims = jwtUtil.getUserInfoFromToken(token);
} else {
throw new IllegalArgumentException("Token Error");
}
// 토큰에서 가져온 사용자 정보를 사용하여 DB 조회
User user = userRepository.findByUsername(claims.getSubject()).orElseThrow(
() -> new IllegalArgumentException("사용자가 존재하지 않습니다.")
);
List<Folder> folderList = new ArrayList<>();
for (String folderName : folderNames) {
Folder folder = new Folder(folderName, user);
folderList.add(folder);
}
return folderRepository.saveAll(folderList);
} else {
return null;
}
}
// 로그인한 회원이 등록된 모든 폴더 조회
public List<Folder> getFolders(HttpServletRequest request) {
// 사용자의 정보를 가져온다
// Request에서 Token 가져오기
String token = jwtUtil.resolveToken(request);
Claims claims;
// 토큰이 있는 경우에만 관심상품 조회 가능
if (token != null) {
// Token 검증
if (jwtUtil.validateToken(token)) {
// 토큰에서 사용자 정보 가져오기
claims = jwtUtil.getUserInfoFromToken(token);
} else {
throw new IllegalArgumentException("Token Error");
}
// 토큰에서 가져온 사용자 정보를 사용하여 DB 조회
User user = userRepository.findByUsername(claims.getSubject()).orElseThrow(
() -> new IllegalArgumentException("사용자가 존재하지 않습니다.")
);
return folderRepository.findAllByUser(user);
} else {
return null;
}
}
}
package com.sparta.myselectshop.repository;
import com.sparta.myselectshop.entity.Folder;
import com.sparta.myselectshop.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface FolderRepository extends JpaRepository<Folder, Long> {
List<Folder> findAllByUser(User user);
}
package com.sparta.myselectshop.controller;
import com.sparta.myselectshop.service.FolderService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
@Controller
@RequiredArgsConstructor
@RequestMapping("/api")
public class ShopController {
private final FolderService folderService;
@GetMapping("/shop")
public ModelAndView shop() {
return new ModelAndView("index");
}
// 로그인 한 유저가 메인페이지를 요청할 때 가지고있는 폴더를 반환
@GetMapping("/user-folder")
public String getUserInfo(Model model, HttpServletRequest request) {
model.addAttribute("folders", folderService.getFolders(request));
return "/index :: #fragment";
}
}