사이트픽 블로그
← 목록으로

개발일지 2026-07-15

개발일지 (2026-07-15)

개요

학원 이동 이력 시스템 설계·구현, 수강권 정책 설정 백엔드 저장, 인기 상품 필드, KSNET 결제 개선, 관리자 UI 개선(토글/포맷/검색) 등 대규모 작업을 진행했다.


수정/생성 파일 목록

백엔드 (piano-backend)

  • migration-20260715-ticket-policy-settings.sql (신규) — 정책 설정 + 이력 테이블
  • migration-20260715b-student-academy-history.sql (신규) — 학원 이동 이력 테이블
  • migration-20260715c-ticket-popular.sql (신규) — 수강권 인기 컬럼
  • TicketPolicySettings.java (신규) — 정책 설정 엔티티
  • TicketPolicySettingsHistory.java (신규) — 정책 이력 엔티티
  • TicketPolicySettingsMapper.java/xml (신규) — 정책 설정 CRUD + 이력
  • TicketPolicySettingsService.java (신규) — 정책 저장 + 변경 diff 이력
  • TicketPolicySettingsController.java (신규) — GET/PUT /academy/ticket-policy, GET /history
  • StudentAcademyHistory.java (신규) — 학원 소속 이력 엔티티
  • StudentMapper.java/xml (수정) — 학원 이동, 소속 이력 쿼리 추가
  • StudentService.java (수정) — transfer() 학원 이동 + getAcademyHistory()
  • StudentMyTicketService.java (수정) — 수강권 조회를 student_id 기준으로 변경
  • TicketPaymentService.java (수정) — 주문 조회를 student_id 기준으로 변경
  • StudentTicketMapper.java/xml (수정) — findAllByStudentId 추가
  • TicketOrderMapper.java/xml (수정) — findAllByStudentId 추가
  • LessonBookingMapper, LessonMapper, LessonNoteMapper, AttendanceMapper, ConsultationMapper, LessonProgressMapper (수정) — 각각 findAllByStudentId 추가
  • AcademyTicket.java (수정) — popular 필드 추가
  • AcademyTicketMapper.xml (수정) — popular 컬럼 INSERT/UPDATE/SELECT
  • AcademyTicketRequest.java (수정) — popular 파라미터
  • TicketService.java (수정) — popular 변경 이력 기록
  • SellableTicketResponse.java (수정) — popular 필드 추가
  • MeResponse.java (수정) — academyName 필드 추가
  • AuthController.java (수정) — getMe에서 학원명 조회
  • TicketPayReplyController.java (수정) — 성공/실패/취소 메시지 분리 (KSNET_SUCCESS/FAIL/CANCEL)
  • StudentAcademyController.java (수정) — 소속 이력 API 추가

프론트 — 학원 앱 (academy)

  • ticketPolicy.ts (신규) — 정책 설정 API
  • Tickets.tsx (수정) — 정책 설정 백엔드 연동, 인기 토글 컬럼, 입금 대기 탭 제거
  • Login.tsx (수정) — 편집 폼 전화번호/사업자등록번호 포맷, 네이버 플레이스 ID 표시
  • AcademySettings.tsx (수정) — 사업자등록번호 포맷 + NumericField
  • AppBarHeader.tsx (수정) — 하드코딩 학원명 → user.academyName
  • UserManagement.tsx (수정) — 토글 확인 팝업 제거, 이름 밑줄, 연락처 포맷, 상세 팝업 상태 토글
  • Academies.tsx (수정) — 고유번호 링크, 활성화 토글, 진행상태/관리 컬럼 제거
  • Terms.tsx (수정) — 전체 탭 추가
  • Menus.tsx (수정) — 노출 Switch 토글
  • ReviewDetail.tsx (수정) — 로고 영역 제거
  • 기타: Switch 전체 초록색, 검색 조건 분리(이름/이메일/연락처/학원)

프론트 — 학생 앱 (student)

  • TicketPurchasePage.tsx (수정) — 인기 뱃지를 popular 필드 기준으로
  • TicketCheckoutPage.tsx (수정) — KSNET 메시지 분기 + 팝업 감시 폴링 + 딤 레이어 자동 정리
  • MyPage.tsx (수정) — 로그아웃 확인 팝업 제거

