[소프트웨어공학]JPetStore6에 차별화 기능 추가하기

0

프로젝트

목록 보기
3/14

🌟 차별화 기능 아이디어

우리조에서 차별화 기능 아이디어로 제출한 것은 다음과 같다.
1. JPetStore가 상점이므로 리뷰 게시판을 추가하자!
2. JPetStore가 펫샵의 기능을 하고 있으므로 사용자들의 참여를 이끌어내기 위하여 양육일기를 작성할 수 있는 게시판을 구현하자.

결국 2번의 아이디어로 선정되었으며, 우리가 제안하는 양육일기의 테마를 소개한다.

  • 비로그인 사용자
    - 다른 사람이 작성한 양육일기 목록을 조회할 수 있다.
    - 다른 사람이 작성한 양육일기 상세 내용을 조회할 수 있다.
    - 내가 조회하고 있는 양육일기 사용자가 이전에 작성한 양육일기 목록을 조회할 수 있다.
    - 양육일기에 대해 필터링(카테고리별, 좋아요 많은 순, 덧글 많은 순, 최신순)을 적용할 수 있다.
    - 양육일기를 검색할 수 있다.
  • 로그인 사용자
    - 기본적으로 비로그인 사용자와 동일한 기능을 사용할 수 있다.
    - 내 반려동물에 대한 양육일기를 작성, 수정, 삭제할 수 있다. (사진 1장 + 내용)
    - 양육일기에 덧글을 작성, 수정, 삭제할 수 있다.
    - 양육일기에 좋아요를 누르거나 취소할 수 있다.
    - 'My Diary' 메뉴를 통해 내가 작성한 글들만 모아서 볼 수 있다.
  • 어드민 사용자
    - 양육일기 게시판에 적절하지 않은 양육일기를 삭제할 수 있다.
    - 양육일기에 적절하지 않은 덧글을 삭제할 수 있다.

🌟 차별화 기능 설계

1. JPetStore6에 존재하지 않는 컴포넌트들을 생성할 필요가 있다.

  • DiaryActionBean

    기본적으로 다이어리와 관련한 모든 URL에 대응한다. 형식적 Validation이 진행되고 서비스를 통하여 로직을 수행하고, View 컴포넌트(JSP)에 연결된다.

  • DiaryService

    기본적으로 다이어리와 관련된 모든 로직을 수행한다. DiaryActionBean에서 서비스를 호출하게 된다. 해당 서비스는 DiaryMapper, DiaryCommentsMapper, DiaryLikesMapper을 이용하여 데이터베이스와 상호작용하며 자세한 내용은 다음에서 설명한다.

  • ~DiaryMapper

    기본적으로 Mybatis를 사용하기 위한 인터페이스 클래스이며 데이터베이스 테이블에 대응하는 매퍼들이 존재한다. DiaryMapper는 DIARY 테이블, DiaryCommentsMapper는 COMMENTS 테이블, DiaryLikesMapper는 LIKES 테이블과 상호작용하게 된다.

  • ~DiaryMapper.xml

    Mapper 인터페이스에서 정의한 메소드에 해당하는 쿼리를 동적으로 생성하여 실행하게 되며, 해당 쿼리가 명시되어 있는 파일이다.

2. JPetStore6에서 양육일기에서 필요한 정보들을 담아둘 테이블들이 필요하다.

  • Likes 테이블이 따로 존재하는 이유는 누가 어떤 게시글에 대해 좋아요를 눌렀는지 조회가 가능해야할 뿐만 아니라, 중복으로 좋아요를 누르게 되는 일을 없애기 위해서 만들었다.
    그래서 해당 ERD에는 포함되어있지 않지만 Likes테이블에서 (d_no, userid)는 복합키이자 기본키로 작동된다.

3. NAS를 이용하여 DB를 원격에 놔두고 사용하게 한다. 도커를 이용하여 변경사항이 업데이트 될 때마다 배포한다.

  • Synology NAS에 MariaDB에 기존 JPetStore6 DB를 마이그레이션하고, 해당 데이터베이스만 접근할 수 있는 계정을 생성한다. 해당 계정은 같이 협업하는 팀원 역시도 데이터베이스에 대한 제어를 획득할 수 있었어야 한다.(차별화 기능을 제외하고 추가기능(어드민)을 구현하는 팀원에도 DB를 설계하는 팀원이 존재했다.) 그래서 해당 계정에 GRANT ALL = 모든 권한을 주었다.

  • 도커에서 Apache Tomcat + JDK 11 버전을 가지고 있는 이미지를 컨테이너로 빌드하고 해당 컨테이너에 빌드된 프로젝트 .war파일을 담아서 외부에서 접근가능할 수 있도록 하였다. 해당 톰캣에는 다른 웹앱 프로젝트도 작동하고 있어서 SSL인증과 DNS 레코드 설정이 이미 되어있는 상태이다.

🌟 Disign Decision 및 나의 역할

1. MyBatis에서 쿼리?의 오작동으로 인한 변경점!!

  • 좋아요를 누를시 Likes 테이블에 사용자 id와 게시글 넘버를 삽입하고 count(*)를 통해 가지고 올 시 갯수가 업데이트가 안되는 문제가 발생했다. 기존 쿼리는 아래와 같다.
