코딩항해기

[Team/붕어빵원정대(최프)] 중프(JSP기반)에서 Spring 이관 작업 2차 본문

Project

[Team/붕어빵원정대(최프)] 중프(JSP기반)에서 Spring 이관 작업 2차

miniBcake 2024. 10. 12. 23:36

 

기존 중간 프로젝트의 product관련 요청처리(action)기능을 Spring으로 이관하는 작업과 로직 수정을 진행했다.

개인 개발 계획 내에서 이관은 일단 거의 진행했는데, 로직 수정이 생각보다 오래 걸리고 있다..

아직 서버가 돌아가는 상태가 아니므로 코드 검증이 필요한 상태이다.

 

product파트는 이관+로직 작업까지만 진행하고, 추후 기능 관리, 수정, 추가는 다른 팀원이 진행하므로 최우선으로 작업을 진행했으며 월요일 코드리뷰 시간을 통해 리뷰 후 전달할 예정이다.

(아직 진도가 나가지 않은 비동기 부분은 추가 작업 진행해 따로 전달 또는 담당 팀원이 진행 예정이다.)

 

기존 코드

기존 코드는 util을 제외한 Action코드를 파일로 첨부했다.

 

Spring

사용한 함수화한 메서드

해당 메서드들은 추후 설계 하에 다른 Controller도 사용할 수 있도록 별개의 클래스에 각각 선언했다.

 

FileUtil (다른 기능 추가예정)

package com.bungeabbang.app.view.util;

import lombok.extern.slf4j.Slf4j;

import java.io.File;

@Slf4j
public class FileUtil {
    //해당 폴더 하위의 폴더와 파일 삭제
    public static boolean deleteFileAndDirctory (File folder){
        log.info("log: deleteFile - start");
        //서버에 해당 경로의 폴더가 있다면
        if(folder.exists()) {
            File[] files = folder.listFiles(); //해당 폴더의 파일리스트 데이터
            if(files != null) { //빈 폴더가 아니라면
                for(File file : files) {
                    log.info("log: deleteFile - board image file delete file : [{}]", file);
                    if(file.isDirectory()){ //만약 파일이 아니라 폴더라면
                        log.info("log: deleteFile - {} is directory", file);
                        //재귀함수
                        deleteFileAndDirctory(file);
                    }
                    if(!file.delete()){ //해당 파일 삭제
                        //파일 삭제 실패 시
                        //개발자에게 안내
                        log.error("log: deleteFile - board image file delete fail!!!! file : [{}]", file.getPath());
                    }
                }
                if(!folder.delete()){
                    //폴더 삭제 실패 시 개발자에게 안내
                    log.error("log: deleteFile - board image folder delete fail!!!! folder : [{}]", folder.getPath());
                    return false;
                }
            }
        }
        else{
            //해당 경로에 폴더가 존재하지 않음을 안내
            log.error("log: deleteFile - no image folder error imagePath: [{}]", folder.getPath());
            return false;
        }
        log.info("log: deleteFile - end / true");
        return true;
    }
}

 

CookieUtil

package com.bungeabbang.app.view.util;

import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;

