› 기술문서 › 데이터·통신 흐름

데이터 · 통신 흐름

경기장에서 수집한 GPS 좌표가 어떤 경로로 저장·분석되어 리포트가 되는지, 그리고 각 구간이 어떤 프로토콜과 보안 방식으로 통신하는지 설명합니다.

AWS Serverless HTTPS / TLS 1.2+ 1Hz GPS 샘플링

1. 한눈에 보기

SoccerGPS는 기기에서 1차 계산 → 서버에서 검증·재계산하는 2단 구조입니다. 워치(Wear OS)가 GPS 위치와 심박 등 컨디션 지표를 수집하고, 폰이 이를 중계해 서버로 업로드합니다. 네트워크나 블루투스가 끊겨도 각 기기가 로컬(Room DB)에 계속 기록하고 지연 재전송 큐에 보관하므로 경기 데이터가 유실되지 않습니다.

1수집워치 · GPS + 심박(1분)
2로컬 저장Room DB + 재전송 큐
3중계·업로드워치→폰(BT)→서버 HTTPS+JWT
4분석Lambda 재계산 · 쿼터 단위
5리포트지도 1분 마커 · 컨디션
계층 구조 — 팀 → 매치 → 쿼터

팀(KRTM…)의 감독·코치·사무장이 경기일에 매치(KRMC…)를 만들고, 매치 안에 쿼터(KRQT…, 실제 뛴 구간)를 20/25/30/45분 등 커스텀 시간으로 생성합니다. GPS 트랙과 컨디션 지표는 쿼터 단위로 업로드됩니다. 선수는 고유 ID KRPL…를 갖습니다.

2. 경기 기록 데이터 흐름 (워치 → 폰 → 서버)

