πŸ“ GitHub λ°”λ‘œκ°€κΈ°

1 JWT 섀계

1️⃣ JWT dependency

compileOnly group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.2'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.2'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.2'

2️⃣ SecretKey β†’ application.properties

jwt.secret.key=7ZWt7ZW0OTntmZTsnbTtjIXtlZzqta3snYTrhIjrqLjshLjqs4TroZzrgpjslYTqsIDsnpDtm4zrpa3tlZzqsJzrsJzsnpDrpbzrp4zrk6TslrTqsIDsnpA=

3️⃣ JwtUtil.java

@Slf4j
@Component
@RequiredArgsConstructor
public class JwtUtil {

    /* 1. JWT Token 생성 ν•„μš”κ°’ */
    // Header Key κ°’
    public static final String AUTHORIZATION_HEADER = "Authorization";

    // μ‚¬μš©μž κΆŒν•œκ°’ Key
    public static final String AUTHORIZATION_KEY = "auth";

    // JWT Token 인증방식 (Bearer + JWT Token ν•¨κ»˜ 전솑)
    private static final String BEARER_PREFIX = "Bearer ";

    // JWT Token λ§Œλ£Œμ‹œκ°„
    private static final long TOKEN_TIME = 60 * 60 * 1000L; // msκΈ°μ€€ -> 1μ‹œκ°„

    // application.properties -> JWT Secret Key 뢈러옴
    @Value("${jwt.secret.key}")
    private String secretKey;

    // JWT Token 생성 및 검증 Key μ €μž₯μš©λ„
    private Key key;

    // JWT Token 생성(사인)μ•Œκ³ λ¦¬μ¦˜
    private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

    // Spring Bean μ΄ˆκΈ°ν™” -> μ‹€ν–‰λ˜λŠ” λ©”μ„œλ“œ
    @PostConstruct
    public void init() {
        // secretKey -> λ””μ½”λ”© -> byte배열에 λ‹΄μŒ
        byte[] bytes = Base64.getDecoder().decode(secretKey);

        // λ””μ½”λ”© ν•œ byteλ°°μ—΄ -> JWT Token μ„œλͺ… 및 검증에 ν•„μš”ν•œ key 생성
        key = Keys.hmacShaKeyFor(bytes);
    }


    /* 2. Client Request Header -> JWT Token κ°€μ Έμ˜€κΈ° */
    public String getToken(HttpServletRequest httpServletRequest) {

        // Header에 μžˆλŠ” JWT Token -> clientToken에 λ‹΄μŒ
        String clientToken = httpServletRequest.getHeader(AUTHORIZATION_HEADER);

        // clientToken 쑴재 + Bearer둜 μ‹œμž‘ -> JWT Token κ°’λ§Œ μΆ”μΆœ
        if (StringUtils.hasText(clientToken) && clientToken.startsWith(BEARER_PREFIX)) {
            return clientToken.substring(7);
        }

        return null;

    }


    /* 3. JWT Token 생성 */
    public String createToken(String username, UserRoleEnum userRoleEnum) {

        Date date = new Date();

        return BEARER_PREFIX +
                Jwts.builder()
                        .setSubject(username)
                        .claim(AUTHORIZATION_KEY, userRoleEnum)
                        .setExpiration(new Date(date.getTime() + TOKEN_TIME))
                        .setIssuedAt(date)
                        .signWith(key, signatureAlgorithm)
                        .compact();

    }


    /* 4. JWT Token 검증 */
    public boolean validateToken(String token) {

        try {

            Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
            return true;

        } catch (SecurityException | MalformedJwtException exception) {
            log.info("Invalid JWT signature, μœ νš¨ν•˜μ§€ μ•ŠλŠ” JWT μ„œλͺ… μž…λ‹ˆλ‹€");
        } catch (ExpiredJwtException exception) {
            log.info("Expired JWT Token, 만료된 JWT Token μž…λ‹ˆλ‹€");
        } catch (UnsupportedJwtException exception) {
            log.info("Unsupported JWT Token, μ§€μ›λ˜μ§€ μ•ŠλŠ” JWT Token μž…λ‹ˆλ‹€");
        } catch (IllegalArgumentException exception) {
            log.info("JWT claims is empty, 잘λͺ»λœ JWT Token μž…λ‹ˆλ‹€");
        }

        return false;

    }


