[FitPass] 운동 목표 설정 및 관리 기능 구현

김현정·2025년 6월 10일
0

피트니스 목표 관리 시스템

FitPass의 핵심 기능 중 하나인 피트니스 목표 관리 시스템을 개발했습니다. 사용자가 체중 관련 목표를 설정하고 일일 기록을 통해 진행상황을 추적할 수 있도록 하였습니다.

시스템 아키텍처

패키지 구조

src/main/java/org/example/fitpass/domain/fitnessGoal/
├── controller/          
│   ├── FitnessGoalController.java
│   ├── WeightRecordController.java
│   └── DailyRecordController.java
├── service/             
│   ├── FitnessGoalService.java
│   ├── WeightRecordService.java
│   └── DailyRecordService.java
├── entity/              
│   ├── FitnessGoal.java
│   ├── WeightRecord.java
│   └── DailyRecord.java
├── dto/                 
│   ├── request/
│   └── response/
├── repository/          
├── enums/               
└── scheduler/           

핵심 기능

  1. 목표 관리 : 생성/조회/수정/취소/삭제
  2. 체중 기록 : 일별 체중 추적
  3. 일일 기록 : 운동 인증샷 사진 업로드과 메모
  4. 자동 달성률 계산 : 목표 유형별 맞춤 계산
  5. 스케줄러 : 만료 목표 자동 처리

주요 API

목표 생성

POST /fitness-goals

내 목표 목록 조회

GET /fitness-goals

목표 상세 조회

GET /fitness-goals/{goalId}

목표 수정

PUT /fitness-goals/{goalId}

목표 취소

PATCH /fitness-goals/{goalId}/cancel

목표 삭제

DELETE /fitness-goals/{goalId}

체중 기록 생성

POST /fitness-goals/{goalId}/weight-records

체중 기록 목록 조회

GET /fitness-goals/{goalId}/weight-records

체중 기록 수정

PUT /weight-records/{recordId}

체중 기록 삭제

DELETE /weight-records/{recordId}

일일 기록 생성

POST /fitness-goals/{goalId}/daily-records

일일 기록 목록 조회

GET /fitness-goals/{goalId}/daily-records

일일 기록 삭제

DELETE /daily-records/{recordId}

사용한 기술

RESTful API 설계
도메인 주도 설계
확장 가능한 아키텍처
자동화된 비즈니스 로직 (스케줄링 기반 자동화)

구현 시 어려웠던 점

1. 복잡한 목표 달성률 계산 로직

목표 달성률을 계산하는 과정에서 다양한 제약사항들을 로직으로 체크해야하는 과정이 어려웠음.

null 체크시 누락, 0으로 나누기, 목표 유형 미고려, 음수 진행률, 100% 초과 등 여러 제약들이 있었기에 하나씩 추가하는 과정을 거쳤다.

// 목표 달성률 계산
    public double calculateProgressRate() {
        if (startWeight == null || targetWeight == null || currentWeight == null) {
            return 0.0;
        }

        // 목표 체중과 시작 체중이 같으면 100% (이미 달성)
        if (startWeight.equals(targetWeight)) {
            return 100.0;
        }

        double totalWeightDiff = Math.abs(targetWeight - startWeight);
        double currentWeightDiff = Math.abs(startWeight - currentWeight);

        // 감량 목표인지 증량 목표인지 확인
        if (goalType == GoalType.WEIGHT_LOSS) {
            // 감량: 현재가 목표보다 적게 나가야 함
            if (currentWeight <= targetWeight) {
                return 100.0; // 목표 달성
            }
            return Math.min(currentWeightDiff / totalWeightDiff * 100, 100.0);

        } else if (goalType == GoalType.WEIGHT_GAIN) {
            // 증량: 현재가 목표보다 많이 나가야 함
            if (currentWeight >= targetWeight) {
                return 100.0; // 목표 달성
            }
            return Math.min(currentWeightDiff / totalWeightDiff * 100, 100.0);

        } else {
            // 유지: 목표 체중 ±1kg 범위 내면 100%
            double diff = Math.abs(currentWeight - targetWeight);
            if (diff <= 1.0) {
                return 100.0;
            }
            return Math.max(0, 100 - (diff * 10)); // 1kg당 10%씩 감점
        }
  }

0개의 댓글