select no, imgurl, title, categoryid, userid, date, content,
          (select count(*) from LIKES where d_no = no) as likes,
          (select count(*) from COMMENTS where d_no= no) as comments
        from DIARY where no = #{no};
  • 이후에 Diary 테이블과 Comments 테이블과 Likes 테이블을 Join하는 형태로 가지고 오려고 하여도 똑같은 오류가 발생했다.
  • 결과적으로 DiaryService에서 Likes 테이블에 삽입 이후 갯수를 다시 가져오고 해당 갯수를 Diary 테이블의 likes 컬럼에 업데이트하는 형태로 변경하였다.
diaryLikesMapper.insertLike(likes);
int likesCnt = diaryLikesMapper.getLikesCount(likes.getD_no());
diaryMapper.updateDiaryLikes(likes.getD_no(), likesCnt);
  • insertLike를 통해 좋아요를 삽입하고 likesCnt에 해당 d_no(양육일기 넘버)를 가지는 좋아요가 몇개인지 가져온다. 그리고 불러온 갯수를 Diary테이블의 likes 컬럼에 업데이트한다. 해당 로직은 좋아요 취소에도 똑같이 작동하도록 만든다.

2. MyBatis 동적 쿼리 생성으로 메소드 줄이기

  • 우리 팀의 로직을 구현하는 팀원이 카테고리별 조회, 정렬, 검색 기능을 구현할때 쿼리문이 경우에 따라 전부 다르기 때문에 모두 다른 메소드를 통해 구현을 했었다.
public void setDiaryList(int offset){
        if (orderCategory == null || orderCategory.equals("ALL")) {
            diaryList=diaryService.getDiaryList(orderLikesOrComments, offset);
        } else {
            diaryList=diaryService.getCategoriedDiaryList(offset, orderCategory, orderLikesOrComments);
        }
    }
    public void setSearchedDiaryList(int offset){
        if (orderCategory == null || orderCategory.equals("ALL")) {
            diaryList=diaryService.getSearchedDiaryList(offset, orderLikesOrComments, keyword);
        } else {
            diaryList=diaryService.getSearchedCategoriedDiaryList(offset, orderCategory, orderLikesOrComments, keyword);
        }
    }
  • 다음과 같이 그냥 다이어리 목록을 조회할 때와 검색이 적용된 다이어리 목록을 조회할때 다른 메소드를 통해 작동하도록 설계되어 있다. 하지만 이것은 내 입장에서는 굉장히 불필요한 일이고 기능이 추가될 때마다 코드가 길어지니 협업에도 지장이 생긴다고 판단하여 하나의 메소드로 수정하고 mapper.xml에서 조건문을 이용하여 경우에 따라 다른 쿼리문이 동적으로 생성될 수 있도록 하였다.
<select id="getDiaryList" resultType="Diary">
        select no, imgurl, title, categoryid, userid, date, likes, comments
        from DIARY
        where 1 = 1
        <if test='param2 != null and param2 != "ALL"'>
            and categoryid = #{param2}
        </if>
        <if test="param4 != null">
            and (userid like CONCAT('%',#{param4},'%') OR title like CONCAT('%',#{param4},'%'))
        </if>
        order by ${param3} desc limit #{param1}, 6;
    </select>
  • where 1 = 1을 적은 이유는 and로 경우에 따라 조건을 연결하기 위해 기본적으로 명시해둔 조건이다.

3. 페이징 처리

4. 이미지 업로드

  • Stripes에서 form을 통해 업로드하면 FileBean을 통해 받고 .save()메소드를 이용하여 파일을 저장하였다.
  • 이때 파일은 톰캣의 프로젝트폴더 내부의 static한 경로에 저장하도록 하고, 추후 접근에는 domain/project_war/staticURL을 통해 image를 불러올 수 있도록 하였다.
public void fileUpload() throws IOException {
        String now = new SimpleDateFormat("yyyyMMddHmsS").format(new Date());  //현재시간
        String saveDir = context.getRequest().getSession().getServletContext().getRealPath("/static");
        File dir = new File(saveDir);
        //diary.setImgurl("default.png");
        if (!dir.exists()) {
            dir.mkdirs();
        }
        if (petImage != null && petImage.getSize() != 0) {
            try {
                String fileName = petImage.getFileName();
                int i = -1;
                i = fileName.lastIndexOf("."); // 파일 확장자 위치
                String realFileName = now + fileName.substring(i, fileName.length());  //현재시간과 확장자 합치기
                diary.setImgurl(realFileName);
                //System.out.println(saveDir + "/" + realFileName);
                petImage.save(new File(saveDir + "/" + realFileName));
            } catch (IllegalStateException | IOException e) {
                e.printStackTrace();
            }
        }
    }
  • now 를 이용해서 파일이름에서 중복이 발생하지 않도록 현재시간을 기준으로 변경한다. 그리고 현재 프로젝트의 경로를 받아온뒤 static경로에 저장한다. if문이 존재하는 이유는 업로드된 파일이 없을경우에 발생할 수 있는 오류를 대체하기 위해서 존재한다!!

🌟 차별화 기능 구현 결과

🌟 프로젝트 소스코드 보러가기

https://github.com/SONOGONG2022/JPetStoreAndDiary

profile
최악의 환경에서 최선을 다하기

0개의 댓글