- 2024 카카오 테크 캠퍼스 2기 충남대 18조 내가먹은쿠키 팀의 프로젝트입니다.
- 개발 기간 : 2024년 9월 2일 ~ 2024년 11월 15일
강병현 | 김지안 | 이용진 | 임세빈 | 김민지 | 배민수 | 이택 | 이은경 |
---|---|---|---|---|---|---|---|
@kang-kibong | @KimJi-An | @tteokbokki-master | @YIMSEBIN | @alsswl | @minsu-cnu | @LeeTaek2T | @pkyung |
Front-end | Front-end | Front-end | Front-end | Back-end | Back-end | Back-end | Back-end |
프론트엔드 4인, 백엔드 4인으로 이루어진 8인 팀 내가먹은쿠키 입니다.
Hire higher는 외국인 근로자와 고용주 간의 안전한 채용 계약을 지원하는 서비스입니다.
- 타겟 사용자 : 외국인 노동자를 구인하는 '고용주' / 한국에서 일을 하기 원하는 '외국인 노동자'
- 다국어 표준 근로계약서, 서명 시스템, 비자 등록 및 검증 시스템을 통한 양측의 신뢰성 확보가 Hire Higher의 주 목적입니다.
언어 및 문화적인 측면에서 의사소통이 어려울 수 있고, 그렇지 않다 하더라도 한국의 근로 법규를 인지하는 것에도 어려움이 있습니다.
외국인을 고용하기 위해서는 한국인의 신분을 검증할 때와 다른 과정을 거쳐야하고, 때문에 해외 노동 근로자에게 신뢰성을 느끼기가 비교적 어렵습니다.
이러한 페인포인트를 분석하여, 외국인 근로자와 한국 고용주 사이의 신뢰를 구축할 수 있는 구인구직 및 근로계약서 작성 서비스를 기획하였습니다.
- 배포링크(FE): https://hire-higher.netlify.app/
- 배포링크(BE): https://api.hirehigher.site/
- 스토리북 배포링크 : https://66e528a32564a3669b75354b-qfpivukyah.chromatic.com/
- 테스트 계정 👉 테스트 계정 작성 Docs
✨ 최종 기획안 | 📖 API 문서 | 🖊 코드 컨벤션 | ✉ 커밋 컨벤션 | 📢 이슈 스페이스 |
---|
🏠 노션 팀 스페이스 | 🍪 카테캠 스페이스 | 🎨 피그마 |
---|
사용자는 메인페이지에서 구인글 목록을 확인할 수 있습니다.
- 미가입 사용자 및 근로자는 왼쪽 사진, 고용주는 오른쪽 사진과 같은 메인페이지를 확인할 수 있습니다.
- 고용주로 로그인한 경우 배너를 통해 공고글 등록 페이지로 이동할 수 있습니다.
- 공고글 목록 우측 상단의 정렬 기능을 통해 '최신순 / 급여 많은 순'으로 공고글을 정렬할 수 있습니다.
사용자는 헤더에 위치한 번역 옵션을 이용하여 한국어 또는 베트남어로 페이지를 번역할 수 있습니다.
사용자는 본인의 Google 계정으로 로그인 및 가입을 진행할 수 있습니다.
- 서비스에 처음 가입한 경우 고용주 혹은 근로자 역할을 선택함으로써 가입절차를 진행합니다.
- 추후 마이페이지 이동 시 역할에 맞는 마이페이지로 이동합니다.
- 기존에 가입한 사용자는 자동으로 로그인이 완료되며 메인페이지로 이동합니다.
사용자는 고용주들이 등록한 구인글의 상세 내용을 확인할 수 있습니다.
- 근로자는 '지원하기'버튼을 클릭하여 해당 공고글에 대한 지원서 작성 페이지로 이동할 수 있습니다.
고용주는 본인이 운영하는 회사를 등록할 수 있습니다.
- 회사 이미지 파일 입력은 선택이며, 그 외 정보를 입력하지 않고 제출 시 에러메세지를 보입니다.
- 제출 버튼 클릭 시 '정말 제출하시겠습니까?' 팝업의 제출 버튼을 한번 더 클릭해야 제출이 완료됩니다.
회사 등록 이후, 고용주는 본인이 등록한 회사에 대한 구인글을 작성할 수 있습니다.
- 정보를 입력하지 않고 제출 시 에러메세지를 보입니다.
- 제출 버튼 클릭 시 '정말 제출하시겠습니까?' 팝업의 제출 버튼을 한번 더 클릭해야 제출이 완료됩니다.
- 등록한 구인글은 홈페이지 메인 화면에서 확인할 수 있습니다.
고용주는 마이페이지에서 자신의 회사 정보, 구인글 정보, 지원자 목록을 확인할 수 있습니다.
- 마이페이지에서 특정 회사를 클릭하면, 해당 회사에 대한 구인글 목록 페이지로 이동합니다.
- 구인글 목록 중 특정 구인글을 클릭하면, 해당 구인글에 대한 지원자 목록 페이지로 이동합니다.
고용주 마이페이지 | 지원자 목록 확인 |
- 지원자 목록 페이지에서 각 지원자에 대한 지원서를 확인할 수 있습니다.
- 고용주가 채용을 원하는 지원자에 대해 '계약하기'버튼을 클릭하여 해당 지원자와의 근로계약서를 작성할 수 있습니다.
전체 화면 | 이력서 및 지원서 확인 확인 |
- 고용주가 '계약하기' 버튼을 클릭한 후 근로계약서 작성 페이지로 이동합니다.
- 고용주는 계약서 작성 전 주의사항 모달을 통해 지원자의 외국인 등록번호와 비자 발급일자를 확인할 수 있습니다.
- 주의사항 모달에서는 Hi Korea 하이퍼링크를 제공하며, 해당 사이트에서 위 정보를 토대로 근로자의 외국인 등록번호 및 비자 발급일자 정보를 검증할 수 있습니다.
- 고용주가 근로계약서를 작성하고 서명하기 버튼을 클릭하여 정상 제출이 완료되면, 근로자는 본인의 마이페이지에서 해당 근로계약서를 확인하고 서명할 수 있습니다.
- 3.3 단계와 같이 근로자가 계약서에 서명을 완료하면 채용이 완료됩니다.
고용주 주의사항 | 근로계약서 작성 |
근로자는 마이페이지에서 이력서 / 서명 / 외국인 번호 및 비자발급일자 등록 페이지로 이동할 수 있습니다.
- 근로자가 공고 지원을 하기 위해서는 필수적으로 위 3가지 정보 등록을 마쳐야합니다.
- 등록을 하지 않은 경우 마이페이지의 이력서 / 서명 / 외국인 번호 및 비자발급일자 버튼이 활성화 되며, 등록한 경우 아이콘이 체크표시로 변경되며 버튼이 비활성화됩니다.
이력서 등록 | 사인 등록 | 외국인 번호 및 비자발급 일자 등록 |
근로자는 특정 공고글에 대해 지원서를 작성할 수 있습니다.
- 근로자가 공고글의 '지원하기' 버튼을 클릭하면 먼저 채용 전반에 대한 안내문구, 특히 근로계약서에 대한 주의사항을 담은 페이지로 이동합니다.
- 근로자가 '지원서 작성하기' 버튼을 클릭하면 지원서 작성 페이지로 이동합니다.
지원 프로세스 | 지원서 작성 |
근로자는 마이페이지에서 자신이 지원한 공고에 대한 현황을 확인할 수 있습니다.
- 근로자가 지원한 이후, 현황을 나타내는 버튼은 총 3단계를 거쳐 변화합니다. [지원서 검토중] -> 고용주가 근로계약서를 작성함 -> [근로계약서 서명하기] -> [근로계약서 다운로드]
- 만약 고용주가 채용을 하지 않고 구인글을 마감한다면 [채용 마감] 버튼이 렌더링됩니다.
고용주가 '계약하기' 버튼을 클릭하여 근로계약서 작성을 완료했다면, 근로자는 해당 근로계약서를 확인하고 서명할 수 있습니다.
- 근로자가 서명 후 제출하면 채용 과정이 마무리됩니다.
- 이후 마이페이지에서 근로계약서 다운로드 버튼을 통해 다국어 근로계약서를 다운받을 수 있습니다.
계약서 |
각 팀원 별 구현을 담당한 기능을 나열합니다.
기능 | 상세 내용 | 구현방법 |
---|---|---|
구인글 저장 | 고용주가 구인글을 저장하면, 구인글 내의 정보들을 ai를 통해서 요약한 후, 이 내용과 구인글의 제목을 베트남어로 번역하여 테이블에 저장 | RestTemplate를 사용하여 openAi에 해당 내용을 요청하고, 응답으로 오는 값에서 원하는 정보를 추출하여 저장하였습니다. |
모든 구인글 반환 | 구인중인 모든 구인글을 반환 | 구인글 내의 구인 여부 필드를 이용하여 필터링을 한 후, DTO를 활용하여 메인페이지에 표시되는 데이터로 가공해서 보냅니다. |
구인글 상세 정보 반환 | RequestParam에 들어있는 id에 해당하는 구인글의 상세정보를 반환 | 요청받은 id에 해당하는 구인들을 조회하여, 해당 정보를 반환합니다. |
회사별 구인글 조회 | RequestParam에 들어있는 id에 해당하는 회사가 작성한 구인글을 반환 | 구인글과 회사는 1대다 관계에 있기 때문에 먼저 회사 객체를 찾은 후 findByCompany 메서드를 통해서 회사 별 구인글을 조회합니다. |
최근 등록된 구인글 순서대로 정렬 | 모든 구인글을 최근 등록된 순서대로 반환 | JPA의 기능을 활용하여 findAllByHiringTrueOrderByUploadDateDesc 메서드로 해당 기능을 구현했습니다. HiringTrue는 구인중인 구인글을 필터링하기 위해, OrderByUploadDateDesc는 최근 등록된 순서대로 반환하게 하기 위해서 사용됩니다. |
급여가 높은 순서대로 정렬 | 모든 구인글을 급여가 높은 순서대로 정렬하여 반환 | JPA의 기능을 활용하여 findAllByHiringTrueOrderByUploadSalaryDesc 메서드로 해당 기능을 구현했습니다. HiringTrue는 구인중인 구인글을 필터링하기 위해, OrderByUploadSalaryDesc는 급여가 높은 순서대로 반환하게 하기 위해서 사용됩니다. |
구인종료 | 구인 종료 api가 호출되면, 해당 구인글의 상태를 구인 마감으로 변경 | 해당 api로 들어온 id에 해당하는 구인글의 hiring 필드를 false로 바꾸어, 구인글을 모두 반환할 때 필터링 되도록 합니다. |
이력서 저장 | 사용자가 이력서를 입력하면 이를 저장 | 이력서에 해당하는 데이터가 요청되면 이를 이력서 Entity로 만들어 저장합니다. |
사용자의 이력서 반환 | 특정 사용자의 이력서 상세정보를 반환 | 해당 사용자로 이력서를 조회하여 이를 반환합니다. |
고용주가 이력서 열람 | 고용주가 지원자의 이력서를 열람한다. 이 때 해당 지원자의 지원서 내의 지원동기도 함께 반환 | 이력서에 대한 id와 지원에 대한 id를 받고, 각 필드에서 필요한 부분을 추출하여 반환합니다. |
특정 유저의 이력서 존재 여부 반환 | 사용자의 이력서가 이미 존재하면 에러반환 | 해당 유저의 이력서의 존재 여부를 파악한 후 존재하면 ResumeAlreadyExistException을 반환합니다. |
기능 | 구현 방법 | 설명 |
---|---|---|
토큰 검증 Interceptor | 요청 헤더에 있는 액세스 토큰의 유효성을 검증하는 인터셉터 구현 | 요청 헤더에 담겨져 오는 액세스 토큰에 대해, 만료 여부와 유저 정보 존재 여부, JWT 형식 검증 등을 수행하고, 유효할 시 Argument Resolver로 유저 ID를 넘겨줍니다. |
유저 ID DTO 매핑 Argument Resolver | 액세스 토큰 파싱 후 얻은 유저 ID를 컨트롤러 매개변수 DTO에 매핑하는 Argument Resolver 구현 | 인터셉터에서 넘어온 유저 ID를 DTO 객체로 매핑하여 컨트롤러에게 제공하는 Argument Resolver를 구현하였습니다. |
구글 OAuth 로그인 | 구글 OAuth 기반 로그인 및 서비스 자체 회원가입 처리 기능 구현 | 구글 OAuth 기반 로그인을 수행하고, 신규 사용자인 경우 구글 계정 정보를 가져와 유저 엔티티로 영속화하여 회원가입 처리를 하고, 유저에 대한 정보를 응답하는 API를 구현하였습니다. |
사용자 유형 등록 API | 특정 사용자의 고용주 or 근로자 유형 등록 API 현 | 특정 사용자의 유형을 요청받은(고용주 or 근로자) 유형으로 업데이트하는 API를 구현하였습니다. |
슬라이더 API | 메인 페이지 슬라이더 배너 이미지 API 구현 | 메인 페이지의 슬라이더 배너 영역에 사용할 이미지를 응답하는 API를 구현하였습니다. 해당 이미지 리소스는 GCS에서 관리하였습니다. |
CORS 설정 | 배포 환경에서 프론트엔드와의 CORS 관련 설정 | 프론트단 웹 브라우저에서 오는 preflight 요청에 대해서는 인터셉터를 패싱하도록 설정하였고, 허용 메소드와 허용 헤더의 범위를 최소한으로 축소하여 설정하였습니다. |
GCP 배포 | Nginx 웹 서버, API 서버, MySQL DB 배포 | GCP를 활용하여 배포하였습니다. 서버 API VM에 Jar 파일을 배포하였고, 앞단에 Nginx를 Docker container로 띄워서 클라이언트 요청에 대한 리버스 프록싱을 수행하였습니다. DB는 서버 인스턴스와의 분리 운영을 위해 GCP Cloud SQL을 활용하여 MySQL DB 인스턴스를 띄웠습니다. 그 외 VPC 방화벽과 네트워크 설정 등을 적절히 설정하고, API 서버 VM에 대한 SSH 접속용 키를 따로 만들어 팀원에게 공유하였습니다. |
CI/CD | 프로퍼티, SSH 키 주입 및 SCP로 Jar 파일 VM에 전송 | 민감한 정보를 github에 올리지 않고 action secret에 등록하여 CI/CD 과정에 주입하였고, Jar 파일 빌드 후 SCP로 GCP VM에 전송하였습니다. |
기능 | 상세 내용 | 설명 |
---|---|---|
고용주 근로계약서 작성 | 고용주가 근로계약서 작성 버튼을 눌렀을 때 동작되는 기능입니다. | request body의 내용이 근로계약서에 작성될 뿐 아니라 고용주 서명이 함께 작성되어 gcs에 저장됩니다.이미 만들어진 근로계약서에 대해서는 동작하지 않습니다. |
근로자 근로계약서 작성 | 근로자가 근로계약서 서명 버튼을 눌렀을 때 동작되는 기능입니다. | 고용주가 작성한 근로계약서를 가져와서 근로자 서명을 넣은 근로계약서를 gcs에 pdf 파일과 image 파일이 저장됩니다. |
근로계약서 다운로드 | 근로계약서 url을 반환하는 기능입니다. | 이미지를 반환하는 api와 pdf를 반환하는 api가 있으며 response는 url과 urlV로 url의 경우 한국어판 근로계약서 urlV는 베트남어판 근로계약서입니다. |
근로계약서 조회 | 지원 id로 근로계약서 내용을 조회할 수 있습니다. | query param에 있는 applyId를 통해 근로계약서 객체를 조회하여 내용을 반환합니다. |
기능 | 구현 방법 | 설명 |
---|---|---|
근로자 외국인 번호, 비자 등록 | DB에 외국인 번호 및 비자 정보 저장 | 근로자가 외국인 번호와 비자 정보를 입력하면 DB에 저장됩니다. |
근로자 외국인 번호, 비자 확인 | DB에서 외국인 번호 및 비자 정보 가져오기 | 고용주가 근로자의 외국인 번호 및 비자 정보를 확인합니다. |
사인 등록 | Goole Cloud Storage에 사인 이미지 저장 및 DB에 사인 이미지 URL 저장 | 고용주와 근로자가 자신의 사인을 등록합니다. |
사인 가져오기 | 사인 이미지 URL 가져오기 | 고용주와 근로자가 자신의 사인 이미지 URL을 가져옵니다. |
회사 등록 | DB에 고용주의 회사 정보 저장 | 고용주가 자신의 회사 정보를 입력합니다. |
회사 정보 확인 | DB에서 고용주의 회사 정보 가져오기 | 고용주 자신의 회사 정보를 가져옵니다. |
지원서 등록 | 근로자의 지원서 저장 및 지원 상태 저장. | 근로자가 지원서를 작성하여 특정 구인글에 지원서를 등록합니다. |
지원서 조회 | DB에서 지원서 정보 반환 | 고용주가 지원자의 지원서를 확인합니다. |
(고용주 입장)구인글에 지원한 지원자 확인 | 연관관계와 구인글 id를 통해 필요한 정보들 반환 | 고용주가 자신의 구인글에 지원한 지원자 목록을 확인하고, 지원서 확인 및 계약과 같은 다음 행동을 할 수 있도록 정보를 제공합니다. |
(근로자 입장)자신이 지원한 구인글 확인 | 연관관계를 이용하여 필요한 정보들 반환 | 지원자가 어떤 구인글에 지원했는지 확인할 수 있도록 필요한 정보를 제공합니다. |
비자, 외국인번호, 사인 등록 여부 확인 | DB에 접근하여 비자, 외국인번호, 사인이 등록되었는지 확인 후 boolean타입으로 반환 | 근로자가 비자, 외국인 번호, 사인을 모두 등록한 후에 구인글에 지원서를 등록할 수 있도록 3가지 등록 여부를 알려줍니다. |
기능 | 구현 방법 | 설명 |
---|---|---|
공통 컴포넌트 구현 | Headless UI 패턴 적용 및 스타일 가이드 설정 | 프로젝트 전반에서 사용되는 버튼, 카드, 입력 필드 등의 컴포넌트를 재사용 가능하게 설계하고 Headless UI 접근 방식을 통해 유연하게 구성했습니다. |
로그인 페이지 구현 | 구글 OAuth를 통한 소셜 로그인 | 구글 OAuth 로그인 기능을 추가하고, 사용자 인증 로직을 모듈화하여 재사용성을 높였습니다. |
고용인/피고용인 페이지 구현 | 공통 컴포넌트와 상태 관리 | 고용인과 피고용인에게 맞는 콘텐츠를 렌더링하되, 공통된 UI 구조와 상태 관리 방식으로 구현해 유지보수성을 높였습니다. |
메인 페이지 구현 | 역할별 콘텐츠 조건부 렌더링 | 근로자와 사업주가 동일한 메인 페이지를 사용하면서 역할에 맞는 데이터와 기능을 조건부로 노출하도록 설정했습니다. |
Select 컴포넌트 구현 | 다중 선택 옵션 제공, Headless UI 패턴 적용 | 다양한 선택 옵션을 제공하는 Select 컴포넌트를 Headless UI로 구현해 UX와 접근성을 높였습니다. |
Modal 컴포넌트 리팩토링 | 상태와 스타일 제어 방식 리팩토링 | 다양한 상황에서 사용할 수 있도록 Modal 컴포넌트를 리팩토링하여 상태와 스타일 제어의 유연성을 강화했습니다. |
메인페이지 API 연동 | 역할 기반 데이터 조건부 렌더링 | 메인 페이지의 콘텐츠를 API와 연동해 각 사용자 역할에 맞는 데이터를 동적으로 렌더링했습니다. |
구글 OAuth 로그인 구현 | 구글 OAuth와 커스텀 훅으로 인증 로직 모듈화 | 구글 OAuth 인증을 적용하고, useGoogleOAuth 커스텀 훅으로 인증 상태와 에러 처리를 관리해 재사용성을 높였습니다. |
가입자 정보 선택 API 연동 | 사용자 선택 정보 서버 전달 및 검증 | 사용자가 선택한 가입 정보를 서버에 안정적으로 전달하고, 유효성 검증을 통해 오류 없이 데이터를 처리했습니다. |
Pagination 컴포넌트 추가 및 Google OAuth 훅 리팩토링 | 페이지네이션 컴포넌트 추가 및 인증 훅 개선 | 페이지 전환을 위해 Pagination 컴포넌트를 추가하고, Google OAuth 훅의 상태 및 에러 처리를 강화했습니다. |
메인페이지, 로그인, 회원가입 테스트코드 구현 | Vitest와 React Testing Library를 활용한 테스트 구현 | SignInButton , ConditionalRenderer 컴포넌트와 useRecruitmentData 훅에 대해 주요 동작과 렌더링 결과를 검증하는 테스트를 추가하여 기능이 안정적으로 작동함을 확인했습니다. |
기능 | 설명 | 구현 방법 |
---|---|---|
공통 컴포넌트(Header) 구현 | 로그인하지 않은 사용자에게는 로그인 버튼을, 로그인한 사용자에게는 프로필 이미지와 로그아웃 버튼을 동적으로 표시합니다. | 사용자 인증 상태에 따라 내용을 동적으로 변경합니다. |
공통 컴포넌트(Table) 구현 | 고정된 스타일과 함께 유연한 테이블 구조를 제공합니다. | Table, Th, Td 컴포넌트를 사용하여 여러 페이지에서 재사용 가능한 테이블 구조를 제공합니다. |
외국인 등록 번호 및 비자 발급 일자 등록 페이지 구현 | 근로자가 외국인 등록 번호 및 비자 발급 일자를 입력할 수 있는 폼을 제공합니다. | 폼 입력과 유효성 검증을 통해 사용자 입력을 처리하여 올바른 데이터가 등록되도록 합니다. |
고용주 마이페이지 구현 | 로그인한 고용주의 정보를 표시하고, 등록한 회사 목록을 표시합니다. | 고용주의 이름과 회사 등록, 사인 등록 버튼을 렌더링 하고 등록한 회사의 정보를 목록으로 보여줍니다. |
내 회사 페이지 구현 | 회사 정보 및 해당 회사의 구인 공고글 목록을 표시합니다. 지원자 보러가기 버튼을 눌러 지원자 목록 페이지로 이동할 수 있으며, 마감하기 버튼을 눌러 해당 공고를 마감합니다. | 선택한 회사의 채용공고를 등록할 수 있도록 채용 공고 버튼을 렌더링하고, 해당 회사의 공고글을 목록으로 표시합니다. |
지원자 목록 페이지 및 계약하기 팝업창 구현 | 공고글의 정보 및 해당 공고글의 지원자 목록을 표시합니다. 목록에서 지원서 버튼을 클릭하면 해당 지원자의 지원서를 열람할 수 있고, 계약하기 버튼을 누르면 주의 사항과 해당 지원자의 정보를 열람한 후 해당 지원자와 계약서를 작성할 수 있습니다. | 선택한 공고글의 정보와 해당 공고글의 상세 내용을 볼 수 있도록 자세히 보러가기 버튼을 렌더링하며, 공고글에 대한 지원자 목록을 표시합니다. Modal 컴포넌트를 사용하여 계약하기 버튼 클릭 시 고용주에게 주의 사항을 알리는 팝업창을 구현했습니다. |
섹션 | 상세 내용 | 설명 |
---|---|---|
공통 컴포넌트(Footer) 구현 | Layout에 포함되는 공통 Footer 컴포넌트입니다. | 홈페이지 하단에 렌더링되는 공통 Footer 컴포넌트입니다. 사이트 운영 정보에 대한 내용이 기재되어 있습니다. |
구인글 상세 화면 페이지 구현 | 구인글에 대한 상세 정보가 렌더링됩니다. | 근로자는 구인글 상세 화면을 통해 지원할 수 있습니다useGetRequiredFieldCheck 을 통해 근로자의 필수값(사인, 이력서, 비자 및 외국인 번호)등록 여부를 확인할 수 있습니다. |
지원 프로세스 페이지 구현 | 지원서 안내 멘트를 렌더링합니다. | 근로자가 지원서 작성 전 확인할 수 있는 안내 멘트들이 렌더링 되어있습니다. |
지원서 작성 페이지 구현 | 근로자가 구인글을 통해 지원서를 작성합니다. | react-hook-form 을 통해 유효성 검사를 진행했습니다. 이름, 주소, 번호, 지원 동기를 필수로 입력해야 하며, 번호의 경우 010-0000-0000 형식에 맞게 입력해야 합니다. |
이력서 등록 페이지 구현 | 근로자가 이력서를 등록합니다. | react-hook-form 을 통해 유효성 검사를 진행했습니다. 이름, 주소, 번호, 경력, 한국어 실력, 소개를 필수로 입력해야 하며, 번호의 경우 010-0000-0000 형식에 맞게 입력해야 합니다. |
사인 등록 페이지 구현 | 근로자 및 고용주의 사인을 등록합니다다. | react-signature-canvas 를 통해 서명을 받고 사진으로 저장합니다. |
기능 | 구현 방법 | 설명 |
---|---|---|
구인글 등록 | 구인글 정보를 입력받고 유효성 검사 및 서버로 데이터 전달 | 구인글 정보를 입력받아 state로 관리하고, submit 시 서버로 데이터를 전달합니다. 미입력된 데이터의 에러메세지를 화면에 렌더링합니다. |
회사 등록 | 회사 정보를 입력받고 유효성 검사 및 서버로 데이터 전달 | 회사 정보를 입력받아 state로 관리하고, submit 시 JSON을 문자열로 변환한 값과, 이미지 데이터를 formData에 삽입하여 전달합니다. 회사 이미지는 미입력이 가능하며, 이 경우 formData에 이미지를 삽입하지 않고 전달합니다. |
근로자 마이페이지 열람 | 근로자의 프로필과 이력서/서명/비자 등록 버튼, 근로자가 지원한 공고 내역 렌더링 | 근로자가 본인의 이력서/서명/비자 등록 페이지로 이동할 수 있게 하고, 등록 시 버튼을 비활성화합니다. 근로자가 지원한 공고 내역을 리스트로 보여주고, 지원 상태 type을 명세하여 각 type에 해당하는 버튼을 보여줍니다 |
근로계약서 작성 | 고용주가 근로계약서를 작성하고, 서명하여 서버로 데이터 전달 | 근로계약서 정보를 입력받아 state로 관리하고, submit 시 서버로 데이터를 전달합니다. 이때 서명하기 버튼을 클릭해야 submit이 가능하도록 에러메세지를 화면에 렌더링합니다. |
근로계약서 열람 및 서명 | 고용주가 작성한 근로계약서 정보를 근로자가 열람 및 서명 시 서버로 데이터 전달 | 근로계약서 정보를 화면에 렌더링합니다. 근로자가 서명하기 버튼을 클릭하고 submit 시 서버로 서명했음을 전달합니다. 서명하지 않고 submit 시 에러메세지를 화면에 렌더링합니다. |
근로계약서 다운로드 | 고용주 및 근로자의 계약서 서명 후 마이페이지에서 근로계약서 pdf 다운로드. | 마이페이지에서 근로계약서 다운로드 버튼을 클릭하면, 서버로부터 pdf URL을 전달받습니다. 보이지 않는 다운로드 링크를 생성 후 실행하여 로컬PC에 다운로드 후 삭제합니다. |
공통컴포넌트 Card | 공통컴포넌트 Card의 style 지정 | Card 형태의 style을 작성하고, children을 Props로 받아 재사용성을 높이고자 하였습니다. |
공통컴포넌트 Button | Button의 design을 특정(default, outlined, textbutton, deactive)하여 개발 | default, outlined, textbutton, deactive 형태의 Button을 만들어 design Props로 전달받고, 각 design에 해당하는 스타일과 애니메이션을 적용하였습니다. |
프로젝트를 진행하면서 각자 깊게 고민하고 해결하려 노력했던 점에 대한 회고입니다.
-
Mapping 방식
- 고민:
Entity에서 DTO로 변환할 때, 처음에는 직접 Mapper 메서드를 만들어 사용하는 방식을 택했습니다. 하지만 이 방식은 시간이 지나면서 코드가 지저분해 보였고, 필드가 변경될 때마다 해당 메서드를 일일이 수정해야 하는 번거로움이 있었습니다. - 해결책:
이를 개선하고자 MapStruct를 도입했는데, 처음에는 학습에 시간이 꽤 걸렸지만, 일단 익히고 나니 코드가 훨씬 깔끔해졌고 유지보수도 훨씬 용이해졌습니다. MapStruct 덕분에 매핑 작업이 자동화되어 변환 로직을 일관되게 유지할 수 있었고, 필드 변경 시에도 쉽게 대응할 수 있었습니다. 이 경험은 코드의 가독성과 유지보수성 향상에 큰 도움이 되었습니다
- 고민:
-
패키징
- 고민:
AI를 활용한 번역 기능이 구인글에 사용되기는 하지만, 다른 도메인에서도 번역 기능이 필요할 수 있다는 점에서 외부 API를 사용하여 번역하는 메서드를 어느 패키지에 넣어야 할지 고민이 있었습니다. - 해결책:
멘토님의 조언을 받아, 번역 로직을 특정 도메인에 종속시키지 않고 재사용성을 높이기 위해 infrastructure 패키지를 따로 만들어 그 안에 AI와 통신하는 데 필요한 DTO와 클래스들을 배치했습니다. 이로써 코드 구조가 더 명확해졌고, 번역 기능이 다른 도메인에서 필요할 때도 손쉽게 확장할 수 있는 기반을 마련할 수 있었습니다.
- 고민:
-
테이블 분리
- 고민: 고용주 클라이언트로부터 구인글 데이터를 받으면 이를 요약하고 한국어와 베트남어로 번역한 내용을 각각 저장해야 했습니다. 처음에는 요약된 구인 내용과 번역된 필드를 기존 구인글 테이블에 함께 저장할지 고려했지만, 모든 정보를 하나의 테이블에 담으면 데이터의 밀도가 너무 높아져 테이블이 비대해질 우려가 있었습니다.
- 해결책: 데이터의 효율적인 관리와 성능 최적화를 위해 vertical partitioning 방식을 적용했습니다. 주요 구인글 정보와 요약 및 번역된 내용은 별도의 테이블로 나누어 관리함으로써, 각 테이블의 역할이 명확해졌고 성능 또한 개선할 수 있었습니다. 이를 통해 데이터의 확장성과 관리 편의성도 확보할 수 있었습니다.
-
git flow 고민
- 고민:
처음에 원격 레포를 포크 떠서 팀원들과 작업하다가, git flow에 대한 최적화 필요성을 느껴서 이에 대한 멘토링을 받았고, 그 과정에서 포크 레포가 불필요하다는 조언을 들었습니다. - 해결책:
이를 반영하여 포크 레포는 삭제한 뒤 원격 레포서 main → develop → Weekly → feature 브랜치 전략을 구성하였습니다. 팀원들 각자 원격 레포의 담당 feature 브랜치에 push 후 Weekly 브랜치에 PR을 날리고, 테크리더인 제가 검토 후 머지하는 식으로 운영하였습니다.
- 고민:
-
CAA 레코드
- 고민:
배포 과정에서 certbot을 통해 nginx에 자동 SSL 인증서 처리를 수행하려고 했는데, 그 과정에서 계속 실패하는 문제가 발생하였습니다. - 해결책:
여러 가지 방법을 시도해봐도 해결책을 찾기가 어려웠는데, 에러 로그에서 CAA 레코드에 관한 내용이 보여서 이에 대해 더 찾아보았고, 그 결과 해당 레코드의 역할은 인증 기관이 저희 서비스의 도메인에 대한 인증서를 발급할 수 있게 허용해주는 것임을 알게 되었습니다. 이후에 GCP DNS 설정에서 CAA 레코드를 추가하여 letsencrypt에 대해 허용해주었고, 정상적으로 HTTPS를 적용할 수 있었습니다.
- 고민:
-
CI/CD
-
고민:
보안을 위해 VPC 방화벽 설정에서 SSH 허용 범위를 전역에서 팀원들 IP로 축소하고 나니, CI/CD 과정에서 github actions 서버가 SCP를 통해 API 서버 VM으로 Jar 배포 파일을 전송할 때, SSH 접근을 할 수 없는 상황이 발생하였습니다. -
해결책:
github actions 서버의 IP를 SSH 허용 규칙에 추가해줘야하는데, 해당 IP가 유동적이라는 문제가 있었습니다. 이에 대한 해결 방법으로 다음과 같이 3가지 방법을 추려내었습니다.-
깃헙에서 유료로 제공하는 고정 IP 활용
-
SSH 터널링
-
github actions에서 GCP에 접근해서 직접 SSH 규칙에 유동 IP 추가하고, SCP 수행 후 규칙에서 해당 IP 제거하도록 yml 코드 구성하기
비용과 난이도 측면을 고려했을 때 가장 합리적인 3번 방법을 채택하여 문제를 해결하였습니다. 관련 yml 코드는 GPT를 참고하였는데, 리눅스 명령어 문법과 정규표현식 부분에서 반복적으로 이상한 답을 제시해서 애를 많이 먹었습니다.
GPT가 제시한 답에서 sed 명령어와 정규표현식 구성에 대한 이해도를 높이기 위해 추가적인 질의 응답을 진행하였고, 이후 해결할 수 있었습니다.
-
-
- 요청 데이터 처리
- 고민:
JSON과 이미지 파일은 서로 다른 Content-Type을 가지기 때문에 서버에서 동일한 요청에서 이 두 가지를 처리할 방법을 찾기 어려웠습니다. - 해결책:
서로 다른 Content-Type을 처리하기 위해 consumes = MediaType.MULTIPART_FORM_DATA_VALUE를 사용하여 데이터를 한 번에 받았습니다. JSON 파일을 문자열로 받되, Object Mapper를 이용해 객체로 변환하였고 이미지 파일은 MultipartFile형태로 받아 처리했습니다.
- 고민:
-
Service 파일 관리
- 고민: 근로계약서 도메인에는 file을 처리하는 서비스와 pdf를 처리하는 서비스와, db를 처리하는 서비스가 존재하고 이를 어떻게 관리해야 할 지에 대한 고민을 했습니다.
- 해결책: 처음에는 contract 패키지 내의 service에 모아 관리하였으나 멘토님의 조언을 받아 common 패키지에 file service와 pdf service를 모아두고 contract service에서 위의 서비스들을 불러와서 처리하는 방식으로 해결했습니다.
- 고민: 근로계약서 도메인에는 file을 처리하는 서비스와 pdf를 처리하는 서비스와, db를 처리하는 서비스가 존재하고 이를 어떻게 관리해야 할 지에 대한 고민을 했습니다.
-
OneToOne 매핑 시 처리해야 하는 것
- 고민: 지원과 근로 계약서를 1대1 매핑한 상황에서 지원에 대한 근로계약서 생성 버튼을 프론트 측에서 두 번 누를 가능성에 대해 고민을 했습니다.
- 해결책: 근로계약서 api에서는 지원 객체에 근로계약서가 존재하지 않을 때만 처리하도록 구현하였으나 이미 지원자에 대한 근로계약서를 작성한 근로자에 대해서 고용주에게 보여지지 않을 경우 더 완전하게 처리할 수 있을 것 같아서 지원 api를 담당한 분께 요청하여 수정했습니다.
- 고민: 지원과 근로 계약서를 1대1 매핑한 상황에서 지원에 대한 근로계약서 생성 버튼을 프론트 측에서 두 번 누를 가능성에 대해 고민을 했습니다.
-
공통 컴포넌트 및 스타일 재사용성과 유연성 확보
- 고민:
반복적으로 사용되는 컴포넌트와 스타일을 효울적으로 관리하기위해 컴포넌트를 재사용 가능하게 만들기 유연한 설계가 필요했으며, 디자인의 일관성을 유지해야 했습니다. 특히, 다양한 상황에서 활용될 수 있는 Select와 Modal 컴포넌트를 클린하게 설계하는 것이 큰 도전이었습니다. - 해결책:
기능과 스타일 측면에서 분리하기 위해Headless UI
접근 방식을 도입했습니다. 이를 통해Select
,Modal
컴포넌트는 로직과 상태 관리에 집중하고, 스타일은 외부에서 주입하여 커스터마이징할 수 있도록 설계했습니다.
스타일 가이드 및 공통 스타일 설정:
프로젝트 초기에 팀원들과 함께 스타일 가이드를 수립하여 폰트, 색상, 버튼 크기 등 공통 요소에 대한 규칙을 정했습니다.
반응형 디자인 구현을 위한responseStyle
함수 개발:
responseStyle
함수를 구현하여 미디어 쿼리를 보다 명시적으로 작성하고, 반복되는 반응형 디자인을 정의하여 일관성 있게 관리할 수 있도록 했습니다.
- 고민:
-
Google OAuth 구현
- 고민:
Google OAuth를 통한 소셜 로그인 인증 흐름을 이해하고, 유지보수하기 쉽게 모듈화하는 방법에 대해 고민했습니다. - 해결책:
- 커스텀 훅useGoogleOAuth
모듈화:
인증 로직을useGoogleOAuth
커스텀 훅으로 분리하여 모듈화하여 인증 과정에서 필요한 상태 관리와 함수들을 포함하며, 로그인 버튼 클릭부터 토큰 발급, 사용자 정보 저장까지의 과정을 캡슐화했습니다.
- 고민:
-
사용자 인증 상태에 따른 동적 UI 변경
- 고민: 헤더 컴포넌트에서는 사용자의 로그인 상태에 따라 로그인하지 않은 사용자에게는 로그인 버튼을, 로그인한 사용자에게는 프로필 이미지와 로그아웃 버튼을 표시해야 했습니다. 이에 따라 사용자 인증 상태를 어떻게 효율적으로 관리할지에 대해 고민했습니다.
- 해결책: UserProvider를 구현하여 사용자 데이터를 상태로 관리하고, 로컬 스토리지에서 사용자 정보를 가져와 상태를 초기화하도록 했습니다. 또한 스토리지에 변경이 있을 때마다 사용자 정보를 업데이트 하도록 설정했습니다. 헤더 컴포넌트에서는 useUser 훅을 통해 UserContext의 사용자 데이터를 조회하고, 이 데이터에 따라 조건부 렌더링을 수행하도록 하여 사용자 인증 상태에 따라 UI가 동적으로 반응하도록 했습니다.
- 고민: 헤더 컴포넌트에서는 사용자의 로그인 상태에 따라 로그인하지 않은 사용자에게는 로그인 버튼을, 로그인한 사용자에게는 프로필 이미지와 로그아웃 버튼을 표시해야 했습니다. 이에 따라 사용자 인증 상태를 어떻게 효율적으로 관리할지에 대해 고민했습니다.
-
재사용하지 않는 컴포넌트의 분리
- 고민: 고용주에게 계약 시 주의 사항을 알리는 팝업 창을 구현하면서, 가독성을 향상 시키기 위해 텍스트 부분을 별도의 컴포넌트로 분리했습니다. 하지만 이렇게 분리한 컴포넌트가 다른 곳에서 재사용되지 않아 분리한 것에 대한 고민이 있었습니다.
- 해결책: 컴포넌트의 분리가 재사용성만을 기준으로 판단되어야 하는 것은 아니며, 컴포넌트를 관심사 단위로 분리하면 렌더링 최적화도 자연스럽게 이뤄질 수 있다는 조언을 받았습니다. 이를 통해, 분리한 컴포넌트가 현재 프로젝트에서 재사용되지 않더라도 코드의 질을 향상 시킬 수 있음을 알게 되었습니다.
- 고민: 고용주에게 계약 시 주의 사항을 알리는 팝업 창을 구현하면서, 가독성을 향상 시키기 위해 텍스트 부분을 별도의 컴포넌트로 분리했습니다. 하지만 이렇게 분리한 컴포넌트가 다른 곳에서 재사용되지 않아 분리한 것에 대한 고민이 있었습니다.
-
효율적이며 협력적인 참여자가 되는 법
- 고민 및 어려움: 말을 하면서 생각하는 습관 때문에, 대화가 주제를 두루뭉술한 방향으로 흘러가게 할 때가 많았습니다. 어떻게 하면 제 스스로가 커뮤니케이션 비용을 줄임과 동시에 협력적인 참여자가 될 수 있을지에 대한 고민이 많았습니다.
- 결과:팀원들의 화법을 집중해서 듣고 이를 따라 하거나 일부 적용하는 방식을 시도했습니다. 확실히 혼자만의 방식으로 대화할 때보다 훨씬 더 효율적인 소통이 가능했습니다. 물론 대화에 절대적인 정답은 없기에 이 방법이 완벽한 해답은 아니지만, 앞으로의 협업에서 대화를 어떻게 진행해야 할지에 대한 유용한 지표를 얻을 수 있었습니다.
- 고민 및 어려움: 말을 하면서 생각하는 습관 때문에, 대화가 주제를 두루뭉술한 방향으로 흘러가게 할 때가 많았습니다. 어떻게 하면 제 스스로가 커뮤니케이션 비용을 줄임과 동시에 협력적인 참여자가 될 수 있을지에 대한 고민이 많았습니다.
-
Input 렌더링
- 고민:
많은 Input이 존재하는 구인글 등록 페이지를 구현하였습니다. 이때 모든 input을 useState로 한꺼번에 관리하게 되면, 코드가 깔끔해지고 가독성이 높아진다는 장점이 있지만, 하나의 input만 수정해도 전체 페이지가 렌더링되는 문제가 발생합니다. - 해결책:
현재는 위 방식을 사용하고 있지만, 추후 고도화 작업에서 useRef를 사용하여 Input 상태관리를 최적화하고자 합니다.
- 고민:
-
컴포넌트 종류에 따른 사용 규칙
- 고민:
팀에서 개발한 공통 컴포넌트, 라이브러리를 가져와 사용하는 컴포넌트, 개인이 사용하기 위해 만든 컴포넌트 등 여러 종류가 존재하는데, 이를 혼용하게 되면 파악이 어려워집니다. - 해결책:
특정 컴포넌트 내에서만 사용되는 임시 컴포넌트의 경우, 다른 파일로 분리되어있으면 더 헷갈릴 수 있다는 멘토님의 조언을 참고하여, style 관련 컴포넌트는 같은 파일의 하단에 작성하고, 나머지는 파일을 분리하는 방법을 택해 진행하였습니다. 이러한 규칙을 인지하고 개발하니 더욱 빠르게 코드를 파악할 수 있었습니다.
- 고민: