온라인 출석부 (QR 서명 방식)

구조

구글 계정이 여러 개 있는 폰에서 script.google.com 페이지를 직접 열면, 구글이 계정 컨텍스트를 정하는 과정에서 주소가 .../u/1/exec, .../u/3/exec 처럼 바뀌면서 접속이 안 되는 문제가 있습니다. 이 문제는 링크를 단축 URL로 감싸도, 최종적으로 그 페이지를 "열람"하는 순간 다시 발생합니다.

그래서 이 구성에서는 학생이 여는 페이지는 구글 도메인이 아닌 곳에 두고, Apps Script는 그 페이지가 백그라운드에서 fetch()로 호출하는 JSON API 역할만 하도록 나눴습니다. 브라우저가 script.google.com을 화면에 열람하는 게 아니라 JS가 조용히 API만 호출하기 때문에 계정 선택 문제가 아예 발생하지 않습니다.

  • Code.gs → Google Apps Script에 붙여넣음. 서명 이미지 저장 + 시트 기록을 담당하는 JSON API (doPost)
  • site/index.htmlApps Script가 아닌 일반 정적 호스팅(GitHub Pages 등)에 올림. 학생이 QR로 여는 실제 페이지 (이름 입력 + 손글씨 서명 캔버스)

기록되는 열: 날짜, 이름, 입실시각, 퇴실시각, 학습시간(자동 계산), 입실서명(이미지), 퇴실서명(이미지), 입실기기ID·입실기기정보·퇴실기기ID·퇴실기기정보(기본적으로 숨김 처리됨)

  • 학습시간은 입실시각·퇴실시각이 둘 다 있을 때만 자동 계산되는 수식(=D-C)입니다. 시각이 바뀌면 자동으로 다시 계산됩니다.
  • 이름 입력창 자동완성: 페이지를 열 때 시트에 이미 기록된 이름 목록을 불러와 입력창에 제안으로 띄워줍니다 (브라우저 기본 자동완성 UI 사용, 새 이름도 자유롭게 입력 가능).
  • 페이지 상단에 학교 로고 이미지가 표시됩니다.

시트는 학생 1명 + 날짜 1개 = 1행으로 관리됩니다. 같은 날 같은 학생이 여러 번 서명해도 행이 늘어나지 않고, 다음 규칙으로 같은 행이 갱신됩니다.

  • 입실: 그날 기록된 시각보다 더 빠른 경우에만 갱신 (그날 첫 입실 시각 유지)
  • 퇴실: 그날 기록된 시각보다 더 늦은 경우에만 갱신 (그날 마지막 퇴실 시각 유지)
  • 갱신되지 않는 제출은 서명 이미지도 기록되지 않습니다 (시트에 남는 서명은 항상 실제로 채택된 입실/퇴실 시각과 같은 순간에 찍힌 서명입니다).

이 구조라서 날짜별로 등교/하교 시간을 볼 때 피벗테이블이 단순해집니다 — 이미 한 줄에 그날의 입실/퇴실시각이 정리돼 있으므로, 피벗테이블에서 행에 날짜(또는 이름), 값에 입실시각/퇴실시각을 그대로 넣으면 됩니다 (별도 MIN/MAX 집계 설정 불필요).

열 구조가 바뀔 때마다 기존 출석부 시트는 그대로 재사용하면 안 됩니다 (헤더가 이미 있으면 코드가 헤더를 다시 쓰지 않기 때문에 새 열이 추가되지 않습니다). 학습시간 열을 추가하면서 다시 한 번 열 구조가 바뀌었으니, 기존 출석부 탭 이름을 출석부_구버전 등으로 바꿔 보관해두고 Apps Script를 재배포하면 새 구조의 출석부 시트가 새로 만들어집니다.

