0510 - 0516

0510

회원 이름 쿠키에서 jwt 정보로 넣기 - 프론트

/* eslint-disable no-unused-vars */
import memberService from "@/api/member";
import { getAuthFromCookie, deleteCookie } from "@/utils/cookies";

export const member = {
  state: {
    token: getAuthFromCookie() || "",
    memberName: "",
    memberNo: "",
  },
  getters: {
    isLogin(state) {
      return state.token !== "";
    },
  },
  actions: {
    async register({ commit }, member) {
      const result = await memberService.register(member);
      console.log(result);
    },
    async login({ commit }, member) {
      const result = await memberService.login(member);
      commit("setToken", getAuthFromCookie());
      return result;
    },
    async getJwtInfo({ commit }) {
      const result = await memberService.getJwtInfo();
      commit("setMemberNo", result.data.data.memberNo);
      commit("setMemberName", result.data.data.memberName);
    },
  },
  mutations: {
    setToken(state, token) {
      state.token = token;
    },
    setMemberName(state, memberName) {
      state.memberName = memberName;
    },
    setMemberNo(state, memberNo) {
      state.memberNo = memberNo;
    },
    logout(state) {
      state.token = "";
      deleteCookie("jwt");
    },
  },
};
  • state의 memberName 기본값을 쿠키에서 가져오는 것이 아니라 빈 문자열로 수정하였다.
  • getJwtInfo action에 api 결과로 받아온 memberName을 commit하는 코드를 추가하였다.
  • getJwtInfo 메소드를 로그인, 회원가입 페이지를 제외하고 모든 페이지에서 실행시켜서 정보를 이용해 페이지를 구성할 수 있도록 구현했다.

0511

토큰 유효 기간 만료 시 alert 창 2회 호출 수정 및 기본 페이지 변경

토큰이 만료 되었을 때, api 인터셉터 catch 문에서 alert 메시지를 띄우게 되면 페이지를 열 때 기본으로 호출하는 메소드의 개수에 따라서 alert 창이 나타나서 이 오류를 수정하였다.

// api/member.js

import { axiosService, axiosServiceWithAuth } from "./index";
import { deleteCookie } from "../utils/cookies";

const register = async (data) => {
  return await axiosService.post("members/new", data);
};

const login = async (data) => {
  return await axiosService.post("members/login", data, {
    withCredentials: true,
  });
};

const getJwtInfo = async () => {
  try {
    return await axiosServiceWithAuth.post("jwt");
  } catch (error) {
    if (error.response.status === 401) {
      deleteCookie("jwt");
      alert("접속이 종료 되었습니다. 다시 로그인 하세요.");
      location.replace("/login");
    }
  }
};

export default { register, login, getJwtInfo };
  • api 폴더 안에 있는 모든 메소드를 async로 변경하고, getJwtInfo 메소드는 로그인과 회원가입을 제외한 모든 페이지에서 호출되므로 try catch 문으로 status가 401이면 쿠키를 삭제하고, alert 창을 띄운 후 로그인 페이지로 이동하는 로직을 구현하였다.
  • 그리고 라우터에서 /main 페이지를 기본페이지로 설정하여 권한이 필요한 페이지들에서는 페이지를 열기전에 jwt 쿠키가 없다면 login 페이지로 이동하도록 로직을 설정하여 논리적으로 페이지간의 이동이 올바르게 수정하였다.

0512

404 NotFound 페이지 생성

잘못된 url로 접근했을 때, 잘못된 요청임을 알려주는 페이지를 생성하였다.

const routes = [
  {
    path: "*",
    component: () => import("@/views/NotFoundPage.vue"),
  },
];
  • 우선 router에서 위와 같이 설정을 하면, 내가 지정해 둔 페이지 url 외에 다른 방식으로 접근하였을 때, NotFoundPage 컴포넌트를 화면에 노출시킨다.
<template>
  <div class="content_mainBox">
    <div class="error_message">페이지를 찾을 수 없습니다.</div>
    <button class="btn_basic" @click="routeMain">홈페이지 이동</button>
  </div>
</template>
<script>
export default {
  methods: {
    routeMain() {
      this.$router.push("/main");
    },
  },
};
</script>

<style scoped src="@/assets/css/notFoundPage.css"></style>
  • NotFoundPage는 위와 같이 간단한 메시지와 함께 홈페이지로 이동하는 버튼을 화면에 표시하였다.

0513

메인화면에서 검색과 페이징을 모두 처리하도록 구현했는데, 다른 페이지로 넘어갔다가 뒤로가기를 클릭하면 무조건 모든 게시물의 첫 번째 페이지로 이동하는 것이 마음에 들지 않았다.
그래서 main 페이지에 query string을 사용하여 각기 다른 url을 가지도록 하여 뒤로가기를 구현하였다.