    /* 5. JWT Token -> νšŒμ›μ •λ³΄ κ°€μ Έμ˜€κΈ° */
    public Claims getUserInfoFromToken(String token) {

        return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();

    }

}
  • import org.springframework.beans.factory.annotation.Value; β†’ @Value importλ¬Έ μˆ˜μ •μ μš©

2 JWT적용 섀계

λ‘œκ·ΈμΈμ„±κ³΅ β†’ HTTP Response Header에 JWT Token 생성 ν›„, μ‹€μ–΄ Clientμ—κ²Œ 보냄

1️⃣ UserController.java

/* μ•žλΆ€λΆ„ μƒλž΅ */

	// 둜그인
    @ResponseBody
    @PostMapping("/login")
    public String login(@RequestBody LoginRequestDto loginRequestDto, HttpServletResponse httpServletResponse) {

        userService.login(loginRequestDto, httpServletResponse);

        return "success";

    }

2️⃣ UserService.java

/* μ•žλΆ€λΆ„ μƒλž΅ */
// 둜그인
    @Transactional(readOnly = true)
    public void login(LoginRequestDto loginRequestDto, HttpServletResponse httpServletResponse) {

        // 1. RequestDto -> ID/PW κ°€μ Έμ˜΄
        String username = loginRequestDto.getUsername();
        String password = loginRequestDto.getPassword();

        // 2. νšŒμ›μœ νš¨μ„±κ²€μ‚¬
        User user = userRepository.findByUsername(username).orElseThrow(
                () -> new IllegalArgumentException("λ“±λ‘λœ μ‚¬μš©μžκ°€ μ—†μŠ΅λ‹ˆλ‹€")
        );

        // 3. λΉ„λ°€λ²ˆν˜Έμœ νš¨μ„±κ²€μ‚¬
        if (! user.getPassword().equals(password)) {
            throw new IllegalArgumentException("λΉ„λ°€λ²ˆν˜Έκ°€ μΌμΉ˜ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€");
        }

        // 4. λ‘œκ·ΈμΈμ„±κ³΅ -> Http Response Header에 JWT Token 보내기
        httpServletResponse.addHeader(JwtUtil.AUTHORIZATION_HEADER, jwtUtil.createToken(user.getUsername(), user.getRole()));

    }

3 JWT인가 섀계

1️⃣ κ΄€μ‹¬μƒν’ˆ 쑰회

πŸ“ ProductController.java

/* μ•žλΆ€λΆ„ μƒλž΅ */

    // κ΄€μ‹¬μƒν’ˆ 쑰회
    @GetMapping("/products")
    public List<ProductResponseDto> getProducts(HttpServletRequest httpServletRequest) {

        return productService.getProducts(httpServletRequest);

    }
    
/* λ’·λΆ€λΆ„ μƒλž΅ */

πŸ“ ProductService.java

/* μ•žλΆ€λΆ„ μƒλž΅ */

	// κ΄€μ‹¬μƒν’ˆ 쑰회
    @Transactional(readOnly = true)
    public List<ProductResponseDto> getProducts(HttpServletRequest httpServletRequest) {

        // 1. HTTP Request Header -> JWT Token κ°€μ Έμ˜€κΈ°
        String token = jwtUtil.getToken(httpServletRequest);
        Claims claims;

        // 2. JWT Token μžˆλŠ”κ²½μš°μ—λ§Œ -> κ΄€μ‹¬μƒν’ˆ 쑰회 κ°€λŠ₯
        if (token != null) {

            // 2-1. JWT Token 검증
            if (jwtUtil.validateToken(token)) {

                // true -> Tokenμ—μ„œ μ‚¬μš©μžμ •λ³΄ κ°€μ Έμ˜€κΈ°
                claims = jwtUtil.getUserInfoFromToken(token);

            } else {
                throw new IllegalArgumentException("Token Error");
            }

            // 2-2. Tokenμ—μ„œ κ°€μ Έμ˜¨ μ‚¬μš©μžμ •λ³΄ -> DB쑰회
            User user = userRepository.findByUsername(claims.getSubject()).orElseThrow(
                    () -> new IllegalArgumentException("μ‚¬μš©μžκ°€ μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€")
            );

            // 2-3. μ‚¬μš©μžκΆŒν•œ ADMIN ; μ „μ²΄μ‘°νšŒ, USER ; 본인 κ΄€μ‹¬μƒν’ˆμ‘°νšŒ
            UserRoleEnum userRoleEnum = user.getRole();
            System.out.println("role = " + userRoleEnum);

            List<ProductResponseDto> productResponseDtoList = new ArrayList<>();
            List<Product> productList;

            if (userRoleEnum == UserRoleEnum.USER) {
                productList = productRepository.findAllByUserId(user.getId());
            } else {
                productList = productRepository.findAll();
            }

            for (Product product : productList) {
                productResponseDtoList.add(ProductResponseDto.of(product));
            }

            return productResponseDtoList;

        } else {

            return null;

        }

    }