import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Slf4j
public class CookieUtil {
    //해당하는 쿠키 반환 (쿠키데이터 전체에서 cookieName을 가진 쿠키 반환)
    public static Cookie cookieData (Cookie[] cookies, String cookieName){
        log.info("log: cookieData start");
        Cookie data = null; //쿠키 변수
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if (cookie.getName().equals(cookieName)) {
                    // URL 인코딩된 값을 디코딩하여 쿠키에서 저장된 상품 목록을 가져옴
                    data = cookie;
                    log.info("log: cookieData data : [{}]", data);
                    break; //상품 목록 확인 시 종료
                }
            }
        }
        log.info("log: cookieData end");
        return data;
    }

    //쿠키 값에 넣을 데이터 생성 후 인코딩해서 반환 (cookieData에서 찾은 쿠키로부터 list를 만들어 productNum 데이터 추가(쿠키 추가할 준비))
    public static String cookiesListCreate (Cookie cookie, int productNum){
        log.info("log: cookiesListCreate start");
        List<String> viewedProductList = null; //리스트로 변환할 데이터를 담을 리스트
        String cookieslist = URLDecoder.decode(cookie.getValue(), StandardCharsets.UTF_8); //디코딩
        String product = "" + productNum; //형변환
        if (cookieslist == null || cookieslist.isEmpty()) {
            log.warn("log: cookiesListCreate null or Empty return : [{}]", product);
            //쿠키에 저장된 값이 없을 때 (== 처음으로 상품을 본 경우)
            // 해당 상품 번호만 반환
            log.info("log: cookiesListCreate end");
            return URLEncoder.encode(product, StandardCharsets.UTF_8); //인코딩
        }
        //쿠키에 이미 값이 있을 때 (==이미 본 상품이 있는 경우)
        //기존에 본 상품 목록이 있을 경우 기존 값을 리스트로 변환
        viewedProductList = new ArrayList<>(Arrays.asList(cookieslist.split(",")));
        if(viewedProductList.remove(product)){ //이미 리스트에 있는 값인 경우 (==이미 조회한 상품) 리스트에서 제거 (없다면 제거X)
            log.info("log: cookiesListCreate remove product : [{}]", product);
        }
        viewedProductList.add(product); //해당 상품을 리스트에 추가

        //10개까지만 저장
        while (true) {
            //종료조건
            if (viewedProductList.size() <= 10) {
                log.info("log: cookiesListCreate list size is 10 down");
                break;
            }
            viewedProductList.remove(0); //가장 먼저 저장된 값 삭제
            log.warn("log: cookiesListCreate remove index 0");
        }
        cookieslist = String.join(",", viewedProductList);
        log.info("log: cookiesListCreate end retrun : [{}]", cookieslist);
        return URLEncoder.encode(cookieslist, StandardCharsets.UTF_8); //인코딩
    }

    //쿠키 데이터 수정 (유효하지 않은 데이터가 있는 경우 호출해서 해당 데이터 쿠키에서 제거 후 재 등록)
    public static boolean cookieDataDelete (HttpServletResponse response, Cookie cookie, ArrayList<String> productNums){
        log.info("log: cookieDataDelete start");
        List<String> viewedProductList = null; //리스트로 변환할 데이터를 담을 리스트
        String cookieslist = URLDecoder.decode(cookie.getValue(), StandardCharsets.UTF_8); //디코딩
        if (cookieslist == null || cookieslist.isEmpty()) {
            //쿠키에 저장된 값이 없을 때
            log.info("log: cookieDataDelete end null or Empty false");
            return false;
        }
        //쿠키에 이미 값이 있을 때 (==이미 본 상품이 있는 경우)
        //기존에 본 상품 목록이 있을 경우 기존 값을 리스트로 변환
        viewedProductList = new ArrayList<>(Arrays.asList(cookieslist.split(",")));
        for(String num : productNums){
            viewedProductList.remove(num);
            log.info("log: cookieDelete delete num : " + num);
        }
        cookieslist = String.join(",", viewedProductList);
        Cookie modifyCookie = new Cookie(cookie.getName(), cookieslist);
        if(viewedProductList.isEmpty()){//쿠키에 넣을 데이터가 없다면
            modifyCookie.setMaxAge(0);//데이터 만료
            log.info("log: cookieDelete empty cookie delete");
        }
        else {
            modifyCookie.setMaxAge(cookie.getMaxAge()); // 쿠키 만료 시간
        }
        modifyCookie.setPath("/"); // 애플리케이션 전체에서 사용 가능하도록 설정
        response.addCookie(modifyCookie); //쿠키에 등록
        log.info("log: cookieDelete end true");
        return true;
    }

    //쿠키에 데이터 추가 (새로운 쿠키를 추가하거나 기존 쿠키를 업데이트(cookiesListCreate) 진행)
    public static boolean cookieAdd(HttpServletResponse response, String cookieName, String value, int day){
        log.info("log: cookieAdd start");
        Cookie cookie = new Cookie(cookieName, value); //쿠키로 변환
        cookie.setMaxAge(60 * 60 * 24 * day); // 쿠키 만료 시간
        cookie.setPath("/"); // 애플리케이션 전체에서 사용 가능하도록 설정
        response.addCookie(cookie); //쿠키에 등록
        log.info("log: cookieAdd end cookie : [{}]", cookie);
        return true;
    }

    //(통합기능) 쿠키에 새 데이터 추가
    //쿠키에서 해당 하는 이름을 가진 쿠키를 찾아 리스트를 뽑아내 해당 리스트에 새로운 데이터를 추가하고 해당 데이터를 다시 쿠키에 등록하는 메서드
    public static boolean cookieAddNewData(HttpServletResponse response, String cookieName, Cookie[] cookies, int productNum, int day){
        log.info("log: cookieAddNewData add product : [{}], cookieName : [{}]", productNum, cookieName);
        //day 쿠키 정보 기간 설정
        return cookieAdd(response, cookieName, cookiesListCreate(cookieData(cookies, cookieName),productNum), day);
    }
    //오버로딩
    //쿠키에서 해당 하는 이름을 가진 쿠키를 찾아 리스트를 뽑아내 해당 리스트에 새로운 데이터를 추가하고 해당 데이터를 다시 쿠키에 등록하는 메서드를 호출하며 30일 기간 설정을 디폴트로 전달
    public static boolean cookieAddNewData(HttpServletResponse response, String cookieName, Cookie[] cookies, int productNum){
        log.info("log: cookieAddNewData day 30 version");
        return cookieAddNewData(response, cookieName, cookies, productNum, 30);
    }

}

 

 