파일별 상세

1. 학원 이동 이력 시스템

학생이 학원을 옮겼을 때 과거 결제/레슨/수강권 이력이 끊기지 않도록 user_id 기준 조회로 전환했다.

설계 원칙:

  • 학생 앱: student_id 기준 전체 이력 (학원 무관)
  • 학원 앱: academy_id 필터 유지 (자기 학원만)
  • 학원 이동: students.academy_id만 변경 + student_academy_history에 이력 기록
// StudentService.transfer() — 학원 이동
studentMapper.insertAcademyHistory(LEFT, currentAcademyId);
studentMapper.updateAcademyId(studentId, newAcademyId);
studentMapper.insertAcademyHistory(JOINED, newAcademyId);

8개 테이블(수강권/주문/레슨예약/레슨기록/레슨노트/출결/상담/진도)에 findAllByStudentId 쿼리를 추가해 학원 무관 전체 조회를 지원한다.

2. 수강권 정책 설정 백엔드 저장

기존에 UI만 있고 백엔드 저장이 없었던 정책 설정을 DB에 저장하고, 변경 이력도 남기도록 했다.

  • ticket_policy_settings 테이블: 학원별 1행 (UPSERT)
  • ticket_policy_settings_history 테이블: 저장 시점 전체 스냅샷 + 변경 요약(note)
  • 변경 시 이전값과 비교해 diff 항목만 note에 기록

3. KSNET 결제 취소 시 딤 레이어 문제 해결

KSNET 결제 팝업에서 취소하면 reply URL이 호출되지 않아 postMessage가 안 오고, 검은 배경(딤)이 남는 문제.

해결:

  1. reply에서 KSNET_DONE → KSNET_SUCCESS/FAIL/CANCEL 분리
  2. 취소 시 현재 화면 유지 (성공 알림 안 뜸)
  3. _pay() 호출 후 3초 폴링으로 KSNET iframe 사라짐 감지 → 딤 자동 정리

4. 수강권 인기 상품

  • DB: academy_tickets.popular 컬럼 추가
  • 학원 앱: 수강권 목록에 인기 Switch 토글 (주황색)
  • 학생 앱: 하드코딩 idx === 1 → popular 필드 기준 인기 뱃지 + 기본 선택

5. 관리자 UI 일괄 개선

  • 계정관리 4개 페이지: 상태 Switch 토글(확인 팝업 제거), 이름 밑줄 링크, 연락처 포맷, 검색 분리
  • 학원 목록: 고유번호 링크, 활성화 토글, 진행상태/관리 컬럼 제거
  • 약관: 전체 탭 추가
  • MeResponse에 academyName 추가 → 학원 앱 헤더 동적 표시

6. 결제 방식 전환 계획 (다음 세션)

현재 KSNET 팝업(WebHost) 방식 → 빌링키 방식으로 전환 예정.

  • KsnetBillingService.issueBillingKey() + pay() 이미 구현됨
  • 카드 입력을 앱 내 폼으로 → 빌링키 발급 → 빌링키로 결제 → 팝업 필요 없음
  • 단기/정기 결제 모두 빌링키 하나로 통일

트러블슈팅 메모

  • KSNET 딤 레이어: 취소 시 reply URL이 안 타서 postMessage가 안 옴 → iframe 감시 폴링으로 해결
  • 정책 이력 불일치: 프론트가 스냅샷 방식으로 바뀌었는데 백엔드 Mapper가 필드별 diff 방식 → 스냅샷으로 통일
  • SSH 터널 끊김: 장시간 세션에서 RDS 터널이 자주 끊김 → 매 DB 작업 전 nc -z 확인 필수

결론 / 배운 점

  • 학원 이동은 user_id 기준이 답이다. academy_id에 종속된 조회는 학원 이동 시 이력이 끊긴다. 학생 앱은 student_id(학원 무관), 학원 앱은 academy_id 필터를 유지하면 양쪽 다 만족.
  • PG 팝업의 한계. KSNET WebHost 팝업은 취소 시 콜백이 안 오고 딤 레이어가 남는 등 제어가 어렵다. 빌링키 방식(서버 직접 통신)으로 전환하면 팝업 문제가 원천 해소.
  • 정책 변경은 반드시 이력을 남겨야 한다. "이 학생이 결제할 당시 정책이 뭐였는지" 추적하려면 저장 시점 스냅샷이 필수.

