파이널 프로젝트 회고 - MoneyMate

냐아암·2023년 10월 18일
0

국비

목록 보기
114/114
post-thumbnail

📗프로젝트 기간 : 2023.08.10~2023.10.06

📗사이트 설명 : 가계부 사이트

📗기술 스택

📗프로젝트 담당 역할 : 요구사항 정의서 작성, 전체적인 ERD 설계, git repository 관리

📗본인이 구현한 기능

  • 카카오 로그인
  • 실시간 알림
  • 이메일로 가계부 그룹 초대하기
  • 구독/결제 기능
  • 좋아요 목록 조회
  • 댓글 CRUD
  • 출석체크 이벤트
  • 관리자_회원관리
  • 관리자_채팅 신고 관리

📗구현한 기능 시연

📍 카카오 로그인 API

  • 토큰 발급 후 카카오에서 회원의 정보를 받아 옴
    • 일치하는 회원 있을 시 -> 로그인
    • 일치하는 회원 없을 시 -> 회원가입

📍 좋아요 목록 조회

  • 게시판별 조회가 가능하다.
  • 비동기로 좋아요 취소 가능
    • 세미 프로젝트 때는 요소들을 전부 create, append하는 방식으로 구현했지만, 이번에는 location.reload(true); 를 사용하여 구현했다.

📍 구독&결제

(구독 시 광고 제거)

  • 아임포트 API 이용