/* λ’·λΆ€λΆ„ μƒλž΅ */

2️⃣ κ΄€μ‹¬μƒν’ˆ 등둝

πŸ“ ProductController.java

/* μ•žλΆ€λΆ„ μƒλž΅ */

	// κ΄€μ‹¬μƒν’ˆ 등둝
    @PostMapping("/products")
    public ProductResponseDto createProduct(@RequestBody ProductRequestDto productRequestDto, HttpServletRequest httpServletRequest) {

        return productService.createProduct(productRequestDto, httpServletRequest);

    }

/* λ’·λΆ€λΆ„ μƒλž΅ */

πŸ“ ProductService.java

/* μ•žλΆ€λΆ„ μƒλž΅ */

// κ΄€μ‹¬μƒν’ˆ 등둝
    @Transactional
    public ProductResponseDto createProduct(ProductRequestDto productRequestDto, HttpServletRequest httpServletRequest) {

        // 1. HTTP Request Header -> JWT Token κ°€μ Έμ˜€κΈ°
        String token = jwtUtil.getToken(httpServletRequest);
        Claims claims;

        // 2. JWT Token μžˆλŠ”κ²½μš°μ—λ§Œ -> κ΄€μ‹¬μƒν’ˆ 등둝 κ°€λŠ₯
        if (token != null) {

            // 2-1. JWT Token 검증
            if (jwtUtil.validateToken(token)) {

                // true -> Tokenμ—μ„œ μ‚¬μš©μžμ •λ³΄ κ°€μ Έμ˜€κΈ°
                claims = jwtUtil.getUserInfoFromToken(token);

            } else {
                throw new IllegalArgumentException("Token Error");
            }

            // 2-2. Tokenμ—μ„œ κ°€μ Έμ˜¨ μ‚¬μš©μžμ •λ³΄ -> DB쑰회
            User user = userRepository.findByUsername(claims.getSubject()).orElseThrow(
                    () -> new IllegalArgumentException("μ‚¬μš©μžκ°€ μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€")
            );

            // 2-3. μš”μ²­λ°›μ€ DTO -> DB에 μ €μž₯ν•  객체생성
            Product product = productRepository.saveAndFlush(Product.of(productRequestDto, user.getId()));

            return ProductResponseDto.of(product);

        } else {

            return null;

        }

    }

/* λ’·λΆ€λΆ„ μƒλž΅ */

3️⃣ κ΄€μ‹¬μƒν’ˆ μ΅œμ €κ°€ 등둝

πŸ“ ProductController.java

/* μ•žλΆ€λΆ„ μƒλž΅ */

	@PutMapping("/products/{id}")
    public Long updateProduct(@PathVariable Long id, @RequestBody ProductMypriceRequestDto productMypriceRequestDto, HttpServletRequest httpServletRequest) {

        return productService.updateProduct(id, productMypriceRequestDto, httpServletRequest);

    }

/* λ’·λΆ€λΆ„ μƒλž΅ */

πŸ“ ProductService.java