1단계 — Apps Script API 배포

  1. sheets.google.com에서 새 스프레드시트 생성 (이름 예: "출석부")
  2. 상단 메뉴 확장 프로그램 → Apps Script 클릭
  3. 기본 생성된 Code.gs 내용을 전부 지우고, 이 폴더의 Code.gs 내용을 붙여넣기
  4. Ctrl+S로 저장
  5. 오른쪽 위 배포 → 새 배포
    • 유형: 웹 앱
    • 실행 계정:
    • 액세스 권한이 있는 사용자: 전체
    • 배포 클릭
  6. 처음 배포 시 권한 승인 창이 뜨면 고급 → (프로젝트명)으로 이동 → 허용 (Sheets/Drive 접근 권한 승인 — 본인 계정으로만 실행되므로 안전합니다)
  7. 발급된 웹 앱 URL(https://script.google.com/macros/s/.../exec)을 복사 — 이 URL은 학생에게 직접 주지 않습니다. 2단계에서 페이지 안에만 넣습니다.

2단계 — 학생용 페이지 호스팅 (구글이 아닌 곳)

site/index.html을 열어 맨 위 스크립트의

const EXEC_URL = 'PASTE_YOUR_APPS_SCRIPT_WEB_APP_URL_HERE';

부분을 1단계에서 복사한 실제 URL로 교체한 뒤, 정적 호스팅에 올립니다. 아무 계정 없이 가장 빨리 해보려면:

  • Netlify Drop: app.netlify.com/dropsite 폴더를 그대로 드래그 앤 드롭 → 즉시 URL 발급 (계정 없이도 되지만, 계속 쓰려면 무료 계정 연결 권장)

계속 쓸 주소를 원하면:

  • GitHub Pages: GitHub 저장소에 site 폴더 내용을 올리고 저장소 설정에서 Pages를 켜면 https://<사용자명>.github.io/<저장소명>/ 형태의 고정 주소가 생김
  • Cloudflare Pages / Vercel: 같은 방식으로 site 폴더 하나만 배포하면 됨 (별도 빌드 설정 불필요, 정적 HTML 파일 1개)

3단계 — QR 코드 만들기

호스팅에서 받은 주소(구글 도메인이 아닌 주소)로 QR 코드를 생성해 인쇄 후 교실 입구에 부착하세요.

  • Chrome 주소창 오른쪽 공유 아이콘 → "QR 코드 만들기" 기능을 쓰면 별도 사이트 없이 바로 생성됩니다.
  • 원하는 QR 생성 사이트/앱을 사용해도 무방합니다.

이제 QR을 찍으면 곧바로 site/index.html 페이지가 열리고(구글 계정과 무관), 제출 시에만 뒤에서 조용히 Apps Script API를 호출합니다.

대리서명 확인 방법

  • 입실기기ID/퇴실기기ID 열(기본 숨김)이 같은데 이름이 다른 경우, 같은 기기(브라우저)에서 여러 사람 이름으로 서명이 이뤄진 것이므로 대리서명 의심 대상입니다. 확인할 때만 열을 펼쳐서(선택 후 우클릭 → 열 표시) 기기ID 기준으로 정렬하거나 조건부 서식으로 중복을 표시하면 됩니다.
  • 입실기기정보/퇴실기기정보 열로 기종·브라우저가 같은 서명이 몰려 있는지 함께 확인하세요.
  • 기기ID는 브라우저의 localStorage에 저장되는 값이라, 시크릿 모드나 브라우저 데이터 삭제 시 매번 새로 생성됩니다. 완벽한 기기 식별은 아니고 보조 지표로만 활용하세요.

피벗테이블로 일자별 등교/하교 시간 보기

  1. 출석부 시트에서 데이터 범위를 선택하고 삽입 → 피벗 테이블
  2. 행: 날짜 (필요하면 이름도 추가)
  3. 값: 입실시각, 퇴실시각 — 요약 방식은 "없음"(그대로 표시)이나 "최소"/"최대" 어느 걸 골라도 결과가 같습니다 (한 학생·한 날짜에 이미 값이 하나뿐이기 때문입니다).

코드 수정 시 주의

  • Code.gs를 수정한 뒤에는 배포 → 배포 관리 → 수정(연필 아이콘) → 버전: 새 버전 → 배포를 해야 반영됩니다. (같은 URL이 유지되므로 site/index.htmlEXEC_URL은 그대로 둬도 됩니다.)
  • site/index.html을 수정한 뒤에는 호스팅에 다시 올려야 반영됩니다 (GitHub Pages는 push, Netlify Drop은 다시 드래그).

자동 생성되는 것들

첫 제출이 들어오면 자동으로:

  • 스프레드시트에 출석부 시트와 헤더가 생성됩니다.
  • 내 드라이브에 출석_서명이미지 폴더가 생성되고, 서명 PNG 파일들이 여기에 저장됩니다 (시트 셀에는 이 이미지가 바로 삽입되어 보입니다).
Description
자율학습 QR 출석체크
Readme 40 KiB
Languages
HTML 56%
JavaScript 44%