| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |
- 유레카 프론트엔드 대면
- TypeScript
- 핏로그
- 스프링부트
- 입력처리방식
- LG유플러스 유레카 프론트엔드 대면
- 멀티캠퍼스it부트캠프
- 마이배티스
- 나눔스퀘어
- 유레카프론트엔드대면
- input="password"
- 프론트엔드
- 유레카 부트캠프 프론트엔드
- 타입스크립트
- streaming metadata
- 이미지 파일 관리
- jandi
- 엘지유플러스프론트엔드대면
- 부트캠프후기
- 미니프로젝트
- 멀티캠퍼스부트캠프
- 유레카프론트엔드
- LG U+
- sentry
- 유플텍플
- 상태관리
- 유레카
- 리액트
- React
- 웹시큐리티
- Today
- Total
joooii
[회고] 유레카 부트캠프 프론트엔드 과정: 미니 프로젝트 핏로그 - 백엔드 본문

유레카에서 첫 미니 프로젝트를 시작했다. (두근두근)
📌 주제 선정
프론트엔드의 꽃인 To Do List를 적용하기 위해서 고민을 하던 중 운동 목표를 세우고 이에 따라 운동을 수행하면 캘린더에 깃허브 잔디처럼 심어놓는 식으로 하자는 팀원의 좋은 아이디어를 채택해서 해당 주제로 프로젝트를 진행하게 되었다!!
🌟 미니 프로젝트 최종 주제 🌟
하루의 운동량을 기록하고 시각적으로 성취감을 느끼며 꾸준한 동기부여를 통해 건강한 삶을 실천하는 운동 관리 웹앱 프로젝트이다.
협업은 Notion, Github을 사용해서 진행하였다.

https://github.com/FitLog-ureca/BE
GitHub - FitLog-ureca/BE: FitLog BE 레포지토리입니다.
FitLog BE 레포지토리입니다. Contribute to FitLog-ureca/BE development by creating an account on GitHub.
github.com
⚙️ 백엔드 기술스택
- Java 17
- Spring Boot 3.4.10
- Gradle
- MyBatis 3.0.3
- MySQL
- Swagger UI 2.7.0
처음에 Swagger Open Api의 버전을 2.0.2로 했으나, GlobalExceptionHandler를 사용하면서 SpringDoc과 SpringBoot의 버전이 불일치해서 Swagger의 버전을 업데이트했다.
💵 DB 설계

현재는 기획했던 기능이 크게 없기 때문에 테이블은 3개로 이루어져 있다.
users
: 회원 정보 관련
exercises
: 운동 종목 관련
todos
: 운동 목표 및 기록 관련
기능은 크게 Auth, Todos, Exercises, Profile로 구성되어 있습니다.
🧾 명세
명세는 구글링해서 제일 보편화되어있는 템플릿을 채택해 적용했다.
사실 명세를 4일동안 수정하려니 정말 어렵고 생각할 게 너무 많아서 더 오래 걸린 감이 있다.
명세가 중요하다 라는 말이 괜히 있는 게 아니었다..

