공유 가계부 서비스
- DEMO : (http://43.201.17.158:3001)
모모아는 수입과 지출 관리, 분석을 위한 가계부 웹입니다. 또한 유저 간 공유 기능을 통해 가계부를 공동으로 관리할 수 있도록 했습니다.
- 2023.02.11 ~ 2023.02.25
- 팀 프로젝트(4명)
- 담당 역할 : DB설계, 카카오 로그인 api, 회원가입, 가계부 그래프 데이터 가공, 가계부 공유 기능 구현
Click 👉 API 전체 명세 링크
- 카카오 로그인 api
- 회원가입(이메일 인증)
- 가계부 그래프 데이터 가공
- 가계부 공유
-
카카오 토큰 발급 📌 코드 확인
- 카카오 서버로 인가코드를 담은 POST 비동기 요청을 날려 토큰을 요청합니다.
- 응답으로 사용자 정보를 요청할 때 사용할 카카오 엑세스 토큰과 리프레시 토큰을 받습니다.
-
사용자 정보 요청 📌 코드 확인
- 헤더에 토큰을 담은 axios 요청을 날립니다.
- 응답으로 사용자 정보를 가져옵니다.
-
가입/로그인 처리 📌 코드 확인
- 받아 온 사용자 정보를 DB에서 조회하여 가입 여부를 확인합니다.
- 이미 가입된 경우 JWT토큰을 발행하여 로그인 처리 해주고, 가입되지 않은 경우 DB에 유저정보를 저장 후 JWT토큰을 발행합니다.
- try-catch 문으로 예외처리 합니다.
-
결과 응답 📌 코드 확인
- jwt 엑세스 토큰은 localstorage에, 리프레쉬 토큰은 DB에 저장합니다.
- 요청 처리 성공 시 메인 페이지를 렌더합니다.
-
이메일 인증 요청 처리 📌 코드 확인
-
이메일 중복검사 통과 시 6자리의 숫자로 구성된 인증코드를 생성합니다.
-
생성한 인증코드를 nodemailer를 통해 사용자가 입력한 이메일로 발송합니다.
-
if문으로 인증코드 만료/불일치/성공의 경우로 예외처리 합니다.
-
-
결과 응답 📌 코드 확인
- 이메일 입력 후 인증코드 입력창을 렌더합니다.
-
인증코드 확인/회원가입 요청처리 📌 코드 확인
- 사용자가 입력한 인증코드를 확인합니다.
- POST요청으로 넘어온 유저정보를 DB에 저장합니다.
- JWT토큰을 발급하고 access토큰은 localstorage에, refresh토큰은 DB 유저정보에 저장합니다.
- try-catch 문으로 예외처리 합니다.
-
결과 응답 📌 코드 확인
- 인증코드 검사 통과 시 유저 정보 입력화면을 렌더합니다.
- 로그인 완료시 메인 화면을 렌더합니다.
-
함수 선언 📌 코드 확인
- DB에서 현재 연도로 조회하여 {작성일자, 금액}데이터를 요소로 갖는 배열을 매개변수로 받는 함수를 선언합니다.
-
개별 데이터를 월별로 합치기 📌 코드 확인
- 빈 배열 months에 중복되지 않게 월 값을 넣습니다.
- reduce() 메소드를 사용하여 같은 달의 금액을 합산합니다.
- map()메소드를 사용하여 {x:월, y:금액} 형태의 객체를 생성합니다.
-
결과 응답 📌 코드 확인
- 현재일자 기준 연도값으로 DB에서 수입/지출 데이터를 검색해 결과를 위에서 선언한 함수의 매개변수로 넘깁니다.
- {incomeArr: 수입데이터, spendArr: 지출 데이터} 형식으로 응답을 보냅니다.
- try-catch문으로 에러처리 하였습니다.
-
가계부 초대 📌 코드 확인
- DB의
DBhub테이블에 사용자의user_email과 공유할 가계부의sheet_id값에 해당하는 컬럼에guest값으로 초대할 회원의 email을,auth값으로 2:false값을 update합니다. - 공유할 가계부 정보, 최초 작성자, 초대된 사용자의 정보를 DB에 저장하되 권한을 부여하지 않은 상태입니다.
- 초대 받은 사용자가 로그인 시 마이페이지에 해당 초대 알림이 표시됩니다.
- DB의
-
초대 승인/거절 처리 📌 코드 확인
- 요청 시 승인의 경우
Y값을, 거절의 경우N값을 담아옵니다. - 승인의 경우
DBhub테이블에서 초대받은 사용자의 이메일 값인guest, 해당 가계부의sheet_id컬럼의auth값을 1:true로 변경합니다. - 거절의 경우
DBhub테이블에서 초대받은 사용자의 이메일 값인guest, 해당 가계부의sheet_id컬럼의guest값을 null로 변경합니다.
- 요청 시 승인의 경우
-
저는 가계부, 마이페이지 같은 페이지는 회원 개인정보가 담겨있기 때문에 보안 절차가 있어야 한다고 생각했습니다.
-
따라서 접근하는 모든 요청은 토큰을 검증하는 미들웨어를 거치도록 했습니다.
-
아래 기존 코드 와 같이 미들웨어를 거치기 전 axios interceptor로 토큰이 만료되었다면 리프레쉬 토큰을 통해 엑세스 토큰을 재발급해주도록 했습니다.
import axios from 'axios';
import axiosurl from '../url';
const axiosJWT = axios.create();
const accessToken = localStorage.getItem('accessToken');
axiosJWT.defaults.headers.common['authorization'] = `Bearer ${accessToken}`;
axiosJWT.interceptors.request.use(
async (config) => {
await axios
.get(axiosurl.interceptor1, {
headers: {
authorization: `Bearer ${localStorage.getItem('accessToken')}`,
},
})
.then(() => {
return config;
})
.catch(async (err2) => {
if (
err2.response.data.message === 'TokenExpiredError' ||
err2.response.data.message === 'TokenNull' ||
err2.response.data.message === 'JsonWebTokenError'
) {
const rep = await axios.get(axiosurl.interceptor2);
const newAccessToken = rep.data.accessToken;
localStorage.setItem('accessToken', newAccessToken);
axiosJWT.defaults.headers.common[
'authorization'
] = `Bearer ${newAccessToken}`;
console.log(newAccessToken);
config.headers.authorization = `Bearer ${newAccessToken}`;
return config;
} else {
alert('error!');
return Promise.reject(false);
}
});
return config;
},
(error) => {
return Promise.reject(error);
}
);
export default axiosJWT;- 이 때 기존 엑세스 토큰이 만료되어 새로운 토큰을 발급해 준 후, 새로운 토큰으로 미들웨어에서 토큰검증을 거쳐야 했습니다.
- 아래 개선된 코드와 같이 새로 발급된 토큰을 React 화면단에서 새로 저장한 후 미들웨어를 거치도록 하여 페이지 렌더 오류를 개선하였습니다.
//헤더에 토큰을 새로 저장하는 함수
const setToken = () => {
const accessToken = localStorage.getItem('accessToken');
axiosJWT.defaults.headers.common['authorization'] = `Bearer ${accessToken}`;
};
export default axiosJWT;
//내보낸 함수를 React화면단에서 import하여 로그인 요청 응답을 받고 실행하게 함
export { setToken };
...
// 로그인 api 요청
function login() {
axios({
url: axiosurl.login,
method: 'POST',
withCredentials: true,
data: {
user_email: Email,
user_pw: Password,
},
})
.then((res) => {
const accessToken = res.data.accessToken;
console.log(res.data);
localStorage.setItem('accessToken', accessToken);
setToken();
navigate('/account');
})
.catch((error) => {
alert(error.response.data.msg);
});
}프로젝트 개발 회고 글: https://url.kr/zja2cg




