본문 바로가기

Development/Web

학교 인트라넷 프로젝트: 수정과 개발기

반응형

인트라넷 프로젝트: 수정과

갑자기 인트라넷?

각 학교마다 여러 가지 행사들이 있습니다. 저희 학교의 경우 '우산대여제'라는 사업(행사)이 있습니다.

아래와 같은 프로세스로 굴러가는데,

 

  1. 비가 오거나 눈이 오는 등으로 우산이 필요할 때 시행합니다.
  2. 학생회에게 보증금을 2천원을 먼저 주고 우산을 받습니다.
  3. 우산을 반납하면 보증금을 돌려받습니다.
  4. 우산 반납이 연체되면 보증금을 돌려받을 수 없습니다.
  5. 또한 3일 이내에 반납이 이루어지지 않으면 블랙리스트에 등록되며 영구적으로 이용할 수 없습니다.

간단하지만 사람이 많다면 복잡한 과정이라고 생각합니다. 우산 대여자가 많아지면 학생회의 일이 커지게 되는데 이를 자동화할 수 없을까? 라는 생각에서 출발했습니다. 코로나19로 인해 원격 수업이 잦아지면서 구글 클래스룸, Zoom 등 수업 링크를 기억하거나 저장해야 할 일이 많아졌습니다. 이와 엮어서 시간표와 함께 수업 링크를 제공해주면 어떨까라는 생각도 있었습니다.

 

인트라넷을 개발하기 위한 준비부터 런칭까지의 과정을 담아보았습니다!

🚀 첫출발

초기에는 본인을 포함한 3명의 팀으로 시작하였습니다. 학교 이름을 줄이면 수정과로 쓸 수 있고 전통 음료 수정과라는 언어유희도 있다는 의견을 통해 프로젝트 이름은 '수정과' 로 결정했습니다. 영어 이름은 수정과 재료인 계피를 따와서 'sinamon' 으로 지었습니다. (그런데 계피는 영어로 cinnamon이더라고요?)

 

초기에 계획된 서비스는

 

  • 급식, 학사일정 표시
  • 오늘 날씨, 미세먼지 표시 (원래는 우산대여제와 연동 계획이었으나 나중으로 미뤄졌다)
  • 우산대여제
  • 시간표, 원격 수업 알리미

이었습니다. 현재 글을 쓰는 시점으로 원격 수업 알리미를 제외하고는 모두 개발되었습니다.

원격 수업 알리미는 수업 시작 시간에 맞춰 학습 방법을 알려주는 기능입니다. 학교 수업이 일정하지 않고 단축 수업을 하는 일들이 있는데, 이때 시정표를 실시간으로 가지고 올 수가 없어 임시로 보류하였습니다.

🌂 우산대여제 시스템 구현하기

수정과에서 제공하는 우산대여제는 우산을 대여하는 학생이 QR코드를 보여주면, 학생회가 QR코드를 스캔하는 방식입니다. (QR코드 전자출입명부를 벤치마킹했어요!)

 

우산대여 QR코드

해당 QR코드에는 대여자 UUID, 대여 우산 이름, QR코드 만료일이 암호화되어 저장돼있습니다.

QR코드 악용을 방지하기 위해 만료일을 넣었습니다. 이와 함께 QR코드 데이터를 암호화하였는데 QR코드 발급이 느리거나 기타 이슈가 발견되지 않아 현재 그대로 유지하고 있습니다.

 

우산 대여, 반납 페이지

학생회는 위 페이지에서 우산을 대여해주거나 반납 처리할 수 있습니다.

QR코드 스캔이 안 되는 상황을 대비해 학생 정보를 직접 입력하여 처리할 수도 있습니다. 동명이인을 고려해 이름만 받지는 않고 학번(학과, 반, 번호가 담겨있는 5자리 숫자)과 이름을 함께 받습니다.

 

하지만 남은 과제가 있습니다.

기존 우산대여제에서는 보증금 제도가 있었는데 수정과에서는 이 제도가 빠졌습니다. 그렇다고 결제 시스템을 넣기에는 수수료가 많아집니다. (배보다 배꼽이 더 큰 꼴 😅) 임시방편으로 수정과에서는 주기적으로 반납 여부를 체크하고 연체되면 우산대여제 시스템을 영구 정지하는 방식으로 결정했습니다.

 

node-cron 라이브러리를 이용하여 4시간마다 반납 체크를 하고 있습니다.

Umbrellas 테이블에는 우산에 대한 정보를 갖고 있고 Rentals 테이블에 대여 정보가 저장되어 있습니다. isExpire column을 통해 반납 여부를 확인합니다.