📮 기능 및 API
Auth
- 로그인 (`POST` /auth/login)
- JWT를 사용하여 Access Token (유효기간: 1시간)을 쿠키에 저장하는 방식
- 로그아웃 (`POST` /auth/logout)
- 로그아웃 시 쿠키에서 Access Token 삭제
- 회원가입 (`POST` /auth/signup)
- DB에 회원 정보 저장
- Refresh Token 발급 (`POST` /auth/refresh)
- Access Token 만료 시 Refresh Token API를 호출하여 Access Token 재발급
- Refresh Token 유효기간: 7일
Todos
- 캘린더 월간 운동 요약 (`GET` /todos/summary)
- 완료한 운동 상태에 따라 캘린더에 색상을 표현하기 위한 월간 운동 조회
- 운동 항목 생성 (`POST` /todos)
- 세트번호가 1인 세트 항목 생성
- 운동 세트 항목 생성 (`POST` /todos/{todoId}/sets)
- 세트 항목 생성 (2, 3, .., N 세트)
- 운동 목표 수정 (`PATCH` /todos/record/{todoId})
- 세트 기록 수정 (reps + weight)
- 운동 완료 체크 (`PATCH` /todos/complete/{id})
- 개별 세트 완료 시 완료 체크 여부 (isCompleted)
- 휴식시간 기록 (`PATCH` /todos/rest/{todoId})
- 휴식 시작/종료 시간을 기반으로 휴식시간 기록
- 로컬 스토리지에 시간을 기록해서 휴식 시간을 기록하는 방식
- 휴식시간 초기화 (`DELETE` /todos/rest/reset/{todoId})
- 휴식시간 초기화 (휴식 시작 시간, 종료 시간 제거)
- 하루 운동 완료 (`PATCH` /todos/done/{date})
- 하루 운동을 완료했는지에 대한 여부 (isDone) 변경
- 운동 목표 삭제 (`DELETE` /todos/{id})
- 저장된 운동 목표 삭제
- 운동 세트 항목 삭제 (`DELETE` /todos/{todoId})
- 설정한 목표 세트를 삭제
- 운동 항목 삭제 (`DELETE` /todos/workouts/{workoutId})
- 설정한 목표 운동 항목을 삭제
Exercises
- 운동 기록 조회 (`GET` /exercises)
- 특정 날짜의 운동 기록 및 총 칼로리 조회
- 운동 종목 리스트 조회 (`GET` /exercises/search)
- 운동명 검색 및 페이징 목록 조회
Profile
- 프로필 조회 (`GET` /profile/me)
- 이름, 나이, 프로필 이미지 조회
- 프로필 수정 (`PUT` /profile/update)
- 이름, 생년월일, 프로필 이미지 수정 (생년월일 수정 시 나이 자동 계산)
🗂️ 프로젝트 구조
com
└── ureca
└── fitlog
├── auth // 🔐 인증 및 회원 관리
│ ├── controller
│ ├── dto
│ ├── jwt
│ ├── mapper
│ └── service
│
├── todos // ✅ 운동 목표(To-Do) 관리 기능
│ ├── controller
│ ├── dto
│ │ ├── request
│ │ └── response
│ ├── mapper
│ └── service
│
├── exercise // 💪 운동 종목 및 기록 관리
│ ├── controller
│ ├── dto
│ ├── mapper
│ └── service
│
├── profile // 👤 사용자 프로필 관리
│ ├── controller
│ ├── dto
│ ├── mapper
│ └── service
│
├── common // ⚙️ 공통 모듈 (예외, 유틸 등)
│ ├── exception
│ └── util
│
└── config // 🧩 전역 환경 설정
📚 트러블슈팅
1. Access Token 쿠키에 저장 + Refresh Token
기존에는 Access Token을 header에 저장하는 방식으로 구현을 했었다.
진행하고 있던 프로젝트에서는 header에 저장했던 것 같아서 그렇게 구현을 했는데, API 요청 테스트를 할 때마다 Bearar Token에 담으려고 하니 그것도 일이었다.
그리고 좀 그렇긴 하지만 Swagger에서 Bearer Token을 저장해서 하는 방법을 몰랐다. ㅎㅎ
그래서 다른 팀의 팀원이 쿠키에 저장하면 그런 과정 필요없다는 것을 말해줘서 그때 아 쿠키에 저장해야겠다 라고 방향을 돌렸다.
/** 로그인 (JWT 발급 포함) */
@PostMapping("/login")
@Operation(
summary = "로그인"
)
public ResponseEntity<LoginResponseDTO> login(@RequestBody LoginRequestDTO request, HttpServletResponse response) {
try {
LoginResponseDTO loginResult = authService.login(request);
// JWT 토큰 생성
String token = jwtTokenProvider.createToken(loginResult.getLoginId());
int cookieMaxAge = (int) (jwtTokenProvider.getValidityInMilliseconds());
Cookie cookie = new Cookie("accessToken", token);
cookie.setHttpOnly(true); // JS 접근 불가
cookie.setSecure(true); // HTTPS 전용
cookie.setPath("/"); // 모든 경로에 유효
cookie.setMaxAge(cookieMaxAge); // 1일
response.addCookie(cookie);
// Builder로 새 객체 생성
LoginResponseDTO responseBody = LoginResponseDTO.builder()
.message("로그인에 성공했습니다.")
.loginId(loginResult.getLoginId())
.name(loginResult.getName())
.token(token)
.build();
return ResponseEntity.ok(responseBody);
} catch (IllegalArgumentException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(LoginResponseDTO.builder()
.message("아이디 또는 비밀번호가 올바르지 않습니다.")
.build());
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(LoginResponseDTO.builder()
.message("로그인 처리 중 오류가 발생했습니다.")
.build());
}
}
쿠키에 저장까지는 어찌저찌 했는데, Refresh Token 재발급하는 과정에서 크케 난관을 맞아서 일단 Refresh Token 재발급 구현은 뒤로 미뤄뒀다 ..
왜냐면 Access Token과 Refresh Token 저장 위치가 너무 고민이 됐다.

