212 lines
16 KiB
Markdown
212 lines
16 KiB
Markdown
# GTSIT 사이트 설계안
|
||
|
||
최종 수정일: 2026-08-18
|
||
|
||
## 1. 구축 목표
|
||
|
||
- 회사와 서비스를 명확히 소개하는 반응형 웹사이트 구축
|
||
- 게시글과 문의를 운영자가 관리할 수 있는 관리자 기능 제공
|
||
- Cafe24 웹호스팅의 PHP 및 MariaDB 환경에 맞춘 단순하고 유지보수 가능한 구성
|
||
- 비밀번호와 문의 내용 등 민감정보를 브라우저 저장소가 아닌 서버에서 안전하게 처리
|
||
|
||
## 2. 확인된 운영 환경
|
||
|
||
| 항목 | 확인 내용 | 후속 작업 |
|
||
| --- | --- | --- |
|
||
| 호스팅 계정 | `gts0201` | 인증 정보는 문서에 저장하지 않음 |
|
||
| 상품 | 뉴아우토반호스팅 일반형 | 현재 용량으로 초기 운영 가능 |
|
||
| 웹 환경 | PHP 8.4 | 배포 전 필수 PHP 확장 확인 |
|
||
| DB 환경 | MariaDB 10.6.17, `localhost:3306`, 계정 `gts0201` | `utf8mb4_unicode_ci`, 스키마 생성 대기 |
|
||
| 서버 IP | `112.175.85.162` | DNS 생성 후 실제 응답과 대조 |
|
||
| 기본 도메인 | `gts0201.mycafe24.com` | 연결 도메인으로 유지, 운영 노출 정책 결정 대기 |
|
||
| 대표 도메인 | `gtsit.co.kr` | 2026-08-04 Cafe24 대표 도메인 변경 완료 |
|
||
| 대표 이메일 | `gts0201@naver.com` | 공개 화면 및 문의 안내에 사용 |
|
||
| 회사 주소 | `서울시 송파구 정의로7길 13, B동 오피스 1020호` | 공개 화면 사업자 정보에 사용 |
|
||
| 네임서버 | Cafe24 네임서버 위임 확인 | 호스팅 연결 후 DNS 레코드 생성 확인 |
|
||
| 현재 DNS | 루트·`www`가 `112.175.85.162`로 연결, 공개 DNS 전파 중 | Cloudflare DNS 전파 재확인 |
|
||
| SSL | Let’s Encrypt 적용, 루트·`www` 포함 | 자동 갱신 상태 지속 확인 |
|
||
| 웹 방화벽 | ModSecurity 사용 | 적용 후 기능 오탐 점검 |
|
||
| UploadGuard | 사용 | 파일 업로드 기능과 호환성 점검 |
|
||
| SSH/SFTP | RSA 4096 인증키, 만료일 2026-09-03 | 새 다운로드 키로 접속 성공 |
|
||
| 서버 홈·웹 루트 | `/home/hosting_users/gts0201`, `www` | 임시 페이지 배포 완료 |
|
||
| PHP 안티웹쉘 | 미사용 | 본 사이트 배포 전 활성화 검토 |
|
||
| 외부 MySQL 접속 | 차단 | 서버 내부 PHP에서만 DB 사용 권장 |
|
||
|
||
## 3. 페이지 구조
|
||
|
||
### 공개 페이지
|
||
|
||
| 경로 | 역할 | 주요 구성 |
|
||
| --- | --- | --- |
|
||
| `/` | 홈 | 회사 소개, 핵심 서비스, 최신 게시글, 문의 유도 |
|
||
| `/blog` | 게시글 목록 | 검색 또는 분류, 목록, 페이지네이션 |
|
||
| `/blog/{slug}` | 게시글 상세 | 제목, 본문, 이미지, 작성일, 이전·다음 글 |
|
||
| `/contact` | 문의 접수 | 작성자 정보, 문의 내용, 개인정보 동의 |
|
||
| `/inquiry/lookup` | 문의 확인 | 접수번호와 숫자 4자리 조회 PIN을 통한 본인 확인 |
|
||
|
||
### 관리자 페이지
|
||
|
||
| 경로 | 역할 | 주요 구성 |
|
||
| --- | --- | --- |
|
||
| `/admin/login` | 관리자 인증 | 서버 세션 기반 로그인 |
|
||
| `/admin` | 대시보드 | 게시글 및 문의 요약 |
|
||
| `/admin/posts` | 게시글 관리 | 목록, 상태 변경, 수정, 삭제 |
|
||
| `/admin/posts/new` | 게시글 작성 | 제목, 본문, 대표 이미지, 공개 상태 |
|
||
| `/admin/posts/{id}/edit` | 게시글 수정 | 기존 데이터 편집 |
|
||
| `/admin/inquiries` | 문의 관리 | 목록, 검색, 처리 상태 |
|
||
| `/admin/inquiries/{id}` | 문의 상세 | 내용 확인, 답변 메모, 상태 변경 |
|
||
| `/admin/company` | 회사 정보 | 연락처와 사이트 공통 정보 관리 |
|
||
|
||
## 4. 권장 기술 구성
|
||
|
||
- 서버: PHP 8.4
|
||
- 데이터베이스: MariaDB 10.6.17, PDO MySQL과 prepared statement 사용
|
||
- 화면: 서버 렌더링 HTML, CSS, 필요한 범위의 경량 JavaScript
|
||
- 인증: PHP 세션, `password_hash()`와 `password_verify()` 사용
|
||
- URL: Apache rewrite 사용 가능 여부 확인 후 의미 있는 경로 적용
|
||
- 설정: DB 접속값을 공개 디렉터리 밖의 환경별 설정 파일로 분리
|
||
|
||
확인된 웹 PHP 환경은 PHP 8.4.21p1, UTF-8, `Asia/Seoul`, PDO MySQL, mysqli, mbstring, OpenSSL, fileinfo, 업로드 최대 100MB, 메모리 256MB입니다. DB 접속 설정은 서버 웹 루트 밖의 `.gtsit-db.env`에 권한 600으로 저장합니다.
|
||
|
||
Cafe24의 실제 웹 루트는 `/home/hosting_users/gts0201/www`로 확인했습니다. PHP 확장은 본 사이트 개발 단계에서 확정합니다. 아래 구조는 공유호스팅 권한 범위에 맞춰 조정합니다.
|
||
|
||
```text
|
||
app/
|
||
Controllers/
|
||
Repositories/
|
||
Views/
|
||
config/
|
||
database/
|
||
schema.sql
|
||
docs/
|
||
public/
|
||
assets/
|
||
uploads/
|
||
index.php
|
||
storage/
|
||
logs/
|
||
```
|
||
|
||
초기 임시 배포는 현재의 `public/index.html` 단일 파일을 유지합니다. 본 사이트는 `public/index.php` 프런트 컨트롤러와 공통 PHP 뷰로 구현하며, 운영 전환 시 Apache가 `index.php`를 우선 사용합니다.
|
||
|
||
## 5. 데이터 모델 초안
|
||
|
||
| 테이블 | 목적 | 핵심 데이터 |
|
||
| --- | --- | --- |
|
||
| `admins` | 관리자 계정 | 로그인 ID, 비밀번호 해시, 상태, 마지막 로그인 |
|
||
| `posts` | 게시글 | 제목, slug, 본문, 공개 상태, 작성·수정일 |
|
||
| `post_images` | 게시글 이미지 | 게시글 ID, 파일 경로, 대체 텍스트, 정렬값 |
|
||
| `inquiries` | 고객 문의 | 접수번호, 작성자, 연락처, 내용, 조회 PIN 해시, 처리 상태, 조회 실패·잠금, 종료일 |
|
||
| `site_settings` | 회사 및 공통 설정 | 설정 키, 값, 수정일 |
|
||
|
||
2026-08-04 초기 스키마를 MariaDB에 적용했습니다. `admins`, `posts`, `post_images`, `inquiries`, `site_settings` 5개 테이블은 모두 `utf8mb4_unicode_ci`를 사용합니다. 문의는 접수번호와 숫자 4자리 조회 PIN의 해시로 공개 답변을 조회합니다. 2026-08-05 접수번호별 조회 실패 횟수와 잠금 시각, 처리 종료일을 추가했으며 5회 실패 시 15분 동안 해당 접수번호의 조회를 제한합니다.
|
||
|
||
## 5-1. 현재 구현 구조
|
||
|
||
```text
|
||
app/
|
||
Auth/AdminAuth.php
|
||
Repositories/
|
||
Support/helpers.php
|
||
Views/
|
||
bin/create-admin.php
|
||
config/
|
||
database/schema.sql
|
||
public/
|
||
assets/css/
|
||
assets/images/company-logo-v3.png
|
||
assets/js/
|
||
index.html
|
||
index.php
|
||
```
|
||
|
||
- 공개 페이지: 서버 렌더링, 페이지별 title·description·canonical·Open Graph 적용
|
||
- 브랜드 로고: 투명 배경 `GTS` 워드마크 PNG를 이미지로 사용하고, 법인명은 정확한 표기와 접근성을 위해 HTML 텍스트로 분리
|
||
- 서비스 아이콘: 네트워크·키폰·CCTV·유지보수 항목에 대응하는 인라인 SVG를 고정 UI 자산으로 관리하며 관리자 입력 대상에서 제외
|
||
- 메인 비주얼: 기존 사이트 백업의 실제 시공 이미지 4장을 `public/assets/images/field/`에 1200×900 JPEG로 최적화해 5초 간격 자동 슬라이드로 표시
|
||
- 슬라이드 접근성: 이전·다음·직접 선택·일시정지 제공, 포인터·키보드 조작 중 자동 전환 정지, `prefers-reduced-motion` 환경 자동재생 중지
|
||
- 이전 콘텐츠 이관: LAN·옥외 무선·키폰·CCTV·통신 랙 관리 5개 주제를 현재 표현으로 재구성하고 `database/seed-legacy-content.sql`로 멱등 반영
|
||
- 문의: 이메일은 아이디와 주요 도메인을 조합하고 `직접입력`에서만 전체 주소 작성, 연락처는 단일 필드에서 휴대전화·서울·기타 지역번호·대표번호·인터넷전화·안심번호 형식으로 자동 변환
|
||
- 문의 보안: 서버 필수값 검증, CSRF, 20초 중복 방지, 숫자 4자리 조회 PIN 해시, 세션 및 접수번호별 조회 실패 제한
|
||
- 개인정보: 2026 개인정보보호위원회 작성지침을 기준으로 처리 목적·항목·법적 근거·보유기간·제3자 제공·위탁·파기·권리 행사·안전조치·세션 쿠키·담당부서·시행일을 공개
|
||
- 개인정보 파기: 문의가 `closed`로 전환된 시각을 기록하고 1년이 지난 문의를 `bin/purge-inquiries.php`로 데이터베이스에서 완전 삭제
|
||
- 파기 실행 환경: 현재 뉴아우토반 공유 웹호스팅은 cron을 지원하지 않아 정리 명령을 수동 실행할 수 있도록 배포했으며, 외부 스케줄러 또는 지원 상품 전환 시 정기 실행 등록 필요
|
||
- 관리자: 서버 세션, 비밀번호 해시, 계정별 잠금, 세션 단위 시도 제한, 모든 하위 경로 인증 선행
|
||
- 방문 통계: 무료 GA4 표준 속성으로 운영 공개 페이지만 측정하고 관리자·테스트 경로 제외, Analytics Data API는 서버의 읽기 전용 서비스 계정과 15분 캐시를 통해 관리자 대시보드에 집계값만 제공
|
||
- 통계 개인정보 경계: 문의 이름·연락처·이메일·본문과 관리자 식별정보는 GA4 이벤트에 포함하지 않고, 통계 쿠키 동의 전에는 Analytics 저장을 비활성화
|
||
- GA4 식별자: 계정 `(주)지티에스정보통신`, 속성 ID `549573245`, 운영 웹 스트림 ID `15422491033`, 측정 ID `G-92XBR314YD`
|
||
- 통계 동의 흐름: 최초 선택 전 Google 태그 미로딩, 허용 시 운영 호스트에서만 측정, 거부·철회 시 Analytics 저장 거부와 회사 도메인 `_ga` 쿠키 삭제 시도, 광고 저장·Google 신호·광고 개인화는 항상 비활성화
|
||
- 관리자 통계: Google Cloud 프로젝트 `gtsit-ga4-dashboard-20260812`의 전용 서비스 계정에 GA4 속성 `뷰어` 권한만 부여하고, 서버에서 Analytics Data API를 조회해 15분간 비공개 파일 캐시
|
||
- GA4 키 보관: JSON 개인키는 Git과 웹 루트 밖의 앱 `.secrets/`에 권한 `600`으로 보관하며 브라우저 HTML·JavaScript·로그에 포함하지 않음
|
||
- 통계 장애 대응: API 오류 시 마지막 정상 캐시를 표시하고, 캐시도 없을 때는 관리자 화면에 일반 안내만 표시해 자격 증명·API 오류 세부 정보 비노출
|
||
- 전체 방문자 기준: GA4 측정 시작일 `2026-08-12`부터 오늘까지의 `totalUsers`를 표시하며 이전 방문 기록은 포함하지 않음
|
||
- 초기 관리자: 24시간 유효한 토큰을 세션으로 교환한 뒤 비밀번호를 직접 설정하며, 생성 즉시 토큰 파일 삭제
|
||
- 게시글: 신규·기존 slug는 DB 게시글 번호 기반 `post-{id}` 자동 생성, 종전 slug는 정적 별칭을 통한 301 이동, 분류, 초안·공개 상태, 소프트 삭제 기반
|
||
- 대표 이미지: JPG·PNG·WebP, 8MB, 6000px 제한, MIME 재검증, 임의 파일명, 대체 텍스트 적용
|
||
- 게시글 미디어 실험: 테스트 경로에서 대표 이미지 최대 6장 슬라이드와 본문 이미지 최대 10장을 지원하고, 본문은 독립 줄의 `[[image:번호]]` 토큰 위치에 안전하게 렌더링
|
||
- 관리자 본문 편집: 범용 HTML 에디터 대신 textarea 문단과 이미지 카드만 제공하는 전용 블록 UI 사용, 위·아래 이동과 삭제 후 기존 텍스트·토큰 형식으로 직렬화
|
||
- 작성 화면 미리보기: 저장 전 폼 상태와 선택 파일의 브라우저 임시 URL을 모달에 안전한 DOM API로 렌더링하며, 미리보기 조작은 서버 요청이나 DB 저장을 발생시키지 않음
|
||
- 본문 보안 경계: 임의 HTML·`contenteditable`·`innerHTML`을 사용하지 않고 텍스트는 기존 출력 이스케이프, 이미지는 서버 검증 파일과 DB 연결 정보만 렌더링
|
||
- 운영 호환성: 공유 DB 스키마 변경 없이 `post_images.caption`의 `@gtsit:gallery`, `@gtsit:inline:번호` 내부 표식과 정렬순서를 사용하며, 운영 코드가 항상 첫 대표 이미지를 선택하도록 유지
|
||
- 실험 공개 제한: 다중 대표 이미지 또는 본문 이미지가 포함된 게시글은 운영 반영 승인 전까지 초안으로만 저장
|
||
- 이미지 없는 게시글 카드: 글을 숨기지 않고 `GTS` 로고와 카테고리 색상을 사용한 16:9 브랜드 플레이스홀더 표시, 대표 이미지 등록 시 실제 이미지 우선
|
||
- 회사 정보: 관리자 화면에서 연락처·주소·사업자 정보를 DB 설정값으로 관리
|
||
- 오시는 길: NAVER Cloud Dynamic Map으로 주소 기반 지도와 회사 마커 표시, API 오류 시 네이버 지도 검색 링크 제공
|
||
- 운영 사이트: `https://gtsit.co.kr`에서 본 사이트 공개, 애플리케이션은 웹 루트 밖의 `gtsit-app-20260810`에 배치
|
||
- 이전 테스트 경로: `/site-preview-7c2a91f4`, 운영 전환 후 비교·복구용으로 유지하며 검색엔진 색인 차단
|
||
|
||
## 6. 디자인 구현 전 수정 원칙
|
||
|
||
- 19개 HTML 시안의 공통 헤더, 푸터, 내비게이션을 공통 뷰로 통합
|
||
- 게시글 상세 페이지를 개별 정적 파일이 아닌 하나의 동적 템플릿으로 통합
|
||
- 공개 화면의 모바일 헤더 가로 넘침 수정
|
||
- 관리자 게시글 목록의 모바일 조작 영역 잘림 수정
|
||
- `localStorage` 기반 게시글과 문의 데이터를 MariaDB로 이전
|
||
- 클라이언트 코드에 포함된 관리자 인증값 제거
|
||
- 문의 등록 시 접수번호와 조회 PIN 해시를 서버에 저장하고 접수번호별 시도 제한을 적용한 조회 절차 구현
|
||
- 업로드 파일의 확장자, MIME, 용량, 저장 파일명을 서버에서 검증
|
||
|
||
## 7. 보안 원칙
|
||
|
||
- 모든 운영 페이지 HTTPS 강제
|
||
- 관리자 로그인 시도 제한과 세션 쿠키 보안 옵션 적용
|
||
- CSRF 토큰, 출력 이스케이프, SQL 파라미터 바인딩 적용
|
||
- 비밀번호와 개인키를 저장소, 로그, HTML, JavaScript에 포함하지 않음
|
||
- 업로드 디렉터리에서 PHP 실행 차단
|
||
- 관리자 경로에 검색엔진 차단 및 필요 시 추가 접근 제한 적용
|
||
- Cafe24 일일 백업과 별도의 외부 백업 병행
|
||
|
||
## 8. 배포 순서
|
||
|
||
1. SSH/SFTP 개인키 준비 및 서버 접속 확인
|
||
2. 기존 웹 루트 파일 확인 및 백업
|
||
3. 임시 `index.html` 업로드
|
||
4. `gts0201.mycafe24.com`에서 응답 및 모바일 화면 확인
|
||
5. 확정한 사용자 도메인을 호스팅에 연결
|
||
6. 루트 도메인과 `www` DNS 응답 확인
|
||
7. 무료 SSL 신청, 발급, HTTPS 리디렉션 확인
|
||
8. MariaDB 접속 확인 및 최소 권한 구성
|
||
9. 본 사이트 스키마와 애플리케이션 배포
|
||
10. 보안, 백업, 알림, 최종 회귀 QA
|
||
|
||
2026-08-10 운영 루트를 본 사이트로 전환했습니다. 웹 루트에는 공개 에셋·업로드 디렉터리·운영 진입 파일만 두고, PHP 애플리케이션은 웹 루트 밖에 배치합니다. 이전 테스트 경로는 비교·복구용으로 유지하며 경로 접두사와 검색엔진 차단 설정을 유지합니다.
|
||
|
||
## 9. 미확정 의사결정
|
||
|
||
현재 대표 주소는 `https://gtsit.co.kr`입니다. Cafe24 대표 도메인도 `gtsit.co.kr`로 변경했으며, HTTP와 `www` 요청은 `public/.htaccess`에서 대표 주소로 301 리디렉션합니다. 2026-08-04 확인한 Let’s Encrypt 인증서는 루트와 `www`를 포함하며 2026-11-02까지 유효합니다.
|
||
- 회사 대표 메일을 Cafe24 메일로 사용할지 외부 메일 서비스를 사용할지 결정
|
||
- 관리자 계정 수와 권한 구분 필요 여부 확정
|
||
- 게시글 분류, 검색, 임시저장, 예약발행 필요 여부 확정
|
||
|
||
## 10. 네이버 지도 연동
|
||
|
||
- NAVER Cloud Platform의 Maps Application에서 `Dynamic Map` 선택
|
||
- Web 서비스 URL은 공식 형식에 맞춰 `http://gtsit.co.kr` 등록(HTTP·HTTPS 구분 없음, `www` 제외)
|
||
- 브라우저 지도 로더에는 Client ID를 `ncpKeyId`로 사용
|
||
- Client Secret은 프런트엔드, 저장소, HTML에 포함하지 않음
|
||
- 고정 회사 위치는 확인된 위도 `37.48524`, 경도 `127.11478`을 사용해 `Dynamic Map`만 호출하고, 별도 Geocoding 상품과 일일 주소 변환 호출은 사용하지 않음
|
||
- 주소 표시는 `서울시 송파구 정의로7길 13, B동 오피스 1020호`, 네이버 지도 검색은 건물 주소를 기준으로 연결
|
||
- 지도 SDK가 컨테이너 위치 속성을 덮어써도 높이가 유지되도록 지도 요소에 `height: 100%` 적용
|
||
- NAVER 기본 컨트롤에 전체화면이 없으므로 Fullscreen API 기반 사용자 정의 아이콘을 지도 안에 제공하고, 미지원 환경에서는 네이버 지도 새 창 열기로 대체
|
||
- 정상 지도 상태에서는 별도 텍스트 링크를 숨기고, API 로드 실패 또는 JavaScript 비활성 환경에서만 주소가 지정된 네이버 지도 링크 제공
|