ESP32-S3 Bench Guide
ESP32-S3 N16R8 bench guide: live pinout, lessons, simulators and the bench log.
연수 참석 등록부 — QR로 직위·성명·전자서명을 받아 구글 스프레드시트에 인쇄용 등록부를 자동으로 쌓는 Google Apps Script 웹앱 (학교 배포용 템플릿)
Translation pending
EducationJavaScript · HTMLGitHub @jjsolnae-highOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Loading the full README. The saved excerpt is shown below.
QR로 접속해 직위·성명·전자서명을 남기면 구글 스프레드시트에 인쇄용 등록부가 자동으로 쌓이는 Google Apps Script 웹앱. 전주솔내고등학교에서 쓰고 있고, 다른 학교도 사본을 떠서 쓸 수 있게 정리한 저장소다. 설치 안내서는 이 저장소에 없다. 스프레드시트 안의 📖 설치 안내 시트가 안내서이며, 내용은 Code.gs의 guideContent_()에 있다. 문구를 고치려면 그 함수를 고치고 설치안내시트만들기를 다시 실행한다. 템플릿 스프레드시트 사본 뜨기. 스크립트가 스프레드시트에 바인딩되어 있어서, 시트를 복사하면 코드가 함께 복사된다. • 템플릿: [템플릿] 통합연수등록부(다른 학교 공유용) • 사본 뜨기 링크: https://docs.google.com/spreadsheets/d/1eVJs9K9W5wlKVI7V4-db2pwyGH-Lu8oedTQSuB-11U8/copy (누르면 바로 "사본 만들기" 화면이 뜬다. 템플릿을 새로 관리해야 하면 그 시트의 .../edit 주소를 .../copy 로 바꾸면 된다) • 공유 설정: 링크가 있는 모든 사용자 · 뷰어 (+ 뷰어에게 복사 허용)Read the full README ↗
QR로 접속해 직위·성명·전자서명을 남기면 구글 스프레드시트에 인쇄용 등록부가 자동으로 쌓이는 Google Apps Script 웹앱. 전주솔내고등학교에서 쓰고 있고, 다른 학교도 사본을 떠서 쓸 수 있게 정리한 저장소다.
설치 안내서는 이 저장소에 없다. 스프레드시트 안의
📖 설치 안내시트가 안내서이며, 내용은Code.gs의guideContent_()에 있다. 문구를 고치려면 그 함수를 고치고설치안내시트만들기를 다시 실행한다.
템플릿 스프레드시트 사본 뜨기. 스크립트가 스프레드시트에 바인딩되어 있어서, 시트를 복사하면 코드가 함께 복사된다.
[템플릿] 통합연수등록부(다른 학교 공유용).../edit 주소를 .../copy 로 바꾸면 된다)받는 쪽은 /copy 링크 하나만 누르면 시트·코드·안내서가 한 번에 온다. 코드를 따로 받거나 파일을 만들 필요가 없다.
스크립트 속성은 복사되지 않는다(확인함). 그래서 학교마다 직접 설정한다.
| 속성 | 용도 | 없으면 |
|---|---|---|
ADMIN_PASSWORD | 담당자 화면 비밀번호 | 담당자 메뉴가 열리지 않음 (의도된 동작) |
SCHOOL_EMBLEM | 교표 (data: 또는 https: URL) | 학교명만 표시. 나머지는 정상 동작 |
배포도 딸려가지 않는다. 각 학교가 직접 배포해야 웹앱 URL이 생긴다. 템플릿 자체는 배포할 필요가 없다.
| 파일 | 역할 |
|---|---|
Code.gs | 서버 로직 · 시트 서식 · 설치 안내 시트 생성 |
Register.html | 참석자 등록 화면 (서명 캔버스) |
Admin.html | 담당자 화면 (링크 발급 · 명단 · 인쇄) |
교표는 파일이 아니라 스크립트 속성에 있다. (예전엔 Logo.html이 있었으나 제거)
| 함수 | 언제 |
|---|---|
설치안내시트만들기 | 템플릿을 만들 때 한 번. 안내 문구를 고친 뒤에도 재실행 |
교표설정하기 | 드라이브에 이름이 교표인 이미지를 올려두고 실행 |
교표지우기 | 교표를 뺄 때 |
SpreadsheetApp.getUi().alert()를 편집기 실행 함수에 넣지 말 것. 알림창은 스프레드시트 탭에 뜨는데 편집기에서 실행하면 그 탭을 보고 있지 않으므로, 아무도 누르지 않는 [확인]을 기다리다 시간 초과로 죽는다. try/catch로도 못 잡는다(예외가 아니라 무한 대기). 결과는 Logger.log로 알린다.
줄마다 getRange().setXxx()를 부르지 말 것. setter 하나가 서버 왕복 한 번이다. 74줄 × 5개 = 370회면 실행이 10분을 넘긴다. 열 전체 배열을 만들어 setFontSizes/setBackgrounds처럼 한 번에 넣고, 테두리는 getRangeList로 묶는다. (buildGuideSheet_ 참고)
시트를 다시 만들 때는 새로 만든 뒤에 옛것을 지울 것. 반대로 하면 그 시트가 문서의 유일한 시트일 때 '문서의 모든 시트를 삭제할 수 없습니다'로 막힌다. 연수 시트가 아직 없는 새 사본이 정확히 그 상태다. (설치안내시트만들기 참고)
안내 시트는 연수가 아니다. listEventSheets·getAttendees·makePrintSheet가 시트를 훑을 때 📖 설치 안내를 걸러야 한다. eventSheets_() / firstEventSheet_()를 쓸 것. 안 그러면 연수 드롭다운에 안내 시트가 뜨고, 연수 미선택 시 안내 시트를 읽는다.
include()는 템플릿을 평가하지 않는다. createHtmlOutputFromFile은 원본을 그대로 반환하므로 포함되는 파일 안에서는 <? ?>가 동작하지 않는다.
이 저장소는 배포 템플릿을 공개하기 위해 새로 시작했다. 아래는 처음 운영하던 코드에서 공개 전에 고친 것들이며, 그 이전 이력은 이 저장소에 없다.
저장형 XSS — signature에 data:image/png;base64,AAA" onerror="..." 를 보내면 서버가 그대로 시트에 저장하고, 담당자가 명단을 조회할 때 <img> 속성을 탈출해 실행됐다. 그 화면엔 ADMIN_PW 변수에 실제 비밀번호가 들어 있어 유출로 이어진다. 등록 링크만 있으면 누구나 가능했다. 서버에서 isSignatureDataUrl_()로 형식을 강제하고(핵심), 출력 시에도 이스케이프한다. 기존 indexOf('data:image')===0 검사는 '시작'만 봐서 뒤에 붙는 따옴표를 막지 못했다.
클라이언트가 준 sheetId 신뢰 — 웹앱이 '실행: 나'로 배포되므로, 등록 링크를 가진 사람이 임의의 시트 ID를 보내면 소유자 권한으로 남의 시트에 쓸 수 있었다. 추적해보니 어떤 화면도 sheetId를 보내지 않는 죽은 경로였기에 getActiveSpreadsheet() 전용으로 바꿔 기능 손실 없이 제거했다.
비밀번호가 코드에 — 코드를 나눠주면 비밀번호도 퍼진다. PropertiesService로 옮기고, 네 함수에 중복돼 있던 검사를 requireAdmin_() 하나로 합쳤다.
위 세 가지는 이 저장소의 코드에 모두 반영되어 있다. 사본을 뜨면 고쳐진 상태로 시작한다.
통합연수등록부 © 2026 KIMYERANG.
학교에서 자유롭게 쓰실 수 있습니다. 만든 이의 허락 없이
재배포하거나 자기 저작물로 공개하는 것은 금지하며,
이 저작권 표시를 지우지 말아 주세요.
경계선은 **'고쳤느냐'가 아니라 '밖으로 내보냈느냐'**다.
해도 되는 것 — 학교에서 쓰는 데 필요한 일은 전부
허락이 필요한 것 — 밖으로 내보내는 일
저작권 표시는 앱 화면 안에 있어서 사본을 뜨면 함께 따라간다. 지우지만 않으면 된다.
쓰다가 좋아졌으면 알려주면 반갑고, 문제가 있으면 이슈로 남겨주면 된다.