스프링부트+jsp로 배달사이트 만들기-31 웹소켓으로 실시간 주문받기

2021. 12. 17. 21:08스프링부트

STOMP를 이용해 아래와 같은 방식으로 구현하였습니다

  1. 주문페이지에서 주문완료 시 관리자페이지로 메세지전달 
  2. 관리자페이지에서 메세지를 주문완료 받으면 db에서 검색

 

storeDetail.js의 해당 부분을 수정합니다

input의 값을 가져오는 방식에서 url에서 가져오는 방식으로 변경하였습니다

 

주문시 이동페이지를 가게id를 붙여서 이동하게끔 변경합니다

이동 url에 맞게 orderController도 수정합니다

 

 

order폴더의 order.jsp와 admin폴더의 order.jsp 두 곳 상단에 추가합니다

<!-- sock js -->
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.5.2/sockjs.min.js"></script>
<!-- STOMP -->
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>

 

 

 

order.js의 상단에 코드를 추가하고

const pathArr = location.pathname.split("/");
const storeId = pathArr[pathArr.length-1];

아래 부분 주석을 해제합니다

주석 해제한 messageSend()를 수정합니다

// 관리자 페이지로 주문요청 메세지
function messageSend() {
	let socket = new SockJS('/websocket');

	let stompClient = Stomp.over(socket);

	stompClient.connect({}, function() {
		const message = {
			message : "새 주문이 들어왔습니다"
		}
		stompClient.send("/message/order-complete-message/" + storeId, {}, JSON.stringify(message));
		stompClient.disconnect();
	});
}

주문 완료시 messageSend() 요청하면 /message/order-complete-message/" + storeId로 메세지를 보냅니다

 

 

pom.xml에 추가합니다

		<!-- 웹소켓 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-websocket</artifactId>
		</dependency>

 

config 패키지에 웹소켓 설정을 위한 클래스를 생성합니다

@EnableWebSocketMessageBroker
@Configuration
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

	@Override
	public void registerStompEndpoints(StompEndpointRegistry registry) {
		  registry.addEndpoint("/websocket").withSockJS();
	}
	
	@Override
	public void configureMessageBroker(MessageBrokerRegistry registry) {
		registry.enableSimpleBroker("/topic");
		registry.setApplicationDestinationPrefixes("/message");
	}
}
  • @EnableWebSocketMessageBroker 웹소켓 사용
  • addEndpoint - 웹 소켓으로 사용할 주소
  • withSockJs()  - 버전이 낮은 브라우저에서도 사용가능
  • enableSimpleBroker - "/topic"이 붙은 클라이언트에게 메세지를 전송
  • setApplicationDestinationPrefixes - 클라이언트에게서 메세지를 받을 서버주소의 prefix

 

메세지를 관리할 컨트롤러를 생성합니다

@Controller
public class MessageController {
	
	
	@MessageMapping("/order-complete-message/{storeId}")
	@SendTo("/topic/order-complete/{storeId}")
	public String message(@DestinationVariable long storeId, String message) {
		System.out.println("가게번호 : " + storeId);
		System.out.println("메세지 도착 :" + message);
		return message;
	}

}

메세지 전달 시 사용할 주소는 @GetMapping 대신 @MessageMapping을 사용하고 {} 사이에 받은 변수는 @PathValiable 대신 @DestinationVariable을 사용합니다

 

주문완료 시 /message/order-complete-message/" + storeId로 메세지를 보내지만 설정파일의 setApplicationDestinationPrefixes() 메서드에 /"message"를 prefix로 사용했기 때문에 

"/order-complete-message/{storeId}"만 사용합니다

 

@SendTo("/topic/order-complete/{storeId}") 해당 주소를 구독하고 있는 클라이언트에게 메세지를 전달합니다

 

 

adminOrder.js의 아래 부분의 주석을 해제하고 주소를 수정합니다 

메세지를 받았을때 현재 출력중인 주문목록의 수가 10개 이하일때만 새 주문목록을 가져올 수 있게 했습니다

 

 

 

 


21-12-21 수정

현재 페이지가 2 이상일땐 주문수락,거부 시 목록이 사라지지 않는 문제가 있어서 수정합니다

 

AdminMapper

페이지의 시작 부분을 1로 수정합니다

 

 

 

adminOrder.js를 수정합니다

주문수락, 주문거부, 배달완료 처리시 마다 orderList를 불러오지 않고 추가한 listRefresh 함수로 목록을 삭제합니다

