Java(JSP/Servlet, JavaFX)와 PHP로 회원·인증, 데이터 수집, 관리자 화면까지 갖춘 팀 프로젝트 4개를 만들었고, 지금은 팀장으로 열차 예매 시스템 제자리를 만들고 있습니다.
비밀번호 해시를 Argon2id로 바꾸고 기존 사용자를 로그인 때 자동으로 옮기는 것처럼, 실제로 운영할 때 생기는 문제를 먼저 챙깁니다.
팀이 함께 쓰는 ERD 편집기 · 테이블 60개 (Outbox·Saga 테이블이 제 담당 영역)
제자리 명절 열차 공정 예매 시스템
2026.11 ~ 진행 중 · 3주 · 5인 팀 프로젝트 · 팀장
명절처럼 요청이 몰리는 순간에도 좌석을 중복 없이, 공정한 순서로 판매하는 가상 철도 예매 시스템. 대량 구매·반복 취소가 의심되는 이용자는 행동 데이터로 위험도를 매겨 대기 순번을 뒤로 조정합니다.
문제5명이 3주 동안 Oracle·MongoDB·Redis 스키마와 설계 문서를 동시에 고치면 버전이 엇갈림
해결스키마는 ERD 편집기 한 파일, 동시성 절차는 문서 한 곳을 원본으로 두고, 변경은 패치로 쌓아 검사 스크립트로 확인
결과v9 패치까지 검사 통과 · Oracle DDL 자동 생성 · 팀원 누구나 웹에서 문서 수정
Spring Boot
Kafka
Redis
Oracle
MongoDB
Cloudflare Pages
0건목표: 중복 결제
100%목표: 결제 실패 시 좌석 복구율
내 역할
팀장·기획: 기획서 작성, 5명의 담당 범위와 핵심 수치 분배, 3주 일정 수립
결제 신뢰성 (구현 중): Kafka 이벤트 설계, PG 테스트 모드 결제, 결제 실패 시 좌석을 되돌리는 Saga, 이벤트 유실을 막는 Outbox, 중복 요청을 한 번만 처리하는 멱등성
설계 원천 정리: 테이블 60개를 담은 ERD 편집기와 검사 스크립트를 두어 패치 멱등성·제약·참조를 자동 확인하고 DDL을 생성. 트랜잭션 순서·락 순서는 동시성 문서 한 곳에 모음
팀 문서 사이트: 기획서·기능설계·DB설계·화면설계를 웹에서 보고 고치는 사이트. 이름+PIN으로 로그인하면 각자 이름 브랜치에 커밋되고, 버튼 한 번으로 main에 합침 (Cloudflare Pages Functions + GitHub API, 가짜 GitHub로 API 흐름 39개 검사)
해결예전 PBKDF2 해시도 검증되게 두고, 로그인에 성공하면 그 자리에서 Argon2id로 다시 저장
결과기존 회원이 비밀번호를 바꾸지 않아도 새 방식으로 옮겨짐
Java 17
JSP/Servlet
MySQL
HikariCP
LLM API
GitHub Actions
6.4s → 1.1s대시보드 응답 (팀 성과)
40% → 100%기술명 매칭 정확도 (팀 성과)
내 역할
자소서·이력서 첨삭 AI: LLM 프롬프트를 설계하고 STAR 기법 기준으로 "추가하면 좋을 내용"을 안내하도록 개선
비밀번호 해시 전환: PBKDF2 → Argon2id로 교체. 예전 해시도 검증되게 두고, 로그인할 때 자동으로 새 해시로 다시 저장해 기존 회원이 비밀번호를 바꾸지 않아도 옮겨지게 함
테스트 계정 시뮬레이션: 목표 점수까지 하루치 활동을 몇 분 안에 쌓아, 오래 써야 보이는 화면을 시연·검증할 수 있게 함 (일시정지·초기화 지원)
스펙 아카이브 게시판: 사진이 다른 팀원 서버에서도 보이도록 파일을 DB에 저장하는 방식으로 수정
데스크톱 캐릭터 앱: 하루 요약·트렌드·자동 실행·설치 파일·사이트 내 업데이트 확인까지 구현
자세히 보기: 구조 · DB · 트러블슈팅 · 시연 영상
시연 영상
테스트 계정에서 목표 점수를 3,000점으로 올리고 시뮬레이션을 이어 실행한 화면 (1.4배속). 점수·등급·여정 지도가 새로고침 없이 바뀝니다.
구조
Jakarta Servlet 기반의 계층 구조입니다. 요청은 필터에서 로그인·권한을 확인한 뒤 서블릿 → 서비스 → DAO 순서로 내려가고, DB 연결은 HikariCP 풀로 관리합니다. 숫자는 저장소의 클래스 수입니다.
DB (핵심 테이블 12개)
전체 55개 테이블 중 회원·직무·기술·로드맵 흐름과 제가 손댄 테이블만 골랐습니다. 진한 테두리가 직접 만들거나 바꾼 테이블이고, 선 끝의 점이 외래키를 가진 쪽입니다.
트러블슈팅
1기존 회원을 깨지 않고 비밀번호 해시 방식 바꾸기
상황
회원 비밀번호와 계정 복구 코드를 PBKDF2로 저장하고 있었고, 메모리를 많이 쓰게 해서 GPU 대입 공격에 더 강한 Argon2id로 바꾸기로 했습니다.
원인
해시는 되돌릴 수 없어서, 저장 방식을 바꾸면 이미 가입한 회원의 해시를 새 방식으로 바꿀 방법이 없습니다. 전원에게 비밀번호를 다시 정하게 할 수도 없었습니다.
해결
저장된 문자열이 $argon2id$로 시작하는지로 형식을 구분해, 예전 PBKDF2 해시는 검증만 계속 지원했습니다. 로그인에 성공한 순간에는 평문 비밀번호를 알고 있으므로 그때 Argon2id로 다시 해시해 저장합니다. 설정값(OWASP 권장 최소치 m=19MiB, t=2, p=1)을 나중에 올려도 같은 경로로 갱신되고, 해시가 끝나면 평문 문자 배열을 지웁니다.
// UserService.login: 비밀번호 확인 직후
if (PasswordUtil.needsRehash(user.getPasswordHash())) {
String upgraded = PasswordUtil.hash(rawPassword);
userDao.updatePasswordHash(user.getId(), upgraded);
}
결과
회원은 아무것도 하지 않아도 다음 로그인 때 새 방식으로 옮겨집니다. 복구 코드도 같은 유틸을 씁니다.
2다른 팀원 서버에서 게시판 사진이 안 보임
상황
팀원마다 자기 PC에서 서버를 켜고 같은 DB를 함께 쓰는 방식으로 개발했습니다. 스펙 아카이브 게시판에 사진을 올리면 올린 사람 화면에서만 보이고, 다른 팀원 서버에서는 깨졌습니다.
원인
글 정보는 공용 DB에 들어가지만 사진 파일은 올린 사람 PC의 업로드 폴더에 저장되고 있었습니다.
해결
첨부 테이블에 file_data(MEDIUMBLOB, 최대 16MB) 컬럼을 추가해 사진 내용을 DB에 저장했습니다. 목록·상세 조회는 이 컬럼을 읽지 않고, 이미지 주소(/spec-archive/image/{{id}})로 요청할 때만 읽어 조회가 무거워지지 않게 했습니다. 예전에 디스크에 저장된 사진은 기존 경로로 읽도록 남겨 두었습니다.
결과
어느 팀원 서버에서 열어도 같은 사진이 보입니다.
3몇 달 쓴 화면을 몇 분 만에 만들기
상황
등급, 연속 기록, 로드맵 진행처럼 오래 써야 보이는 기능은 시연하거나 확인하려면 실제로 몇 주씩 써야 했습니다.
원인
점수를 DB에 직접 넣으면 화면은 채워지지만 실제 로직(미션 채점, 연속 보너스, 등급 계산)을 거치지 않아 검증이 되지 않습니다. 서비스 코드가 각자 현재 날짜를 읽고 있어서 과거 날짜로 실행할 수도 없었습니다.
해결
서비스가 "오늘"을 AppClock을 통해서만 읽게 바꾸고, 시뮬레이션 작업 스레드에서만 ThreadLocal로 날짜를 그날로 바꿔 끼웠습니다. 그래서 미션·점수·연속 보너스·로드맵이 실제 사용자와 같은 코드로 하루씩 실행되고, 다른 요청 스레드는 실제 날짜 그대로입니다. 성향(성실형·보통형·작심삼일형)마다 접속 확률과 정답률이 달라 판마다 결과가 다르고, 테스트 계정만 쓸 수 있으며 초기화도 그 계정 데이터만 지웁니다.
결과
목표 점수를 넣고 시작하면 수십 일치 기록이 몇 분 안에 쌓이고, 화면은 새로고침 없이 바뀝니다. 위 대시보드 스크린샷과 시연 영상이 이 기능으로 만든 화면입니다.
지하철 승객이 열차 번호·호선을 골라 민원을 접수하고, 관리자가 처리 상태와 통계를 관리하는 웹 서비스
문제웹 신고 말고 문자로 오는 민원도 받아야 하는데, KT 회선 문자는 IMAP 방식으로 받아지지 않음
해결문자를 메일함으로 받아 DB에 자동 접수하고, 수신 방식을 POP3로 바꿈
결과통신사와 상관없이 문자 민원이 관리자 화면에 같이 쌓임
PHP
MySQL(MariaDB)
PDO
bcrypt
IMAP/POP3
내 역할
문자 민원 수신: 문자로 들어온 신고를 메일함(Gmail)으로 받아 DB에 넣는 기능을 구현. IMAP에서 POP3로 바꿔 KT 회선 문자도 받을 수 있게 수정하고, 처리한 메시지는 삭제 대신 읽음 표시
관리자 대시보드: 신고 처리 상태 일괄 변경, 통계 화면 추가
입력 UX 개선: 열차 번호·호선 선택을 다이얼 방식으로 바꿔 잘못된 입력을 줄임
자세히 보기: 문자 접수 흐름 · 트러블슈팅
문자 민원 접수 흐름
신고자에게 전화번호를 직접 입력받지 않고, 신고자가 보낸 문자에서 번호를 확인합니다.
트러블슈팅
1문자로 보낸 사람의 번호를 웹 신고와 연결하기
상황
웹 신고만 받으면 신고자에게 연락할 방법이 없고, 번호를 직접 입력받으면 잘못 적거나 남의 번호를 적을 수 있습니다.
원인
웹 화면은 신고자의 실제 전화번호를 알 수 없습니다. 번호를 확실히 아는 곳은 신고자가 보낸 문자뿐입니다.
해결
신고를 먼저 익명으로 저장하면서 접수코드를 발급하고, 본문에 [접수코드: …]가 들어간 문자 앱을 바로 엽니다. 문자는 메일 주소로 보내져 Gmail에 쌓이고, 관리자 쪽에서 메일을 읽어 보낸 번호와 접수코드를 찾은 뒤 같은 접수코드의 익명 신고에 번호를 붙입니다. 5분 안에 번호가 붙지 않은 익명 신고는 지웁니다.
결과
신고자는 번호를 입력하지 않아도 되고, 관리자는 실제 문자를 보낸 번호로 신고를 확인할 수 있습니다.
2메일 수신 방식을 IMAP에서 POP3로 바꾸기
상황
처음에는 PHP IMAP 함수로 메일함을 읽었는데, KT 회선에서 보낸 문자가 받아지지 않았습니다.
원인
수신 방식을 POP3로 바꿔 보니 KT 회선 문자도 받아졌습니다. 다만 문자가 메일로 바뀌어 올 때 통신사마다 형식이 달라, 본문이 EUC-KR이나 quoted-printable로 인코딩되어 오면 번호와 접수코드를 그대로 찾을 수 없었습니다.
해결
SSL 소켓(995 포트)으로 POP3 명령을 직접 주고받는 작은 클라이언트를 만들어 최근 메일을 읽고, 제목과 본문의 인코딩을 풀어 UTF-8로 바꾼 뒤 정규식으로 010 번호와 접수코드를 찾게 했습니다.