본문으로 건너뛰기

학생 대량 가져오기

관리자 학생 가져오기 페이지가 파일 업로드에 실패하거나, 행 수준 오류를 반환하거나, 파일의 일부만 가져오거나, 재시도 후 예기치 않게 동작하는 경우 이 가이드를 사용하세요.

현재 워크플로는 의도적으로 동기식입니다. POST /v1/students/bulk는 전체 BulkImportResult와 함께 200 OK를 반환합니다. OMR 일괄 확인이나 보고서 일괄 생성과 같은 비동기 작업이 아닙니다.

소스 경로

영역소스 경로
페이지 업로드lumie-frontend/app/admin/students/import/page.tsx
클라이언트 폼lumie-frontend/app/admin/students/import/_components/ImportStudentsClient.tsx
대량 가져오기 UIlumie-frontend/src/features/student-management/bulk-import/ui/*
생성된 네트워크 호출lumie-frontend/src/entities/student/api/generated.ts
쿼리 래퍼lumie-frontend/src/entities/student/api/queries.ts
백엔드 엔드포인트lumie-backend/modules/student/src/main/java/com/lumie/student/adapter/in/web/StudentController.java
파서lumie-backend/modules/student/src/main/java/com/lumie/student/application/service/StudentExcelParser.java
가져오기 서비스lumie-backend/modules/student/src/main/java/com/lumie/student/application/service/StudentCommandService.java
응답 계약lumie-backend/modules/student/src/main/java/com/lumie/student/application/dto/response/BulkImportResult.java

증상

증상가능한 원인
UI가 업로드 전에 파일을 거부합니다.파일 확장자가 .xlsx 또는 .csv가 아닙니다
백엔드가 템플릿 오류를 반환함처음 두 헤더가 정확히 예상되는 한국어 레이블이 아닙니다
CSV는 로컬에서 작동하지만 Excel에서 내보낸 후 실패인코딩 또는 잘못된 인용문 처리
재시도는 혼란을 야기합니다프런트엔드는 선택한 파일별로 Idempotency-Key를 생성합니다.
일부 행은 가져오고 일부 행은 실패유효성 검사는 행 단위이며 BulkImportResult.errors를 반환합니다.
할당량 오류 이후의 행은 모두 실패서비스는 할당량이 소진된 후 남은 유효한 행을 할당량 오류로 표시합니다.

템플릿 계약

프런트엔드 템플릿은 다음 헤더를 사용합니다.

위치헤더필수
1학생 이름
2학생 연락처
3학교명아니
4출생 연도아니
5학부모 연락처아니
6메모아니

백엔드 파서는 처음 두 헤더 셀을 정확하게 확인합니다. CSV의 경우 UTF-8, Windows-949 및 EUC-KR을 지원하고 UTF-8 BOM이 있는 경우 이를 제거합니다.

파서는 또한 실제 경계를 적용합니다.

제한
최대 데이터 행1,000
최대 CSV 크기5MiB
행당 최대 셀 수32

진단 경로

  1. 선택한 파일 확장자를 확인하세요. UI는 .xlsx.csv만 허용합니다.
  2. 첫 번째 행을 열고 처음 두 셀이 정확히 학생 이름학생 연락처인지 확인합니다.
  3. 파일이 CSV인 경우 파일이 유효한 UTF-8, Windows-949 또는 EUC-KR인지 확인하고 인용된 셀이 균형을 이루고 있는지 확인하세요.
  4. 동일한 파일 선택이 재시도되는지 확인하세요. UI는 선택한 파일에 대해 하나의 멱등성 키를 유지하고 파일이 변경되거나 재설정될 때 이를 다시 생성합니다.
  5. 반환된 BulkImportResult.errors를 검사합니다. rowNumber는 1-인덱싱되고 헤더 행을 포함하므로 첫 번째 데이터 행은 행 2입니다.
  6. 중복 전화 실패의 경우 중복 확인 전에 전화번호가 표준화되어 있다는 점을 기억하세요.
  7. 부분 가져오기의 경우 전체 파일이 롤백되었다고 가정하는 대신 successCount, failureCount 및 행 오류를 비교합니다.

실패 처리 방식

백엔드는 두 가지 패스로 실행됩니다.

  1. 모든 행을 구문 분석 및 검증하고 행 오류를 수집합니다.
  2. 유효성 검사를 통과한 행만 삽입합니다.

응답 계약은 lumie-backend/modules/student/src/main/java/com/lumie/student/application/dto/response/BulkImportResult.java에 정의되어 있으며, lumie-frontend/src/entities/student/api/generated.ts에 생성된 프런트엔드 타입이 이를 반영합니다.

대표적인 형태:

type BulkImportResult = {
totalRows: number;
successCount: number;
failureCount: number;
errors: Array<{
rowNumber: number;
column: string | null;
value: string | null;
code: string;
message: string;
}>;
};

단순화된 예:

{
"totalRows": 3,
"successCount": 2,
"failureCount": 1,
"errors": [
{
"rowNumber": 3,
"column": "phone",
"value": "010-1234-5678",
"code": "BULK_IMPORT_DUPLICATE_PHONE",
"message": "이미 등록된 연락처입니다."
}
]
}

이것은 부분 성공 계약입니다. 운영자는 서버 상태를 직접 편집하는 대신 보고된 행을 수정하고 수정된 파일을 다시 업로드해야 합니다.

예방

  • 다운로드 가능한 CSV 템플릿을 StudentExcelParser와 동기화 상태로 유지하세요.
  • 프런트엔드 쿼리 래퍼에 Idempotency-Key 헤더를 유지합니다.
  • 실제 업로드가 요청 제한 시간 예산을 초과하지 않는 한 이 끝점을 비동기 작업으로 변환하지 마십시오.
  • StudentExcelParser 내부에 파서별 파일 형식 논리를 유지합니다. 비즈니스 검증은 StudentCommandService에 속합니다.
  • 허용되는 열을 확장하기 전에 사용자가 제어하는 텍스트 셀에 대한 스프레드시트 삽입 검사를 유지합니다.

운영 세부사항

이 workflow는 operator 관점에서 all-or-nothing transaction이 아니라 partial-success import입니다. 응답 자체가 복구 표면입니다.

관심사기대 동작
File acceptanceUI는 .xlsx.csv를 허용하고 backend는 실제 template shape를 검증합니다.
Header validation처음 두 cell은 정확히 일치해야 합니다. optional column은 비어 있을 수 있습니다.
Duplicate handlingduplicate check 전에 phone number를 normalize합니다.
Quota handlingquota가 소진되면 남은 valid row도 실패로 표시될 수 있습니다.
Retry behavior같은 selected file은 같은 Idempotency-Key를 유지하고 reset 또는 새 file은 key를 바꿉니다.

operator는 partial result 후 누락된 성공 row를 수동 insert하지 않습니다. 더 안전한 경로는 보고된 row를 고친 뒤 깨끗한 file을 다시 upload하는 것입니다.

복구 절차

  1. spreadsheet만 받지 말고 반환된 BulkImportResult를 요청합니다.
  2. row data를 조사하기 전에 처음 두 header를 검증합니다.
  3. CSV라면 encoding과 quote balance를 확인합니다. Excel-exported CSV가 흔한 원인입니다.
  4. header를 포함하는 1-indexed row number를 사용해 row error를 spreadsheet와 매핑합니다.
  5. retry가 이상하게 보이면 사용자가 새 file을 선택했는지 같은 selected file을 retry했는지 확인합니다.
  6. quota error가 보이면 앞선 successful row가 남은 capacity를 소비했는지 확인합니다.

검증 증거

닫기 전에 다음을 확인합니다.

  • corrected sample file이 기대한 successCount로 import됩니다.
  • row-level error가 의도한 spreadsheet row를 가리킵니다.
  • frontend가 여전히 Idempotency-Key를 보냅니다.
  • generated frontend type이 BulkImportResult와 일치합니다.
  • downloadable template이 parser expectation과 일치합니다.

피해야 할 패턴

  • failureCount > 0을 full rollback으로 취급하는 것.
  • partial import를 보정하려고 database row를 수동 편집하는 것.
  • StudentExcelParser를 갱신하지 않고 frontend template만 바꾸는 것.
  • validation과 spreadsheet-injection check 없이 accepted column을 늘리는 것.