추가 정리 (21:34)

개요

빌링키 결제 전환, 학생 앱 대시보드/예약 페이지 리뉴얼, KST 시간대 통일, 공휴일 API 연동 등 대규모 작업을 진행했다.


수정/생성 파일 목록

백엔드 (piano-backend)

  • migration-20260715-student-billing-cards.sql (신규) — 학생 빌링카드 테이블
  • migration-20260715b-public-holidays.sql (신규) — 공휴일 테이블
  • KstClock.java (신규) — KST 기준 today()/now() 유틸 (서버 UTC 대응)
  • StudentBillingCard.java (신규) — 빌링카드 엔티티
  • StudentBillingCardMapper.java/xml (신규) — 빌링카드 CRUD
  • StudentBillingCardService.java (신규) — 카드 등록(빌링키 발급→저장), 목록, 삭제
  • CardRegisterRequest.java (신규) — 카드 등록 요청 DTO
  • StudentBillingCardController.java (신규) — POST/GET/PUT/DELETE /student/billing-cards
  • PublicHoliday.java (신규) — 공휴일 엔티티
  • PublicHolidayMapper.java/xml (신규) — 공휴일 CRUD
  • PublicHolidayService.java (신규) — 공공데이터 API 연동 + DB 동기화
  • PublicHolidayController.java (신규) — GET/POST /public/holidays
  • ApiKeyExpiryScheduler.java (신규) — API 키 만기 알림 스케줄러 (2028-07-08 1회)
  • TicketPaymentService.java (수정) — billingKeyPayment() 추가
  • StudentMyTicketController.java (수정) — POST /payment/billing 엔드포인트
  • StudentTicketService.java (수정) — 만료일 계산 RECURRING도 적용
  • BookingSlotService.java (수정) — 1시간 단위 슬롯 + KST 시간 기준
  • StudentBookingService.java (수정) — 당일 취소 차단, 수강 기간 내 예약만 허용, 만료 수강권 복원 차단, 근무 요일 API
  • StudentBookingController.java (수정) — GET /work-days 추가
  • 14개 파일 — LocalDate.now() / LocalDateTime.now() → KstClock.today() / KstClock.now() 전환

프론트 — 학생 앱 (student)

  • api/billingCards.ts (신규) — 빌링카드 API 클라이언트
  • api/publicHolidays.ts (신규) — 공휴일 API 클라이언트
  • pages/MyCardsPage.tsx (신규) — 등록 카드 관리 (등록/삭제/기본 설정)
  • pages/TicketCheckoutPage.tsx (수정) — KSNET 팝업 → 앱 내 카드 입력/선택 + 빌링키 결제
  • pages/MyPaymentsPage.tsx (수정) — 결제 항목 클릭 → 영수증 팝업
  • pages/DashboardPage.tsx (수정) — 와이어프레임 기준 전면 리뉴얼
  • pages/LessonBookingPage.tsx (수정) — 목록/달력 뷰, 3단계 예약 플로우, 공휴일 표시
  • pages/MyPage.tsx (수정) — 등록 카드 관리 메뉴 추가
  • pages/ProfileEditPage.tsx (수정) — 이메일 disabled, 전화번호 제거
  • components/Layout.tsx (수정) — 하단 5탭 (예약 추가)
  • App.tsx (수정) — /my-cards 라우트 추가

프론트 — 학원 앱 (academy)

  • pages/OperatingHours.tsx (신규) — 운영시간/휴무 설정 별도 페이지
  • pages/AcademySettings.tsx (수정) — 운영시간 탭 제거 (별도 메뉴로 이동)
  • pages/Tickets.tsx (수정) — 정기권 유효기간 30일 고정, 수강권 발급 UI 활성화
  • pages/Students.tsx (수정) — 수강권 발급 UI 주석 해제 + 시작일/종료일/보유목록 표시

프론트 — 관리자 앱 (admin)

  • api/publicHolidays.ts (신규) — 공휴일 API 클라이언트
  • pages/PublicHolidays.tsx (신규) — 공휴일 관리 (동기화/조회)
  • App.tsx (수정) — /public-holidays 라우트

