애플리케이션(PHP, 화면) 개선
This commit is contained in:
@@ -166,6 +166,7 @@ ORDER BY A.AppID;
|
||||
```
|
||||
|
||||
- 기존 코드는 기록이 없는 앱도 `AppHighestRecord = 0`으로 응답하므로 `LEFT JOIN` + `COALESCE(…, 0)`으로 **응답 형식을 그대로** 유지합니다.
|
||||
- ⚠️ (2026-09-15 검토) `HighestRecord`는 `FLOAT`라 `COALESCE`를 쓰면 결과가 `DOUBLE`로 바뀌어 JSON 소수점이 길어질 수 있습니다. 실제 구현은 `MAX(...) GROUP BY`로 조회하고 0은 PHP에서 채웠습니다.
|
||||
- `get_typing_practice_app_list()`와 조건(`AppType = ? AND Status = 1`)이 같으므로 두 조회를 합쳐 앱 이름까지 한 번에 가져올 수도 있습니다.
|
||||
- 안1의 UNIQUE 키 `(MaestroID, PlayerID, AppID)`가 있으면 `LEFT JOIN` 한 건당 인덱스 1회 탐색입니다.
|
||||
|
||||
@@ -274,8 +275,8 @@ function build_period_condition($column, $period, $date, $time) {
|
||||
|
||||
| 파일 | 내용 | 수정 |
|
||||
|---|---|---|
|
||||
| [typing_exam_collection.php](../../../src/web/php/db/typing_exam_collection.php) `getHighestRecordArrayForAllWriting()` | `bind_param("iii", …)`에 값 2개 | 호출처가 있으면 `"ii"`, 없으면 함수 삭제 |
|
||||
| `server/record/*.php` 여러 파일 | `if($replyJSON.length === 0)` (PHP에서는 항상 거짓) | `count(...) === 0`으로 수정하거나 불필요하면 삭제 |
|
||||
| [typing_exam_collection.php](../../../src/web/php/db/typing_exam_collection.php) `getHighestRecordArrayForAllWriting()` | `bind_param("iii", …)`에 값 2개 | 호출처가 있으면 `"ii"`, 없으면 함수 삭제 → (2026-09-15) 호출처 없음, `"ii"`로 수정하고 함수는 유지 |
|
||||
| `server/record/*.php` 여러 파일 | `if($replyJSON.length === 0)` (PHP에서는 항상 거짓) | **if 블록 삭제**. `count(...) === 0`으로 고치면 분기 안의 `send_error_message()`(정의되지 않은 함수)가 실행되어 Fatal error 발생 |
|
||||
| [request_license_timer_player_record_list.php](../../../src/web/server/record/request_license_timer_player_record_list.php) | 종료일 당일 기록 누락, 존재하지 않는 `LS.AppID` 참조 | 3-1 패턴으로 재작성 |
|
||||
|
||||
---
|
||||
@@ -342,11 +343,15 @@ function build_period_condition($column, $period, $date, $time) {
|
||||
|
||||
## 8. 체크리스트
|
||||
|
||||
- [ ] 기록 목록 API 3종: 바인딩 전환, COUNT 제거, 상한, 종료일 통일
|
||||
- [ ] 기록 목록 화면: 상한 안내 / 페이지
|
||||
- [x] **기록 목록 API 3종: 바인딩 전환, COUNT 유지, LIMIT 상한 없이, 종료일 통일** (2026-09-15 완료)
|
||||
- request_app_player_record_list.php ✅
|
||||
- request_writing_player_record_list.php ✅
|
||||
- request_license_timer_player_record_list.php ✅
|
||||
- [ ] 기록 목록 화면: 상한 안내 / 페이지 (생략)
|
||||
- [ ] 변경 전후 검색 결과 건수 비교, SQL Injection 입력 테스트
|
||||
- [ ] 랭킹 기간 조건 공용 함수 (안1 작업과 함께)
|
||||
- [ ] 메뉴 N+1 제거 (연습·테스트, `menu_collection.php`, `writing_collection.php`)
|
||||
- [ ] 작은 버그 3종
|
||||
- [ ] (측정 후 필요 시) 랭킹 캐시 + 저장 시 무효화
|
||||
- [ ] `ranking` 테이블 처리 결정 (캐시 재활용 또는 삭제)
|
||||
- [ ] ~~랭킹 기간 조건 공용 함수 (안1 작업과 함께)~~ — **보류** (2026-09-15): 날짜 조건은 커밋 629c8ff에서 12곳 모두 인덱스 적용 형태로 이미 수정됨. 회귀 위험 대비 이득이 작음
|
||||
- [x] **메뉴 N+1 제거 — 새 메뉴 경로** (2026-09-15 코드 완료): `menu_collection.php`, `writing_collection.php`, `menu_list.php`
|
||||
- [ ] 메뉴 N+1 제거 — 옛 메뉴 `server/app/menu_active_typing_*_app_list.php` (범위 제외, 테스트 계정 경로에서만 사용)
|
||||
- [x] **작은 버그** (2026-09-15 코드 완료): `.length` if 블록 삭제 13개 파일(추가 발견 4개 포함), `history_record.php` 미정의 변수 push 삭제, `getHighestRecordArrayForAllWriting()` `"ii"` 수정(함수 유지). 라이선스 타이머 `AppID` 참조는 유지
|
||||
- [ ] (측정 후 필요 시) 랭킹 캐시 + 저장 시 무효화 — **이번 작업에서 제외** (2026-09-15, 적용 후 측정해서 재검토)
|
||||
- [ ] `ranking` 테이블 처리 결정 (캐시 재활용 또는 삭제) — 랭킹 캐시와 함께 보류
|
||||
|
||||
@@ -0,0 +1,331 @@
|
||||
# 안5. 애플리케이션(PHP/화면) 개선 실행 계획
|
||||
|
||||
> 참고: [`doc/plan/db/06-option5-application-layer.md`](../06-option5-application-layer.md) | 단계: **병행** | 난이도: 하~중 | 위험도: 낮음 | 예상 작업량: 2~4일
|
||||
|
||||
---
|
||||
|
||||
## 📋 요약
|
||||
|
||||
DB 구조는 그대로 두고 PHP 코드의 비효율과 보안 이슈를 고친다:
|
||||
|
||||
| 항목 | 개선 내용 | 파일 수 | 영향 범위 |
|
||||
|------|---------|--------|---------|
|
||||
| **3-1. 기록 목록 API** | SQL Injection 제거 + COUNT 제거 + 상한 추가 | 3개 + 화면 1개 | 관리자 화면 |
|
||||
| **3-2. 메뉴 N+1 쿼리** | 앱마다 조회 → 종류별 1회 조회 | 4개 | 학생 메뉴 |
|
||||
| **3-4. 랭킹 코드 정리** | 중복 함수 → 공용 함수 1개 (**보류**) | 5개 파일 (12개 함수) | 랭킹 화면 |
|
||||
| **3-3. 랭킹 캐시** | 선택 (측정 후 필요시) | 2개 + DB | 동시 접속 시 |
|
||||
| **3-5. 작은 버그** | 각 파일 수정 중 처리 | 3개 파일 | 부분 |
|
||||
|
||||
---
|
||||
|
||||
## 🔄 실행 순서
|
||||
|
||||
### 1단계: 기록 목록 API 보안 + 성능 (우선순위 높음)
|
||||
**소요: ~3시간 | 위험도: 낮음 | 롤백: git revert만 가능**
|
||||
|
||||
#### 대상 파일
|
||||
```
|
||||
src/web/server/record/
|
||||
├── request_app_player_record_list.php
|
||||
├── request_writing_player_record_list.php
|
||||
└── request_license_timer_player_record_list.php
|
||||
|
||||
src/web/module/
|
||||
└── maestro_section_record.html (화면 안내)
|
||||
```
|
||||
|
||||
#### 변경 내용
|
||||
- ✅ SQL 문자열 연결 → PreparedStatement + bind_param()
|
||||
- ✅ `get_player_record_total_count()` 호출 제거 (화면에서 미사용)
|
||||
- ✅ "전체 보기" 상한 도입 (예: MAX 1,000건)
|
||||
- ✅ 종료일 처리 통일 (`< DATE(?) + INTERVAL 1 DAY`)
|
||||
- ✅ 자격증 목록 `AppID` 참조 수정
|
||||
|
||||
#### 테스트 항목
|
||||
```
|
||||
□ 과목 전체/묶음(1000~1003)/개별 앱 검색
|
||||
□ 이름 검색 있음/없음, 기간 1일/1개월
|
||||
□ "전체 보기" → 상한 안내 표시
|
||||
□ 종료일 당일 기록 포함 여부
|
||||
□ 보안: 이름 칸에 ' OR '1'='1 입력 → 결과 비거나 해당 검색만
|
||||
□ 자격증 목록 정상 조회
|
||||
```
|
||||
|
||||
#### 체크리스트
|
||||
- [x] 3개 파일 bind_param 변환 완료
|
||||
- [x] COUNT 유지 (요청사항)
|
||||
- [x] LIMIT 상한 없이 유지 (요청사항)
|
||||
- [x] 종료일 < DATE(?) + INTERVAL 1 DAY 통일
|
||||
- [x] 결과 0건 분기(if 블록) 삭제 — 기존 동작(빈 목록 + success) 유지
|
||||
- [ ] maestro_section_record.html 상한 안내 추가 (생략)
|
||||
- [ ] 스테이징 테스트 완료
|
||||
- [ ] SQL Injection 테스트 통과
|
||||
- [ ] 전후 검색 결과 건수 비교 통과
|
||||
|
||||
---
|
||||
|
||||
### 2단계: 랭킹 코드 정리 — ⏸️ 보류 (2026-09-15)
|
||||
**소요: ~2시간 | 위험도: 낮음 | 이유: 안1 쿼리 조건 변경 시 공용 함수 1곳만 수정**
|
||||
|
||||
> **보류 사유 (검토 결과)**
|
||||
> - 날짜 조건은 커밋 `629c8ff`에서 12곳 모두 인덱스 적용 형태로 이미 수정됨 → 원래 목적 달성
|
||||
> - 실제 대상은 5개 파일·12개 함수이고, 기간 기준이 서버 시각(`app_ranking.php`)과 클라이언트 시각(나머지) 두 가지라 공용 함수가 단순하지 않음
|
||||
> - `server/record`(함수 + `global $db_conn`)와 `php/db`(클래스 + `$this->mysqli`)의 구조·include 경로가 달라 통합 비용이 큼
|
||||
> - 랭킹은 수업 중 가장 많이 쓰는 화면이라 회귀 위험 대비 이득이 작음
|
||||
> - 참고: `app_ranking.php`는 호출처 없음(`requestAppRanking()` 미사용), `getHighestRecordArrayForAllWriting()`도 호출처 없음
|
||||
|
||||
#### 대상 파일
|
||||
```
|
||||
src/web/server/lib/ (신규)
|
||||
├── ranking_period.php ← 새로 생성 (기간 조건 공용 함수)
|
||||
|
||||
src/web/server/record/
|
||||
├── app_ranking.php (get_ranking_hour/day/month)
|
||||
├── ranking_record_hour.php
|
||||
├── ranking_record_day.php
|
||||
└── ranking_record_month.php
|
||||
|
||||
src/web/php/db/
|
||||
└── typing_exam_collection.php (getRankingRecord*, getRankingMinusRecord*)
|
||||
```
|
||||
|
||||
#### 변경 내용
|
||||
- ✅ 공용 함수 `build_period_condition($column, $period, $date, $time)` 생성
|
||||
- 반환: (SQL 조각, 타입 문자열, 바인딩 값 배열)
|
||||
- 사용자 입력 금지: `$column`은 코드 고정값만
|
||||
- ✅ 기존 엔드포인트 응답 형식 그대로 유지 (클라이언트 변경 없음)
|
||||
- ✅ 10개 파일의 중복 쿼리 → 공용 함수 호출로 통합
|
||||
|
||||
#### 체크리스트
|
||||
- [ ] `src/web/server/lib/ranking_period.php` 생성
|
||||
- [ ] app_ranking.php 내부 구현만 변경
|
||||
- [ ] ranking_record_*.php 내부 구현만 변경
|
||||
- [ ] typing_exam_collection.php 내부 구현만 변경
|
||||
- [ ] 스테이징 랭킹 조회 테스트
|
||||
- [ ] 게임 클라이언트 변경 불필요 확인
|
||||
|
||||
---
|
||||
|
||||
### 3단계: 메뉴 N+1 쿼리 제거 — ✅ 코드 완료 (2026-09-15, 새 메뉴 경로만)
|
||||
**소요: ~2시간 | 위험도: 낮음 | 영향: 메뉴 진입 속도 (4 + 앱 수 → 약 6회 쿼리)**
|
||||
|
||||
> **검토 결과 및 결정**
|
||||
> - 사용 중인 메뉴는 `client/main_menu.html` → `php/menu/menu_list.php` → `menu_collection.php` / `writing_collection.php` 경로 (탭 클릭마다 호출)
|
||||
> - 옛 메뉴 `server/app/menu_active_typing_*_app_list.php`는 `start.js` 링크가 주석 처리되어 마에스트로 테스트 계정 경로에서만 접근 → **이번 범위 제외**
|
||||
> - 탭당 쿼리: `1 + 1 + 앱(글) 수` → `3` (예: 한글 테스트 12 → 3, 한글 긴글 7 → 3)
|
||||
> - 아래 쿼리 예시의 `COALESCE(MAX(...), 0)`은 **사용하지 않음**: `FLOAT`가 `DOUBLE`로 바뀌어 JSON 소수점이 길어질 수 있음 → `MAX(...) GROUP BY` + PHP에서 0 채움
|
||||
> - UNIQUE 키가 운영에는 아직 없으므로 `MAX + GROUP BY`로 앱당 1행 보장, 목록이 비면 쿼리 생략(`IN ()` 오류 방지)
|
||||
>
|
||||
> **변경 내용**
|
||||
> - `menu_collection.php`: `getTypingHighestRecordList()`를 `AppID IN (...)` 1회 조회로 교체 (`getTypingHighestRecord()` → `getTypingHighestRecordMap()`)
|
||||
> - `menu_collection.php`: `getWritingHighestRecordList()`를 1회 조회로 교체
|
||||
> - `writing_collection.php`: `getWritingHighestRecord()` → `getWritingHighestRecordMap()`
|
||||
> - `menu_collection.php`: `getHighestRecordList()`에 `$appGroup` 인자 추가 (미정의 변수 버그 수정), `menu_list.php` 호출부 수정
|
||||
> - `menu_collection.php`: 지역 변수 `$writingCollection` → `$this->writingCollection`
|
||||
> - 응답 형식 유지: 앱(글)마다 1항목, ID 정수, 기록 없으면 정수 `0`, 순서 = 앱 목록 순서
|
||||
|
||||
#### 대상 파일
|
||||
```
|
||||
src/web/server/app/
|
||||
├── menu_active_typing_practice_app_list.php
|
||||
└── menu_active_typing_test_app_list.php
|
||||
|
||||
src/web/php/db/
|
||||
├── menu_collection.php (getTypingHighestRecordList)
|
||||
└── writing_collection.php (getWritingHighestRecord)
|
||||
```
|
||||
|
||||
#### 변경 내용
|
||||
- ✅ 앱마다 조회 (N+1) → 종류별 1회 + LEFT JOIN 통합
|
||||
- ✅ 기존 응답 형식 그대로 (기록 없는 앱도 AppHighestRecord=0)
|
||||
- ✅ COALESCE(..., 0) 사용해 NULL → 0 변환
|
||||
|
||||
#### 쿼리 변경 예시
|
||||
```php
|
||||
// Before: 앱 수만큼 반복
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
SELECT MAX(AHR.HighestRecord)
|
||||
FROM app A
|
||||
INNER JOIN app_highest_record AHR ...
|
||||
WHERE A.AppID = ?
|
||||
}
|
||||
|
||||
// After: 1회 조회
|
||||
SELECT A.AppID, COALESCE(MAX(AHR.HighestRecord), 0) AS AppHighestRecord
|
||||
FROM app A
|
||||
LEFT JOIN app_highest_record AHR
|
||||
ON AHR.AppID = A.AppID AND AHR.MaestroID = ? AND AHR.PlayerID = ?
|
||||
WHERE A.AppType = ? AND A.Status = 1
|
||||
GROUP BY A.AppID
|
||||
ORDER BY A.AppID;
|
||||
```
|
||||
|
||||
#### 테스트 항목
|
||||
```
|
||||
□ 새 메뉴(main_menu.html) 탭별: 한글/영어 × 연습/테스트/긴글/타자게임, 마우스
|
||||
□ 기록 있는 앱/없는 앱의 최고기록(동물 아이콘) 표시가 변경 전과 일치
|
||||
□ menu_list.php 응답 JSON의 highestRecordList를 변경 전후 비교 (소수점 자릿수 포함)
|
||||
□ 기록이 전혀 없는 학생 계정으로 메뉴 진입 (오류 없이 모두 0)
|
||||
□ PHP 에러 로그에 Warning/Notice 없음
|
||||
```
|
||||
|
||||
#### 체크리스트
|
||||
- [ ] menu_active_typing_practice_app_list.php 수정 (범위 제외: 옛 메뉴)
|
||||
- [ ] menu_active_typing_test_app_list.php 수정 (범위 제외: 옛 메뉴)
|
||||
- [x] menu_collection.php 수정 (AppID 배열 처리, `$appGroup` 버그, `$this->writingCollection`)
|
||||
- [x] writing_collection.php 수정 (`getWritingHighestRecordMap()`)
|
||||
- [x] menu_list.php 호출부 수정
|
||||
- [ ] 스테이징 메뉴 진입 테스트
|
||||
- [ ] 기록 있는/없는 앱 표시 일치 확인
|
||||
|
||||
---
|
||||
|
||||
### 4단계: 작은 버그 수정 — ✅ 코드 완료 (2026-09-15)
|
||||
**소요: ~30분 | 위험도: 매우 낮음**
|
||||
|
||||
> **검토 결과 및 결정**
|
||||
> - PHP 7.3에서 `$x.length === 0`은 `$x . "length"` → `"Arraylength" === 0` → **항상 거짓**. 분기는 한 번도 실행된 적 없고 요청마다 에러 로그에 Warning만 남음. PHP 8에서는 Fatal error
|
||||
> - 계획의 6개 파일 외에 4개 추가 발견 → **10개 파일 모두 if 블록 삭제** (동작 변화 없음)
|
||||
> - `record/ranking_record_hour/day/month.php`, `record/history_record.php`, `record/request_app_highest_record.php`, `app/how_to_play.php`
|
||||
> - (추가) `app/menu_active_app_list.php` — 비교 대상 `$replyJSON` 자체가 미정의라 `count()`로 고치면 항상 실패 응답이 되어 메뉴가 깨짐
|
||||
> - (추가) `app/mouse_app_list.php`, `app/typing_app_list.php`, `player/search_player_list.php` — `search_player_list`는 검색 결과 0명일 때 지금처럼 빈 목록 표시 유지
|
||||
> - `history_record.php`: 루프 안 미정의 `$return_array`에 `array_push()` 하던 줄 삭제 (응답 동일, 행마다 Warning 제거)
|
||||
> - `typing_exam_collection.php` `getHighestRecordArrayForAllWriting()`: `"iii"` → `"ii"` 수정, 함수는 유지 (호출처 없음)
|
||||
> - 참고(범위 밖): `src/web/php/`에는 `.htaccess`가 없고 스테이징 Dockerfile에 `php.ini` 설정이 없어 `display_errors` 기본값(On)이 적용될 수 있음 → Warning이 JSON 응답에 섞이는지 `phpinfo()`로 확인 권장
|
||||
|
||||
| 파일 | 문제 | 수정 |
|
||||
|------|------|------|
|
||||
| typing_exam_collection.php | bind_param("iii", …)에 값 2개만 | "ii"로 수정 또는 함수 삭제 |
|
||||
| server/record/*.php 여러 파일 | `if($replyJSON.length === 0)` (PHP에서 항상 거짓) | **if 블록 삭제** (`count()`로 고치면 정의되지 않은 `send_error_message()`가 실행되어 Fatal error) |
|
||||
| request_license_timer_player_record_list.php | 종료일 당일 기록 누락, 존재하지 않는 LS.AppID | 종료일은 1단계에서 수정 완료. AppID 참조는 화면이 3000만 보내므로 유지 (추후 정리) |
|
||||
|
||||
#### 체크리스트
|
||||
- [x] typing_exam_collection.php bind_param `"iii"` → `"ii"` (함수 유지)
|
||||
- [x] request_*_player_record_list.php 3개: if 블록 삭제 (1단계에서 처리)
|
||||
- [x] 남은 파일 if 블록 삭제: ranking_record_hour/day/month.php, history_record.php, request_app_highest_record.php, app/how_to_play.php
|
||||
- [x] 추가 발견 파일 if 블록 삭제: app/menu_active_app_list.php, app/mouse_app_list.php, app/typing_app_list.php, player/search_player_list.php
|
||||
- [x] history_record.php 미정의 `$return_array` push 줄 삭제
|
||||
- [ ] 스테이징 테스트: 결과·랭킹 화면, 시작 화면(히스토리·최고기록·게임 방법), 메뉴, 마에스트로 앱 활성화·기록·학생 검색(0명 포함)
|
||||
- [ ] 스테이징 PHP 에러 로그에서 `Use of undefined constant length` Warning 사라졌는지 확인
|
||||
|
||||
---
|
||||
|
||||
### 5단계: 랭킹 캐시 — ⏸️ 이번 작업에서 제외 (2026-09-15)
|
||||
|
||||
> 1·3·4단계 스테이징/운영 적용 후 수업 시간대 랭킹 쿼리 부하를 측정해서 필요할 때 다시 검토합니다. 진행 시 2단계 보류로 랭킹 코드가 5개 파일·12개 함수에 흩어져 있다는 점을 고려해 캐시 적용 위치부터 재검토해야 합니다.
|
||||
**소요: ~2시간 | 위험도: 낮음 | 조건: 1·2 완료 후 측정에서 필요 판단**
|
||||
|
||||
#### 배경
|
||||
- 교실 수업: 학생 30명 동시 → 동일한 랭킹 쿼리 90회 실행
|
||||
- 랭킹 화면 1시간 열어 둠 → 기록 변화 없어도 720회 쿼리 (5초마다)
|
||||
|
||||
#### 방법 (DB 캐시 테이블 권장)
|
||||
```sql
|
||||
-- ranking 테이블 백업 후 삭제
|
||||
CREATE TABLE ranking_cache (
|
||||
MaestroID INT UNSIGNED NOT NULL,
|
||||
AppID INT UNSIGNED NOT NULL,
|
||||
RankingType CHAR(10) NOT NULL,
|
||||
CachedDateTime DATETIME NOT NULL,
|
||||
Payload MEDIUMTEXT NOT NULL,
|
||||
PRIMARY KEY (MaestroID, AppID, RankingType)
|
||||
);
|
||||
```
|
||||
|
||||
#### 체크리스트
|
||||
- [ ] 3-1·3-2 완료 후 성능 측정
|
||||
- [ ] 랭킹 쿼리 슬로우 로그 확인 (필요시만 진행)
|
||||
- [ ] ranking_cache 테이블 생성
|
||||
- [ ] 캐시 조회/저장 로직 구현
|
||||
- [ ] update_result_record.php에 캐시 무효화 추가
|
||||
- [ ] ranking 테이블 백업 후 삭제 또는 유지 결정
|
||||
|
||||
---
|
||||
|
||||
## 📊 예상 효과
|
||||
|
||||
| 화면 / API | 변경 전 | 변경 후 | 개선율 |
|
||||
|---|---|---|---|
|
||||
| 기록 목록 조회 | COUNT 1회 + 목록 1회 | 목록 1회 | **50% ↓** |
|
||||
| 메뉴 진입 (앱 16개) | ~20회 쿼리 | ~6회 | **67% ↓** |
|
||||
| 결과 화면 랭킹 (학생 30명) | 90회 | 3회 (캐시 미스) | **97% ↓** |
|
||||
| 보안 | SQL Injection 취약 | 안전 (바인딩) | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 주의사항
|
||||
|
||||
| 항목 | 위험 | 대응 |
|
||||
|------|------|------|
|
||||
| **검색 결과 범위 변경** | 과목 묶음 범위, 종료일 처리로 결과 건수 달라질 수 있음 | 변경 전후 같은 조건으로 건수 비교 |
|
||||
| **bind_param 타입 오류** | `$types`와 `$params` 개수 불일치 | 항상 같은 줄 묶음에서 함께 추가 |
|
||||
| **캐시 무효화 누락** | 새 기록 후 오래된 랭킹 표시 | 기록 저장 시 해당 캐시 DELETE |
|
||||
| **응답 형식 변경** | 클라이언트가 다른 형식 기대 | 기존 응답 형식 유지 (LEFT JOIN + COALESCE) |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 스테이징 배포 절차
|
||||
|
||||
```bash
|
||||
# 1. 각 단계별 개발
|
||||
# 2. 로컬 테스트 통과
|
||||
git add src/web/server/record/ src/web/module/ ...
|
||||
git commit -m "3-1: 기록 목록 API SQL Injection 제거 및 성능 개선"
|
||||
|
||||
# 3. 스테이징 배포
|
||||
git push origin 08-app-improvements
|
||||
|
||||
# 4. 스테이징 테스트 완료
|
||||
# 5. 운영 서버 배포 (새벽 2시)
|
||||
git push origin release
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 최종 체크리스트
|
||||
|
||||
### Phase 1: 기록 목록 API
|
||||
- [x] 3개 파일 bind_param 변환 (2026-09-15 완료)
|
||||
- request_app_player_record_list.php ✅
|
||||
- request_writing_player_record_list.php ✅
|
||||
- request_license_timer_player_record_list.php ✅
|
||||
- [x] COUNT 유지, LIMIT 상한 없이 유지
|
||||
- [x] 종료일 < DATE(?) + INTERVAL 1 DAY 통일
|
||||
- [x] 결과 0건 분기(if 블록) 삭제 — `send_error_message()` 미정의로 인한 Fatal error 방지
|
||||
- [x] get_subject_app_range() 함수 추가
|
||||
- [ ] maestro_section_record.html 상한 안내 (생략)
|
||||
- [ ] SQL Injection 테스트
|
||||
- [ ] 스테이징 배포
|
||||
|
||||
### Phase 2: 랭킹 코드 정리 — ⏸️ 보류 (2026-09-15, 사유는 2단계 본문 참고)
|
||||
|
||||
### Phase 3: 메뉴 N+1
|
||||
- [x] 새 메뉴 경로 3개 파일 변경 (2026-09-15): menu_collection.php, writing_collection.php, menu_list.php
|
||||
- [x] `IN (...)` + `MAX ... GROUP BY` 1회 조회, 0 채움은 PHP (COALESCE 미사용)
|
||||
- [ ] 옛 메뉴 server/app 2개 파일 (범위 제외)
|
||||
- [ ] 메뉴 진입 테스트
|
||||
- [ ] 스테이징 배포
|
||||
|
||||
### Phase 4: 작은 버그
|
||||
- [x] typing_exam_collection.php `"iii"` → `"ii"` (2026-09-15)
|
||||
- [x] `.length` if 블록 삭제: 13개 파일 (1단계 3개 + 4단계 10개)
|
||||
- [x] history_record.php 미정의 변수 push 삭제
|
||||
- [ ] ~~라이선스 타이머 재작성~~ (종료일은 1단계 완료, AppID 참조는 유지 결정)
|
||||
- [ ] 스테이징 테스트
|
||||
|
||||
### Phase 5: 랭킹 캐시 — ⏸️ 이번 작업에서 제외 (2026-09-15, 측정 후 재검토)
|
||||
|
||||
---
|
||||
|
||||
## 🔗 참고 문서
|
||||
|
||||
| 링크 | 설명 |
|
||||
|------|------|
|
||||
| [`06-option5-application-layer.md`](../06-option5-application-layer.md) | 전체 개선 계획 및 코드 예시 |
|
||||
| [`00-overview.md`](00-overview.md) | DB 인덱스 개선 계획 (병행) |
|
||||
| `AGENTS.md` | 프로젝트 아키텍처 |
|
||||
|
||||
---
|
||||
|
||||
**작성일:** 2026-09-15
|
||||
**상태:** 1·3·4단계 코드 완료, 2·5단계 보류 → ⏳ 스테이징 테스트 대기 (2026-09-15)
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
## 📋 문서 구조
|
||||
|
||||
### 안1: DB 인덱스 + 쿼리 최적화
|
||||
|
||||
| # | 파일 | 설명 | 소요시간 |
|
||||
|---|---|---|---|
|
||||
| 00 | `00-overview.md` | 전체 계획 및 사전 확인 | - |
|
||||
@@ -15,6 +17,12 @@
|
||||
| 04 | `04-verify.md` | **개선 후 성능 측정 및 비교** (EXPLAIN, 성능 비교) | 15분 |
|
||||
| 05 | `05-checklist.md` | **최종 체크리스트** (성공 기준) | 10분 |
|
||||
|
||||
### 안5: 애플리케이션 개선 (병행)
|
||||
|
||||
| # | 파일 | 설명 | 소요시간 |
|
||||
|---|---|---|---|
|
||||
| 08 | `08-application-layer-improvements.md` | PHP/화면 개선 전체 계획 (5단계) | 2~4일 |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 빠른 시작
|
||||
@@ -67,19 +75,30 @@
|
||||
|
||||
## 🔗 관련 문서
|
||||
|
||||
- **계획:** [`doc/plan/db/02-option1-index-and-query-rewrite.md`](../02-option1-index-and-query-rewrite.md)
|
||||
- **계획 (안1):** [`doc/plan/db/02-option1-index-and-query-rewrite.md`](../02-option1-index-and-query-rewrite.md)
|
||||
- **계획 (안5):** [`doc/plan/db/06-option5-application-layer.md`](../06-option5-application-layer.md)
|
||||
- **운영 서버:** 260915-improve-production 디렉토리 (진행 예정)
|
||||
|
||||
---
|
||||
|
||||
## 📝 진행 상황
|
||||
|
||||
### 안1 (DB 인덱스)
|
||||
| 단계 | 상태 | 날짜 |
|
||||
|---|---|---|
|
||||
| 계획 수립 | ✅ 완료 | 2026-09-15 |
|
||||
| 스테이징 테스트 | ⏳ 준비 중 | |
|
||||
| 운영 서버 적용 | ⏳ 대기 중 | |
|
||||
|
||||
### 안5 (Application Layer)
|
||||
| 단계 | 상태 | 날짜 |
|
||||
|---|---|---|
|
||||
| Phase 1: 기록 목록 API | ✅ 코드 완료 | 2026-09-15 |
|
||||
| Phase 2: 랭킹 코드 정리 | ⏸️ 보류 (날짜 조건은 629c8ff에서 이미 수정됨) | 2026-09-15 |
|
||||
| Phase 3: 메뉴 N+1 제거 | ✅ 코드 완료 (새 메뉴 경로) | 2026-09-15 |
|
||||
| Phase 4: 작은 버그 | ✅ 코드 완료 (`.length` 분기 13개 파일 정리) | 2026-09-15 |
|
||||
| Phase 5: 랭킹 캐시 | ⏸️ 이번 작업에서 제외 (측정 후 재검토) | 2026-09-15 |
|
||||
|
||||
---
|
||||
|
||||
## 💾 저장 위치
|
||||
@@ -105,6 +124,12 @@ doc/plan/db/260915-improve-stage/
|
||||
|
||||
## 다음 단계
|
||||
|
||||
1. **지금:** 01-premeasure.md부터 시작
|
||||
2. **스테이징 완료 후:** 260915-improve-production 디렉토리 생성
|
||||
3. **운영 서버:** 동일한 절차 (새벽 2시)
|
||||
### 안1 (DB 인덱스 - 진행 중)
|
||||
1. **지금:** 01-premeasure.md부터 시작 → 05-checklist.md 완료
|
||||
2. **스테이징 완료 후:** 260915-improve-production 디렉토리에서 운영 적용 (새벽 2시)
|
||||
|
||||
### 안5 (Application Layer - 병행 권장)
|
||||
1. **08-application-layer-improvements.md** 읽기 (5단계 실행 계획)
|
||||
2. **각 단계별 개발** (3-1 → 3-4 → 3-2 → 3-5 순서)
|
||||
3. **스테이징 배포** → 테스트
|
||||
4. **운영 서버 배포** (안1 후 또는 동시 가능)
|
||||
|
||||
Reference in New Issue
Block a user