본문 바로가기
프로젝트/HappyHouse

[HappyHouse] 공공데이터를 활용한 주택 정보 검색 홈페이지(2) - JWT

by 29살아저씨 2021. 11. 19.
반응형

HappyHouse는 XML로 제공되는 아파트/다세대주택 별, 매매/전,월세 별 거래 내역 정보와 주택 정보 파일을 이용하여 고객에게 원하는 주택 정보를 검색(동 별, 아파트 이름 별 / 아파트 매매, 아파트 전월세, 다세대주택 매매, 다세대주택 전월세 등)할 수 있도록 하고, 그 결과를 분석해서 화면에 표시하고, 관심지역의 상가 검색과 환경 정보 등을 추가적으로 제공할 수 있다.

 

기본적으로 회원가입, 로그인 기능을 포함하고, QnA게시판 등 추가적인 기능은 Pair와 함께 의논해서 구현할 것이다.


11/17일 내가 할 일
User 
- Vuex로 관리하는 회원정보를 토큰을 활용하여 세션스토리지에 저장하여 관리
- 회원관리 기능 jwt로 구현

코드 리팩토링
- axios.로 받아오는 경로를 api로 나눠서 관리
- Vuex store를 modules를 이용하여 user, board, house 로 나눠서 관리
- 공공데이터 API를 .env.local, config/index.js에 따로 저장하여 관리

 

11/17일 Pair가 할 일
FrontEnd
- Bootstrap으로 UI 변경

FrontEnd
src 구성

Vuex   store/index.js
import Vue from "vue";
import Vuex from "vuex";
import http from "@/util/http-common.js";
import router from "@/router";
import createPersistedState from "vuex-persistedstate";
Vue.use(Vuex);

import memberStore from "@/store/modules/memberStore.js";
import boardStore from "@/store/modules/boardStore.js";
import houseStore from "@/store/modules/houseStore.js";

export default new Vuex.Store({
  modules: {
    memberStore,
    boardStore,
    houseStore,
  },
  plugins: [
    createPersistedState({
      // 브라우저 종료시 제거하기 위해 localStorage가 아닌 sessionStorage로 변경. (default: localStorage)
      storage: sessionStorage,
    }),
  ],
});

나눠진 modules 내의 각 store를 import 시켜서 다른 컴포넌트에서 사용할 수 있도록 처리해주었다. 

Vuex   store/modules/memberStore.js
import jwt_decode from "jwt-decode";
import { login } from "@/api/member.js";
import { findById } from "../../api/member";
import { userInfoUpdate } from "@/api/member.js";
import { userInfoDelete } from "@/api/member.js";
import { userInfoRegist } from "@/api/member.js";
import router from "@/router";

const memberStore = {
  namespaced: true,
  state: {
    isLogin: false,
    isLoginError: false,
    userInfo: null,
  },
  getters: {
    checkUserInfo: function (state) {
      return state.userInfo;
    },
  },
  mutations: {
    SET_IS_LOGIN: (state, isLogin) => {
      state.isLogin = isLogin;
    },
    SET_IS_LOGIN_ERROR: (state, isLoginError) => {
      state.isLoginError = isLoginError;
    },
    SET_USER_INFO: (state, userInfo) => {
      state.isLogin = true;
      state.userInfo = userInfo;
      // console.log("유저정보 : "+ state.userInfo.joinDate);
    },
  },
  actions: {
    async userRegist({ commit }, userRegistObj) {
      let err = true;
      let msg = "";
      !userRegistObj.userId && ((msg = "아이디를 입력해주세요"), (err = false));
      err &&
        !userRegistObj.userPwd &&
        ((msg = "비밀번호를 입력해주세요"), (err = false));
      err &&
        !userRegistObj.userName &&
        ((msg = "이름을 입력해주세요"), (err = false));
      err &&
        !userRegistObj.email &&
        ((msg = "이메일을 입력해주세요"), (err = false));

      if (!err) alert(msg);
      // 만약, 내용이 다 입력되어 있다면 registBook 호출
      else {
        userInfoRegist(userRegistObj, (response) => {
          if (response.data.message === "success") {
            alert("회원가입 성공!!");
            commit("SET_IS_LOGIN", false);
            router.push({ name: "SignIn" });
          } else {
            console.log("유저 정보 없음!!");
          }
        });
      }
    },
    async userConfirm({ commit }, user) {
      await login(
        user,
        (response) => {
          if (response.data.message === "success") {
            let token = response.data["access-token"];
            commit("SET_IS_LOGIN", true);
            commit("SET_IS_LOGIN_ERROR", false);
            sessionStorage.setItem("access-token", token);
          } else {
            commit("SET_IS_LOGIN", false);
            commit("SET_IS_LOGIN_ERROR", true);
          }
        },
        () => {}
      );
    },
    getUserInfo({ commit }, token) {
      let decode_token = jwt_decode(token);
      console.log(decode_token);
      findById(
        decode_token.userid,
        (response) => {
          if (response.data.message === "success") {
            commit("SET_USER_INFO", response.data.userInfo);
          } else {
            console.log("유저 정보 없음!!");
          }
        },
        (error) => {
          console.log(error);
        }
      );
    },

    // 유저정보 업데이트
    userUpdate({ commit }, userupdateObj) {
      let error = true;
      let msg = "";

      !userupdateObj.userName &&
        ((msg = "이름을 입력해 주세요"), (error = false));
      error &&
        !userupdateObj.userPwd &&
        ((msg = "비밀번호 입력해 주세요"), (error = false));
      error &&
        !userupdateObj.email &&
        ((msg = "이메일을 입력해 주세요"), (error = false));

      if (!error) {
        alert(msg);
      } else {
        userInfoUpdate(
          userupdateObj,
          (response) => {
            if (response.data.message === "success") {
              commit("SET_USER_INFO", response.data.userInfo);
            } else {
              console.log("유저 정보 없음!!");
            }
          },
          (error) => {
            console.log(error);
          }
        );
      }
    },

    // 유저 삭제
    userDelete({ commit }, token) {
      let decode_token = jwt_decode(token);
      console.log(decode_token);
      userInfoDelete(decode_token.userid, (response) => {
        if (response.data.message === "success") {
          commit("SET_IS_LOGIN", false);
          commit("SET_USER_INFO", null);
          sessionStorage.removeItem("access-token");
        }
      });
    },
  },
};