일단 조금 더 조합을 고민해봐야겠다.
➡️ 최종적으로 Access Token을 JS 메모리에 저장하고, Refresh Token을 HttpOnly Cookies에 저장하여 관리하는 방법으로 구현하였다.
2. Swagger 기본 응답값이 additionalProp 로 나옴
{
"additionalProp1": "string",
"additionalProp2": "string",
"additionalProp3": "string"
}
❓ 원인
해당 문제의 원인은 Swagger가 Java 객체를 스캔할 때 응답 타입을 명시적으로 알 수 없거나, 제네릭/Map 형태로 감싸져 있을 경우 자동으로 추상화된 응답 스키마를 생성하기 때문이었다.
우리 코드에서는 TodoController에서 Map을 특히나 많이 사용하고 있었어서 저렇게 기본 응답 스키마가 생성되었던 것이다.
❗️ 해결
이를 해결하기 위해서
1) Map 대신 DTO 타입을 명확히 하기 위해서 응답값 갯수가 많은 경우에는 DTO를 따로 빼서 지정하였다.
2) 응답값 갯수가 적은 경우에는 @ApiResponse을 사용해서 기본 응답값을 지정했다.
물론 이게 좋은 방법 같지는 않아서 고민이 됐지만, 시간상 위 2개의 방식으로 이 문제를 해결했다.
추후 리팩토링하면서 공통 응답 스키마를 지정하는 파일을 생성해서 재사용할 수 있게 해야될 것 같다.
3. Swagger 500 오류 - ControllerAdviceBean.<init>(Object) 버전 충돌
❓ 원인
전역 예외 처리용(GlobalExceptionHandler)을 추가한 직후 Swagger UI 접속할 때 500에러가 발생했다.
이는 GlobalExceptionHandler에서 @RestControllerAdvice을 사용하면서 생긴 에러로 Spring Boot (v.3.4.x)와 Springdoc (OpenAPI, v.2.0.2)의 버전 충돌로 인해 발생한 것이었다.
❗️ 해결
찾아보니 Spring Boot의 버전을 3.3.x로 다운그레이드하거나 Swagger를 2.7.0 snapshot 버전을 사용하는 방식을 사용하라고 했다.
이에 우리는 Swagger의 버전을 업그레이드하는 것이 오류를 최소화하는 방법이라 생각되어 해당 방식을 채택하여 오류를 해결하였다.
+ Ngrok
Ngrok은 로컬 개발 환경에서 인터넷을 통해 웹 애플리케이션에 안전하게 접근할 수 있도록 하는 도구이다.
보안 연결을 통해 인터넷에서 로컬 서버를 실행할 수 있으며, 웹 애플리케이션을 외부에 노출시키지 않고도 테스트할 수 있다.
이때 403에러가 계속 났는데, 이는 SecurityConfig 설정 때문이었으며, 아래처럼 anyRequest()를 permitAll()해서 해결할 수 있었다.
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.cors(cors -> {})
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.anyRequest().permitAll()
)
또한 SwaggerConfig에서 아래처럼 @OpenAPIDefinition에 ngrok 서버를 추가해서 403에러를 해결했다.
@OpenAPIDefinition(
servers = {
// 🔹 Swagger UI가 ngrok을 통해 열릴 때, 이 URL로 요청을 보냄
@Server(url = "ngrok서버", description = "Ngrok Tunnel Server"),
@Server(url = "http://localhost:8080", description = "Localhost Server")
}
)
이렇게 백엔드 주간이 끝났다.
백엔드를 오래 배우지 않아 따로 공부하면서 바로 프로젝트를 진행하느라 속도를 붙이기까지 오래 걸려서 너무 아쉬웠다.
그래도 이론 배울 때보다는 확실히 실전으로 프로젝트를 진행하며 백엔드 공부를 하니 정보 습득력이 더 향상되었다.
제일 큰 아쉬운 점은 GPT를 너무 애용했다는 것.. 이긴 한데 이 부분은 백엔드 강의를 조금 더 들어보려고 한다.
그래도 API 명세를 직접 고민하고, 구조를 생각해볼 수 있던 시간이라서 이번 미니프로젝트 백엔드를 통해 얻어가는 게 참 많았다고 생각한다.
이제 또 프론트엔드를 하면서 아마 백엔드 수정이 필수일 것이다. 수업을 나가면서도 지속적으로 코드를 들여다보고, 코드를 어떻게 짜는 것이 좋을지 지속적으로 고민해봐야할 것 같다.
아 정처기 공부 언제하냐
'회고' 카테고리의 다른 글
| [회고] 유레카 부트캠프 프론트엔드 과정: 미니 프로젝트 핏로그 - 프론트엔드 (0) | 2025.12.24 |
|---|---|
| [1~10일차] HTML, CSS, JavaScript, 알고리즘 회고 (0) | 2025.09.06 |
| [회고] 유레카 부트캠프 3기 프론트엔드 신청 및 후기 (4) | 2025.08.11 |