Controller (FileUtil과 CookieUtil 호출 중)

package com.bungeabbang.app.view.controller;

@Controller("product")
@Slf4j
public class ProductController {
    @Autowired
    private ProductService productService;
    @Autowired
    private BoardService boardService;
    @Autowired
    private ImageService imageService;
    @Autowired
    private ProductCateService productCateService;

    private final int PAGE_SIZE = 5; // 페이지당 항목 수 설정
    private final String COOKIE_NAME = "viewedProducts"; //쿠키 이름 설정
    private final String FOLDER_NAME = "/uploads/board/"; //경로

    //상품 상세보기
    @RequestMapping("/viewProduct.do")
    public String viewProduct(HttpServletRequest request, Model model, ProductDTO productDTO, BoardDTO boardDTO, ImageDTO imageDTO,
                              //쿠키 관련
                              HttpServletResponse response) {

        log.info("log: /viewProduct.do viewProduct - start");
        ArrayList<ImageDTO> images = null; //이미지가 있다면 이미지 객체를 담을 리스트

        // 상품 정보 조회
        productDTO.setCondition("MD_ONE"); //조회조건 설정
        productDTO = productService.selectOne(productDTO); // 해당 상품을 조회

        // 게시글과 이미지 조회
        boardDTO.setBoardNum(productDTO.getBoardNum());
        boardDTO.setCondition("ONE_BOARD"); // 조회 조건 설정
        boardDTO = boardService.selectOne(boardDTO);

        if (boardDTO != null) {
            log.info("log: viewProduct - have board");
            //게시글이 있다면 이미지 조회
            imageDTO.setBoardNum(boardDTO.getBoardNum());
            images = imageService.selectAll(imageDTO);
        }
        else {
            log.warn("log: viewProduct - this product not have board");
        }

        //쿠키에 조회 기록 추가
        Cookie[] cookies = request.getCookies();
        if(!CookieUtil.cookieAddNewData(response, COOKIE_NAME, cookies, productDTO.getProductNum())){
            //쿠키 추가에 실패했더라도 해당 상품 정보를 사용자가 볼 수 있도록 개발자에게 안내만 제공
            log.error("log: [ERROR] viewProduct - create history add Cookie fail!");
        }
        else {
            log.info("log: viewProduct - create history add Cookie success");
        }

        //데이터 전달
        model.addAttribute("product", productDTO);
        model.addAttribute("board", boardDTO);
        model.addAttribute("images", images);
        //데이터 확인
        log.info("log: viewProduct - send product [{}]", productDTO);
        log.info("log: viewProduct - send board [{}]", boardDTO);
        log.info("log: viewProduct - send image [{}]", imageDTO);

        log.info("log: /viewProduct.do viewProduct - end / forward");
        return "productDetail";
    }