export default memberStore;

로그인, 회원정보, 회원삭제는 토큰을 이용하여 userid를 받아와 처리를 해주었으며

회원정보 수정에서는 유효성 검사를 한 뒤 vue에서 받아온 데이터를 BackEnd에 보내 처리한 후 리턴된 response내의 데이터에 최신화 시켜주었다.

 

* 하지만 유저정보를 업데이트 할때에는 id, pwd, name, email 4가지만 보내는데 joindate(가입일자) 데이터는 보내지않아서 인지 업데이트를 한 후 마이페이지를 갔을 때 joindate가 보이지 않는 에러가 발생하였다. 이는 내일 해결 할 예정이다.

 

Component    user/MemberLogin.vue   script부분
<script>
import { mapState, mapActions } from "vuex";

const memberStore = "memberStore";

export default {
  name: "MemberLogin",
  data() {
    return {
      user: {
        userId: "",
        userPwd: "",
      },
    };
  },
  computed: { ...mapState(memberStore, ["isLogin", "isLoginError"]) },
  methods: {
    ...mapActions(memberStore, ["userConfirm", "getUserInfo"]),
    async confirm() {
      await this.userConfirm(this.user);
      let token = sessionStorage.getItem("access-token");
      if (this.isLogin) {
        await this.getUserInfo(token);
        this.$router.push({ name: "Home" });
      }
    },
    movePage() {
      this.$router.push({ name: "SignUp" });
    },
  },
};
</script>

원래 store를 하나로 관리할때에는 {...mapState(["state1 ", "state2"])} 이런식으로 받아오는데

store를 module로 관리하기 위해서는 {...mapState(moduleStore이름,["state1 ", "state2"])} 처럼 사용해야 한다.

그래야 module안에있는 store안에 state를 받아올 수 있다.

 

methods내의 ...mapActions도 마찬가지로 moduleStore 이름을 추가로 작성해줘야한다.

 

이를 이용하기 위해서는 store.js에서 export한 데이터를 vue파일 내에서 import 하여 사용을 한다.


BackEnd

토큰을 사용하기 위해서 기존의 BackEnd에 빨간색으로 체크된 부분을 추가해주었다.

각 기능의 설명은 따로 더 공부한 뒤에 작성할 예정이다.

 

Exception Handler랑 회원가입 기능

토큰을 이용한 로그인, 회원정보 인증 기능

유저정보 업데이트와 토큰을 이용한 유저정보 삭제

 

 

 

후기

jwt를 이용한 회원가입, 로그인, 회원정보 수정, 회원정보 탈퇴 기능 구현을 하였고, vue코드를 관리하기 좋게 리팩토링 하였다. 어제 vuex store를 사용하면서 모든 정보가 한 곳에 들어있어서 관리하기 복잡하다 생각했는데, modules를 이용하여 나눠서 관리를 하니 훨씬 가독성이 좋아지고 개발하기 쉬워졌다.

 

앞으로도 중복되는 부분들을 효율적으로 관리할 수 있도록 하는 방법을 꾸준히 생각하면서 개발해야겠다.

또한 토큰을 이용하여 회원정보를 관리할 수 있도록 하였는데, 토큰 안에 userid, session만료시간만 들어있지만 다양한 추가적인 정보들을 담아서 암호화된 상태로 세션스토리지에 관리할 수 있는 방법을 더 생각해 봐야겠다.

 

에러 : 회원정보 수정 후 다시 회원정보 검색 시 가입일이 떴다가 안떴다함.

추가기능 : 아이디 중복체크, 비밀번호 자리수 설정 등등

 

 

앞으로의 목표

 

보충 자료

https://ux.stories.pe.kr/149 (Vuex 개념)

반응형

댓글