요약정리
실시간 주문을 하기 위해서 백엔드에서 MQTT를 사용하기 위한 준비를 함
백엔드에 MQTT설치
npm i aedes --save
웹소켓설치
npm i websocket-stream --save
exp > routes > fd_> fd_broker.js 생성
인증이 필요하고, 인증이 없다면 서버를 알아도 아이디, 비번 송수신을 못함
서버생성
웹용, 앱용 서버 만들음
app.js
require('./routes/fd_/fd_broker')
추가
지금은 웹용할거니 1884만 할거임
참고
MQTT프로토콜은 배달대행이나 배민에서씀.
socket TCP 온라인게임에서 많이씀
socket TCP/IP는 가만히 있어도 데이터가 와야함
websocket 대표적인 예는 web채팅등
웹이랑 앱이랑 혼용을 해야하는데
그런 필요한 것들을 프로토콜이라하는데 종류는
XMPP = > 라인, 카카오
MQTT = > 페이스북, 배달대행
IBM, c버전, java버전, nodejs버전 우리는nodejs버전으로 깔음
publish 발행자
qos 퀄리티오브 서비스(데이터 전송 주기, 이게 주기가 짧을수록, 서버 과부화)
subscribe 구독자
아래서 설명
식당으로 넘어감
CMD> npm i precompiled-mqtt --save
뷰에서 설치( order에서 실시간 주문을 사용하기 위해서)
더보기 버튼 만들어봄
vue > fd_customer > components> RestaurantPage.vue
<button @click="handleNext()">더보기</button>
const state = reactive({
cate : route.query._id,
page : '1',
rows : [],
});
const handleData = async() =>{
const url = ``
const headers = {"Content-Type":"application/json"}
const {data} = await axios.get(url, {headers});
console.log(data);
if(data.status === 200){
state.rows = state.rows.concat(data.rows);
}
};
const handleNext = async() =>{
state.page++;
handleData();
};
화면표기 vue에서 v-for 활용하여 idx 붙여줘봄
vue > fd_customer > components> FoodPage.vue
<div class="item" v-for="(tmp, idx) of rows" :key="tmp" @click="handleOrder(tmp._id)">
{{idx+1}}
</div>
params 로 cnt 넘겨줘보기
param은 한번만 전달 f5누르면 정보 없어짐
주문
npm i precompiled-mqtt --save
Order페이지에 실시간 만드는중
오전일과 ( HomePage, Restaurant, Food, Order(실시간 할 예정))
주문 실시간(Order)을 만들어 볼 것임
MQTT프로토콜은 배달대행이나 배민에서씀.
socket TCP 온라인게임에서 많이씀
지금 우리가 배우는 것은 요청하면 결과를 받는것http WEB => vue에서 요청하면 결과를 반환
socket TCP/IP는 가만히 있어도 데이터가 와야함
websocket web채팅등
우리는 배민 만들고 있으니 MQTT 활용 예정
고객이 주문하고 식당에서는 화면 갱신이 자동으로 되는 것을
만들 예정
백엔드에 MQTT설치
npm i aedes --save
웹소켓설치
npm i websocket-stream --save
exp > routes > fd_> fd_broker.js
var express = require('express');
var router = express.Router();
// CMD > npm i aedes
// 인증설정하기(id=aaa, pw=bbb)
// 클라이언트, 아이디, 암호(byte배열)
// bit(0,1) => byte(010101)
const aedes = require('aedes')({
// 1. 인증하기
authenticate : (client, username, password, callback) => {
//byte배열을 문자열로 변경이 먼저
var pw = Buffer.from(password);
callback(null, (username === 'aaa' && pw.toString() === 'bbb'));
},
// 2. 전송가능
authorizePublish : (client, packet, callback) => {
callback(null, packet)
},
// 3. 수신가능
authorizeSubcribe : (client, packet, callback) => {
callback(null, packet)
},
});
// 서버생성
const httpServer = require('http').createServer();
const ws = require('websocket-stream');
ws.createServer({server:httpServer}, aedes.handle);
httpServer.listen(1884, function(){
console.log('web mqtt 1884포트 구동');
});
const tcpServer = require('net').createServer(aedes.handle);
tcpServer.listen(1883, function(){
console.log('smart app용 mqtt 1883포트 구동');
})
// 잘 접속되나 확인
// client안에 정보가 많아서 아이디만 빼줌
aedes.on('client', function(client){
console.log(`${client.id}접속함`);
});
aedes.on('clientDisconnect', function(client){
console.log(`${client.id} 나감`);
});
module.exports = router;
인증하기
byte배열을 문자열로 변경이 먼저
var pw = Buffer.from(password);
전송가능
수신가능
서버를 알아도 아이디, 비번 모르면 전송 수신 못함
서버생성
웹용, 앱용 서버 만들음
이러면 웹에서 만든데이터가 앱으로 앱에서 만든 데이터가 웹으로 가능
app.js 에 fd_broker 등록
// REST가 아닌 broker
require('./routes/fd_/fd_broker')
참고
서버 구축하고 연결연동하는걸 브릿지라함
지금은 웹용할거니 1884만 할거임
------------------------------
ex)배달대행
publish => 발행자(식당) 부산진구
qos => 0(전송), 1(전송3~4번), 2(전송확인) (뜻 : 퀄리티오브서비스)
채팅 같은데에서 사용, 우리는 간단하게 0번 사용(1번만 보내는거)
1번(3~4번 보내라), 2번(받을때까지)
이게 많아질수록 서버 과부하
subscribe => 구독자(배달자)
/한국/부산/부산진구/ =>2명
/한국/부산/해운대구/ =>1명
topic => (/한국/부산/부산진구/010-1111-1111)
topic => (/한국/부산/부산진구/010-1111-1112)
topic => (/한국/부산/해운대구/010-1111-1113)
topic => (/한국/부산/수영구/010-1111-1114)
뷰작업
vue > fd_customer > components> HomePage.vue
<template>
<div>
{{state}}
<h3>홈페이지</h3>
<div class="box">
<div class="item" v-for="tmp of rows" :key="tmp"
@click="handlePage(tmp._id)">
{{tmp.content}}
</div>
</div>
</div>
</template>
<script>
import { reactive, toRefs } from '@vue/reactivity'
import { onMounted } from '@vue/runtime-core';
import axios from 'axios';
import { useRouter } from 'vue-router';
export default {
setup () {
const router = useRouter();
const state = reactive({
rows : []
});
onMounted(()=>{
handleData();
});
const handleData = async() =>{
const url = `/api/fd_category/select.json`;
const headers = {"Content-Type" : "application/json"}
const {data} = await axios.get(url, {headers});
console.log(data);
if(data.status === 200){
state.rows = data.rows
}
};
const handlePage = async(code) =>{
router.push({path:'/restaurant', query:{_id:code}})
}
return {
state,
...toRefs(state),
handlePage
}
}
}
</script>
<style lang="css" scoped>
.box{
border : 1px solid #cccccc;
display: grid;
width: 600px;
grid-template-columns: 1fr 1fr 1fr;
column-gap: 5px;
row-gap: 5px;
}
.item{
border : 1px solid #cccccc;
padding: 10px;
cursor: pointer;
}
</style>
vue > fd_customer > components> RestaurantPage.vue
<template>
<div>
<h3>식당목록</h3>
<router-link to="/home"><button>이전페이지</button></router-link>
<div class="box">
<div v-show="rows.length <=0">
<p>식당이 없습니다.</p>
</div>
<div v-show="rows.length >0" class="item" v-for="(tmp, idx) of rows" :key="tmp"
@click="handlePage(tmp._id)">
{{idx+1}}
{{tmp._id}}
{{tmp.name}}
{{tmp.phone}}
{{tmp.name}}
</div>
</div>
<button @click="handleNext()">더보기</button>
</div>
</template>
<script>
import { reactive, toRefs } from '@vue/reactivity'
import { useRoute, useRouter } from 'vue-router';
import { onMounted } from '@vue/runtime-core';
import axios from 'axios';
export default {
setup () {
const route = useRoute();
const router = useRouter();
const state = reactive({
cate : route.query._id,
page : '1',
rows : [],
});
const handleData = async() =>{
const url = `/api/fd_restaurant/selectcategory.json?cate=${state.cate}&page=${state.page}`
const headers = {"Content-Type":"application/json"}
const {data} = await axios.get(url, {headers});
console.log(data);
if(data.status === 200){
state.rows = state.rows.concat(data.rows);
}
};
onMounted(()=>{
handleData();
});
const handlePage = async(code) =>{
router.push({path:'/food', query:{_id:code}})
};
const handleNext = async() =>{
state.page++;
handleData();
};
return {state, ...toRefs(state), handlePage, handleNext}
}
}
</script>
<style lang="css" scoped>
.box{
border : 1px solid #cccccc;
display: grid;
width: 500px;
grid-template-columns: 1fr;
column-gap: 5px;
row-gap: 5px;
}
.item{
border : 1px solid #cccccc;
padding: 10px;
cursor: pointer;
}
</style>
더보기 버튼으로 데이터 계속 추가 (handleData)
state.rows = state.rows.concat(data.rows);
concat 합치기
handleNext 변수 선언( page++, data다시 호출)
===============================================
오후일과
vue > fd_customer > components> OrderPage.vue
<template>
<div>
{{state}}
</div>
</template>
<script>
import { reactive, toRefs } from '@vue/reactivity'
import { useRoute, useRouter } from 'vue-router'
import { onMounted } from '@vue/runtime-core';
import axios from 'axios';
import mqtt from 'precompiled-mqtt';
export default {
setup () {
const route = useRoute();
const router = useRouter();
const state = reactive({
rcode : route.params.rcode,
menu : Number(route.params.menu),
cnt : Number(route.params.cnt),
token : sessionStorage.getItem("TOKEN1"),
host : '127.0.0.1', //mqtt broker서버주소
port : 1884, //포트번호
options : {
clean : true, //세션 초기화
reconnectPeriod : 20000, //재접속시간
clientId : 'web_cs_' + new Date().getTime(), // 접속아이디(고유)
username : 'aaa', //아이디
password : 'bbb' //암호
},
client : null, // 접속객체
});
onMounted(() => {
mqttConnection();
if( confirm('주문할까요?') ){
handleOrder();
}
else {
router.push({path:'/food', query:{_id:state.rcode}});
}
});
const handleOrder = async()=>{
const url = `/api/fd_order/insert.json`;
const headers = {
"Content-Type":"application/json",
token :state.token
};
const body = {"cnt":state.cnt, "foodcode":state.menu};
const { data } = await axios.post(url, body, {headers});
console.log(data);
if(data.status === 200){
// 실시간으로 식당으로 주문완료를 전송
// 주문번호 => data.result._id
if(state.client !== null){
//식당한테 보내는 토픽
const topic = `/restaurant/${state.rcode}`
// 보낼내용(type:order로 code:주문번호)
const payload = JSON.stringify({
type:'order',
code : data.result._id
})
const qos = 0;
state.client.publish(topic, payload, qos, error =>{
if(error){
alert('mqtt오류')
}
});
}
alert('주문완료');
//페이지 이동
// router.go(-1);
}
};
const mqttConnection = () => {
const url = `ws://${state.host}:${state.port}`;
try {
state.client = mqtt.connect(url, state.options);
state.client.on('connect',() => {
console.log('connect success');
});
state.client.on('error', error => {
console.log('connect error', error);
});
}
catch(e) {
console.log('mqtt error', e);
}
};
return {
state,
...toRefs(state)
};
}
}
</script>
<style lang="scss" scoped>
</style>
{ path:'/order', name:'Order', component:Order },
params 로 cnt 넘겨줘보기
param은 한번만 전달 f5누르면 정보 없어짐
주문
npm i precompiled-mqtt --save
Order페이지에 실시간 만드는중
import mqtt from 'precompiled-mqtt';
const state = reactive({
rcode : route.params.rcode,
menu : Number(route.params.menu),
cnt : Number(route.params.cnt),
token : sessionStorage.getItem("TOKEN1"),
host : '127.0.0.1', //mqtt broker서버주소
port : 1884, //포트번호
options : {
clean : true, //세션 초기화
reconnectPeriod : 20000, //재접속시간
clientId : 'web_cs_' + new Date().getTime(), // 접속아이디(고유)
username : 'aaa', //아이디
password : 'bbb' //암호
},
client : null, // 접속객체
});
onMounted(() => {
mqttConnection();
if( confirm('주문할까요?') ){
handleOrder();
}
else {
router.push({path:'/food', query:{_id:state.rcode}});
}
});
const handleOrder = async()=>{
const url = `/api/fd_order/insert.json`;
const headers = {
"Content-Type":"application/json",
token :state.token
};
const body = {"cnt":state.cnt, "foodcode":state.menu};
const { data } = await axios.post(url, body, {headers});
console.log(data);
if(data.status === 200){
// 실시간으로 식당으로 주문완료를 전송
// 주문번호 => data.result._id
if(state.client !== null){
//식당한테 보내는 토픽
const topic = `/restaurant/${state.rcode}`
// 보낼내용(type:order로 code:주문번호)
const payload = JSON.stringify({
type:'order',
code : data.result._id
})
const qos = 0;
state.client.publish(topic, payload, qos, error =>{
if(error){
alert('mqtt오류')
}
});
}
alert('주문완료');
//페이지 이동
// router.go(-1);
}
};
const mqttConnection = () => {
const url = `ws://${state.host}:${state.port}`;
try {
state.client = mqtt.connect(url, state.options);
state.client.on('connect',() => {
console.log('connect success');
});
state.client.on('error', error => {
console.log('connect error', error);
});
}
catch(e) {
console.log('mqtt error', e);
}
};
return {
state,
...toRefs(state)
};
식당으로 넘어감
vue > fd_restaurant > components> restaurant> OrderPage.vue
하기전에 해당식당주문내역 불러오기 백엔드를 안짜놔서
먼저 백엔드 작업부터 해줌(고객용에 else if를 사용하여 코드를 하나 더 안짜게끔 만듬)
exp> routes> fd_> fd_order.js
// 주문내역(고객용, 가게용)
// 127.0.0.1:3000/api/fd_order/select.json
router.get('/select.json', auth.checkToken ,async function(req, res, next) {
try {
// 주문고객 아이디
const customerID = req.body.FID;
const role = req.body.ROLE;
console.log(customerID);
console.log(role);
if(role === 'CUSTOMER'){
const query = { customercode : customerID};;
const result = await Order.find(query).sort({regdate:1});
if(result !== null){
// 음식정보 없음
// 주문내역 반복
// 주문내역의 foodcode와 일치하는 Food 정보 가져오기
let arr = [];
for(let tmp of result){
const query1 = {_id : tmp.foodcode};
const project1 = {imagedata : 0, imagename:0, imagetype:0, imagedata:0};
const result1 = await Food.findOne(query1).select(project1);
console.log(result1);
arr.push({order:tmp, result1:result1})
}
return res.send({status:200, result:arr});
}
}
else if(role === 'RESTAURANT'){
const query = { restaurantcode : customerID };;
const project = { _id:1, name:1};
const result = await Food.find(query).select(project).sort({regdate:1});
console.log(result);
if(result !== null){
let arr = [];
for(let tmp of result){
arr.push(tmp._id);
}
const query1 = {foodcode : {$in : arr}};
const result1 = await Order.find(query1).sort({regdate:-1});
console.log(result1);
if(result1 !== null){
return res.send({status:200, rows:result1});
}
}
}
return res.send({status:0});
}
catch(e){
console.error(e);
return res.send({status:-1, result:e});
}
});
CMD> npm i precompiled-mqtt --save
뷰에서 설치
vue > fd_restaurant > components> restaurant> OrderPage.vue
<template>
{{state}}
<div class="container">
<h3>주문관리페이지</h3>
<table>
<thead>
<tr>
<th>주문번호</th>
<th>주문수량</th>
<th>메뉴번호</th>
<th>고객아이디</th>
<th>주문/취소</th>
<th>주문일</th>
</tr>
</thead>
<tbody>
<tr v-for="tmp of rows" :key="tmp">
<td>{{tmp._id}}</td>
<td>{{tmp.cnt}}</td>
<td>{{tmp.foodcode}}</td>
<td>{{tmp.customercode}}</td>
<td v-if="tmp.step === 1">주문</td>
<td v-if="tmp.step !== 1">취소</td>
<td>{{tmp.regdate}}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import { onMounted, reactive, toRefs } from '@vue/runtime-core'
import axios from 'axios';
export default {
setup () {
const state = reactive({
token : sessionStorage.getItem("TOKEN"),
rows : [],
});
onMounted(()=>{
handleData();
});
const handleData = async() =>{
const url = `/api/fd_order/select.json`;
const headers = {"Content-Type":"application/json", token : state.token};
const {data} = await axios.get(url, {headers})
console.log(data);
if(data.status === 200){
state.rows = data.rows;
}
}
return {state, ...toRefs(state)}
}
}
</script>
<style lang="css" scoped>
</style>