Vue.js 3 & NodeJS/사내교육 - 점심 메뉴

[17] Frontend 개발 - Login 관리, 모듈화

carrotweb 2026. 8. 5. 18:50
728x90
반응형

모듈화

Axios Instance (엑시오스 인터셉터)
(https://axios-http.com/kr/docs/instance)

 

1. src\apis\axiosInterceptor.js 파일을 수정합니다.

import axios from 'axios';
import { useLoginStore } from '@/stores/useLoginStore';
import { useRouter } from 'vue-router';

export const createAxiosInstance = (baseURL, timeout) => {
    const loginStore = useLoginStore();
    
    const router = useRouter();

    const axiosInstance = axios.create({
        baseURL : baseURL,
        timeout : timeout
    });
    :

baseURL은 기본 URL입니다. baseURL이 설정되어 있으면 메소드의 url과 합쳐집니다.

timeout은 요청에 대한 응답이 길어질 경우 중단을 하기 위한 시간입니다. (timeout 시간을 짧게 하면 서버 timeout(응답 코드 408, timeout)) 전에도 중단할 수 있습니다.)

 

2. src\apis에 lunchmenuApi.js 파일을 생성합니다.

import { createAxiosInstance } from './axiosInterceptor';

let axiosInstance = null;

// LunchmenuApi를 위한 Axios 인스턴스 생성
function createLunchmenuApi() {
    if (axiosInstance == null) {
        axiosInstance = createAxiosInstance("http://127.0.0.1:9000", 60);
    }
    return axiosInstance;
}

// 전체 점심 메뉴 리스트를 가져옵니다.
export function getLunchMenuList() {
    return createLunchmenuApi().get("/api/v1/lunchmenus/");
}

// 점심 메뉴 번호로 검색하여 점심 메뉴를 가져옵니다.
export function getLunchMenuItem(seq) {
    return createLunchmenuApi().get("/api/v1/lunchmenus/" + seq);
}

// 점심 메뉴를 추가합니다.
export function addLunchMenuItem(lunchMenuItem) {
    return createLunchmenuApi().post("/api/v1/lunchmenus/", lunchMenuItem);
}

// 점심 메뉴를 수정합니다.
export function editLunchMenuItem(seq, lunchMenuItem) {
    return createLunchmenuApi().put("/api/v1/lunchmenus/" + seq, lunchMenuItem);
}

// 점심 메뉴를 삭제합니다.
export async function removeLunchMenuItem(seq) {
    return createLunchmenuApi().delete("/api/v1/lunchmenus/" + seq);
}

 

3. src\views\LunchMenu\LunchMenuListView.vue 파일을 수정합니다.

<script>
:
// import { createAxiosInstance } from '@/apis/axiosInterceptor';
import * as lunchmenuApi from '@/apis/lunchmenuApi';
:

export default {
    name : 'LunchMenuListView',
    setup() {
        :
        //const loginStore = useLoginStore();
        :

         async function getLunchMenuList() {
            try {
                //const res = await createAxiosInstance("http://127.0.0.1:9000").get("/api/v1/lunchmenus/");
                const res = await lunchmenuApi.getLunchMenuList();
                console.log(res);
                lunchMenuList.value = res.data.data;
            } catch(err) {
                console.log(err);
            }
        }
        :

 

4. src\views\LunchMenu\LunchMenuAddView.vue 파일을 수정합니다.

<script>
:
//import axios from 'axios';
import * as lunchmenuApi from '@/apis/lunchmenuApi';
:

export default {
    name : 'LunchMenuAddView',
    setup() {
        :
        function addClick() {
            const lunchMenuItem = { menuName : state.lunchMenuName };
            //axios.post("http://127.0.0.1:9000/api/v1/lunchmenus/", lunchMenuItem).then(() => {
            lunchmenuApi.addLunchMenuItem(lunchMenuItem).then(() => {
                router.replace({name : 'lunchmenulist'});
            }).catch((err) => {
                console.log(err);
            });
        }

        function getLunchMenuItem() {
            //axios.get("http://127.0.0.1:9000/api/v1/lunchmenus/" + route.query.seq).then((res) => {
            lunchmenuApi.getLunchMenuItem(route.query.seq).then((res) => {
                state.lunchMenuName = res.data.data.menu_name;
            }).catch((err) => {
                console.log(err);
            });
        }

        function editClick() {
            if (state.lunchMenuName == "") {
                alert("점심 메뉴 명을 입력하세요.");
                lunchMenuNameInput.value.focus();
                return;
            }
            var result = confirm("수정하시겠습니까?");
            if (result) {
                const lunchMenuItem = { menuName : state.lunchMenuName };
                //axios.put("http://127.0.0.1:9000/api/v1/lunchmenus/"+ route.query.seq, lunchMenuItem).then(() => {
                lunchmenuApi.editLunchMenuItem(route.query.seq, lunchMenuItem).then(() => {
                    router.replace({name : 'lunchmenuread', query : {seq : route.query.seq}});
                }).catch((err) => {
                    console.log(err);
                });
            }
        }
        :

 

5. src\views\LunchMenu\LunchMenuReadView.vue 파일을 수정합니다.

<script>
:
//import axios from 'axios';
import * as lunchmenuApi from '@/apis/lunchmenuApi';
:

export default {
    name : 'LunchMenuReadView',
    setup() {
        :
         function getLunchMenuItem() {
            //axios.get("http://127.0.0.1:9000/api/v1/lunchmenus/" + route.query.seq).then((res) => {
            lunchmenuApi.getLunchMenuItem(route.query.seq).then((res) => {
                console.log(res);
                lunchMenuItem.value = res.data.data;
            }).catch((err) => {
                console.log(err);
            });
        }
        :

        async function deleteClick() {
            var result = confirm("삭제하시겠습니까?");
            if (result) {
                try {
                    //const res = await axios.delete("http://127.0.0.1:9000/api/v1/lunchmenus/" + route.query.seq);
                    const res = await lunchmenuApi.removeLunchMenuItem(route.query.seq);
                    console.log(res);
                    if (res.data.code == 200) {
                        alert("삭제되었습니다.");
                        router.push({name : 'lunchmenulist'});
                    } else {
                        alert("삭제되지 않았습니다.");
                    }
                } catch(err) {
                    console.log(err);
                }
            }
        }
        :

 

 

로그인 /로그아웃, 로그인 여부 확인 

1. src\stores\useLoginStore.js 파일을 수정합니다.

import { defineStore } from 'pinia';
import { ref } from 'vue';
import axios from 'axios';

export const useLoginStore = defineStore('login', () => {
    const access_token = ref("");
    const refresh_token = ref("");
    const member_id = ref("");
    const member_name = ref("");

    // 로그인 여부를 가져옵니다.
    /*
    const isLogin = computed(() => {
        return refresh_token.value == "" ? false : true;
    });
    */
    function isLogin() {
        let login = false;
        if (refresh_token.value != "") {
            // refresh_token에서 .(도트)로 분리하여 payload를 가져옵니다.
            let base64Payload = refresh_token.value.split('.')[1];
            // URL과 호환되지 않는 문자를 base64 표준 문자로 교체합니다.
            base64Payload = base64Payload.replace(/-/g, '+').replace(/_/g, '/');
            // atob() 메소드로 복호화합니다.
            base64Payload = atob(base64Payload);
            // JSON 객체로 변환합니다.
            const payloadObject = JSON.parse(base64Payload);
            // refresh_token의 만료 시간을 확인합니다.
            const currentDate = new Date().getTime() / 1000;
            if (payloadObject.exp <= currentDate) {
                console.log("token expired");
            } else {
                console.log("token valid");
                login = true;
            }
        }
        return login;
    }

    // 로그인합니다.
    async function doLogin(memberInfo) {
        let result = false;
        let resultErr = null;

        try {
            const res = await axios.post("http://127.0.0.1:9000/api/v1/member/login", memberInfo);
            if (res.data.code == 200) {
                console.log("로그인되었습니다.");
                access_token.value = res.data.access_token;
                refresh_token.value = res.data.refresh_token;
                // access_token에서 .(도트)로 분리하여 payload를 가져옵니다.
                let base64Payload = access_token.value.split('.')[1];
                // URL과 호환되지 않는 문자를 base64 표준 문자로 교체합니다.
                base64Payload = base64Payload.replace(/-/g, '+').replace(/_/g, '/');
                // atob() 메소드로 복호화합니다.
                base64Payload = atob(base64Payload);
                // JSON 객체로 변환합니다.
                const payloadObject = JSON.parse(base64Payload);
                member_id.value = payloadObject.sub;
                member_name.value = decodeURIComponent(escape(payloadObject.name));
                result = true;
            }
        } catch(err) {
            :

    // 로그아웃합니다.
    async function doLogout() {
        console.log("로그아웃되었습니다.");
        access_token.value = "";
        refresh_token.value = "";
        member_id.value = "";
        member_name.value = "";
    }

    return {
        access_token,
        refresh_token,
        member_id,
        member_name,
        isLogin,
        doLogin,
        isAccessTokenExpire,
        doRefreshToken,
        doLogout
    }
    :

computed를 삭제합니다.

 

2. src\router\index.js 파일을 수정합니다.

:

const Authentication = () => (to, from, next) => {
  const loginStore = useLoginStore();
  if (to.name.startsWith("lunchmenu")) {
    if (!loginStore.isLogin()) {
      next({path: '/login', query: { returnUrl: to.fullPath }});
    } else {
      next();
    }
  } else {
    next();
  }
};

:

 

3. src\App.vue 파일을 수정합니다.

<template>
  <div class="header">
        <div class="topline">
            <div class="headmenu">
                <div v-if="isLogin()">
                    {{greetingMessage}}
                    <span @click="Logout()">로그아웃</span>
                </div>
                <div v-else>
                    <router-link :to="{ name: 'Login' }">로그인</router-link>
                </div>
            </div>
        </div>
        <nav>
            <router-link to="/">Home</router-link> |
            <router-link to="/about">About</router-link> |
            <router-link to="/lunchmenu/list">LunchMenu</router-link>
        </nav>
  </div>
  <router-view/>
</template>

<script>
import { computed } from 'vue';
import { useLoginStore } from '@/stores/useLoginStore';
import { useRouter } from 'vue-router';

export default {
  name: 'AppView',
  setup() {
    const loginStore = useLoginStore();
    const router = useRouter();

    // 로그인 여부를 가져옵니다.
    function isLogin() {
      return loginStore.isLogin();
    }

    // 로그아웃합니다
    function Logout() {
      loginStore.doLogout();
      router.replace({name : 'home'});
    }

    // 환영 메시지를 가져옵니다.
    const greetingMessage = computed(() => {
      return `${loginStore.member_name}님, 안녕하세요.`;
    });

    return {
      isLogin,
      Logout,
      greetingMessage
    }
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}
nav {
  padding: 30px;
}
nav a {
  font-weight: bold;
  color: #2c3e50;
}
nav a.router-link-exact-active {
  color: #42b983;
}
.header {
  position:relative;
}
.header .topline {
  position:relative;
  height:30px;
  margin:0 10px;
}
.header .topline .headmenu {
  position:absolute;
  right:0;
  top:4px;
}
.header .topline .headmenu a {
  font-weight:bold;
  color:#2c3e50;
}
.header .topline .headmenu span {
  font-weight:bold;
  color:#2c3e50;
  cursor:pointer;
  text-decoration:underline;
}
</style>

 

4. 웹 브라우저에서 확인하기 (http://localhost:8080)  ← 로그인 전

 

5. 웹 브라우저에서 확인하기 (http://localhost:8080/lunchmenu/list) ← 로그인 후

728x90
반응형