출력중인 목록 수가 전체 목록 수보다 적을 시 orderList를 실행합니다

 

adminOrder.js

$(document).ready(function(){

const pathArr = location.pathname.split("/");
const storeId = pathArr[pathArr.length-1];
const listView = 10; // 주문 목록 최대 갯수

$(".move_top").click(function(){
	$("html").animate({ scrollTop: 0 }, 200);
})
	

function errMsg(status){
	if(status.status == 401) {
		alert("권한이 없습니다");
	} else {
		alert("에러");
	}
}

const listInfo = (function(){
	const listArr = ["주문접수 대기 중", "배달 준비 중", "완료"];
	let nowList = listArr[0];
	let page = 1;
	let runNextPage = false; // false일때만 다음페이지 불러올수있다
	let waitCount = 0;
	let procCount = 0;
	let orderList = [];
	let cartList = [];
	
	const getNowList = function(){
		return nowList;
	}
	const setNowList = function(set){
		nowList = listArr[set];
	}
	const resetPage = function(){
		page = 1;
	}
	const nextPage = function(){
		page++;
	}
	const setPage = function(set){
		page = set;
	}
	const nowPage = function(){
		return page;
	}
	const getRunNextPage = function(){
		return runNextPage;
	}
	const setRunNextPage = function(set){
		runNextPage = set;
	}
	const setWaitCount = function(set){
		waitCount = set;
	} 
	const getWaitcount = function() {
		return waitCount;
	}
	const setProcCount = function(set){
		procCount = set;	
	}
	const getProcCount = function(){
		return procCount;
	}
	const getOrderList = function(index){
		return orderList[index];
	}
	const setOrderList = function(set){
		orderList = set;
	}
	const concatOrderList = function(set){
		orderList = orderList.concat(set);
	}
	const getCartList = function(index){
		return cartList[index];
	}
	const setCartList = function(set){
		cartList = set;
	}
	const concatCartList = function(set){
		cartList = cartList.concat(set);
	}
	const resetList = function(){
		cartList = [];
		orderList = [];
	}
	const removeCartList = function(index) {
		cartList.splice(index, 1);
	}
	const removeOrderList = function(index) {
		orderList.splice(index, 1);
	}
	
	return {
		getNowList : getNowList,
		setNowList : setNowList,
		resetPage : resetPage,
		nextPage : nextPage,
		setPage : setPage,
		nowPage : nowPage,
		getRunNextPage : getRunNextPage,
		setRunNextPage : setRunNextPage,
		setWaitCount : setWaitCount,
		getWaitcount : getWaitcount,
		setProcCount : setProcCount,
		getProcCount : getProcCount,
		getOrderList : getOrderList,
		setOrderList : setOrderList,
		getCartList : getCartList,
		setCartList : setCartList,
		concatOrderList : concatOrderList,
		concatCartList : concatCartList,
		resetList : resetList,
		removeCartList : removeCartList,
		removeOrderList : removeOrderList,
		
	}
})();




function htmlWrite(result){
	let html = "";
	for(var i=0;i<result.cartList.length;i++) {
		const orderList = result.orderList[i];
		const cartList = result.cartList[i];
		
		let foodInfo = [];
		for(var j=0;j<cartList.length;j++) {
			foodInfo.push(foodHtml(cartList[j]));	
		}
		
		let btnValue = "";
		let btnClass = "";
		if(listInfo.getNowList() == '주문접수 대기 중') {
			btnValue = "주문 접수";
			btnClass = "order_accept";
		} else {
			btnValue = "완료";
			btnClass = "complete";
		}
		
		html += 
			`<li class="order_box">
				<div class="time">
	    			<div>${moment(orderList.orderDate ).format("MM월 DD일")}</div>
	    			<div>${moment(orderList.orderDate ).format("HH시 mm분")}</div>
	    		</div>
   	
	    		<div class="info">
              		<div style="font-weight: bold;">
               			<span>
              				<span>[메뉴  ${cartList.length }개] ${orderList.totalPrice }원</span> 
              				<span class="payMethod"> ${orderList.payMethod }</span>
            			</span>
           			</div>
                        		
               		<div>${foodInfo } </div>
               		<div>${orderList.deleveryAddress2 }</div>
               		
               		<div>${orderList.storeName }</div> 
	            </div>     	
	            		
                <div class="button_box">
                 	<input type="button" value="${btnValue}" class="${btnClass} btn">
                 </div>
			</li>`;
	}
	return html;
}



function foodHtml(cart){
	let food = cart.foodName;
	
	let option = [];
	if(cart.optionName != null) {
		for(var i=0;i<cart.optionName.length;i++) {
			option.push(cart.optionName[i]);
		}	
	}
	
	if(option != "") {
		option = '[' + option + ']';
	}
	
	return food + option;
}




function orderList(){
	const page = listInfo.nowPage();
	const list = listInfo.getNowList();
	// listInfo.setRunNextPage(true);
	
	$.ajax({
		url: "/admin/management/orderList",
		type: "get",
		data: {
			storeId : storeId,
			list : list,
			page : page
		}	
	})
	.done(function(result){
		const count1 = result.orderList[0].count1;
		const count2 = result.orderList[0].count2;
		
		listInfo.setWaitCount(count1);
		listInfo.setProcCount(count2);
		$(".wait_count").text(count1);
		$(".processing_count").text(count2);
			
			
		const html = htmlWrite(result, list);
		
		$(".order_list").html(html);	
		listInfo.setCartList(result.cartList);
		listInfo.setOrderList(result.orderList);
			
		
	})
	.fail(function(data){
		errMsg(data);
	})	 
}	




// 주문 완료 메세지 받기
const socket = new SockJS('/websocket');
const stompClient = Stomp.over(socket);

stompClient.connect({}, function() {

	stompClient.subscribe('/topic/order-complete/' + storeId, function(message) {
		// 화면에 출력중인 view 갯수 
		const list = $(".order_list li").length;
		
		if(list == listInfo.getWaitcount()) {
			orderList();
		}
	});
});



// 접수대기, 처리 중 목록 클릭
$(".aside_tab li").click(function(){
	$(".order_list").html("");
	$(".aside_tab li").removeClass("active");
	$(this).addClass("active");
	
	const index = $(this).index();
	listInfo.setNowList(index);
	listInfo.resetPage();
	listInfo.setRunNextPage(false);
	
	orderList();
})



// 스크롤시 다음 페이지
$(window).scroll(function(){
	const winHeight = $(window).height();
	const docHeight = $(document).height();
	const top = $(window).scrollTop();
	
	if(docHeight <= winHeight + top + 10) {
		if(!listInfo.getRunNextPage()) {
			
			// listInfo.nextPage();
			const list = $(".order_list li").length;
			if(list == 0) {
				listInfo.resetPage();
			} else {
				if(list == (listView * listInfo.nowPage())) {
					listInfo.setPage(Math.floor((list - 1) / listView) + 2);
				}
			}
			orderList();
			listInfo.setRunNextPage(true);
			
			setTimeout(function(){
				listInfo.setRunNextPage(false);
			},2000);
			
			return;
		}
		
	} 
}) // scroll


orderList();



function listRefresh(index, count){
	listInfo.removeCartList(index);
	listInfo.removeOrderList(index);
	$(".order_box").eq(index).remove();
	
	
	const list = $(".order_list li").length;
	if(list == 0) {
		listInfo.resetPage();
	} else {
		listInfo.setPage(Math.floor((list - 1) / listView) + 1);
	}
	
	if(list < count) {
		console.log("lsit : " +  list + " + count : " + count);
		orderList();
	}
}



// 주문수락 시 
function accept(index){
	const waitCount = listInfo.getWaitcount() - 1;
	const procCount = listInfo.getProcCount() + 1;
	$(".wait_count").text(waitCount);
	$(".processing_count").text(procCount);
	listInfo.setWaitCount(waitCount);
	listInfo.setProcCount(procCount);
	
	const count = listInfo.getWaitcount();
	listRefresh(index, count);
	
}

// 주문취소 시
function cancle(index) {
	const waitCount = listInfo.getWaitcount() - 1;
	$(".wait_count").text(waitCount);
	listInfo.setWaitCount(waitCount);
	
	const count = listInfo.getWaitcount();
	listRefresh(index, count);
}



// 주문완료 시
function complete(index) {
	const procCount = listInfo.getProcCount() - 1;
	$(".processing_count").text(procCount);
	listInfo.setProcCount(procCount);
	
	const count = listInfo.getProcCount();
	listRefresh(index, count);
}


// 주문접수 모달 
$(document).on("click", ".order_accept", function(){
	const modal = $(".order_accept_modal");
	const orderIndex = $(this).parents("li").index();
	console.log("orderIndex = " + orderIndex);
	
	const orderInfo = listInfo.getOrderList(orderIndex);
	const foodInfo = listInfo.getCartList(orderIndex);
	
	const orderNum = orderInfo.orderNum;
	const userId = orderInfo.userId;
	const deleveryAddress2 = orderInfo.deleveryAddress2;
	const deleveryAddress3 = orderInfo.deleveryAddress3 ? orderInfo.deleveryAddress3 : "";
	const request = orderInfo.request ? orderInfo.request : ""; 
	const phone = orderInfo.phone;
	
	let food = "";
	for(i=0;i<foodInfo.length;i++) {
		food += `<li>${ foodHtml(foodInfo[i]) }  ${ foodInfo[i].amount }개</li>`
	}
	
	
	const addressHtml = `<div>${deleveryAddress2}</div>
                    	<div>${deleveryAddress3}</div>
                    	<div>${phone}</div>`
                  	
	
	modal.find(".delevery_address").html(addressHtml);
	modal.find(".request > div").text(request);
	modal.find(".menu ul").html(food);

	openModal(modal);
	
	
	 
	 const timerModal = $(".delevery_timer_modal"); 
	 
	// 배달시간 설정 모달
	$(".delevery_timer_btn").off().click(function(){
		timerModal.find("li").removeClass("select");
		timerModal.find("li[data-time=30]").addClass("select");
		openModal(timerModal);
	})
 		
 		
	// 시간 설정	
	$(".delevery_timer_modal li").off().click(function(){
		timerModal.find("li").removeClass("select");
		$(this).addClass("select");
	})
		
		
	// 주문수락 완료	
	$(".accept").off().click(function(){
		const time = $(".delevery_timer_modal .select").data("time");
		
		if(!time) {
			swal("시간을설정해주세요");
			return;
		}
		
		const data = {
			orderNum : orderNum,
			time : time,
			userId : userId
		}
		
		$.ajax({
			url: "/admin/management/orderAccept",
			data: data,
			type: "PATCH"
		})
		.done(function(){
			swal("주문접수완료");
			closeModal();
			accept(orderIndex);
			
			
		})
		.fail(function(data){
			errMsg(data);
		})
		
	})
	
	
	// 주문 거부하기
	$(".order_cancle_btn").off().click(function(){
		const cancleModal = $(".order_cancle_modal");
		openModal(cancleModal);
		cancleModal.find("li").removeClass("select");
		
		let cancleReason = "";
		
		// 거부사유 선택
		cancleModal.find("li").off().click(function(){
			cancleModal.find("li").removeClass("select");
			$(this).addClass("select");
			cancleReason = $(this).data("reason");
		})


			
		// 거부하기
		$(".order_cancle").off().click(function(){
			const impUid = orderInfo.impUid;
			const totalPrice = orderInfo.totalPrice;
			const usedPoint = orderInfo.usedPoint;
			const deleveryTip = orderInfo.deleveryTip;
			
			if(!cancleReason) {
				swal('주문거부 사유를 선택해주세요');
				return;
			}
			
			const data = {
				orderNum : orderNum,
				cancleReason : cancleReason,
				userId : userId,
				impUid : impUid,
				totalPrice : totalPrice,
				usedPoint : usedPoint,
				deleveryTip : deleveryTip
			}
			
			$.ajax({
				url: "/admin/management/orderCancle",
				type: "PATCH",
				data: data
			})
			.done(function(){
				cancle(orderIndex); 
				swal("취소완료");
				// 결제 취소하기 추가
				
				closeModal();
				
			})
			.fail(function(data){
				errMsg(data);
			})
		})
	})
})



	

// 배달 완료	
$(document).on("click", ".complete", function(){
	const orderIndex = $(this).parents("li").index();
	const orderInfo = listInfo.getOrderList(orderIndex);
	const orderNum = orderInfo.orderNum;
	console.log(orderNum);
	console.log(orderIndex);
	
	const userId =  orderInfo.userId;
	const data = {
		userId : userId,
		orderNum : orderNum
	}
	
	swal("배달 완료후 눌러주세요", {
		  buttons: ["취소", "완료"],
	})
	.then(function(value){
		if(!value) {
			return;
		}
		$.ajax({
			url: "/admin/management/orderComplete",
			type: "PATCH",
			data: data
		})
		.done(function(result){
			complete(orderIndex);
		})
		.error(function(){
			swal("에러");
		})
	}) 
})
	
	
	
})