개인 프로젝트

[Buddy] 업무용 협업 툴

뮤츠 2023. 1. 10. 13:53

한 줄 소개

당신의 동료, 영원한 친구. 그룹채팅 기반 팀 협업 툴 Buddy

 

진행기간

2022.12.12 ~ 2022.01.05

 

기술 스택

Java 11, SpringBoot 2.7.6, Thymeleaf, Oracle11g, gradle

HTML5, CSS3, JavaScript, JQuery

 

라이브러리

Email Sender, download.js, full canlendar, WebSocket

 

Tools

IntelliJ, SqlDeveloper, VSCode

Zandi

 

팀 구성원

팀원 5인.

 

관련 활동

(디지털컨버전스) 공공데이터를 활용한 Springframework기반 웹 개발자 양성과정 파이널 프로젝트.

 

담당 내용

프로젝트 총괄(팀장), 회의록 작성, 그룹채팅방 기본기능 구현, 채팅방/토픽 생성, 그외 채팅방/팀 관련 테이블 CRUD 구현, 관리자 모드 일부 구현(공지사항, 문의내역)

 

GitHub repository

https://github.com/OranGeShine01/Buddy-Project

 

GitHub - OranGeShine01/Buddy-Project: KH Final Project

KH Final Project. Contribute to OranGeShine01/Buddy-Project development by creating an account on GitHub.

github.com

 

프로젝트 소개.

  • 팀 내에서 사용하는 업무용 협업툴에서 영감을 얻었고, 수업시간 말미에 배웠던 웹소켓을 적극적으로 활용하고 싶어서 Buddy 프로젝트를 진행하게 되었다.
  • 주요 기능은 팀 별 그룹채팅방 기능, 파일 클라우드 기능, 캘린더를 통한 일정 저장 기능 등.

메인 화면, 네이버 연동 로그인의 경우 검수요청 진행 중이라 사용 불가.

 

로그인 후 인터페이스

팀 생성은 5개까지로 제한하였고, 팀으로 가기 클릭시 해당 팀으로 이동가능.

 

공지등록은 서머노트 API를 활용.

 

목록 출력은 페이징 처리까지 하였습니다. 이는 문의 내역 또한 마찬가지.

 

문의 답변은 PODO Music과 마찬가지로, 답변은 관리자만, 1개의 댓글만을 허용하는 식으로 구현.

 

팀 접속시 기본적으로 4개의 토픽이 생성가능하고, 팀명과 같은 명칭의 기본 채팅방 (모든 팀멤버가 의무적으로 참가)이 생성되며, 메모장처럼 쓸 수 있는 '나와의 대화' 기능도 제공된다. 그 외 일반 채팅방도 생성 가능.

 

채팅방 생성시, 초대할 멤버를 정할 수 있으며

자신은 포함하거나 말거나 무조건 참여하도록 조치하였음.

 

테스트 채팅 및 채팅 멤버 추출, 팀 목록 및 팀원 목록 출력도 가능.

 

ERD 최종

 

기술 리뷰

1.

 그룹 채팅방 설계에서부터 난항을 겪었는데, 결과적으로 채팅메세지-채팅방참여자목록-채팅방 으로 3중 구조를 가진 DB로 구성하였다. 여기에 '팀' 이라고 하는 구조가 하나 더 들어갔기에, 팀 관련 DB 또한 팀원 목록 - 팀 목록 테이블까지 추가되어 상당히 다층구조가 되었다.

 

 거기에다, 외래키를 사용하지 않고 작업하다보니, 뭐 하나가 정보가 변동되면 나머지 정보들도 요동치면서 엄청나게 버그가 발생했다. 팀에서 추방/탈퇴시 팀의 하위 컨텐츠인 채팅방에서도 같은 조치가 일어나야했고, 그 과정에서 인원수가 0명이 되는 채팅방은 삭제가 되야했는데, 채팅방-채팅방 참여자 목록-채팅방 메세지는 같이 삭제가 되야하는 식이다. 일정 문제상 채팅방 방장 기능은 삭제하는 것으로 타협하였다.

 

2.

 새로운 채팅방을 만드는 작업에서, 새로운 채팅방을 구성할 팀원들을 추가/삭제하는데 구성할 자료형을 고민하였다. 배열의 경우, 추가는 별 지장이 없지만, 삭제의 경우 삭제할 회원의 값을 대조하여 반복문으로 빼주어야 했기에, 시간복잡도 측면에서 비효율적이라 느꼈다. 그래서 순서에 구애받지 않고, 존재 유무만 따지면 될만한 set 자료형을 이용하였다.

 

 문제는, set자료형으로 받은 새로운 채팅방의 구성원과, 제목이라는 문자열값 두 가지를 전달하다보니 생성자를 통해 전달했는데, DTO로 값을 받는게 잘 되지 않았다. 자바스크립트는 제너릭이 따로 없이 Object로 받아서 그런가? 싶어서 set<Object>도 해보고, 별짓 다했는데 잘 되지 않았다. 그나마 String으로 DTO를 만들고, Stringify된 생성자 안에 Stringify된 set으로 보내니까 인식은 됐는데, 그 경우 받아서 다시 toJson을 시켜줘야해서 비효율적으로 느껴졌다.

 

 결국 set으로 회원 목록을 주고 받다가, '채팅방 시작하기' 버튼 클릭시 전체 목록을 배열로 전환하는 과정을 거쳤다. set 자료형으로 변환하는데 SpringBoot에 기본 내장된 jackson 라이브러리와, 별도로 사용한 gson 라이브러리 모두 지원을 안해주는가 하는 발상에서였다. 결과적으로 성공적으로 값을 처리할 수 있었지만, 효율 때문에 사용한 set에서 다시 재변환해주는 과정의 효율성을 따질 수 없어 제대로된 방식인지는 의문이 들었다. CS 지식이 필요하겠다는 생각이 절실하였다.

 

 또 본래 채팅방 회원 목록은 자신을 뺀 나머지만 출력되야하지만, 시간문제로 기존에 출력되었던 팀 회원 목록으로 사용하였고, 그에 따라 자신이 빠진 목록으로 채팅방을 구성할수도 있었다. 채팅 시작하기 버튼을 누를때, 자신이 set 안에 존재하지 않을 경우, 추가하는 방향으로 방지하였다.

 

3.

 그룹 채팅방 구성은, 수업떄 배운 웹소켓을 한층 더 응용해야 했다. 수업때 배웠던 '웹소켓을 활용한 채팅방' 기능은, 'DB에 저장되지 않는' '모두가 하나의 채팅방에 모이는' 구조를 가지고 있었다.

 

 이를 극복하기 위해, 웹소켓을 열때 각각의 방 번호를 전달해주었고, 웹소켓이 열리는 endPoint의 @OnOpen 구간에서는 방 번호를 받아서, 방 번호에 해당하는 Integer를 Key로, 각 참여자들을 식별할 수 있는 회원번호를 담아둔 set<Integer>를 value로 하는 Map자료형으로 담았다. 그 과정에서, http의 영역이 아닌 웹소켓의 endPoint에 어떻게 인자값을 전달해주는지 찾아보니, @PathParam 이라고 하는 어노테이션을 통해 받을 수 있었다는 것을 알게 되었다.

 

 또한 실습에서는 메세지만 주고 받아 String으로 교환했지만, 채팅방은 보낸 사람, 메세지, 보낸 시간 등 다양한 정보를 주고 받아야 했기에, JSON 생성자로 주고 받아야 했다. 이 과정은 Ajax랑 그다지 다르지 않았다.

 

4.

 endPoint에서 값을 받아 DB에 저장하려는 작업을 처리하는 중에, DTO에 값이 제대로 담기지 않고 null로 처리되는 오류를 발견하였다.

 

 알고보니, 웹소켓은 http의 영역이 아니라서, 스프링컨테이너의 주소값을 알지 못하여 첫 request가 인지된 후 생성된 스프링 컨테이너가 컴포넌트 스캔을 통해 생성된 객체들을 사용할 수 없는 문제가 있었다. 이를 위해 ApplicationContextAware (ApplicationContext = SpringContainer 이므로 스프링 컨테이너를 감지하는 인터페이스) 를 상속받은 클래스를 생성, 컴포넌트 스캔을 통해 스프링 컨테이너가 해당 클래스를 감지할 때 해당 클래스에 주소값을 저장하는 일종의 getter-setter를 만들었다. 이 때 getter를 override하였다.

 

 그 후, endPoint에서 DB에 접근할 때, 해당 클래스를 호출하여 getBean을 통해 스프링 컨테이너 내에 있는 bean을 사용하여 정상적으로 가동하는 것을 확인할 수 있었다.

5.

 공지사항 글쓰기에는 서머노트API를 사용하였는데, 서머노트는 이미지 파일 저장시 base64 형태로 파일을 변환하여 DB에 그대로 저장해야 했다. 비효율적인 방식이라, 이를 파일 저장형식으로 구현해야했다.

 