파일별 상세

1. 빌링키 결제 전환

KSNET 팝업(WebHost) 방식 → 앱 내 카드 입력 + 빌링키 결제로 전환했다.

흐름:

  1. 학생이 카드 정보 입력 (카드번호/유효기간/비밀번호2자리/생년월일)
  2. KsnetBillingService.issueBillingKey() → 빌링키 발급
  3. KsnetBillingService.pay() → 빌링키로 결제
  4. 수강권 자동 발급
// TicketPaymentService.billingKeyPayment()
BillingPayResult result = ksnetBillingService.pay(card.getBillingKey(), amount, orderNo);
if (result.success()) {
    Long stId = studentTicketService.issueFromOrder(academyId, studentId, ticketId, KstClock.today());
    orderMapper.updatePaid(order.getId(), orderNo, result.approvalNo(), stId);
}

2. KST 시간대 통일

서버가 UTC로 운영되어 LocalDate.now()가 9시간 차이나는 문제. KstClock 유틸을 만들어 전체 14개 파일을 일괄 교체했다.

public final class KstClock {
    private static final ZoneId KST = ZoneId.of("Asia/Seoul");
    public static LocalDate today() { return LocalDate.now(KST); }
    public static LocalDateTime now() { return LocalDateTime.now(KST); }
}

3. 학생 앱 예약 페이지 리뉴얼

와이어프레임 기준으로 전면 재작성:

  • 목록 뷰: 예정/지난 예약 탭 + 변경/취소 버튼
  • 달력 뷰: 월간 달력 + 공휴일(빨강) + 레슨(초록) + 예약가능(파란점) 표시
  • 3단계 예약: 날짜 선택 → 시간 선택(1시간 정각 단위) → 예약 확인(영수증형)
  • 예약 정책: 당일 취소/변경 불가, 수강 기간 내만 예약 가능, 만료 수강권 복원 차단

4. 공휴일 API 연동

공공데이터 특일 정보 API로 공휴일을 자동 동기화한다.

  • public_holidays 테이블 + INSERT IGNORE로 멱등 저장
  • 관리자 앱에서 연도별 조회 + 동기화 버튼
  • 학생 앱 달력에 공휴일 자동 표시 (예약 차단)
  • API 키 만기(2028-07-15) 7일 전 SMS 알림 스케줄러

5. 운영시간/휴무 설정 분리

학원 앱 설정 탭에서 운영시간을 별도 페이지(/operating-hours)로 분리:

  • 좌측: 요일별 운영시간 (Switch 토글 + 시간 셀렉트)
  • 우측: 휴무일 등록/삭제

트러블슈팅 메모

  • 서버 UTC → KST: LocalDate.now()가 UTC 기준이라 오후 7시에 오전 슬롯이 예약 가능하게 보임 → KstClock 유틸로 전체 교체
  • 공공데이터 API 401: yml에 URL 인코딩된 키를 넣으면 이중 인코딩 → 디코딩된 키를 넣고 URLEncoder.encode()로 전송
  • 수강권 만료일 NULL: RECURRING 타입은 만료일 계산을 안 했음 → validityDays가 있으면 무조건 계산하도록 수정
  • student 배포 404: tar 출력이 너무 많아 SSH 명령이 잘림 → 분리 실행으로 해결

결론 / 배운 점

  • 서버 시간대는 첫날에 잡아야 한다. UTC 서버에서 KST 비즈니스 로직을 돌리면 날짜/시간 비교가 9시간 어긋난다. KstClock 같은 유틸을 만들어 한 곳에서 관리하는 게 유지보수에 좋다.
  • 빌링키 결제는 PG 팝업보다 훨씬 깔끔하다. 팝업 닫힘 감지, 딤 레이어 정리 같은 삽질이 전부 사라진다.
  • 공공데이터 API는 서비스키 인코딩에 주의. yml에 넣을 때 인코딩/디코딩 버전을 구분해야 한다.
  • 예약 정책은 프론트+백엔드 양쪽에서 검증해야 한다. 프론트에서 UI 차단하더라도 백엔드에서 반드시 재검증.

댓글 0

  • 첫 번째 댓글을 남겨보세요.