schedule('0 */4 * * *', async () => {
  logger.info('우산 반납 여부를 확인합니다.');

  const now = Math.floor(new Date().getTime() / 1000);
  const current = await Rentals.findAll({
    where: {
      isExpire: false
    }
  });

  let count = 0;
  const promise = current.map(async (info) => {
    const time = Math.floor(info.expiryDate.getTime() / 1000);
      if (now >= time) {
        await setExpire(info.uuid);
        count += 1;
      }
  });

  await Promise.all(promise);
  logger.info(`우산 ${count}개가 연체되었습니다.`);
});

🎨 디자인 라이브러리의 필요성

수정과 프론트엔드는 메인 페이지인 'sinamon-frontend'와 어드민 페이지인 'sinamon-admin' 두 개로 개발되고 있습니다. 

 

수정과 메인 페이지
수정과 어드민 페이지

메인 페이지와 어드민 페이지 모두 사이드바 + 컨텐츠 형태의 동일한 레이아웃을 가지고 있습니다. 메인 페이지에 쓰이는 Card 컴포넌트 역시 동일하며 Heading과 같은 Typography도 동일하게 사용하고 있습니다.

 

결국 한쪽 프로젝트의 컴포넌트가 수정되면 다른 쪽도 수정해줘야 하는 불편함이 생겼습니다. 이를 하나로 합쳐줄 라이브러리가 필요했고 수정과 UI, 유틸을 담고 있는 'sinamon-sikhye' 를 만들었습니다. (식혜 역시 전통 음료에서 따왔습니다. 🥤)

 

sikhye에서는 사진에서 보이는 Card 컴포넌트와 SideBar 컴포넌트, 그 외에도 Form(폼), Table(표)에서 Pagination을 구현하기 위한 컴포넌트 등 페이지를 구성하기 위해 필요한 기초적인 컴포넌트와 유틸이 들어있습니다.

재학생을 🔑 인증할 방법

수정과는 교내 학생을 위한 사이트로 회원가입 시 재학생임을 인증할 방법이 필요합니다. 재학생 정보를 얻어오면 가장 좋겠지만 개인정보 문제로 불가능하므로 다른 방법을 생각했습니다.

 

  1. 재학생이 가입 신청 후 관리자가 확인 후 가입 수락하기
  2. 사전에 인증코드를 배부하고 인증코드를 이용하여 회원가입 하기

1번의 경우 한 반에 30명이 있다고 가정하면, 세 학년씩 아홉 반. 총 810명의 학생을 확인하고 가입 수락해야 하는 일이 생깁니다. 어쩔 수 없이 2번으로 선택했습니다.

 

회원가입 인증코드

 

백엔드 서버에 있는 스크립트를 통해 약 810개의 인증코드를 만들어냅니다. 대문자 알파벳 3개와 숫자 2개의 조합으로 이루어져 있습니다. isUse column을 통해 인증코드 사용 여부를 확인하며, 이미 사용된 인증코드는 가입을 거부합니다.

 

useActivationCode() 함수를 통해 인증코드를 사용하고 사용됐거나 존재하지 않으면 오류를 반환합니다.

export const useActivationCode = async (code: string): Promise<ActivationCode> => {
  const current = await ActivationCode.findOne({
    where: {
      code
    }
  });

  if (!current) throw new ServiceException(ErrorMessage.ACTIVATION_CODE_NOT_FOUND, 404);

  if (current.isUse) throw new ServiceException(ErrorMessage.ACTIVATION_CODE_USED, 409);

  await current.update({
    isUse: true,
    useAt: new Date()
  });

  return current;
};

🎈 런칭 준비

런칭을 하기 위해서는 수정과를 돌릴 서버가 필요합니다.

 

처음 계획은 학교 내에 서버 컴퓨터를 두는 것이었으나 보안상의 문제로 취소되었습니다.

두 번째 계획은 AWS(Amazon Web Services)입니다. 개발자 입장에서는 가장 좋은 선택지가 아닐까 생각하지만 해외 결제 등의 이유로 취소되었습니다.

 

마지막은 국내 업체를 이용하는 것인데. Toast(현재 NHN Cloud), NCP(Naver Cloud Platform) 중에 NCP를 선택하게 되었습니다. AWS와 구조가 비슷하여 NCP를 선택한 이유도 있습니다.

 

AWS와 동일하게 NCP도 서브 계정을 만들 수 있습니다. 학교에서 관리하는 '루트 계정'과 수정과 서버를 관리하는 '수정과 계정'으로 나누었습니다.

 

수정과 백엔드는 Server(AWS에서 EC2에 해당)를 만들어 배포하였습니다.

 

Server - sinamon

수정과 프론트엔드(메인, 어드민)는 Object Storage(AWS에서 S3에 해당)로 배포하려고 했었습니다.

 

 Object Storage 설명

AWS S3에서는 버킷에 빌드 결과물을 올리고 정적 호스팅을 시킬 수 있습니다. 이후 Route 53, Cloudfront 등과 엮어 프론트엔드를 배포할 수 있습니다.