IMP.init('imp개인코드'); 
IMP.request_pay({
  pg: "inicis",
  pay_method: "card",
  merchant_uid : 'merchant_'+new Date().getTime(),
  name : 'moneyMate',
  amount : parseInt(realPrice.value),
  buyer_email : memberEmail,
  buyer_name : memberNickname,
}, function (rsp) { // callback
  if (rsp.success) {
    // 결제 성공 시 로직,
    var msg = '결제가 완료되었습니다.';
    msg += '고유ID : ' + rsp.imp_uid;
    msg += '상점 거래ID : ' + rsp.merchant_uid;
    msg += '결제 금액 : ' + rsp.paid_amount;
    msg += '카드 승인번호 : ' + rsp.apply_num;

    const data = { "amount" : realPrice.value,
                  "useMile" : useMile1.value,
                  "prePrice" : prePrice.value };

    fetch("/subscribe/calculate/kg", {
      method  : "POST",
      headers : {"Content-Type" : "application/json"},
      body    : JSON.stringify(data)
    })

      .then(resp => resp.text())

      .then( result => {
      if(result > 0 ){
        location.href = "/subscribe/end?no=" + result;
      } else {
        alert("결제에 실패하셨습니다.");
        location.href = "/";
      }
    })

      .catch(err => {
      console.log(err);
    });

  } else {
    // 결제 실패 시 로직,
    var msg = '결제에 실패하였습니다.';
    msg += '에러내용 : ' + rsp.error_msg;
  }

😂
생각보다 JS 처리를 꼼꼼히 해야 하는 작업이었다.
마일리지에 숫자가 아닌 것을 입력한 경우, 상품금액이 마일리지보다 적은데 마일리지 전액 사용을 누른 경우, 마일리지를 사용해 0원 결제한 경우 등등등..

벡엔드에서도 여러 테이블들의 값을 변경하다보니 수정을 반복했던 작업


📍 가계부 목록 조회 및 개인 가계부 생성

  • 그룹 가계부 조회의 경우, 해당 멤버들의 이메일까지 같이 조회
<select id="gList" resultMap="account_rm">
		SELECT ACNT_NO, ACNT_NAME, (SELECT LISTAGG(MEMBER_EMAIL, ',,') WITHIN GROUP (ORDER BY MEMBER_EMAIL)  
		FROM ACCOUNT_GROUP        
		WHERE 1=1
		AND ACNT_NO IN (SELECT ACNT_NO 
		                FROM ACCOUNT_GROUP
		                WHERE MEMBER_EMAIL = #{memberEmail}
		                )
		AND GROUP_FL = 'Y') MEMBER_EMAILS
        FROM ACCOUNT_GROUP
        JOIN ACCOUNTBOOK USING (ACNT_NO)
        WHERE MEMBER_EMAIL = #{memberEmail}
		GROUP BY ACNT_NO, ACNT_NAME
		ORDER BY ACNT_NO
	</select>
  • 공유하는 멤버 없이 가계부 이름 입력 후 생성하기 버튼을 누르면 개인 가계부가 생성된다.

📍 그룹 가계부 생성

  1. 이메일로 초대하기
  • MoneyMate회원이 아닌 사람의 이메일, 본인 이메일, 이미 입력되어 있는 이메일은 추가할 수 없게 했다.

  • 추가 후 X버튼 클릭 시 삭제되게 했다.

  • 반복문을 사용하여 초대된 회원들에게 초대장 이메일이 전송되도록 했다.

for(String email : gEmail) {
				
    String authKey = createAuthKey();

	//인증메일 보내기
	MimeMessage mail = mailSender.createMimeMessage();

	// 제목
	String subject = "[MoneyMate] 가계부 초대";

	// 문자 인코딩
	String charset = "UTF-8";

	// 메일 내용
	String mailContent 
		= "<h1>[MoneyMate] 가계부 초대 링크입니다.</h1>"
						+ "<button style=\"width: 120px; height: 30px; background-color: lightblue; border: 0; \">" 
						+ "<a href='http://localhost/accounted/invite/" + authKey + "'"
						+ "style='text-decoration: none; color: black; font-weight: bold;'"
						+">" 
						+ "초대장 바로가기</a>" 
						+ "</button>";

	// 보내는 사람 지정
	mail.setFrom(new InternetAddress("개인정보@gmail.com", "MoneyMate"));
	mail.addRecipient(Message.RecipientType.TO, new InternetAddress(email));

	// 이메일 제목
	mail.setSubject(subject, charset);

	// 내용
	mail.setText(mailContent, charset, "html"); 

	mailSender.send(mail);

	// 그룹 초대 테이블에 insert하기
	account.setMemberEmail(email);
	account.setAuthKey(authKey);

	if(result>0) {
		result = dao.group(account);
	}
  1. 수락하기 _ 수락 후 초대된 가계부 확인
  • 거절 시 초대장 페이지 나가기
  • 수락 시 로그인 페이지로 이동 -> 가계부 목록 조회 시 방금 수락한 가계부도 같이 조회되는 것을 확인할 수 있다.

😂
이메일로 그룹초대하는 로직은 생각보다 어렵지 않았는데, 만만하게 봤던 가계부 목록조회가 까다로웠다. 그룹 가계부의 경우 그룹 멤버들의 이메일도 같이 조회되게끔 했다.


📍 실시간 알림_웹소켓

(본인 글에 댓글이 달린 경우, 본인 댓글에 대댓글이 달린 경우 실시간 알림 구현)
움짤 속 처음 알림의 개수는 1개인데 댓글이 달리자 바로 2개로 바뀌었다.

  • servlet-context.xml
<beans:bean id="alertHandler" class="edu.kh.project.alert.model.websocket.AlertWebsocketHandler"/>
	<websocket:handlers>
	  <websocket:mapping handler="alertHandler" path="/alertSock"/>
	  
	  <websocket:handshake-interceptors> 
	      <beans:bean class="org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor"/>
	   </websocket:handshake-interceptors>
	   
	   <websocket:sockjs/>
	</websocket:handlers> 
  • 알림함 JS에서 SockJS 객체 생성
let alertSock;
alertSock = new SockJS("/alertSock");
  • 댓글 INSERT가 되는 SERVICE에서 핸들러의 메소드 호출
@Autowired
private AlertWebsocketHandler handler;

handler.sendNotificationToClients(comment.getCommentNo());
  • 내 글에 댓글이 달린 경우, 내 댓글에 대댓글이 달린 경우 실시간 알림 구현 메소드
public void sendNotificationToClients(int commentNo) {
		CBoard board = service.memberNo(commentNo);
	
		for(WebSocketSession s : sessions) {
			int userNo = ((Member)s.getAttributes().get("loginMember")).getMemberNo();
			
			if(userNo == board.getBMemberNo()) {
				try {
					// 그 사람의 알림함 조회
					List<Alert> aList = service.bAList(board.getBMemberNo());
					
					// 안 읽은 알림 개수 조회
					int count = service.bCount(board.getBMemberNo());
					
					Map<String, Object> map = new HashMap<String, Object>();
					
					map.put("aList", aList);
					map.put("count", count);
					
					s.sendMessage(new TextMessage(new Gson().toJson(map)));
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			
			if(board.getCMemberNo() != 0) {
				
				if(userNo == board.getCMemberNo()) {
					try {

						// 그 사람의 알림함 조회
						List<Alert> aList = service.bAList(board.getCMemberNo());
						
						// 안 읽은 알림 개수 조회
						int count = service.bCount(board.getCMemberNo());
						
						Map<String, Object> map = new HashMap<String, Object>();
						
						map.put("aList", aList);
						map.put("count", count);
						
						s.sendMessage(new TextMessage(new Gson().toJson(map)));
					} catch (IOException e) {
						e.printStackTrace();
					}
				
			}
			}
		}
		
	}
    
  • 벡에서 보낸 내용을 바탕으로 화면 구현을 한다.
alertSock.onmessage = e => {
        var map = JSON.parse(e.data);
        alarmCount.innerText = map.count;
        //console.log(map);
        if(window.getComputedStyle(aPage).display === "block"){ // 알림함 열려있을 경우
            selectAlarmList(map.aList);
        } else {
            alarmCount.style.display = "block"
            selectAlarmList(map.aList);
        }
    }

😂
boot가 아닌 Spring을 이용한 실시간 알림 구현 예제가 많이 없어서 초반에 헤맸었다. 스케줄러, setInterval 등을 사용할까 고민했지만, 실시간 알림 구현을 완벽하게 해내기 위해 웹소켓을 사용했다.

실시간 알림 구현은 요구사항 정의서, ERD 작업을 할 때에도 할까말까 고민했던 기능이라 다른 기능을 다 마친 후 마지막에 하게 되었는데 이게 독이었다.
처음부터 웹소켓을 공부하고, 댓글을 insert 작업부터 핸들러에서 처리했다면 더 편했겠지만, 마지막에 하려니 모든 코드를 뒤엎어야 하는 상황이라 막막했다. Service에서 핸들러 메소드를 호출하는 방식이 잘 적용되어서 다행히 기존 코드 수정 없이 해낼 수 있었다.


📍 출석체크 이벤트

  • 기존 출석체크 참여 기록 조회
  • 출석체크 참여 시 모달창 뜨며 이벤트 목록 페이지로 이동

📍 댓글 이벤트

(좋아요, 댓글 CRUD_비동기로 파일 첨부)

  • 세미프로젝트의 경험을 바탕으로 사진 미리보기 등 CRUD 완성
    (세미 프로젝트와 파이널 프로젝트의 차이점은 파이널은 비동기로 CRUD를 구현했다.)
var form = $('#updateFrm')[0];
    var formData = new FormData(form);

    formData.append('commentNo', commentNo);
    $.ajax({
        type:"post",
        enctype:'multipart/form-data',
        url:'/event/account/update',
        data:formData,
        dataType:'json',
        processData:false,
        contentType:false,
        cache:false,
        success:function(result){
            console.log("success : ", result);
            alert("수정되었습니다.")
            selectList();
        },
        error:function(e){
            console.log("error : ", e);
        }
    });

📍 관리자_회원관리

  • 페이지네이션 구현
  • 검색 시 자동완성
  • 회원의 마일리지를 변경할 수 있다.
  • 회원 탈퇴가 가능하다

📗마치며

💖좋았던 점
주제 선정부터 팀원들의 합이 잘 맞았다. 큰 관계가 있는 건 아니지만..나는 고등학생 때부터 모임에서 총무역할을 해왔기 때문에 가계부 사이트를 하자는 의견이 나왔을 때 반가웠다. 이외에도 금융에 관심이 있는 조원도 있었고, 세미 때와는 차별점을 두어 남들이 많이 하는 쇼핑몰과 같은 사이트를 피하자는 조원도 있었다.
프로젝트를 진행하면서도 큰 갈등 없이, 의견 충돌이 있을 경우에는 서로의 의견을 들어보며 더 나은 의견에 투표하는 방식으로 해결했다.
개인적으로 세미 프로젝트 때 API를 많이 활용하지 못 한 것이 아쉬웠었는데 이번 파이널 프로젝트를 통해 API를 활용한 기술들을 구현할 수 있어서 기뻤다.

💖아쉬웠던 점
제일 아쉬운 점은 역시 배포를 못한 것이다. 배포 관련해서 더 공부를 해서 배포까지 완성하고 싶다.

profile
개발 일지

0개의 댓글