/* μ•žλΆ€λΆ„ μƒλž΅ */

	// κ΄€μ‹¬μƒν’ˆ μ΅œμ €κ°€ 등둝
    @Transactional
    public Long updateProduct(Long id, ProductMypriceRequestDto productMypriceRequestDto, HttpServletRequest httpServletRequest) {

        // 1. HTTP Request Header -> JWT Token κ°€μ Έμ˜€κΈ°
        String token = jwtUtil.getToken(httpServletRequest);
        Claims claims;

        // 2. JWT Token μžˆλŠ”κ²½μš°μ—λ§Œ -> κ΄€μ‹¬μƒν’ˆ 등둝 κ°€λŠ₯
        if (token != null) {

            // 2-1. JWT Token 검증
            if (jwtUtil.validateToken(token)) {

                // true -> Tokenμ—μ„œ μ‚¬μš©μžμ •λ³΄ κ°€μ Έμ˜€κΈ°
                claims = jwtUtil.getUserInfoFromToken(token);

            } else {
                throw new IllegalArgumentException("Token Error");
            }

            // 2-2. Tokenμ—μ„œ κ°€μ Έμ˜¨ μ‚¬μš©μžμ •λ³΄ -> DB쑰회
            User user = userRepository.findByUsername(claims.getSubject()).orElseThrow(
                    () -> new IllegalArgumentException("μ‚¬μš©μžκ°€ μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€")
            );

            Product product = productRepository.findByIdAndUserId(id, user.getId()).orElseThrow(
                    () -> new IllegalArgumentException("ν•΄λ‹Ήμƒν’ˆμ€ μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€")
            );

            product.update(productMypriceRequestDto);

            return product.getId();

        } else {

            return null;

        }
    }

/* λ’·λΆ€λΆ„ μƒλž΅ */

4 νŽ˜μ΄μ§• 및 μ •λ ¬

ꡬ뢄쒅λ₯˜
νŽ˜μ΄μ§•- page : μ‘°νšŒν•  νŽ˜μ΄μ§€ 번호 (1λΆ€ν„° μ‹œμž‘)
- size : ν•œ νŽ˜μ΄μ§€μ— 보여쀄 λ‚΄μš© 갯수 (10개둜 κ³ μ •)
μ •λ ¬- sortBy (μ •λ ¬ν•­λͺ©) : id, title, lprice
- isAsc (μ˜€λ¦„μ°¨μˆœ) : true β†’ asc μ˜€λ¦„μ°¨μˆœ, false β†’ desc λ‚΄λ¦Όμ°¨μˆœ

1️⃣ API λͺ…μ„Έμ„œ - Product λ³€κ²½

κΈ°λŠ₯MethodURLRequestResponse
λ©”μΈνŽ˜μ΄μ§€GET/api/shop-index.html
Query둜 μƒν’ˆκ²€μƒ‰, μƒν’ˆκ²€μƒ‰κ²°κ³Ό λͺ©λ‘λ°˜ν™˜GET/api/search?query=검색어-[
{
Β Β "title" : String,
Β Β "image" : String,
Β Β "link" : String,
Β Β "lprice" : int
},
β€’β€’β€’
]
κ΄€μ‹¬μƒν’ˆ 등둝POST/api/productsHeader
Authorization : Bearer <JWT>

{
Β Β "title" : String,
Β Β "image" : String,
Β Β "link" : String,
Β Β "lprice" : int
}
{
Β Β "id" : Long,
Β Β "title" : String,
Β Β "image" : String,
Β Β "link" : String,
Β Β "lprice" : int,
Β Β "myprice" : int
}
κ΄€μ‹¬μƒν’ˆ 쑰회GET/api/products?sortBy=String&isAsc=boolean&size=int&page=intHeader
Authorization : Bearer <JWT>
Page<Product>
κ΄€μ‹¬μƒν’ˆ μ΅œμ €κ°€ 등둝PUT/api/products/{id}Header
Authorization : Bearer <JWT>

{
Β Β "myprice" : int
}
id

2️⃣ ProductController.java

/* μ•žλΆ€λΆ„ μƒλž΅ */

	// κ΄€μ‹¬μƒν’ˆ 쑰회
    @GetMapping("/products")
    public Page<Product> getProducts(
            @RequestParam("page") int page, // μ‘°νšŒν•  νŽ˜μ΄μ§€ 번호 (1λΆ€ν„° μ‹œμž‘)
            @RequestParam("size") int size, // ν•œ νŽ˜μ΄μ§€μ— 보여쀄 개수
            @RequestParam("sortBy") String sortBy, // μ •λ ¬ν•­λͺ©
            @RequestParam("isAsc") boolean isAsc, // μ˜€λ¦„μ°¨μˆœ(True), λ‚΄λ¦Όμ°¨μˆœ(False)
            HttpServletRequest httpServletRequest) {

        return productService.getProducts(httpServletRequest, page-1, size, sortBy, isAsc);

    }

/* λ’·λΆ€λΆ„ μƒλž΅ */

3️⃣ ProductService.java

/* μ•žλΆ€λΆ„ μƒλž΅ */

	// κ΄€μ‹¬μƒν’ˆ 쑰회
    @Transactional(readOnly = true)
    public Page<Product> getProducts(HttpServletRequest httpServletRequest,
                                     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);

        // 1. HTTP Request Header -> JWT Token κ°€μ Έμ˜€κΈ°
        String token = jwtUtil.getToken(httpServletRequest);
        Claims claims;

        // 2. JWT Token μžˆλŠ”κ²½μš°μ—λ§Œ -> κ΄€μ‹¬μƒν’ˆ 쑰회 κ°€λŠ₯
        if (token != null) {

            // 2-1. JWT Token 검증
            if (jwtUtil.validateToken(token)) {

                // true -> Tokenμ—μ„œ μ‚¬μš©μžμ •λ³΄ κ°€μ Έμ˜€κΈ°
                claims = jwtUtil.getUserInfoFromToken(token);

            } else {
                throw new IllegalArgumentException("Token Error");
            }

            // 2-2. Tokenμ—μ„œ κ°€μ Έμ˜¨ μ‚¬μš©μžμ •λ³΄ -> DB쑰회
            User user = userRepository.findByUsername(claims.getSubject()).orElseThrow(
                    () -> new IllegalArgumentException("μ‚¬μš©μžκ°€ μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€")
            );

            // 2-3. μ‚¬μš©μžκΆŒν•œ ADMIN ; μ „μ²΄μ‘°νšŒ, USER ; 본인 κ΄€μ‹¬μƒν’ˆμ‘°νšŒ
            UserRoleEnum userRoleEnum = user.getRole();
            System.out.println("role = " + userRoleEnum);

            Page<Product> productPage;

            if (userRoleEnum == UserRoleEnum.USER) {
                productPage = productRepository.findAllByUserId(user.getId(), pageable);
            } else {
                productPage = productRepository.findAll(pageable);
            }

            return productPage;


        } else {

            return null;

        }

    }

/* λ’·λΆ€λΆ„ μƒλž΅ */

4️⃣ ProductRepository.java

/* μ•žλΆ€λΆ„ μƒλž΅ */

public interface ProductRepository extends JpaRepository<Product, Long> {

    Page<Product> findAllByUserId(Long userId, Pageable pageable);

    Optional<Product> findByIdAndUserId(Long id, Long userId);

    Page<Product> findAll(Pageable pageable);

}

/* λ’·λΆ€λΆ„ μƒλž΅ */

5 폴더섀계 및 JPA 연관관계

1️⃣ μ„œλΉ„μŠ€μš”κ΅¬μ‚¬ν•­

μš”κ΅¬μ‚¬ν•­κΈ°λŠ₯
폴더 생성- νšŒμ›λ³„ 폴더 μΆ”κ°€ κ°€λŠ₯
- 폴더 μΆ”κ°€ μ‹œ, 1~N개 ν•œλ²ˆμ— μΆ”κ°€ κ°€λŠ₯
폴더에 κ΄€μ‹¬μƒν’ˆ μ‚½μž…- κ΄€μ‹¬μƒν’ˆμ€ 폴더 Nκ°œμ— λ“€μ–΄κ°ˆ 수 있음
- κ΄€μ‹¬μƒν’ˆμ΄ λ“±λ‘λ˜λŠ” μ‹œμ  β†’ μ–΄λŠ 폴더에도 μ €μž₯ ❌
폴더 별 쑰회- νšŒμ›μ€ 폴더 별 κ΄€μ‹¬μƒν’ˆ 쑰회 κ°€λŠ₯
- 전체 : 폴더와 상관없이 νšŒμ›μ΄ μ €μž₯ν•œ 전체 κ΄€μ‹¬μƒν’ˆ 쑰회
- 폴더λͺ… : 폴더 별 μ €μž₯된 κ΄€μ‹¬μƒν’ˆλ§Œ 쑰회 κ°€λŠ₯

2️⃣ JPA 연관관계 μ„€μ •

  • νšŒμ› 1λͺ…이 μ—¬λŸ¬κ°œμ˜ 폴더λ₯Ό κ°€μ§ˆ 수 있음 β†’ @OneToMany
public class User {

	@OneToMany
    private List<Folder> folderList;

}

// νšŒμ›μ΄ 가진 폴더 쑰회
List<Folder> folderList = user.getFolderList();
  • 폴더 μ—¬λŸ¬κ°œλ₯Ό νšŒμ› 1λͺ…이 κ°€μ§ˆ 수 있음 β†’ @ManyToOne
public class Folder {
	
    @ManyToOne
    private User user;
    
}

// 폴더 μ†Œμœ  νšŒμ› 쑰회
folder.getUser();
  • JPA 연관관계 Column μ„€μ •
@ManyToOne
@JoinColumn(name = "USER_ID", nullable = false)
private User user;

// name : μ™Έλž˜ν‚€λͺ…
// nullable : μ™Έλž˜ν‚€ null ν—ˆμš©μ—¬λΆ€


6 폴더생성 및 쑰회 섀계

1️⃣ API λͺ…μ„Έμ„œ - 폴더생성 및 쑰회

κΈ°λŠ₯MethodURLRequestResponse
폴더 생성POSTapi/folders{
Β Β "folderNames" : [String, β€’β€’β€’]
}
[String, β€’β€’β€’]
폴더 쑰회GETapi/user-folder-index.html
model μΆ”κ°€ β†’ folders

2️⃣ 폴더생성 섀계

User.java

/* μ•žλΆ€λΆ„ μƒλž΅ */

@OneToMany
List<Folder> folders = new ArrayList<>();

/* λ’·λΆ€λΆ„ μƒλž΅ */

Folder.java

@Entity
@Getter
@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;

    }

}

FolderRequestDto.java

@Getter
public class FolderRequestDto {

    List<String> folderNames;

}

FolderController.java

@RestController
@RequiredArgsConstructor
@RequestMapping("/api")
public class FolderController {

    private final FolderService folderService;

    // 폴더 생성
    @PostMapping("/folders")
    public List<Folder> addFolders(@RequestBody FolderRequestDto folderRequestDto, HttpServletRequest httpServletRequest) {

        List<String> folderNames = folderRequestDto.getFolderNames();

        return folderService.addFolders(folderNames, httpServletRequest);

    }
    
}

FolderService.java

@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 httpServletRequest) {

		// Client Request -> Token κ°€μ Έμ˜€κΈ°
        String token = jwtUtil.getToken(httpServletRequest);
        Claims claims;

        // Token 유효 -> 폴더생성 κ°€λŠ₯
        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 httpServletRequest) {

        String token = jwtUtil.getToken(httpServletRequest);
        Claims claims;

        if (token != null) {
            
            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;
        }
    }

}

FolderRepository.java

public interface FolderRepository extends JpaRepository<Folder, Long> {

    List<Folder> findAllByUser(User user);
    
}

ShopController.java

@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 httpServletRequest) {

        model.addAttribute("folders", folderService.getFolders(httpServletRequest));

        return "/index :: #fragment";
    }

}

7 κ΄€μ‹¬μƒν’ˆ 폴더좔가기λŠ₯ 섀계

1️⃣ μš”κ΅¬μ‚¬ν•­

πŸ“Œ Folder와 Product JPA 연관관계 ➑️ M : N

  • κ΄€μ‹¬μƒν’ˆ β†’ 폴더 0 ~ N개 μ„€μ •κ°€λŠ₯

  • κ΄€μ‹¬μƒν’ˆ λ“±λ‘μ‹œμ  β†’ μ–΄λ–€ 폴더에도 μ €μž₯ ❌

  • κ΄€μ‹¬μƒν’ˆ β†’ κΈ° μƒμ„±λœ 폴더λ₯Ό 선택해 μΆ”κ°€κ°€λŠ₯

2️⃣ API λͺ…μ„Έμ„œ - κ΄€μ‹¬μƒν’ˆ 폴더좔가기λŠ₯

κΈ°λŠ₯MethodURLRequestResponse
폴더 전체 쑰회GETapi/folders-[
{
Β Β "id" : int,
Β Β "name" : String,
Β Β "user" : {
Β Β Β Β "id" : int,
Β Β Β Β "username" : String,
Β Β Β Β "password" : String,
Β Β Β Β "email" : String,
Β Β Β Β  "role" : String,
Β Β Β Β  "folders" : []
Β Β  }
},
β€’β€’β€’
]
κ΄€μ‹¬μƒν’ˆμ— 폴더 μΆ”κ°€POSTapi/products/{productId}/folder{productId} : κ΄€μ‹¬μƒν’ˆ Id

[Form ν˜•νƒœ]
folderId : μΆ”κ°€ν•  폴더 Id
폴더가 μΆ”κ°€ 된 κ΄€μ‹¬μƒν’ˆ Id

3️⃣ 폴더 전체 쑰회

FolderController.java

/* μ•žλΆ€λΆ„ μƒλž΅ */

	// 폴더 전체 쑰회
    @GetMapping("/folders")
    public List<Folder> getFolders(HttpServletRequest httpServletRequest) {
    
        return folderService.getFolders(httpServletRequest);
        
    }

/* λ’·λΆ€λΆ„ μƒλž΅ */

4️⃣ κ΄€μ‹¬μƒν’ˆμ— 폴더좔가

ProductController.java

/* μ•žλΆ€λΆ„ μƒλž΅ */

	// κ΄€μ‹¬μƒν’ˆμ— 폴더좔가
    @PostMapping("products/{productId}/folder")
    public Long addFolder(@PathVariable Long productId, @RequestParam Long folderId, HttpServletRequest httpServletRequest) {

        Product product = productService.addFolder(productId, folderId, httpServletRequest);

        return product.getId();

    }

ProductService.java

/* μ•žλΆ€λΆ„ μƒλž΅ */

	// κ΄€μ‹¬μƒν’ˆμ— 폴더좔가
    @Transactional
    public Product addFolder(Long productId, Long folderId, HttpServletRequest httpServletRequest) {

        String token = jwtUtil.getToken(httpServletRequest);
        Claims claims;

        if (token != null) {

            if (jwtUtil.validateToken(token)) {
                claims = jwtUtil.getUserInfoFromToken(token);
            } else {
                throw new IllegalArgumentException("Token Error");
            }

            // μ‚¬μš©μž μœ νš¨μ„±κ²€μ‚¬
            User user = userRepository.findByUsername(claims.getSubject()).orElseThrow(
                    () -> new IllegalArgumentException("μ‚¬μš©μžκ°€ μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€")
            );

            // μƒν’ˆ μœ νš¨μ„±κ²€μ‚¬
            Product product = productRepository.findById(productId)
                    .orElseThrow( () -> new NullPointerException("ν•΄λ‹Ή μƒν’ˆ 아이디가 μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€") );

            // 폴더 μœ νš¨μ„±κ²€μ‚¬
            Folder folder = folderRepository.findById(folderId)
                    .orElseThrow( () -> new NullPointerException("ν•΄λ‹Ή 폴더 아이디가 μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€") );

            // μƒν’ˆ, 폴더 -> 같은 νšŒμ› μ†Œμœ μΈμ§€ 확인
            Long loginUserId = user.getId();

            if (! product.getUserId().equals(loginUserId) || ! folder.getUser().getId().equals(loginUserId)) {
                throw new IllegalArgumentException("νšŒμ›λ‹˜μ˜ κ΄€μ‹¬μƒν’ˆμ΄ μ•„λ‹ˆκ±°λ‚˜, νšŒμ›λ‹˜μ˜ 폴더가 μ•„λ‹™λ‹ˆλ‹€");
            }

            product.addFolder(folder);

        } else {

            return null;

        }

    }

Product.java

/* μ•žλΆ€λΆ„ μƒλž΅ */

	@ManyToMany
    private List<Folder> folderList = new ArrayList<>();
    
    /* 쀑간뢀뢄 μƒλž΅ */
    
    // κ΄€μ‹¬μƒν’ˆμ— 폴더좔가
    public void addFolder(Folder folder) {
        this.folderList.add(folder);
    }
    
/* λ’·λΆ€λΆ„ μƒλž΅ */

8 폴더별 κ΄€μ‹¬μƒν’ˆμ‘°νšŒ 섀계

1️⃣ μš”κ΅¬μ‚¬ν•­

  • νšŒμ›μ€ 폴더 별 κ΄€μ‹¬μƒν’ˆ μ‘°νšŒκ°€λŠ₯

    • '폴더별' : 폴더별 μ €μž₯된 κ΄€μ‹¬μƒν’ˆ μ‘°νšŒκ°€λŠ₯

    • '전체' : 폴더와 상관없이 νšŒμ›μ΄ μ €μž₯ν•œ 전체 κ΄€μ‹¬μƒν’ˆ μ‘°νšŒκ°€λŠ₯

2️⃣ API λͺ…μ„Έμ„œ - 폴더별 κ΄€μ‹¬μƒν’ˆμ‘°νšŒ