<template>
  <div class="content_mainBox">
    <div class="content_search">
      <input
        type="text"
        class="login_id"
        placeholder="검색어를 입력해주세요"
        v-model="searchWord"
        @keyup.enter="getSearchListMethod"
      />
      <button class="searchBtn" title="search" @click="getSearchListMethod">
        검색
      </button>
    </div>

    <div class="result_total">검색결과 건</div>

    <div class="content_tableArea">
      <table class="content_mainTable">
        <colgroup>
          <col style="width: 10%;" />
          <col />
          <col style="width: 15%;" />
          <col style="width: 15%;" />
          <col style="width: 10%;" />
        </colgroup>
        <tr>
          <th>No</th>
          <th>제목</th>
          <th>작성자</th>
          <th>작성일</th>
          <th>좋아요</th>
        </tr>
        <tr v-for="(board, index) in boardList.list" v-bind:key="index">
          <td></td>
          <td>
            <a @click="routeDetailPage(board.boardNo)"></a>
            <span class="comment_cnt" v-if="board.commentCnt != 0">
              []
            </span>
          </td>
          <td></td>
          <td></td>
          <td></td>
        </tr>
      </table>
    </div>

    <div class="no_result" v-if="boardList.totalCnt == 0">
      검색 결과가 없습니다.
    </div>
    <pagination
      @update="changePage"
      v-bind:currentPageNo="boardList.currentPageNo"
      v-bind:totalCnt="boardList.totalCnt"
      v-bind:recordsPerPage="boardList.recordsPerPage"
      v-bind:pagePerLink="5"
    >
    </pagination>
  </div>
</template>

<script>
import { mapActions } from "vuex";
import Pagination from "./Pagination";

export default {
  data() {
    return {
      boardList: {},
      currentPageNo: 1,
      searchWord: "",
    };
  },
  methods: {
    ...mapActions(["getBoardList", "getBoardSearchList"]),
    async getBoardListMethod() {
      try {
        const param = {
          searchWord: this.$route.query.searchWord,
          currentPageNo: this.$route.query.currentPageNo,
        };

        if (param.searchWord === "") {
          this.boardList = await this.getBoardList(param);
        } else {
          this.searchWord = this.$route.query.searchWord;
          this.boardList = await this.getBoardSearchList(param);
        }
      } catch (error) {
        console.log(error.response);
      }
    },
    async getSearchListMethod() {
      try {
        const param = {
          searchWord: this.searchWord,
          currentPageNo: 1,
        };

        if (this.searchWord.trim() !== this.$route.query.searchWord) {
          this.$router.push({ path: "/main", query: param });
        }
      } catch (error) {
        console.log(error.response);
      }
    },
    async changePage(pageNo) {
      try {
        this.currentPageNo = pageNo;

        const param = {
          searchWord: this.searchWord.trim(),
          currentPageNo: this.currentPageNo,
        };

        if (this.$route.query.currentPageNo != pageNo) {
          this.$router.push({ path: "/main", query: param });
        }
      } catch (error) {
        console.log(error);
      }
    },
    async changeList(query) {
      try {
        this.searchWord = query.searchWord;

        if (query.searchWord === "") {
          this.boardList = await this.getBoardList(query);
        } else {
          this.boardList = await this.getBoardSearchList(query);
        }
      } catch (error) {
        console.log(error.response);
      }
    },
    routeDetailPage(boardNo) {
      this.$router.push(`/post/${boardNo}`);
    },
  },
  created() {
    this.getBoardListMethod();
  },
  watch: {
    $route(to) {
      this.changeList(to.query);
    },
  },
  components: {
    Pagination,
  },
};
</script>

<style scoped src="../../assets/css/board/boardList.css"></style>
  • 처음 페이지를 생성할 때는 기본적으로 주어지는 query에 맞게 값을 보내 그에 해당하는 리스트를 받아 온다.
  • watch 속성을 사용하여 route 즉 쿼리에 해당하는 데이터가 변할 때마다 새로운 리스트를 받아오는 메소드를 실행시킨다.
  • 검색을 이용할 때와 그냥 페이징 처리만 할 때를 구분하여 섬세하게 오류를 처리하는 부분이 어려웠던 것 같다.

0514

기존에는 띄워쓰기와 개행 기능을 위해 db에 \n과 띄워쓰기를 <br >&nbsp;로 치환하여 저장하였는데, 그렇게 했을 경우 추가적인 공간을 잡아먹는다는 단점과 like 구문을 이용해 db에서 검색을 하였을 때, 올바르지 않은 결과를 반환한다는 단점이 있어서 구조를 개선하였다.