⌚ 워치 (Wear OS) GPS(모드별 1~5초) 심박 등 컨디션 1분 수집 Room + 재전송 큐 📱 폰 (중계) 워치 트랙 수신 지연 수신 큐 지도 1분 마커 렌더 🚪 API Gateway HTTP API JWT Authorizer 검증 λ Lambda 쿼터 트랙 저장·재계산 컨디션 요약(심박) 🗄 DynamoDB 쿼터 요약·컨디션·팀(GSI1) 📦 S3 원본 트랙 tracks/{match}/{quarter} ② 블루투스(Data Layer) ③ 업로드 HTTPS+JWT invoke ④ 요약 저장 ④ 원본 트랙 ⑤ 블루투스 끊김 시: 복귀 후 재전송 큐 → 서버 직접 업로드 기기 내부 처리 쿼터 시작 → Foreground Service(위치+헬스) 기동, 화면 꺼져도 유지 GPS는 배터리 모드(정확/균형/절약)에 따라 1~5초 간격 수집 → Room Health Services로 심박 등 컨디션을 1분당 1건 저장 폰이 멀어 전송 실패하면 재전송 큐에 적재(유실 없음) 종료 후 analytics/track 조회 → 지도 1분 마커·컨디션 팝업
  1. 쿼터 시작 — 폰 블루투스 근처에서 쿼터를 시작하면 워치의 Foreground Service(위치+헬스)가 기동합니다. GPS는 배터리 모드에 따라 1~5초 간격으로 수집하고, 심박 등 컨디션 지표를 1분당 1건 Room DB에 저장합니다.
  2. 블루투스 중계 — 워치는 수집한 트랙·컨디션을 Wear Data Layer(블루투스)로 폰에 전달합니다. 폰은 이를 수신 큐에 담아 서버로 중계합니다.
  3. 업로드 — 트랙과 컨디션 지표(metrics[])를 쿼터 단위로 POST /matches/{id}/track?quarterId=…로 전송합니다.
  4. 서버 분석 — Lambda가 원본 트랙을 S3(tracks/{matchId}/{quarterId}.json)에 보관하고, 정확도 필터로 지표를 재계산하며 심박 평균·최대·최소 컨디션 요약을 DynamoDB에 저장합니다.
  5. 지연 재전송 — 경기 중 폰과 멀어져 워치에서 쿼터를 종료하면, 전송 못 한 데이터를 워치의 영속 재전송 큐에 남깁니다. 나중에 폰 블루투스 범위에 다시 들어오면(또는 워치가 직접 인터넷에 연결되면) 큐를 자동으로 비워 서버에 전송합니다.
  6. 리포트 조회GET /matches/{id}/track이 1분 간격 마커(minuteMarkers, 동그라미 안 분 숫자 + 심박)를 반환하고, analytics가 컨디션 요약을 제공합니다. 지도에서 마커를 누르면 그 시점 컨디션 팝업이 열려 교체 판단에 활용됩니다.
  7. 구간 속도 색상 — 웹/앱 모두 원본 좌표(track.points)를 1분 구간으로 나눠 구간 평균 속도를 계산하고, 이동경로 선을 느림(연한 노랑)→중간(주황)→빠름(레드)으로 색칠합니다. 임계값(러닝 12km/h·스프린트 25km/h)은 LiveMetrics.kt를 단일 출처로 웹 app.js와 동일하게 유지합니다.
  8. 지도 표시 성능 — 히트맵·경로·1분마커 오버레이는 데이터 도착 시 한 번만 생성하고, 토글 체크박스는 setMap(null/지도)로 보이기/숨기기만 전환합니다. (이전에는 토글마다 오버레이 전체를 파괴·재생성해 클릭 반응이 몇 초씩 늦어졌습니다) 웹은 applyOverlayVisibility(), 앱은 ResultActivity.applyOverlayVisibility()가 동일한 방식으로 동작합니다. 모바일 앱 경기 결과 화면에도 웹과 동일한 이동경로/히트맵/1분마커 토글이 추가되었습니다.
  9. 1분 마커 표시 통일 — 웹·앱 모두 동그라미 안에 경과 분 숫자를 표시하고, 원 색상은 그 시점 심박수에 따라 초록(안정)→주황→빨강(높음)으로 바뀝니다. 웹은 HTML 마커, 앱은 같은 모양을 Canvas로 그린 아이콘(MapVisuals.minuteMarkerBitmap)을 씁니다. 색 기준은 웹 heartColor()와 앱 MapVisuals.heartColor()가 동일합니다.
  10. 경기 목록의 일시 — 트랙 업로드 시 요약에 실제 GPS 기록 구간(trackStartAt/trackEndAt)과 durationSec을 함께 저장합니다. 목록은 GET /matches?limit=로 최신순 N건만 받아 26년 9월 5일 06:00~13:30 형식으로 표시합니다. 이 필드가 없는 옛 기록은 서버가 ANALYTICS에서 durationSec을 보강해 시간 범위를 복원하고, 그래도 없으면 날짜만 표시합니다.

3. 구간별 통신 · 보안

구간프로토콜인증 · 보안비고
워치 ↔ 폰Wear Data Layer (블루투스)기기 페어링 · 앱 서명같은 서명키의 짝 앱끼리만 통신
App ↔ API GatewayHTTPS (REST/JSON)TLS 1.2+, JWT Bearer모든 보호 라우트에 토큰 필수
API Gateway ↔ LambdaAWS 내부 호출IAM 리소스 정책퍼블릭 노출 없음
Lambda ↔ DynamoDB · S3AWS SDK (HTTPS)IAM Role (최소권한)테이블·버킷 ARN 단위 제한
Lambda ↔ CognitoAWS SDK (HTTPS)IAM Role회원가입·로그인 처리
Browser ↔ 웹사이트HTTPSCloudFront + ACM 인증서S3 직접 접근 차단(OAC)
쿼터 시작·종료 전파 (감독 → 출전 선수 워치)

서버에서 기기로 직접 푸시할 채널(FCM 등)이 없어, 지시 레코드 + 폴링 방식을 쓴다. 감독이 POST /quarters/{id}/start·stop 를 호출하면 서버가 매치 lineup 의 선수마다 USER#{userId} / ACTIVEQUARTER 레코드를 남기고(동시성 5로 병렬 기록), 워치는 GET /me/active-quarter 를 주기적으로 조회한다 (대기 중 15초 · 기록 중 30초, 앱 화면이 보이는 동안만 — 배터리 절약).
RECORDING 을 받으면 워치의 시작 버튼이 "전반 시작"처럼 활성화되고, 누르면 감독이 만든 matchId·quarterId 로 기록이 붙는다. DONE 을 받으면 기록을 자동 종료·업로드하고 분석 요약을 표시한다. 중복 처리를 막기 위해 commandSeq 로 같은 지시를 한 번만 반영한다. 폴링이라 통신이 끊겼다 복구되면 다음 주기에 자동으로 따라잡는다.

