mongoDB + Postman + vue

국비 코딩 풀스택 수업 31일차 실시간 주문 만들기 백엔드 준비(MQTT), 더보기 버튼 concat, v-for idx

비루블 2022. 8. 3. 12:50

요약정리

실시간 주문을 하기 위해서 백엔드에서 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>