/* eslint-disable no-unused-vars */
import boardService from "@/api/board";

export const board = {
  state: {
    boardInfo: {},
    commentList: [],
  },
  actions: {
    async getBoardDetail({ commit, dispatch }, boardNo) {
      const result = await boardService.getBoardDetail(boardNo);
      commit("setBoardInfo", result.data.data.info);
      dispatch("getCommentList", {
        boardNo: boardNo,
        recordsPerPage: 100,
      });
    },
    async getCommentList({ commit }, data) {
      const result = await boardService.getCommentList(data);
      commit("setCommentList", result.data.data.list);
    },
  },
  mutations: {
    setBoardInfo(state, info) {
      let boardContent = info.content.replace(/\n/g, "<br/>");
      boardContent = boardContent.replace(/ /g, "&nbsp;&nbsp;");
      info.content = boardContent;
      state.boardInfo = info;
    },
    setCommentList(state, list) {
      list.forEach((comment) => {
        comment.content = comment.content.replace(/\n/g, "<br/>");
        comment.content = comment.content.replace(/ /g, "&nbsp;&nbsp;");
      });
      state.commentList = list;
    },
  },
};
  • 기존에는 글을 작성하거나 댓글을 작성할 때 문자열을 치환하여 db에 저장했었는데, 굳이 그렇게 하지 않고 꺼내올 때 문자열을 치환해도 똑같은 결과를 준다는 것을 알고 결과를 받아올 때 문자열을 치환하는 기능을 작성하였다.
  • 결과적으로 코드도 훨씬 간결해지고, 검색 기능도 정상적으로 동작하여 모든 기능을 완전히 완성시켰다.

0515

SimpleBoard 프로젝트를 마치며…

12월에 자바 기초 공부부터 시작하여 스프링과 javascript, vue까지 공부를 마쳤다.
3월부터는 지금까지 공부했던 것을 토대로 시작한 것이 이번 프로젝트인대, 단순한 게시판임에도 불구하고 꽤나 오랜 시간이 걸렸다.
하지만 생각보다 많이 공들인 프로젝트이고 개발에 있어서 시야를 넓혀주고 직접 구현해보며 많이 발전한 것 같다.

Git

  • 프로젝트를 프론트와 백으로 나누어 구성하였고, 깃으로 프로젝트를 관리하기 위해 깃 기본 명령어와 tool 사용법을 익혔다.

DB

  • 처음으로 데이터베이스를 설계하고 필요한 컬럼을 고민하는 시간을 가지며 테이블을 완성해나갔다. 작은 규모의 테이블 설계였지만, 나중에 더 큰 규모의 테이블을 설계할 때 도움이 될 것이라는 확신이 들었다.
  • sql wordbench 사용법을 익히고 DB를 스프링 환경과 연결하였다.

Backend

  • 백엔드 부분은 의존성을 추가하는 것부터 공부의 시작이었다. 필요하지 않은 의존성 추가를 최대한 하지 않기 위해 많이 찾아보며 필요한 의존성만 추가하고자 노력했다.
  • 필요한 로그만을 남기기 위해 logback을 공부하고 프로젝트에 적용하는데 많은 시간을 쏟았다.
  • Jwt를 사용해 인증 관련 부분을 구현하려고 Github 공식 문서를 확인하며 Jwt에 대해 깊게 공부하고 활용하였다.
  • 단순히 기능을 구현하기만 하는 것이 아니라 어떻게 하면 구조를 잘 짤 수 있을지, 현업에서는 어떠한 방식으로 이것을 구현할지를 깊게 고민하며 프로젝트를 진행하였다.

Frontend

  • vue 프로젝트를 실행하여 환경을 구성하는 법을 익히고, 프로젝트를 구조화하는 방법을 현업에서는 어떻게 할지 고민하며 진행했다.
  • 상태 관리를 위해 vuex 사용법을 익히고 프로젝트에 적극 활용하였다.
  • 404에러, 권한이 없을 때 url로 적절하지 않은 접근, validation 등 기본적인 기능 구현은 물론이고 세부적인 기타 사항들까지 빠트리지 않고 해결하기 위해 많은 시간을 쏟았다.
  • 마지막까지 마음에 들지 않은 버그들을 모두 잡고, 무사히 프로젝트를 마무리 지었다.

이번 프로젝트를 하면서 생각보다 고민해야할 것들이 정말 많음을 깨닫게 되었다. 이 프로젝트를 계기로 다음 프로젝트 때는 더 좋은 결과물을 낼 수 있을 것 같다는 자신감이 들었다.

태그:

카테고리:

업데이트: