프로젝트 목록으로
팀 프로젝트2025.07 - Present

MJSEC LMS

MJSEC 스터디 운영을 자동화하기 위한 Spring Boot 기반 LMS 백엔드입니다. 2026-09-30부터 유지보수를 맡고 있습니다.

내 역할

과제, 출석, 스터디 그룹, 활동 이미지, 관리자 알림, Wiki.js 연동 일부를 구현했고, 지금은 유지보수를 맡고 있습니다.

결과

JVM과 JDBC의 시간대 기준을 서울로 맞추고 기존 데이터 보정 코드를 넣어 시각 오류를 고쳤고, 비관적 락과 알림 조건 정리로 운영 안정성을 높였습니다.

담당 역할

과제(Assignment), 스터디 계획(Calendar/Plan), 출석체크, 스터디 그룹, 스터디 활동 이미지, 관리자 알림, Wiki.js 연동 일부를 담당했습니다. 단순 CRUD보다 운영 중 발생할 수 있는 중복 제출, 알림 누적, 다중 이미지 정리, 파일 업로드 보안 문제를 백엔드에서 제어하는 데 집중했습니다. 2026-09-30부터는 유지보수를 맡아 동아리원이 제보한 시각 표시 오류를 추적하고 고쳤습니다.

Tech Stack

Java 17Spring BootMySQLRedisJPASpring SchedulerJavaMailSenderApache TikaWiki.js GraphQL

아키텍처 / ERD

LMS의 중심 도메인은 User, StudyGroup, GroupMember, Calendar/Plan, Assignment, AssignmentSubmission, Attendance로 구성됩니다. 스터디 그룹에 멘토와 멘티가 소속되고, 그룹 단위로 계획과 과제가 생성되며, 사용자는 과제를 제출하고 출석 상태를 기록합니다.

시스템 구조

LMS API 서버가 MySQL, Redis, 파일 저장소를 쓰고, 예약 작업이 미제출과 결석 알림 메일을 보냅니다. 관리자가 가입을 승인하면 Wiki.js 계정도 함께 만듭니다.

MJSEC LMS 시스템 구조

ERD

MJSEC LMS ERD

문제 해결 과정

과제 기록은 9시간 이르게, 마감은 9시간 늦게 저장되던 문제를 JVM과 JDBC의 시간대 불일치에서 찾았다

  • 생성 시각 오차 -9시간 → 0
  • 마감 저장 오차 +9시간 → 0

문제 상황

동아리원이 과제 기록의 시각이 실제와 다르다고 제보했다. 서버가 만든 시각과 사용자가 입력한 마감 시각을 나누어 DB 저장값과 API 응답을 비교했더니, 제출과 생성 시각은 실제보다 9시간 이르게 보였고 사용자가 입력한 마감 시각은 DB에 9시간 늦게 저장돼 있었다. 결석 알림도 09:00가 아닌 18:00에, 주간 미제출 보고는 월요일 0시가 아닌 월요일 09:00에 나갔다. 2025-09-30에는 과제 제출 엔티티만 ZonedDateTime으로 바꿨다가, 다른 시각 필드와 기준이 달라져 같은 날 되돌린 적이 있었다.

해결 방안

운영과 같은 MySQL 8.0 컨테이너에서 JVM 시간대와 JDBC 설정을 바꿔 가며 DB 원본값과 API 반환값을 비교했다. JVM 기본 시간대는 UTC인데 Hibernate의 JDBC 시간대는 Asia/Seoul이라, 시간대 정보가 없는 LocalDateTime이 서로 다른 기준으로 만들어지고 변환되고 있었다. 한국에서 쓰는 동아리 서비스이므로 앱 시작 시 JVM 기본 시간대를 Asia/Seoul로 고정하고 cron에도 시간대를 적었다. 기존 계획의 시작일과 마감일만 +9시간으로 저장돼 있어서, 이 두 값만 9시간 앞당기는 일회성 보정 코드를 따로 만들었다. 중복 보정을 막으려고 보정 완료 기록과 UPDATE를 같은 트랜잭션에 묶고, 보정 전 백업과 실패 시 기동 중단도 넣었다.

결과

JVM을 UTC로 띄운 실제 앱에서 수정 전에는 생성 시각이 -9시간, 마감값이 +9시간이었고, 수정 뒤에는 생성 시각이 실제 한국 시각과 같고 DB 마감값이 입력값과 같았다. 2026-10-30에는 MySQL 8.0.41, Connector/J 8.0.33에서 JVM 시간대 2가지와 serverTimezone 2가지의 4가지 조합을 다시 재, 저장값이 serverTimezone이 아니라 JVM 시간대에 따라 달라진다는 것을 확인했다. 2026-09-30 운영 배포가 성공했고 배포 이미지에 수정 코드가 들어 있는 것을 확인했다. 기존 데이터 보정이 운영에 실제로 적용됐는지는 아직 확인하지 못했다.

근거

같은 과제의 동시 제출이 두 번 저장되는 문제를 비관적 락으로 막았다

문제 상황

같은 사용자가 같은 과제에 제출 요청을 거의 동시에 보내면, 이미 제출했는지 확인하고 저장하는 사이에 두 요청이 모두 통과해 제출이 두 번 저장될 수 있었다.

해결 방안

제출 트랜잭션 안에서 과제가 속한 Plan을 PESSIMISTIC_WRITE로 먼저 잠근 뒤 중복 확인과 저장을 하게 했다. 애플리케이션 플래그 대신 DB 잠금을 써서 서버가 늘어나도 같은 기준이 유지된다.

결과

동시 요청을 흉내 낸 단위 테스트로 모든 요청이 잠금을 거치고 1건만 저장되는 흐름을 확인했다. 저장소를 목으로 둔 테스트라, 실제 MySQL에서 동시 요청을 보내는 통합 테스트는 후속 과제로 남겼다.

오래전에 마감된 과제까지 매주 알림에 담기던 문제를 조회 조건으로 좁혔다

문제 상황

오래전에 마감된 과제까지 매주 알림 대상에 포함될 수 있었다.

해결 방안

최근 1주일 안에 마감된 과제만 조회하도록 기간과 상태 조건을 명확히 두었다.

결과

매주 나가는 알림에 운영에 필요한 과제만 담기고, 발송 대상을 계산하는 범위도 줄었다.

활동 글 이미지를 바꿀 때 DB와 실제 파일이 어긋나던 문제를 정리 흐름으로 막았다

문제 상황

활동 게시글 이미지를 교체할 때 DB의 이미지 목록과 실제 업로드 파일이 어긋날 수 있었다.

해결 방안

수정과 삭제 시 기존 파일을 함께 정리하는 흐름을 추가했다. 업로드는 클라이언트가 보낸 Content-Type을 믿지 않고 Apache Tika와 Magic Number, 이중 확장자 차단으로 검사한다.

결과

저장소에 아무도 참조하지 않는 파일이 남지 않는다.

아쉬웠던 점

초기에는 기능 구현 속도에 집중했지만, 실제 운영 시나리오에서는 중복 제출, 오래된 알림 누적, 파일 정리 누락처럼 기능 사이의 경계에서 문제가 발생했습니다. 이후 PR 리뷰와 운영 이슈를 반영하면서 단순 기능 구현보다 데이터 정합성, 알림 정책, 실패 격리를 먼저 고려해야 한다는 점을 배웠습니다.

프로젝트 유형

팀 프로젝트

기간

2025.07 - Present

사용 기술

9 Technologies