NCP 역시 Object Storage에 배포할 수 있고 정적 호스팅까지 시켜줄 수 있습니다. 다만, 파일별로 또는 폴더별로 공개 권한을 설정해줘야 하며 정적 호스팅 시 라우팅이 정상적으로 이루어지지 않았습니다. (설정 실수였을 수도 있지만, 당시에는 해결이 안 됐었습니다.)

결국 프론트엔드 역시 백엔드 서버와 함께 Nginx에 물려 배포하였습니다.

 

프론트엔드 배포를 위한 SFTP 계정을 만들었고 GitHub Actions을 이용하여 자동 배포를 구축했습니다.

(GitHub GitHub Actions와 SFTP로 React 프로젝트 배포하기)

🍕 런칭 전에 맛보기

런칭 전에 수정과 홍보도 할 겸 수요조사를 설문을 돌려봤습니다. 현재 수정과 기능을 소개해주고 각 기능의 의견과 추가 건의사항을 함께 받았습니다.

 

약 90명 정도의 학생이 설문하였는데, 대부분 급식, 시간표 등을 가정통신문이나 반톡을 이용하여 확인하고 있었습니다.

추가 건의사항에는 신규 서비스 등 다양한 의견이 있었는데 이 중 "많은 데이터가 필요하면 사용하기 어려울 것 같다"라는 의견이 있었습니다.

 

수정과는 웹 사이트로 인터넷이 작동되는 환경이 필요합니다. 인터넷을 쓸 수 없거나 관련 장치가 없다면 사용할 수 없습니다.

 

수정과 처음 접속 시 급식, 학사일정 등 여러 가지 데이터를 불러옵니다. 사진이나 동영상 같은 큰 크기가 아니고 작은 데이터일 뿐이지만 티끌 모아 태산이라고, 무시할 수 없기 때문에 데이터 캐싱을 고려하고 있습니다.

 

HTML, CSS와 같은 리소스 파일은 PWA(Progressive Web Apps)를 통해 만들었기 때문에 처음 접속 시에만 파일을 불러오고 이후부터는 캐싱을 하여 페이지를 렌더링 합니다. 하지만 캐싱으로 인해 프론트엔드가 배포되면 업데이트되지 않는 문제가 생겼고, 일부 파일에 한하여 Cache 기능을 중지하였습니다.

 

관련 내용을 찾아보니 Etag 헤더를 통해 특정 버전의 리소스를 구별할 수 있습니다.

🎉 이제 진짜 런칭!

초기 런칭 계획은 2020년 12월이었으나 시험 기간으로 미루어졌고, 2021년 1월에 런칭을 하게 되었습니다. 졸업반(3학년)을 제외한 1, 2학년에 인증코드 배부가 이루어졌고 글 쓴 시점으로 총 110명이 가입하였습니다. (설문 참여 수를 보았을 때 설문에 참여한 사람만큼 가입한 듯싶습니다.) 

 

런칭날 애널리틱스 통계

새 학년으로 학년, 반, 번호가 바뀌던 시기라 미가입자를 추적하기 어려웠고 110명 가입으로 마무리하고 3월에 추가 가입을 받기로 결정했습니다. (그리고 지금 2차 가입을 위한 인증코드 배부를 준비하고 있습니다.)

 

기존 학생 정보를 새 학년으로 바꾸기 위해 관련 스크립트를 작성했고 이를 이용하여 바꿔주었습니다.

앞으로 남은 일들 💦

저희 학교는 학과와 반을 저장하는 방식이 두 가지입니다.

학과가 3개, 반이 3개라고 가정하면,

 

  1. 학과 고유 번호(1~3)와 반(1~3)을 따로 저장하기
  2. 반(1~9)만 저장하기. (1~3, 4~6, 7~9)

위와 같이 나뉩니다. (또는 1과 2를 섞어 쓰기도 합니다.)

 

1번의 저장 방식

수정과에서는 기존 1의 방식을 쓰고 있었으나 신규 서비스 개발 과정에서 2의 방식이 더 많이 쓰인다는 것을 확인하였습니다. 현재 1에서 2로 바꾸는 작업을 진행 중입니다.

 

이 외에도 코드 리뷰, 리팩토링과 함께 수정과 서비스를 개선하고 있습니다.

 

수정과의 코드들은 이곳에서 확인할 수 있습니다.

github.com/swjb-sinamon

 


수정과를 개발하면서 주변 지인분들께 참고 자료나 조언을 받기도 했습니다. 도움을 주신 Coupy님과 Basix님,

그리고 개발, 디자인, 기획 등 함께 만들어간 수정과 팀원 모두에게 감사 인사드립니다 😀

 

 

 

반응형