κΈ°λŠ₯MethodURLRequestResponse
폴더 별 κ΄€μ‹¬μƒν’ˆ 쑰회GETapi/folders/{folderId}/products{folderId} : 쑰회λ₯Ό μ›ν•˜λŠ” 폴더 IdPage<Product>

FolderController.java

/* μ•žλΆ€λΆ„ μƒλž΅ */

	// 폴더 별 κ΄€μ‹¬μƒν’ˆ 쑰회
    @GetMapping("/folders/{folderId}/products")
    public Page<Product> getProductsInFolder (@PathVariable Long folderId,
                                              @RequestParam int page,
                                              @RequestParam int size,
                                              @RequestParam String sortBy,
                                              @RequestParam boolean isAsc,
                                              HttpServletRequest httpServletRequest) {

        return folderService.getProductsInFolder(folderId, page-1, size, sortBy, isAsc, httpServletRequest);

    }

FolderService.java

/* μ•žλΆ€λΆ„ μƒλž΅ */

	@Transactional(readOnly = true)
    public Page<Product> getProductsInFolder(Long folderId, int page, int size, String sortBy, boolean isAsc, HttpServletRequest httpServletRequest) {

        // νŽ˜μ΄μ§• 처리
        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.getToken(httpServletRequest);
        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 productRepository.findAllByUserIdAndFolderList_Id(user.getId(), folderId, pageable);

        } else {
            return null;
        }

ProductRepository.java

public interface ProductRepository extends JpaRepository<Product, Long> {

	Page<Product> findAllByUserIdAndFolderList_Id(Long userId, Long folderId, Pageable);

}

9 폴더λͺ… 쀑볡 Issue

1️⃣ 쀑볡검사 λ©”μ„œλ“œμΆ”κ°€

FolderService.java

private boolean isExistFolderName(String folderName, List<Folder> existFolderList) {

        // κΈ°μ‘΄ 폴더 λ¦¬μŠ€νŠΈμ—μ„œ folder name 이 μžˆλŠ”μ§€ 검사
        for (Folder existFolder : existFolderList) {
        
            if (existFolder.getName().equals(folderName)) {
                return true;
            }
            
        }

        return false;
    }

2️⃣ 폴더생성 μ‹œ, 쀑볡검사 λ©”μ„œλ“œμ‚¬μš©

FolderService.java

/* 폴더생성 addFolders λ©”μ„œλ“œ */

	// 폴더λͺ… 쀑볡검사
    List<Folder> existFolderList = folderRepository.findAllByUserAndNameIn(user, folderNames);

	List<Folder> folderList = new ArrayList<>();

	for (String folderName : folderNames) {

		if (! isExistFolderName(folderName, existFolderList)) {

			Folder folder = new Folder(folderName, user);
            folderList.add(folder);

		}

	}

	return folderRepository.saveAll(folderList);

3️⃣ FolderRepository.java

/* μ•žλΆ€λΆ„ μƒλž΅ */

	List<Folder> findAllByUserAndNameIn(User user, List<String> names);

10 폴더 λ‚΄ κ΄€μ‹¬μƒν’ˆ 쀑볡생성 Issue

1️⃣ ProductService.java

/* κ΄€μ‹¬μƒν’ˆμ— 폴더좔가 addFolder λ©”μ„œλ“œ */

	// 쀑볡확인
    Optional<Product> overlapFolder = productRepository.findbyIdAndFolderList_Id(product.getId(), folder.getId());
    
    if (overlapFolder.isPresent()) {
    	throw new IllegalArgumentException("μ€‘λ³΅λœ ν΄λ”μž…λ‹ˆλ‹€");
    }
    
    product.addFolder(folder);

2️⃣ ProductRepository.java

/* μ•žλΆ€λΆ„ μƒλž΅ */

	Optional<Product> findByIdAndFolderList_Id(Long productId, Long folderId);

Outro

  • 둜그인 ν•œ Userκ°€ λ©”μΈνŽ˜μ΄μ§€ μš”μ²­ μ‹œ, μœ μ €μ˜ 이름 λ°˜ν™˜

  • ShopController.java

@GetMapping("/user-info")
    @ResponseBody
    public String getUserName(HttpServletRequest httpServletRequest) {
        String token = jwtUtil.getToken(httpServletRequest);
        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 user.getUsername();
        }else {
            return "fail";
        }
    }
profile
🐰 I'm Sunyeon-Jeong, mallang

0개의 λŒ“κΈ€

κ΄€λ ¨ μ±„μš© 정보