MegaBoxProject
TeamProject Code Review 1
신입 개발자 박상우의 개발자 도전기
2023. 2. 25. 19:12
1. HomePage
- 홈페이지를 만들기에 앞서 가장 어려웠던 부분은 Header Hover처리하는 부분이였다. hover시 아래에 title 세부사항이 표시되게 만들려고 하는데 Css지식이 부족하다는 것을 느낄수 있었다. 물론 임의로 값을 지정하여 밑에 끼워 넣는것은 쉬웠지만 반응형으로 제작하는것이 어려웠다는 뜻이다. 이부분을 해결하면서 느꼈던 점은 개발자는 만들다가 도저히 못고치겠거나 더이상의 진전이 없을때는 과감하게 다시 만들 부분도 필요하다고 생각했다. 기존에 짜고있던 코드에서 고치려고 하니까 도저히 방법이 생각이 나지 않아서 다르게 접근을 하니 다행스럽게도(?) 원하는 결과물을 얻을 수 있었다.


2. 예매 Page 만들기
const form = window.document.getElementById('form');
const nextBtn = window.document.getElementById('nextBtn');
const previousBtn = window.document.getElementById('previousBtn');
const timeBox = window.document.querySelector('.time-box');
const timeContainer = window.document.querySelector('.reservation-container');
const paymentContainer = window.document.querySelector('.body-wrap');
const seatContainer = window.document.querySelector('.seat-select');
const region = window.document.querySelector('.region');
const quickCity = window.document.querySelector('.quick-city');
const beforeSelectMovieTime = window.document.querySelector('.before-select-movie-time');
const selectMovieTime = window.document.querySelector('.select-movie-time');
const listCollect = window.document.querySelector('.list-collect');
let allScreenInfos = [];
let branches = [];
let movieTitles = [];
let screenInfoSeats = [];
let screenInfoSeatColumns = [];
let completeSeatBooking = [];
let allSeat = [];
let count = 0;
let value = 0;
let date = new Date();
let utc = date.getTime() + (date.getTimezoneOffset() * 60 * 1000);
let kstGap = 9 * 60 * 60 * 1000;
let today = new Date(utc + kstGap);
let currentMonth = ((today.getMonth()) + 1);
let currentYear = today.getFullYear();
let currentDay = today.getDate();
let thisMonthEndDay = new Date(currentYear, currentMonth, 0);
let thisMonthLast = thisMonthEndDay.getDate();
let nextStartDay = new Date(currentYear, today.getMonth() + 1, 1);
let nextMonthStartWeek = nextStartDay.getDay();
let thisMonthArr = [];
let thisMonthArrCode = [];
let thisMonthDate;
let thisWeek;
[ 위 변수 지정 값들은 예매 페이지 구현 시 사용된 모든 변수명을 정리 해 놓은 것입니다. ]
2-1. 예매 Page Front설계
- 이 페이지를 설계 및 구조를 하면서 Slide 방식의 페이지 구현방식에 있어서 많은 성장을 하였다.

- 날짜를 하면서 가장 기억에 남았던 점은 아무래도 날짜 알고리즘이였다. 살면서 달력 만들일이 있다면 이번 프로젝트로 인해 기본적인 날짜관련 알고리즘을 많이 알게 되었다. 물론 날짜 api도 있었지만 사소한 부분에 있어서 내가 코드를 직접 짜보는 것도 좋다고 판단하여
2-2. 예매 Page에 모든 값 전송(PATCH방식)
- 내가 맡은 직무의 하이라이트 부분이다. 엄청나게 기나긴 여정이 될것같다. 다른 부분에서도 똑같지만 예매 에서는 연관되어있는 부분이 로그인이 완료된 회원의 경우, 예매 페이지에서 영화를 클릭하거나 지역, 지점을 클릭했을때 그 영화가 포함되어있는 경우 등 서로 주고받아야 할 데이터가 관련이 많기 때문에 초반에 설계를 하는데 있어서 많은 고민이 있었다. 내가 생각한 방식은 페이지를 우선 4개로 나뉘었다.
- 날짜, 영화제목, 지점 을 선택했을때 영화시간표가 나오는 페이지 1
- 사용자가 골라서 클릭한 영화시간표 정보가 표시되며 좌석 선택이 가능한 페이지 2
- 좌석선택까지 완료된 경우에서의 결제정보를 나타내는 페이지 3
- 결제까지 정상적으로 완료 되었을경우 예매 완료 인증을 보여주는 페이지 4
- 페이지를 나눔에 앞서서 하나의 Controller에 이 4개의 페이지의 기능을 구현하는게 효율적일까를 생각해보면서 검색엔진의 힘을 빌려보았다. 결과는 일단 상황에 맞는 구현이 최고라는 것이였다. 내가 생각해 보았을때 나중에 코드를 수정하거나 다시 볼때 편한것은 Controller를 4개로 나누는 것이 편하다고 생각했는데 완성작을 보고나니 하나의 Controller에 다 담은것이 이번 프로젝트에서는 더 효율적이라고 느껴졌다. page간의 서로 보내고 받는 데이터의 흐름을 더욱 잘볼 수 있었기 때문이다. 물론 회사를 다니거나 더 많은 방대한양의 데이터를 코드로 표현할때는 Controller도 지금보다는 세세하게 나누는것이 좋은 습관일것 같다. MVC의 목적은 "View와 Controller"의 분리, 즉 관심사가 분리되어야 한다는 명목만 이루어 진다면 그 이후부터는 개발자의 편리함과 효율성 사이에서 각자의 사용됨에 맞게 쓰는것이 좋은것 같다는 생각이 들었다.
@RequestMapping(value = "booking", method = RequestMethod.PATCH, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String patchBooking() {
JSONArray branchesJson = new JSONArray();
for (BranchEntity branch : this.movieService.getBranches()) {
JSONObject branchJson = new JSONObject();
branchJson.put("index", branch.getIndex());
branchJson.put("text", branch.getText());
branchJson.put("regionIndex", branch.getRegionIndex());
branchesJson.put(branchJson);
}
JSONArray moviesJson = new JSONArray();
for (MovieVo movie : this.movieService.getMovieVoByList()) {
JSONObject movieJson = new JSONObject();
movieJson.put("movieIndex", movie.getIndex());
movieJson.put("movieTitle", movie.getTitle());
movieJson.put("movieBranchIndex", movie.getMovieBranchIndex());
movieJson.put("movieReleaseDate", new SimpleDateFormat("yyyy-MM-dd").format(movie.getReleaseDate()));
movieJson.put("movieAgeLimit", movie.getAgeLimit());
moviesJson.put(movieJson);
}
JSONArray seatsJson = new JSONArray();
for (SeatVo seat : this.movieService.getSeatVos()) {
JSONObject seatJson = new JSONObject();
seatJson.put("seatIndex", seat.getIndex());
seatJson.put("seatColumnIndex", seat.getColumnIndex());
seatJson.put("seatColumnText", seat.getColumnText());
seatJson.put("seatRow", seat.getRow());
seatJson.put("seatAuditoriumIndex", seat.getAuditoriumIndex());
seatJson.put("seatCode", seat.getSeatCode());
seatsJson.put(seatJson);
}
JSONArray seatsAllJson = new JSONArray();
for (SeatVo seatAll : this.movieService.getSeatByAll()) {
JSONObject seatCountObject = new JSONObject();
seatCountObject.put("seatAudIndex", seatAll.getAuditoriumIndex());
seatCountObject.put("seatCountAll", seatAll.getCountSeatAll());
seatsAllJson.put(seatCountObject);
}
JSONArray seatColumnsJson = new JSONArray();
for (SeatVo seatColumn : this.movieService.getSeatVosGroupByColumn()) {
JSONObject seatColumnJson = new JSONObject();
seatColumnJson.put("seatColumnNumberOfColumn", seatColumn.getNumberOfColumn());
seatColumnJson.put("seatColumnAudIndex", seatColumn.getAuditoriumIndex());
seatColumnJson.put("seatColumnText", seatColumn.getColumnText());
seatColumnsJson.put(seatColumnJson);
}
JSONArray screenInfosAllJson = new JSONArray();
for (MovieScreenInfoVo screenInfo : this.movieService.getScreenInfos()) {
JSONObject screenInfoAllJson = new JSONObject();
screenInfoAllJson.put("screenInfoIndex", screenInfo.getIndex());
screenInfoAllJson.put("screenInfoMovieIndex", screenInfo.getMovieIndex());
screenInfoAllJson.put("screenInfoAuditoriumIndex", screenInfo.getAuditoriumIndex());
screenInfoAllJson.put("screenInfoMovieStartTime", new SimpleDateFormat("HH:mm").format(screenInfo.getMvStartTime()));
screenInfoAllJson.put("screenInfoMovieTime", new SimpleDateFormat("HH").format(screenInfo.getMvStartTime()));
screenInfoAllJson.put("screenInfoMovieEndTime", new SimpleDateFormat("HH:mm").format(screenInfo.getMvEndTime()));
screenInfoAllJson.put("screenInfoMovieTitle", screenInfo.getInfoMovieTitle());
screenInfoAllJson.put("screenInfoDate", new SimpleDateFormat("yyyy-MM-dd").format(screenInfo.getScreenDate()));
screenInfoAllJson.put("screenInfoMovieState", screenInfo.getInfoMovieState());
screenInfoAllJson.put("screenInfoBranchIndex", screenInfo.getInfoBranchIndex());
screenInfoAllJson.put("screenInfoBranchText", screenInfo.getInfoBranchText());
screenInfoAllJson.put("screenInfoAuditoriumText", screenInfo.getInfoAudText());
screenInfoAllJson.put("screenInfoMovieAgeLimit", screenInfo.getInfoMovieAgeLimit());
screenInfoAllJson.put("screenInfoMoviePoster", screenInfo.getInfoMoviePoster());
screenInfoAllJson.put("screenInfoSeatCountAll", screenInfo.getSeatIndex());
screenInfoAllJson.put("screenInfoSeatRemain", screenInfo.getSeatRemain());
screenInfosAllJson.put(screenInfoAllJson);
}
JSONArray bookingCompleteSeats = new JSONArray();
for (BookingVo bookingSeat : this.movieService.getBookings()) {
JSONObject bookingCompleteSeat = new JSONObject();
bookingCompleteSeat.put("bookingSeatComplete", bookingSeat.getSeatIndex());
bookingCompleteSeat.put("bookingSeatScreenInfoIndex", bookingSeat.getScreenInfoIndex());
bookingCompleteSeat.put("bookingSeatMvStartTime", new SimpleDateFormat("HH:mm").format(bookingSeat.getMvStartTime()));
bookingCompleteSeat.put("bookingSeatMvEndTime", new SimpleDateFormat("HH:mm").format(bookingSeat.getMvEndTime()));
bookingCompleteSeat.put("bookingSeatIndex", bookingSeat.getSeatIndex());
bookingCompleteSeats.put(bookingCompleteSeat);
}
JSONObject responseJson = new JSONObject();
responseJson.put("allScreenInfo", screenInfosAllJson);
responseJson.put("branch", branchesJson);
responseJson.put("movieTitle", moviesJson);
responseJson.put("seat", seatsJson);
responseJson.put("seatColumn", seatColumnsJson);
responseJson.put("seatComplete", bookingCompleteSeats);
responseJson.put("seatAll", seatsAllJson);
return responseJson.toString();
}
- 아래 코드는 최초 예매페이지를 접속했을시 PATCH요청방식으로 보내지는 데이터이다. 아래 코드를 보다보니 정말 비효율적이라는 것을 알 수 있었다. 프로젝트를 만들때는 몰랐던 필요한 정보뿐만 아니라 굳이 필요하지 않은 정보까지 같이 PATCH 요청방식을 통해 보내버린다는것이 나중에 데이터양이 극대화가 되었을때는 많이 비효율적이라는것을 다시 한번 느꼈다. 아래 코드를 보면 xml에서 select전체값을 우선 받아온다. 그 후 IMovieMapper(interface 역할)에서 배열로 받아온 값을 for문을 통해 JSONObject 각 객체에 담아준다. for문이 select된 길이 만큼 돌기 때문에 모든값을 한번씩 전부 JSONObject에 담기게 되고 그 JSONObjext들을 다시 JSONArray에 담는다. 각각의 Select된 값들이 이렇게 처리된후 responseObject에 담아주어서 return을 해주는 방식이다. 즉, 최초 영화, 날짜, 시간표, 등 모든 값을 페이지에 한번 요청하고 나서부터 작업이 시작된다는 뜻이다.

- 왜? 저렇게 보냈을까 라는 생각이 벌써든다. 만약 다시 구조를 설계한다면 클릭이 완료된 값들만 보냈으면 훨씬 효율적이였을텐데.. 시간을 가지고 천천히 수정해 봐야겠다.
2-3 요청된 값들 중 조건에 맞게 응답처리
- 모든값을 예매 페이지로 요청했기 때문에 실제 클라이언트가 필요한 정보만 보여지게 해야한다. 이에 대한 처리를 하기 위해서 JavaScript를 많이 활용하였다.(Service단과 Controller단, Xml단에서는 서로 데이터의 연결 및 회원인가의 유무, 비정상적인 루트로 접속하는 것의 여부 등의 기본적 조건만 제시하고 나머지 User의 정보와 DB정보의 비교에 대해서는 대부분 JavaScript로 처리하였다.)
let date = new Date();
let utc = date.getTime() + (date.getTimezoneOffset() * 60 * 1000);
let kstGap = 9 * 60 * 60 * 1000;
let today = new Date(utc + kstGap);
let currentMonth = ((today.getMonth()) + 1);
let currentYear = today.getFullYear();
let currentDay = today.getDate();
let thisMonthEndDay = new Date(currentYear, currentMonth, 0);
let thisMonthLast = thisMonthEndDay.getDate();
let nextStartDay = new Date(currentYear, today.getMonth() + 1, 1);
let nextMonthStartWeek = nextStartDay.getDay();
let thisMonthArr = [];
let thisMonthArrCode = [];
let thisMonthDate;
let thisWeek;
if (currentMonth < 10) {
currentMonth = '0' + currentMonth;
}
for (let i = currentDay; i <= thisMonthLast; i++) {
if (i < 10) {
thisMonthDate = currentYear + '-' + currentMonth + '-' + '0' + i;
} else {
thisMonthDate = currentYear + '-' + currentMonth + '-' + i;
}
thisMonthArrCode = thisMonthDate;
thisMonthArr.push(thisMonthArrCode);
let WEEKDAY = ['일', '월', '화', '수', '목', '금', '토'];
let week = new Date(today.setDate(i)).getDay();
thisWeek = WEEKDAY[week];
const dayElement = window.document.createElement('div');
dayElement.classList.add('day', 'current');
dayElement.dataset.value = thisMonthDate;
dayElement.innerText = i + '•' + thisWeek;
timeBox.append(dayElement);
let day = window.document.querySelectorAll('.day');
if (i === currentDay) {
dayElement.setAttribute('selected', 'selected');
}
if (thisWeek === '토') {
dayElement.style.color = 'blue';
} else if (thisWeek === '일') {
dayElement.style.color = 'red';
}
if (dayElement.getAttribute('selected')) {
dayElement.style.backgroundColor = 'rgb(235, 235, 235)'
}
for (let j = 0; j < day.length; j++) {
day[0].addEventListener('click', () => {
day[0].setAttribute('selected', 'selected');
day[0].style.backgroundColor = 'rgb(235, 235, 235)';
day[0].classList.add('on');
drawSubs();
});
day[j].addEventListener('click', () => {
let nextDay = window.document.querySelectorAll('.day.next');
if (day[j].classList[0] === 'on') {
day[j].classList.remove('on');
day[j].removeAttribute('selected');
} else {
for (let e = 0; e < day.length; e++) {
day[e].classList.remove('on');
day[e].removeAttribute('selected');
}
day[j].classList.add('on');
day[j].setAttribute('selected', 'selected');
if (!(day[j].getAttribute('selected')) && i === currentDay) {
day[0].setAttribute('selected', 'selected');
day[0].style.backgroundColor = 'rgb(235, 235, 235)';
day[0].classList.add('on');
} else {
day[0].removeAttribute('selected');
day[0].style.backgroundColor = 'rgb(255, 255, 255)';
day[0].classList.remove('on');
}
}
nextDay.forEach(nextDay => {
if (nextDay.classList.contains('on')) {
nextDay.classList.remove('on');
}
})
drawSubs();
Cover.hide();
});
}
}
- 위 코드는 이번달에 대한 알고리즘이다. today변수는 JavaScript에서 제공하는 Date의 값을 현재 우리나라 오늘의 시간으로 변경한 값이 된다. 이 today변수를 사용하여 이번달, 이번년도 등 다양한 메서드를 사용하여 필요한 값을 나타낼 수 있다. 특히 메가박스 예매 페이지 슬라이드 달력의 경우 21일치를 보여주는데 이번달과 다음달의 경계가 없기 때문에 특정 날짜부터는 다음달의 일수도 보여지게 된다. 고민을 하다가 결국 이번달과 다음달을 나타내는 경우로 나누기로 하고 다음달 관련 코드도 작성하게 되었다.
더보기
let nextMonthArr = [];
let nextMonthArrCode = [];
let nextMonthDate;
let nextWeek;
let nextMonth = (today.getMonth()) + 2;
if (currentMonth === '12') {
currentYear = (today.getFullYear() + 1);
nextMonth = (currentMonth - 11);
}
if (nextMonth < 10) {
nextMonth = '0' + nextMonth;
}
for (let i = 1; i <= 21 - (thisMonthLast - currentDay + 1); i++) {
if (i < 10) {
nextMonthDate = currentYear + '-' + nextMonth + '-0' + i;
} else {
nextMonthDate = currentYear + '-' + nextMonth + '-' + i;
}
nextMonthArrCode = nextMonthDate;
nextMonthArr.push(nextMonthArrCode);
let WEEKDAY = ['일', '월', '화', '수', '목', '금', '토'];
let week = new Date(today.setDate(nextMonthStartWeek + i)).getDay();
nextWeek = WEEKDAY[week];
const dayElement = window.document.createElement('div');
dayElement.classList.add('day', 'next');
dayElement.dataset.date = nextMonthDate;
dayElement.innerText = i + '•' + nextWeek;
timeBox.append(dayElement);
if (nextWeek === '토') {
dayElement.style.color = 'blue';
} else if (nextWeek === '일') {
dayElement.style.color = 'red';
}
if (dayElement.getAttribute('selected')) {
dayElement.style.backgroundColor = 'rgb(235, 235, 235)'
}
let day = window.document.querySelectorAll('.day.next');
let dayCurrent = window.document.querySelectorAll('.day.current');
for (let j = 0; j < day.length; j++) {
day[j].addEventListener('click', () => {
if (day[j].classList[0] === 'on') {
day[j].classList.remove('on');
day[j].removeAttribute('selected');
} else {
for (let e = 0; e < day.length; e++) {
day[e].classList.remove('on');
day[e].removeAttribute('selected');
}
day[j].classList.add('on');
day[j].setAttribute('selected', 'selected');
}
for (let x = 0; x < dayCurrent.length; x++) {
if (day[j].classList.contains('on')) {
dayCurrent[x].classList.remove('on');
dayCurrent[0].classList.remove('on');
dayCurrent[0].removeAttribute('selected');
dayCurrent[0].style.backgroundColor = 'rgb(255, 255, 255)';
}
}
drawSubs();
});
}
}
- 이 달력을 만들당시가 22년 12월이였는데 문제에 봉착하게 된건 다음년도의 값이 나타나야 하는 경우 였다. 다음년도값만 표시하는 것은 어렵지 않았으나 월,일,요일 등의 이어짐이 달력의 기본적인 알고리즘인데 이것들이 생각보다 시간이 오래걸렸다.
- 우선 월요일~일요일을 배열안에 집어 넣고 Weeks 상수로 잡았다. 그후 달마다의 일수를 나타내는 변수 week의 set메서드를 통해 그달의 일수만큼 반복이 되게 하여 월~일요일을 계속해서 반복하여 나타나게 하였다. 이 후 토요일과 일요일일 경우에는 각각 파란색, 빨간색의 color를 표시하였고 dayElement중 selected(선택되어진) 블럭의 경우 회색으로 표시되게 하였다. 다음달을 나타내는 로직에서 전체 for문은 이번달의 마지막일수에서 현재 오늘의 일수를 뺀값을 21에서 빼서 나타낸 값만큼 반복을 시켰다. 위에서 얘기했던것 처럼 예매 페이지에서는 21일 만큼의 일자만 나타내었는데 이때 이번달,다음달이 같이 나타나야 하는경우 21일에서 이번달 남은 수만큼뺀 일수 만큼 다음달의 일수가 채워져야 했기 때문에 위와 같이 로직을 구성하였다. 그후 세부 for문에서는 각각의 블록이 하나만 클릭되게 하고 다른 블록이 클릭되면 이전 블록은 선택이 되지 않도록 하기위한 목적으로 로직을 구성하였다