Fullstack 109

heo4·2026년 9월 20일

Fullstack

목록 보기
64/70

풀스택

🏥 CareMatch - 요양보호사 매칭 플랫폼 프로젝트 회고

국비과정 4인 팀 프로젝트로 진행한 CareMatch를 발표까지 마쳤다. 기획부터 배포, 발표까지의 과정을 정리해본다.

🔗 서비스 링크: https://care-match-lake.vercel.app


1. 서비스 소개

한 줄 소개

CareMatch는 요양보호사·간병인·가사도우미와 요양시설을 연결하는 구인구직 매칭 플랫폼입니다.

고령화 사회로 접어들면서 요양 인력에 대한 수요는 늘고 있지만, 정작 구직자와 시설을 연결해주는 서비스는 파편화되어 있다는 문제의식에서 출발했다. 단순 매칭을 넘어 조건 기반 매칭 점수까지 제공하는 게 핵심 차별점이다.

타겟 사용자

  • 구직자 (요양보호사·간병인·가사도우미) — 일자리 검색, 지원, 프로필/자격증 관리, "인증구직자" 마크로 신뢰도 표시
  • 시설 (요양원·방문요양센터 등) — 공고 등록, 인재 검색, 지원자 관리
  • 보호자회원 — 구직 의사 없이 개인적으로 요양보호사를 찾는 소비자 계정

2. 핵심 기능

회원 / 로그인

  • 구직자 / 시설 / 보호자 회원가입을 분리했고, 시설은 사업자등록증 첨부 후 관리자 승인 전까지 대기 상태로 처리
  • 아이디·비밀번호 로그인(JWT) + 카카오 소셜 로그인
    • 백엔드는 네이버·구글까지 3사 공용 구조로 만들어뒀지만, 심사 리스크를 줄이기 위해 프론트에는 카카오만 노출
  • 로그인 5회 연속 실패 시 15분 계정 잠금
  • 비밀번호 찾기, 회원 탈퇴 시 재로그인 세션 전부 무효화
  • 고령 사용자를 고려한 "쉬운 화면 모드" / 글자 크기 조절 — 기기 저장 + 로그인 시 서버 동기화로 다른 기기에서도 유지

구인공고 (시설 → 구직자)

  • 등록·수정·마감·임시저장 지원
  • 정렬 옵션: 추천순(노출등급+매칭점수) / 최신순 / 마감임박순 / 급여순 / 조회순 / 매칭점수순
  • 카카오맵 연동으로 위치 기반 반경 검색, 지도 뷰포트 안의 공고 마커 표시 및 클러스터링
  • 스크랩(찜) 기능, 유료 상단노출 등급(NORMAL / PREMIUM / SPECIAL)

매칭 시스템 (핵심 차별점)

구직자가 등록한 희망조건(직종·지역·근무형태·근무시간대·희망급여)과 공고 조건을 비교해 0~100점 매칭 점수를 자동 계산한다.

  • 가중치: 직종 35 + 지역 30 + 근무형태/시간대 20 + 급여 15
  • 공고 상세에는 왜 이 점수가 나왔는지 매칭 이유(사유 칩)까지 함께 보여준다 (예: "직종 일치", "지역 불일치")

인재정보 (시설 → 구직자 찾기)

  • 승인된 시설회원만 지역/직종/경력/자격증/희망급여 등 조건으로 인재 검색 가능
  • 개인정보 비공개 원칙 — 목록/상세 모두 이름을 마스킹하고, 연락처는 포인트로 "열람(unlock)"해야 확인 가능하며 한 번 열람하면 재열람은 무료

구직신청, 인증구직자 마크

  • 공고 상세에서 온라인 지원/취소, 시설은 지원자 수락·반려 처리
  • 승인된 자격증 + 승인된 경력인증을 각 1건 이상 보유해야 "인증구직자" 마크를 신청할 수 있고, 관리자 최종 승인 시 부여

포인트 & 결제

  • 공고 등록, 인재 연락처 열람 시 포인트가 차감되는 구조
  • 포트원(PortOne) V2로 실제 결제 연동 — 서버가 결제 금액/상태를 포트원 서버 API로 재조회해 검증(클라이언트 위변조 방지)

알림, 고객센터 & 관리자

  • 지원 결과, 시설 승인/반려, 문의 답변, 마크 승인/반려 시점에 알림 생성 — 헤더 배지가 30초 주기로 폴링
  • 공지사항/FAQ/1:1 문의, 관리자용 회원 관리·심사·통계 기능 일체 구현

파일 업로드

자격증, 경력인증 증빙, 사업자등록증, 프로필 사진 등은 presigned URL 방식으로 업로드해서, 서버를 거치지 않고 클라이언트가 스토리지에 직접 업로드하도록 만들어 서버 부하를 줄였다.


3. 기술 스택

구분기술
프론트엔드React 19, Vite, TypeScript, Tailwind CSS, React Router — Vercel 배포
백엔드Spring Boot 3, Spring Security(JWT/OAuth2), Spring Data JPA — Render 배포(Docker)
DBPostgreSQL(Neon), 로컬은 H2 인메모리
마이그레이션Flyway (운영에만 자동 적용)
파일 스토리지Cloudflare R2 (S3 호환, presigned URL)
결제포트원(PortOne) V2
지도카카오맵 SDK
CIGitHub Actions (PR마다 프론트/백엔드 자동 빌드·린트·타입체크)

전부 무료/저비용 티어로 구성해서 실제 서비스처럼 엔드투엔드 배포까지 완료한 게 포인트다.


4. 아키텍처 & 협업 구조

전체 구조

[React (Vercel)]  ──HTTPS/JWT──▶  [Spring Boot API (Render, Docker)]
                                        │
                                        ├──▶ PostgreSQL (Neon)
                                        ├──▶ Cloudflare R2 (파일 presigned URL)
                                        └──▶ PortOne (결제 검증)

모노레포로 backend/, frontend/, docs/를 분리했고, 프론트/백엔드가 완전히 분리된 SPA + REST API 구조에 JWT(Access/Refresh) 기반 인증을 적용했다.

협업 프로세스

4명이 겹치지 않고 개발하기 위해 아래 규칙을 세웠다.

  • 브랜치 전략: GitHub Flow — main(직접 push 금지) + feature/*. 담당 영역별 접두사를 고정해서(feature/fe-*, feature/be-*) 브랜치 이름만 봐도 누가 어느 영역을 작업 중인지 파악 가능
  • 커밋 컨벤션: feat: / fix: / refactor: / style: / docs: / chore: 태그로 의도 구분, 작업 중간엔 wip: 커밋 후 나중에 Squash merge
  • PR 규칙: 팀원 1명 이상의 리뷰 승인 후에만 머지, 승인/머지는 항상 사람이 GitHub에서 직접 진행
  • CI 게이트: PR을 열면 GitHub Actions가 프론트(lint→tsc→build)/백엔드(gradle build+test)를 자동 실행해서 리뷰 전에 빌드 깨짐을 걸러냄
  • 배포 태깅: 배포 시점마다 main에 vX.Y.Z 태그만 남기고, main push 시 Vercel/Render가 자동 배포

백엔드 도메인 구조

member, auth, jobposting, application, certificate, badge, contact, notification, point, terms, verification, support, storage, security — 도메인 단위로 패키지를 나누고, 각 도메인 안에서 controller/service/repository/domain/dto를 분리해 기능 단위로 독립적으로 개발·리뷰할 수 있게 구성했다.

보안/설계 포인트

  • presigned URL 기반 업로드/다운로드 — 영구 공개 URL 없이 만료 시간 존재
  • 인재 정보(연락처 등)는 승인된 시설회원만 접근 가능하며, 마스킹 + 포인트 열람 구조로 이중 보호
  • 로그인 실패 잠금, Refresh Token 해시 저장(SHA-256)
  • 결제/포인트처럼 "확인 → 부수효과 → 저장" 흐름은 비관적 락으로 동시요청 이중처리를 방지
  • 개인정보(전화번호·거주지)는 엔티티에는 원본을, DTO 응답에서만 마스킹하는 원칙을 일관되게 적용

5. 개발 과정에서 겪은 문제들

실제로 부딪혔던 문제와 해결 과정을 정리하면 아래와 같다.

  • CORS: Vercel 프리뷰 배포마다 서브도메인이 바뀌어 고정 목록으로 막힘 → 패턴 매칭(AllowedOriginPatterns)으로 전환
  • Render 배포 실패: 동적 포트 미바인딩 문제 → server.port=${PORT:8080}으로 해결
  • Hibernate @Lob 이슈: PostgreSQL에서 문자열 컬럼이 oid로 생성되는 버그 → TEXT 컬럼 명시 + Flyway 마이그레이션 도입
  • 병합 충돌 잔여물 커밋 사고: 리뷰 없이 머지되며 충돌 마커가 그대로 커밋되어 배포 브랜치가 깨짐 → GitHub Actions CI 도입으로 재발 방지
  • 결제/포인트 동시요청 이중처리: "확인 → 차감/적립 → 저장" 흐름에서 예외를 catch하는 방식으로는 막을 수 없다는 걸 동시성 테스트로 직접 재현 → 비관적 락으로 재설계
  • 카카오 로그인 연동: 이메일 동의항목은 사업자 심사가 필요해서, 닉네임만으로 로그인 가능하도록 임시 이메일 placeholder로 우회

6. 마무리 & 향후 계획

현재 구현 완료 범위: 회원가입/로그인(소셜 포함)/비밀번호 찾기/회원 탈퇴, 구인공고 CRUD/검색/지도/매칭점수순 정렬, 매칭 점수 계산, 인재검색+연락처 열람, 구직신청, 인증구직자 마크, 포인트 실결제(포트원), 알림, 관리자 대시보드, 고객센터, 파일 업로드, 접근성 설정까지 — 엔드투엔드로 배포된 상태다.

향후 과제

  • 회원가입 인증코드가 아직 mock 상태 (서버 로그에만 찍히고 실제 이메일/SMS 미발송)
  • 프론트 테스트 코드 0개, 에러 모니터링 미도입
  • 시설 상세 정보(시설유형/담당자 직책 등) 확장
  • 거동등급 표기 추가

발표를 마치며

기획부터 배포까지 4명이 나눠서 진행한 프로젝트를 실제로 발표까지 해보니, 기능 구현 자체보다도 CORS, 배포 환경, 동시성 문제처럼 실제 서비스 단계에서만 만날 수 있는 이슈들을 겪고 해결한 경험이 가장 값진 부분이었다. 특히 결제/포인트 동시요청 문제를 직접 테스트로 재현하고 비관적 락으로 재설계했던 과정은 이후 포트폴리오에서도 강조할 만한 부분인 것 같다.

0개의 댓글