    //상품 출력
    @RequestMapping("/listProduct.do")
    public String listProduct(HttpServletResponse response, Model model, ProductDTO productDTO,
                              //검색용 현재 페이지 정보
                              int currentPage,
                              //쿠키 작업에서 사용
                              HttpServletRequest request ) {

        log.info("log: /listProduct.do listProduct - start");
        
        ArrayList<ProductDTO> recommendedProducts = null; //추천 상품 목록을 담을 리스트
        HashMap<String, String> filterList; //필터 검색용
        List<String> viewedProductList; //상품 조회용
        ArrayList<ProductDTO> resentProducts = null; //쿠키 저장용
        Map<Integer, Integer> categoryCount; //상품 카테고리 계산용
        ArrayList<String> deleteList; //쿠키 삭제용
        ArrayList<ProductCateDTO> productCategories; //카테고리 목록

        //쿠키 가져와서 디코딩 : 사용자가 최근에 조회한 상품 목록
        Cookie[] cookies = request.getCookies();
        Cookie cookie = CookieUtil.cookieData(cookies, COOKIE_NAME);
        String viewedProducts = URLDecoder.decode(cookie.getValue(), StandardCharsets.UTF_8);
        log.info("log: listProduct - viewedProducts : [{}]", viewedProducts);

        // 쿠키에서 가져온 상품 목록이 있다면 리스트에 저장
        if (viewedProducts != null && !viewedProducts.isEmpty()) {
            //","를 통해 데이터 구분
            viewedProductList = Arrays.asList(viewedProducts.split(","));
            log.info("log: listProduct - viewedProductList : [{}]", viewedProductList);

            deleteList = new ArrayList<>(); //삭제용 리스트
            resentProducts = new ArrayList<>(); //추천 상품 목록
            categoryCount = new HashMap<>(); //카테고리 계산용

            // 쿠키에서 가져온 상품 ID를 기반으로 상품을 조회하고 카테고리별로 개수 카운팅
            for (String pNum : viewedProductList) {
                productDTO.setProductNum(Integer.parseInt(pNum));
                ProductDTO product = productService.selectOne(productDTO); // 상품 정보 조회
                log.info("log: listProduct - viewedProductList for product : [{}]", product);

                //존재하지 않는 상품일 경우
                if (product == null) {
                    log.error("log: listProduct - deleteList.add : [{}]", pNum);
                    //추후 쿠키에서 제거 작업하기 위해 저장
                    deleteList.add(pNum);
                    continue;
                }
                //해당 상품을 저장
                resentProducts.add(product);
                log.info("log: listProduct - resentProducts.add");
                // 카테고리 번호별로 상품 수 계산
                int categoryNum = product.getProductCateNum();
                log.info("log: listProduct - categoryNum : [{}]", categoryNum);
                categoryCount.put(categoryNum, categoryCount.getOrDefault(categoryNum, 0) + 1);
            }
            //없는 상품 쿠키에서 제거
            if(CookieUtil.cookieDataDelete(response, cookie, deleteList)){
                //쿠키 제거에 실패했더라도 상품리스트를 사용자가 볼 수 있도록 개발자에게 안내만 제공
                log.error("log: [ERROR] listProduct - delete history Cookie fail!");
            };

            // 가장 많이 본 카테고리
            // categoryCount Map에 저장된 카테고리별 조회 횟수를 기준으로 가장 많이 조회된 카테고리를 찾는다.
            // Map.Entry<Integer, Integer>는 카테고리 번호(Integer)와 해당 카테고리의 조회 수(Integer)를 의미.
            // max() 메서드는 가장 큰 값을 가진 카테고리를 찾는다.
            Optional<Map.Entry<Integer, Integer>> mostViewedCategoryOpt = categoryCount.entrySet().stream()
                    .max(Map.Entry.comparingByValue());

            // Optional이 비어있지 않으면 (가장 많이 본 카테고리가 존재할 경우)
            if (mostViewedCategoryOpt.isPresent()) {
                // 가장 많이 본 카테고리의 key 값을 가져옴 (카테고리 번호)
                int mostViewedCategory = mostViewedCategoryOpt.get().getKey();
                // 가장 많이 본 카테고리에 속하는 상품을 필터링하여 추천 상품으로 설정
                filterList = new HashMap<>(); //필터 검색용
                filterList.put("GET_MD_CATEGORY", "" + mostViewedCategory);  // 카테고리 필터 추가
                // ProductDTO 객체 생성 후 필터 설정
                productDTO.setFilterList(filterList);  // 필터를 설정하여 해당 카테고리의 상품만 검색
                // 데이터 범위 설정 (추천 상품 수를 3개로 제한)
                productDTO.setStartNum(1);
                productDTO.setEndNum(3);
                //확인
                log.info("log: listProduct - mostViewedCategory : [{}]", mostViewedCategory);
                log.info("log: listProduct - filter search filterList : [{}]", filterList);

                // 필터링된 상품 목록을 조회하여 추천 상품 리스트 생성
                recommendedProducts = productService.selectAll(productDTO); // 상품 목록 조회
            }
        }


        //카테고리 목록 조회
        productCategories = productCateService.selectAll(null); // 전체 카테고리 목록 조회

        //페이지에 해당하는 상품 목록
        int startNum = (currentPage-1)*PAGE_SIZE+1;
        productDTO.setStartNum(startNum);
        productDTO.setEndNum(startNum+PAGE_SIZE-1);
        ArrayList<ProductDTO> productList = productService.selectAll(productDTO);
        //페이지네이션을 위한 총 데이터 정보
        productDTO.setCondition("FILTER_CNT");
        int allDataCnt = productService.selectOne(productDTO).getCnt();

        //데이터 전달
        model.addAttribute("resentProducts", resentProducts); // 최근에 본 상품
        model.addAttribute("recommendedProducts", recommendedProducts); // 추천 상품
        model.addAttribute("productCategories", productCategories); // 카테고리 목록
        model.addAttribute("productList", productList); //페이지 별 상품 리스트
        model.addAttribute("allDataCnt", allDataCnt); //페이지네이션용 정보

        //확인
        log.info("log: listProduct - send resentProducts : [{}]", resentProducts);
        log.info("log: listProduct - send recommendedProducts : [{}]", recommendedProducts);
        log.info("log: listProduct - send productCategories : [{}]", productCategories);
        log.info("log: listProduct - send productList : [{}]", productList);
        log.info("log: listProduct - send allDataCnt : [{}]", allDataCnt);

        log.info("log: /listProduct.do listProduct - end / forward");
        return "productList";
    }

    //신규 상품 등록 페이지 이동
    @RequestMapping(value = "/newProduct.do", method = RequestMethod.GET)
    public String newProduct() {
        log.info("log: newProduct.do newProduct - redirect");
        return "redirect:productRegister.jsp";
    }

    //신규 상품 등록 TODO 비동기처리로 진행 중
    @RequestMapping(value = "/newProduct.do", method = RequestMethod.POST)
    public String newProduct(ProductDTO productDTO) {
        log.info("log: newProduct.do - insert start");
        //TODO 작업 진행
        return null;
    }

    //상품 업데이트 페이지 이동
    @RequestMapping(value = "/checkProduct.do", method = RequestMethod.GET)
    public String checkProduct(Model model, ProductDTO productDTO, BoardDTO boardDTO, ImageDTO imageDTO) {
        log.info("log: /checkProduct.do checkProduct - start");
        ArrayList<ImageDTO> images = null; //이미지데이터를 담을 리스트

        //Condition 값 세팅
        productDTO.setCondition("MD_ONE"); //condition 설정
        boardDTO.setCondition("ONE_BOARD"); // 조회 조건 설정

        //확인
        log.info("log: checkProduct productDTO condition MD_ONE");
        log.info("log: checkProduct boardDTO condition ONE_BOARD");

        //데이터 조회
        productDTO = productService.selectOne(productDTO); // 해당 상품을 조회
        boardDTO = boardService.selectOne(boardDTO); //상품 설명 조회

        //상품 설명이 있을 경우 (게시글이 있을 경우)
        if (boardDTO != null) {
            // 상품 설명에 대한 이미지 조회
            imageDTO.setBoardNum(boardDTO.getBoardNum());
            images = imageService.selectAll(imageDTO);
        }
        else {
            log.warn("log: checkProduct - no board");
        }

        //데이터 전달
        model.addAttribute("product", productDTO);
        model.addAttribute("board", boardDTO);
        model.addAttribute("images", images);

        //확인
        log.info("log: checkProduct - send product : [{}]", productDTO);
        log.info("log: checkProduct - send board : [{}]", boardDTO);
        log.info("log: checkProduct - send image : [{}]", imageDTO);

        log.info("log: /checkProduct.do checkProduct - end / forward");
        return "productUpdate";
    }