오프라인·블루투스 단절 내구성 — 지연 재전송 큐

업로드는 쿼터 종료 시점에 배치로 수행됩니다. 네트워크가 없거나 폰과 블루투스가 끊긴 상태여도 각 기기의 Room DB에 데이터가 남고, 전송 실패 건은 영속 재전송 큐에 적재됩니다. 워치가 다시 폰 블루투스 범위에 들어오거나 인터넷에 연결되면 큐를 자동으로 비워 서버에 올리므로, 먼 거리에서 쿼터를 종료해도 데이터가 사라지지 않습니다.

4. 주요 API 엔드포인트

메서드경로인증설명
GET/health공개헬스체크
POST/auth/signup공개회원가입
POST/auth/login공개로그인 (JWT 발급)
POST/auth/refresh공개Refresh Token으로 재발급(자동 로그인)
POST/matchesJWT매치 생성(경기장·라인업 지정, 팀 매치는 운영진만)
GET/matchesJWT내 경기 이력 — 최신순, ?limit= 로 개수 제한 (?teamId= 시 팀 매치 목록)
POST/matches/{id}/lineupJWT매치 출전 선수(라인업) 입력·수정
POST/matches/{id}/quartersJWT쿼터 생성(운영진만, 커스텀 시간)
GET/matches/{id}/quartersJWT쿼터 목록
POST/quarters/{id}/startJWT쿼터 시작(운영진만)
POST/quarters/{id}/stopJWT쿼터 종료(운영진만) — 출전 선수 기기에 종료 전파
GET/me/active-quarterJWT내게 내려온 쿼터 시작/종료 지시(워치·폰이 폴링)
POST/matches/{id}/trackJWT트랙+컨디션 업로드(쿼터 단위)
GET/matches/{id}/trackJWT경로 + 1분 마커(minuteMarkers)
GET/matches/{id}/analyticsJWT분석 · 히트맵 · 컨디션 요약
POST/teamsJWT팀 생성
POST/teams/{id}/membersJWT선수 등록(등급·역할, 운영진만)
POST/teams/{id}/roster/bulkJWT엑셀 명단 일괄 등록(계정 자동 생성, 운영진만)
GET/teams/{id}/player-matchesJWT선수별 개인 경기 조회(팀 분석 라인업 자동 연결, 운영진만)
GET/teams/{id}/statsJWT팀 통계 · 랭킹

5. 데이터 저장 구조

DynamoDB 단일 테이블 설계로 조회 패턴을 커버하고, 용량이 큰 원본 트랙은 S3로 분리해 저장 비용을 낮춥니다.

PKSK엔티티주요 속성
USER#<userId>PROFILE사용자email, name(한글), nameEn(영문), playerId, position/positionGroup, grade
USER#<userId>MATCH#<matchId>경기 요약distance, maxSpeed, sprints
MATCH#<matchId>META매치teamId, title, status, venueId/venueName, lineup[]
MATCH#<matchId>ANALYTICS#<quarterId>쿼터 분석avgSpeed, heatmap, conditionSummary
MATCH#<matchId>QUARTER#<quarterId>쿼터(목록)label, durationMin, status
QUARTER#<quarterId>META쿼터(단건)matchId, teamId, durationMin
USER#<userId>ACTIVEQUARTER쿼터 지시status(RECORDING/DONE), quarterId, matchId, label, durationMin, commandSeq
TEAM#<teamId>PROFILEname, ownerId, inviteCode
TEAM#<teamId>MEMBER#<userId>팀원playerId(KRPL), name(한글), nameEn(영문), position(상세), positionGroup(GK/DF/MF/FW), role, grade
← 홈으로 인증 · 인가 흐름 보기 →