서머노트 이미지 첨부 관련해서는 많은 레퍼런스가 있었고, 코드도 거의 엇비슷해서 큰 문제없이 처리할 수 있었다.

서머노트 API에서 특정 기능 실행시 callback하는 콜백함수를 제공하고 있었으며, 그 중 onImageUpload는 이미지가 업로드되는 순간을 포착하여 업로드가 되는 시스템이었다. 따라서 파일 첨부를 통해 이미지가 서버에 업로드되고, 이미지 URL을 반환하는 식으로 처리하였다.

 여기서 문제가 두가지 있었다. 하나는 스크립트어택/타임리프 문제였다. 스크립트 방지를 해두는 상태에서도 해당 URL이 먹혀야했고, 특히나 문장 하나하나를 p태그로 저장하는 서머노트에서는 더더욱 그러했다. 그래서 고민끝에, 전체 태그를 다 막는것이 아닌, <script> 태그만 막아 일반 html 태그는 허용하는 방향으로 타협하였다.

 나중에 알고보니, 타임리프의 th:text는 스크립트어택 방지기능이 자동으로 내장되어있었다. 따라서 서버에서 스크립트 어택을 막는 조치를 취한 뒤, 출력은 th:utext로 하여 이미지 출력이 정상적으로 처리되도록 조치하였다.

 

6.

 웹과 관련이 없지만 자주 쓰는 클래스들은, util로 빼서 구현하였다. 대표적으로 파일 저장 시스템이 그러했고, 내가 담당한 부분에서는 페이징 기능을 넣었다. RecordCountPerPage, NaviCountPerPage, RecordTotalCount, URL, Parameter를 각각 받으면 알아서 페이지가 생성되는 식으로 만들었다. 아쉽게도, 나 말고는 페이징 기능을 따로 사용한 경우가 없어서 많이 쓰지는 못했고, 나는 각각 문의내역 및 공지사항에 페이징을 만들었기에 적절하게 사용하였다.

 

전체 후기

 세미보다 훨씬 많은 기간에, 향상된 기술 스택으로 진행하여 많은 것을 배울 수 있었다. 서블릿에서는 내가 일방적으로 가르쳐주던 관계였다고 한다면, 각 구성원이 성장하고, 내가 모든 컨텐츠를 배울 수 없어서 팀원들에게도 많이 배웠다.

 

원래 팀장 입장에서 프로젝트가 좌초될 위기만은 막아야 했기에, 세미프로젝트의 성공 이후 자신감이 넘치던 팀원들에 비해 보수적 입장으로 접근하였다. 수업에서 배우지 않았던 기술스택의 증가 (Eclipse->IntelliJ, Spring->SpringBoot 등 다양한 기술스택의 업그레이드), 그리고 컨텐츠의 대폭 확장 등 양쪽 중 한가지만 업그레이드하고, 나머지는 현상유지를 하자는 쪽이었는데, 어쩌다보니 서로서로 욕심내는 분위기였다.

 

 특히 새로운 기술스택은 걱정을 많이 했는데, 매번 수업때 지정된 버전을 강사님이 어디가서 뭘 깔고 어떻게 세팅하는지 다 알려주셨지만, 이번에는 우리가 스스로 극복해야 했기 때문이다. 하지만, 끝까지 기술스택의 업그레이드를 고집한 이유는, 어차피 회사에 들어가면 해당 회사에서 사용하는 버전 등을 설치하여 세팅을 해야할 것이고, 어차피 얼타는 과정을 동반할텐데, 여기서 평등한 구성원끼리 서로서로 알아보고 알려주면서 시행착오를 거치는쪽이, 결과적으로 '욕을 덜 먹겠다' 라는 생각 때문이었다. 언제 이렇게 마음놓고 실수해도 되는 기회가 있을까 싶었다.

 

 초반에 여유로웠지만, 어느순간 일정이 촉박해지고, 결국엔 상당히 많은 기능들을 포기해야 했다. 웹소켓이 그룹채팅방에만 구현되있지만, 실제로는 팀 페이지부터 웹소켓이 열려있는 다중웹소켓 구조로 되어 있어서, 알림이나 팀 접속상태 등이 표기되는것을 상상했는데 거기까지 하지 못했다.

'개인 프로젝트' 카테고리의 다른 글

[PODO Music] 음악 스트리밍 사이트  (0) 2023.01.10