    //상품 업데이트 TODO 비동기 처리 중
    @RequestMapping(value = "/checkProduct.do", method = RequestMethod.POST )
    public String checkProduct(ProductDTO productDTO, BoardDTO boardDTO, ImageDTO imageDTO) {
        return null;
    }

    //상품 제거
    @RequestMapping("/deleteProduct.do")
    public String deleteProduct(HttpServletRequest request, ProductDTO productDTO, ImageDTO imageDTO, BoardDTO boardDTO) {
        log.info("log: /deleteProduct.do deleteProduct - start");

        ArrayList<ImageDTO> images = null; //이미지 데이터를 담을 리스트
        String imagePath; //이미지 폴더 경로

        boardDTO.setCondition("ONE_BOARD");
        boardDTO = boardService.selectOne(boardDTO); //상품에 해당하는 게시글 조회
        if (boardDTO != null) {
            imageDTO.setBoardNum(boardDTO.getBoardNum());
            images = imageService.selectAll(imageDTO);  // 해당 게시글의 이미지 목록 가져오기
            //이미지가 존재하는 게시글인 경우
            if(!images.isEmpty()) {
                //이미지가 저장된 경로
                imagePath = request.getContextPath() + FOLDER_NAME + boardDTO.getBoardNum();  // 이미지 경로 설정
                if(FileUtil.deleteFileAndDirctory(new File(imagePath))){
                    log.error("log: deleteProduct - delete file fail check");
                }

                //부모 테이블인 게시글이 삭제되면 이미지도 함께 삭제될 수 있도록 구성
                // 데이터베이스에서 이미지 정보 삭제
                //boolean imageDeleted = imageService.delete(img);  // 이미지 정보를 데이터베이스에서 삭제
                //if (!imageDeleted) {
                //    log.error("[ERROR] 이미지 삭제 실패: 이미지 번호 {}", img.getImageNum());
                //    throw new RuntimeException();
                //} else {
                //    log.info("[INFO] 이미지 삭제 성공: 이미지 번호 {}", img.getImageNum());
                //}
            }
            else {
                log.warn("log: deleteProduct - no image");
            }

            // 게시글 삭제
            if (!boardService.delete(boardDTO)) {
                //상품 설명이 없을 수 있으므로 개발자에게 안내만 진행
                log.error("log: /deleteProduct.do deleteProduct - board delete failed");
                log.error("log: /deleteProduct.do deleteProduct - boardDTO [{}]", boardDTO);
            }
            else {
                log.info("log: /deleteProduct.do deleteProduct - board delete success");
            }
        }

        // 상품 삭제
        if (!productService.delete(productDTO)) {
            //상품 삭제 실패 시 실패 안내 페이지로 이동
            log.error("log: /deleteProduct.do deleteProduct - product delete failed");
            return "redirect:failInfo.do";
        }
        log.info("log: /deleteProduct.do deleteProduct - product delete success");

        log.info("log: /deleteProduct.do deleteProduct - end / redirect");
        return "redirect:listProduct.do";
    }

    //상품 검색 TODO ==> 비동기통신처리
    //@RequestMapping("/searchProductMD.do")
}