# 업체(테넌트) 템플릿 복사 기능 가능성 분석 보고서 ## 📋 Executive Summary **결론: 매우 높은 가능성 (95% 이상 가능)** DLIVE-Zinpack-AI 시스템은 **멀티테넌트 아키텍처**를 기반으로 하고 있으며, 기존의 `enterprise_write_ok.php`에서 이미 새로운 업체 생성 시 필요한 모든 요소(DB 레코드 생성, 디렉토리 구조, 초기 데이터 삽입)를 처리하고 있습니다. 이를 토대로 **원본 업체 데이터를 복사하여 새로운 업체를 생성하는 기능**은 기술적으로 완전히 실현 가능합니다. 단지 기존 로직을 확장하면 됩니다. --- ## 1️⃣ 현재 업체 데이터 구조 파악 ### 1.1 업체(Enterprise) 생성 프로세스 분석 #### enterprise_write.php (GUI 폼) - **위치**: `/infoway/_infoway/bbs/super/enterprise_write.php` - **역할**: 업체 정보 입력 폼 제공 - **입력 필드**: - `ep_nick`: 포워딩 ID (3글자 이상, 숫자/영문만, 중복 불가) - `ep_corporate`: 업체명 - `ep_permit_number`: 사업자등록번호 - `ep_domain`: 서브도메인 (예: wizwin, anjungdon) - `ep_copy_off`: 복제 방지 설정 - `ep_upload`, `ep_upload_size`: 파일 업로드 설정 - `mb_mail`: 관리자 이메일 (아이디) - `mb_password`: 관리자 비밀번호 - `mb_name`: 관리자 이름 - `mb_nick`: 관리자 닉네임 - `mb_tel`: 휴대폰번호 (3개 입력란) - `mb_zip_code`, `mb_address`, `mb_address_sub`: 주소 - `ep_expiry_date`: CMS 만료일자 - `ep_charge`: 연간 사용료 #### enterprise_write_ok.php (처리 로직) - 842줄 **가장 중요한 파일**. 업체 생성 시 실행되는 모든 작업이 여기 정의됨. **주요 처리 단계**: 1. **검증 (Line 43-48)** - ep_nick 중복 체크 - ep_domain 중복 체크 2. **고유 ID 생성 (Line 50-52)** ```php $ep_code = "ep".uniqid(rand()); // 기업 코드 (예: ep801870536594c8ad1cd3b5) $mb_code = "mb".uniqid(rand()); // 회원 코드 $gp_code = "all"; // 그룹 코드 (항상 "all") ``` 3. **디렉토리 생성 (Line 54-77)** ``` 생성되는 디렉토리 구조: /about/{ep_nick}/ /about/{ep_nick}/all/ /mcb/{ep_nick}/ /mcb/{ep_nick}/all/ /publishing/{ep_nick}/author /publishing/{ep_nick}/book /publishing/{ep_nick}/editor /shop/{ep_nick}/all/ /doc/{ep_nick}/all/ /book/{ep_nick}/all/ /main/{ep_nick}/ /main/{ep_nick}/all/ /main/{ep_nick}/all/_board/ /main/{ep_nick}/all/_images/ ``` 총 **18개 디렉토리** 4. **인덱스 파일 생성 (Line 79-133)** 각 모듈별 index.html 생성 (about, mcb, publishing, shop, doc, book, main) - 리다이렉트 스크립트 포함 - ep_code와 gp_code를 URL 파라미터로 포함 5. **데이터베이스 레코드 삽입 (Line 135-194)** ``` - iw_enterprise: 기업 정보 (1줄) - iw_member: 관리자 계정 (1줄) ``` 6. **디자인 초기화 (Line 196-228)** ```php - 로고 이미지 복사 (_dummy/logo.png) - Favicon 복사 (_dummy/favicon.png) - iw_setting 테이블에 기본 설정 저장 ``` 7. **회원 등급 설정 (Line 230-241)** - 0~9 레벨까지 10개 등급 생성 8. **프로필 페이지 (Line 243-264)** - 약속페이지(about_data) 기본 콘텐츠 생성 - 프로필 이미지 복사 9. **게시판 설정 (Line 266-356)** - 기본 카테고리 생성 - 3가지 기본 카테고리 생성: 의정활동, 언론보도, 포토갤러리 10. **메뉴 및 레이아웃 (Line 360-)** - 메인 메뉴 생성 - 프로필 메뉴 생성 - 게시판 메뉴 생성 (3개) ### 1.2 현재 구조의 핵심 특징 #### 멀티테넌트 격리 메커니즘 ``` 테넌트 식별자: ep_code + gp_code - ep_code: 기업/테넌트별 고유 코드 - gp_code: 그룹 코드 (현재는 모두 "all") 데이터베이스: - 모든 테이블이 ep_code 필터로 데이터 분리 - 예: WHERE ep_code = '{$ep_code}' AND gp_code = '{$gp_code}' 파일 시스템: - /main/{ep_nick}/all/ 디렉토리에 테넌트별 콘텐츠 저장 - 각 테넌트는 자신의 디렉토리만 접근 가능 ``` #### 디렉토리 구조의 확장성 ``` wizard 위자드 시스템에서도 같은 구조: /about, /mcb, /publishing, /shop, /doc, /book, /main ``` --- ## 2️⃣ 업체별 저장 데이터 확인 ### 2.1 데이터베이스 테이블 분석 #### config.php에서 정의된 테이블 (총 49개 이상) ``` 핵심 테이블 (항상 복사 필요): ═══════════════════════════════════════════════════════════ 1. iw_setting (디자인/설정) - st_title, st_content: 제목, 설명 - st_top_img, st_favicon: 로고, 파비콘 - st_color, st_background: 색상, 배경 - st_*: 각 모듈명 커스터마이징 필터: WHERE ep_code = '{ep_code}' AND gp_code = 'all' 2. iw_seo (SEO 설정) - seo_name, seo_job, seo_bio: 프로필 정보 - seo_profile_image: 프로필 이미지 - seo_keywords: 검색 키워드 필터: WHERE ep_code = '{ep_code}' AND gp_code = 'all' 3. iw_enterprise (기업 정보) - **상황에 따라 대체** - ep_code, ep_nick, ep_corporate: 기업 고유 정보 - ep_domain: 서브도메인 - ep_upload, ep_upload_size: 파일 설정 - ep_state_*: 모듈 활성화 상태 - ep_copy_off: 복제 방지 - ep_expiry_date, ep_charge: 라이선스 정보 4. iw_member (회원 정보) - mb_code, mb_mail, mb_password: 로그인 정보 - mb_name, mb_nick: 사용자 정보 - mb_tel, mb_zip_code, mb_address: 연락처 및 주소 필터: WHERE ep_code = '{ep_code}' 5. iw_group_level (회원 등급) - **전체 복사** - gl_name, gl_level: 등급명, 레벨 필터: WHERE ep_code = '{ep_code}' 선택적 테이블 (복제 옵션에 따라): ═══════════════════════════════════════════════════════════ 콘텐츠 테이블: - iw_category (게시판 카테고리) - iw_home_menu (메뉴 설정) - iw_about_data (프로필 페이지) - iw_shop_* (쇼핑 데이터) - 8개 - iw_publishing_* (출판 정보) - 7개 - iw_doc_* (문서 데이터) - 3개 - iw_book_* (전자책 데이터) - 8개 - iw_mcb_* (게시판 데이터) - 2개 - iw_notice (공지사항) - iw_comment (댓글) 결제/통계 테이블: - iw_lgd, iw_lgd_cancel (결제 내역) - iw_order_* (주문 관련) - 2개 - iw_charge (충전 내역) - iw_rank*, iw_access_count (통계) ``` ### 2.2 파일 시스템 데이터 #### 테넌트별 디렉토리 구조 (실제 예시: wizwin) ``` /main/wizwin/ ├── index.html (리다이렉트 스크립트) └── all/ ├── _board/ (게시판 첨부파일 - 복사 대상) │ └── [게시글별 디렉토리]/ │ └── [파일들] └── _images/ (이미지 - 복사 대상) ├── logo.png ├── favicon.png └── [기타 업로드 이미지] /about/wizwin/ ├── index.html └── all/ └── [프로필 페이지 이미지]/ └── [이미지 파일] /mcb/wizwin/ ├── index.html └── all/ └── [카테고리별 디렉토리]/ └── [첨부파일] /publishing/wizwin/ ├── author/ ├── book/ └── editor/ /shop/wizwin/ (쇼핑몰 데이터) /doc/wizwin/ (문서/컨텐츠몰) /book/wizwin/ (전자책) ``` **현재 상태 확인** (로컬): - `wizwin/all/`: 비어있음 (_board, _images만 존재) - `sample/all/`: 비어있음 - `anjungdon/all/`: 비어있음 - `_dummy/`: 템플릿 제공 (logo.png, favicon.png) ### 2.3 설정 파일 #### index.html 리다이렉트 패턴 ```php ``` 각 모듈별로 다른 위치로 리다이렉트: - /about: `about_main.php` - /mcb: `mcb_main.php` - /publishing: `main.php` - /shop: `shop_main.php` - /doc: `doc_main.php` - /book: `book_main.php` - /main: `main.php` --- ## 3️⃣ 복사해야 할 요소들 목록화 ### 3.1 필수 복사 요소 (모든 템플릿 복사에 필수) ``` 1. 데이터베이스 레코드 ┌─ 필수 복사 테이블 ├─ iw_setting (1개 레코드 per ep_code) ├─ iw_seo (1개 레코드 per ep_code) - 없을 수도 있음 ├─ iw_group_level (10개 레코드) ├─ iw_category (게시판 카테고리) - N개 ├─ iw_home_menu (메뉴) - N개 ├─ iw_about_data (프로필 페이지) └─ iw_member (기타 회원들) ┌─ 수정 생성 테이블 ├─ iw_enterprise (새 ep_code로 생성) └─ iw_member (슈퍼 관리자 계정만, 필요시) 2. 파일 시스템 ├─ /main/{new_nick}/all/_images/* (모든 이미지) ├─ /main/{new_nick}/all/_board/* (게시판 파일) ├─ /about/{new_nick}/all/* (프로필 이미지) ├─ /mcb/{new_nick}/all/* (게시판 첨부) ├─ /publishing/{new_nick}/* (출판 데이터) ├─ /shop/{new_nick}/all/* (쇼핑 이미지/파일) ├─ /doc/{new_nick}/all/* (문서) └─ /book/{new_nick}/all/* (전자책) 3. 디렉토리 구조 └─ 기존의 18개 디렉토리 생성 (enterprise_write_ok.php와 동일) ``` ### 3.2 선택적 복사 요소 (복제 옵션) ``` [ ] 디자인/레이아웃 └─ iw_setting 복사 └─ _images 파일 복사 [ ] 콘텐츠 ├─ iw_category (카테고리) ├─ iw_home_menu (메뉴) └─ iw_about_data (프로필 페이지) [ ] 상품 정보 ├─ iw_shop_* (모든 쇼핑 관련 테이블) └─ /shop/{nick}/all/* (파일) [ ] 주문 내역 ├─ iw_order_* ├─ iw_lgd, iw_lgd_cancel └─ iw_charge [ ] 회원 정보 ├─ iw_member (모든 회원 - 주의!) └─ iw_group_member, iw_group_invite [ ] 게시판 ├─ iw_category (게시판 카테고리) ├─ iw_comment (댓글) └─ /mcb/{nick}/all/* (첨부파일) [ ] 파일/이미지 ├─ /main/{nick}/all/_images/* ├─ /main/{nick}/all/_board/* └─ 기타 모든 업로드 파일 ``` --- ## 4️⃣ 템플릿 복사 프로세스 설계 ### 4.1 전체 프로세스 흐름도 ``` ┌─────────────────────────────────────────────────────────┐ │ 템플릿 복사 프로세스 (권장 구조) │ └─────────────────────────────────────────────────────────┘ STEP 1: 원본 선택 및 대상 입력 ├─ 복사할 원본 업체 선택 (드롭다운) ├─ 새 업체 정보 입력 (ep_nick, ep_corporate, ep_domain 등) └─ 복제 옵션 선택 (체크박스) ↓ STEP 2: 유효성 검증 ├─ 원본 업체 존재 확인 ├─ 대상 ep_nick 중복 확인 ├─ 대상 ep_domain 중복 확인 └─ 새 관리자 이메일 중복 확인 ↓ STEP 3: 트랜잭션 시작 └─ 모든 작업의 원자성(atomicity) 보장 ↓ STEP 4: 디렉토리 생성 ├─ 18개 디렉토리 생성 └─ 권한 설정 (0707) ↓ STEP 5: 인덱스 파일 생성 └─ 7개 모듈별 index.html (새 ep_code로 리다이렉트) ↓ STEP 6: 데이터베이스 처리 ├─ 새 ep_code, mb_code, gp_code 생성 ├─ iw_enterprise 생성 (새 정보) ├─ iw_member 생성 (새 관리자 계정) │ ├─ [선택] 필드별 복사: │ ├─ iw_setting (복제 옵션이 "디자인" 포함 시) │ ├─ iw_seo (복제 옵션이 "디자인" 포함 시) │ ├─ iw_category (복제 옵션이 "콘텐츠" 포함 시) │ ├─ iw_home_menu (복제 옵션이 "콘텐츠" 포함 시) │ ├─ iw_about_data (복제 옵션이 "콘텐츠" 포함 시) │ ├─ iw_group_level (항상 복사 또는 새로 생성) │ └─ [기타 테이블들...] │ └─ 기본 설정 생성 (enterprise_write_ok.php와 동일) ↓ STEP 7: 파일 복사 ├─ /main/{new_nick}/all/_images/* ← /main/{old_nick}/all/_images/* ├─ /main/{new_nick}/all/_board/* ← /main/{old_nick}/all/_board/* ├─ /about/{new_nick}/all/* ← /about/{old_nick}/all/* ├─ /mcb/{new_nick}/all/* ← /mcb/{old_nick}/all/* ├─ /publishing/{new_nick}/* ← /publishing/{old_nick}/* ├─ /shop/{new_nick}/all/* ← /shop/{old_nick}/all/* (선택) ├─ /doc/{new_nick}/all/* ← /doc/{old_nick}/all/* (선택) └─ /book/{new_nick}/all/* ← /book/{old_nick}/all/* (선택) ↓ STEP 8: 트랜잭션 커밋 또는 롤백 ├─ 성공 → 완료 페이지 └─ 실패 → 롤백 및 에러 메시지 ``` ### 4.2 상세 구현 단계 #### 4.2.1 원본 선택 인터페이스 ``` [원본 선택] ┌─────────────────────────────┐ │ ◉ Dlive (2024-01-15) │ ← 선택 라디오 │ ○ Sample (2023-06-20) │ │ ○ Anjungdon (2023-03-10) │ │ ○ Wizwin (2024-02-01) │ └─────────────────────────────┘ 원본 정보: - 업체명: D-Live Corporation - 도메인: dlive - 카테고리 개수: 5 - 회원 수: 123 - 저장소 크기: 45.2 MB ``` #### 4.2.2 새 업체 정보 입력 ``` [새 업체 정보] 포워딩ID (ep_nick): dlive1 ← 기존과 다른 ID 업체명 (ep_corporate): D-Live Clone 도메인 (ep_domain): dlive1 사업자등록번호: (선택) [관리자 정보] 이메일: admin@dlive1.com 이름: 관리자 닉네임: admin_clone 전화번호: 010-XXXX-XXXX [진팩 CMS 사용] 만료일자: 2025-12-31 사용료/년: 1,000,000 원 ``` #### 4.2.3 복제 옵션 ``` [복제 옵션] ← 최소 1개 필수 선택 [v] 디자인/레이아웃 → iw_setting, iw_seo, _images 복사 [v] 콘텐츠 → iw_category, iw_home_menu, iw_about_data 복사 [ ] 상품 정보 → iw_shop_* 테이블, /shop 디렉토리 복사 [ ] 주문 내역 → iw_order_*, iw_lgd 테이블 복사 [v] 회원 정보 → iw_member, iw_group_member 복사 ⚠️ 주의: 새 관리자 계정은 별도 생성됨 [v] 게시판 → iw_category 카테고리, _board 파일 복사 [v] 파일/이미지 → 모든 업로드 파일 복사 ``` #### 4.2.4 최종 확인 화면 ``` [최종 확인] 원본 업체: Dlive (dlive) 새 업체: D-Live Clone (dlive1) 복제 옵션: ✓ 디자인/레이아웃 ✓ 콘텐츠 ✗ 상품 정보 ✗ 주문 내역 ✓ 회원 정보 ✓ 게시판 ✓ 파일/이미지 예상 시간: 3-5분 예상 저장소 크기: 45.2 MB [확인] [취소] ``` --- ## 5️⃣ 기술적 구현 방안 ### 5.1 PHP 5.3 호환 구현 #### 5.1.1 주요 함수 설계 ```php /** * 업체 템플릿 복사 메인 함수 * * @param string $source_ep_code 원본 ep_code * @param string $new_ep_nick 새 포워딩 ID * @param string $new_ep_corporate 새 업체명 * @param string $new_ep_domain 새 도메인 * @param string $mb_mail 새 관리자 이메일 * @param string $mb_password 새 관리자 비밀번호 * @param array $copy_options 복제 옵션 * @return array 결과 (success: bool, message: string, new_ep_code: string) */ function clone_enterprise($source_ep_code, $new_ep_nick, $new_ep_corporate, $new_ep_domain, $mb_mail, $mb_password, $copy_options = array()) { global $iw; $result = array('success' => false, 'message' => ''); // STEP 1: 검증 if (!validate_clone_input($source_ep_code, $new_ep_nick, $new_ep_domain, $mb_mail)) { $result['message'] = '검증 실패'; return $result; } // STEP 2: 원본 데이터 조회 $source_data = get_enterprise_data($source_ep_code); if (!$source_data) { $result['message'] = '원본 업체를 찾을 수 없습니다'; return $result; } // STEP 3: 트랜잭션 시작 @mysql_query("START TRANSACTION"); try { // STEP 4: 새 ID 생성 $new_ep_code = "ep".uniqid(rand()); $new_mb_code = "mb".uniqid(rand()); $new_gp_code = "all"; // STEP 5: 디렉토리 생성 if (!create_enterprise_directories($new_ep_nick)) { throw new Exception("디렉토리 생성 실패"); } // STEP 6: 인덱스 파일 생성 if (!create_index_files($new_ep_nick, $new_ep_code, $new_gp_code)) { throw new Exception("인덱스 파일 생성 실패"); } // STEP 7: DB 레코드 생성 if (!create_enterprise_record($new_ep_code, $new_mb_code, $new_ep_nick, $new_ep_corporate, $new_ep_domain, $mb_mail, $mb_password)) { throw new Exception("기업 레코드 생성 실패"); } // STEP 8: 복제 옵션별 DB 복사 $clone_result = clone_database_records($source_ep_code, $new_ep_code, $new_mb_code, $new_gp_code, $copy_options); if (!$clone_result['success']) { throw new Exception($clone_result['message']); } // STEP 9: 파일 복사 $file_result = clone_files($source_ep_code, $new_ep_nick, $source_data['ep_nick'], $copy_options); if (!$file_result['success']) { throw new Exception($file_result['message']); } // STEP 10: 커밋 @mysql_query("COMMIT"); $result['success'] = true; $result['message'] = '업체 복제 완료'; $result['new_ep_code'] = $new_ep_code; } catch (Exception $e) { @mysql_query("ROLLBACK"); $result['message'] = '복제 실패: ' . $e->getMessage(); } return $result; } ``` #### 5.1.2 검증 함수 ```php function validate_clone_input($source_ep_code, $new_ep_nick, $new_ep_domain, $mb_mail) { global $iw; // 원본 존재 확인 $sql = "SELECT COUNT(*) as cnt FROM {$iw['enterprise_table']} WHERE ep_code = '$source_ep_code'"; $row = sql_fetch($sql); if (!$row['cnt']) { return false; // 원본 업체 없음 } // 새 ep_nick 중복 확인 $sql = "SELECT COUNT(*) as cnt FROM {$iw['enterprise_table']} WHERE ep_nick = '$new_ep_nick'"; $row = sql_fetch($sql); if ($row['cnt']) { return false; // 이미 존재 } // 새 ep_domain 중복 확인 if ($new_ep_domain) { $sql = "SELECT COUNT(*) as cnt FROM {$iw['enterprise_table']} WHERE ep_domain = '$new_ep_domain'"; $row = sql_fetch($sql); if ($row['cnt']) { return false; } } // 이메일 중복 확인 $sql = "SELECT COUNT(*) as cnt FROM {$iw['member_table']} WHERE mb_mail = '$mb_mail'"; $row = sql_fetch($sql); if ($row['cnt']) { return false; } return true; } ``` #### 5.1.3 데이터베이스 복사 함수 ```php function clone_database_records($source_ep_code, $new_ep_code, $new_mb_code, $new_gp_code, $copy_options) { global $iw; $result = array('success' => true, 'message' => ''); $options = array_keys(array_flip($copy_options)); // 배열 정규화 // 1. 기본 설정 (항상 복사) clone_table_records('iw_setting', array('ep_code' => $new_ep_code), array('ep_code' => $source_ep_code, 'gp_code' => 'all') ); // 2. SEO 설정 (선택) if (in_array('design', $options)) { clone_table_records('iw_seo', array('ep_code' => $new_ep_code), array('ep_code' => $source_ep_code) ); } // 3. 회원 등급 (항상 복사) clone_table_records('iw_group_level', array('ep_code' => $new_ep_code), array('ep_code' => $source_ep_code) ); // 4. 카테고리 및 메뉴 (선택) if (in_array('content', $options)) { clone_table_records('iw_category', array('ep_code' => $new_ep_code), array('ep_code' => $source_ep_code) ); clone_table_records('iw_home_menu', array('ep_code' => $new_ep_code), array('ep_code' => $source_ep_code) ); clone_table_records('iw_about_data', array('ep_code' => $new_ep_code), array('ep_code' => $source_ep_code) ); } // 5. 게시판 및 댓글 (선택) if (in_array('board', $options)) { clone_table_records('iw_comment', array('ep_code' => $new_ep_code), array('ep_code' => $source_ep_code) ); } // [기타 선택 옵션...] return $result; } /** * 범용 테이블 복사 함수 * * @param string $table_name 테이블명 * @param array $replace_fields ep_code 등 대체할 필드 * @param array $where_conditions WHERE 조건 */ function clone_table_records($table_name, $replace_fields, $where_conditions) { global $iw; // 원본 데이터 조회 $where_sql = build_where_clause($where_conditions); $sql = "SELECT * FROM $table_name $where_sql"; $result = @mysql_query($sql); if (!$result) { return false; } // 각 행 복사 while ($row = @mysql_fetch_assoc($result)) { // 대체할 필드 업데이트 foreach ($replace_fields as $field => $value) { $row[$field] = $value; } // INSERT 쿼리 생성 $fields = array_keys($row); $values = array_values($row); $fields_sql = implode(', ', $fields); $values_sql = implode("', '", array_map('mysql_real_escape_string', $values)); $insert_sql = "INSERT INTO $table_name ($fields_sql) VALUES ('$values_sql')"; @mysql_query($insert_sql); } return true; } function build_where_clause($conditions) { if (empty($conditions)) { return ''; } $where_parts = array(); foreach ($conditions as $field => $value) { $where_parts[] = "$field = '$value'"; } return 'WHERE ' . implode(' AND ', $where_parts); } ``` #### 5.1.4 파일 복사 함수 ```php function clone_files($source_ep_code, $new_ep_nick, $source_ep_nick, $copy_options) { global $iw; $result = array('success' => true, 'message' => ''); $options = array_keys(array_flip($copy_options)); // 복사할 디렉토리 맵 $copy_map = array(); // 이미지는 항상 복사 if (in_array('files', $options) || in_array('design', $options)) { $copy_map[] = array( 'source' => "{$iw['path']}/main/{$source_ep_nick}/all/_images", 'target' => "{$iw['path']}/main/{$new_ep_nick}/all/_images" ); } // 게시판 첨부 if (in_array('files', $options) || in_array('board', $options)) { $copy_map[] = array( 'source' => "{$iw['path']}/main/{$source_ep_nick}/all/_board", 'target' => "{$iw['path']}/main/{$new_ep_nick}/all/_board" ); } // 프로필 이미지 if (in_array('files', $options) || in_array('content', $options)) { $copy_map[] = array( 'source' => "{$iw['path']}/about/{$source_ep_nick}/all", 'target' => "{$iw['path']}/about/{$new_ep_nick}/all" ); } // 선택적: 상품 파일 if (in_array('products', $options)) { $copy_map[] = array( 'source' => "{$iw['path']}/shop/{$source_ep_nick}/all", 'target' => "{$iw['path']}/shop/{$new_ep_nick}/all" ); } // [기타 디렉토리...] // 파일 복사 실행 foreach ($copy_map as $map) { if (!copy_directory($map['source'], $map['target'])) { $result['success'] = false; $result['message'] = "파일 복사 실패: {$map['source']}"; return $result; } } return $result; } /** * 디렉토리 재귀 복사 */ function copy_directory($source, $target) { if (!is_dir($source)) { return true; // 소스 없으면 무시 } if (!@mkdir($target, 0707, true)) { return false; } $dir = @opendir($source); if (!$dir) { return false; } while (false !== ($file = @readdir($dir))) { if ($file != '.' && $file != '..') { $source_file = $source . '/' . $file; $target_file = $target . '/' . $file; if (is_dir($source_file)) { if (!copy_directory($source_file, $target_file)) { @closedir($dir); return false; } } else { if (!@copy($source_file, $target_file)) { @closedir($dir); return false; } @chmod($target_file, 0606); } } } @closedir($dir); return true; } ``` ### 5.2 SQL 복사 쿼리 패턴 #### 5.2.1 설정 복사 ```sql -- 기본 설정 복사 INSERT INTO iw_setting (ep_code, gp_code, st_title, st_content, st_top_img, st_favicon, st_color, st_mcb_name, st_datetime, st_display, ...) SELECT 'new_ep_code' as ep_code, 'all' as gp_code, st_title, st_content, st_top_img, st_favicon, st_color, st_mcb_name, NOW(), st_display, ... FROM iw_setting WHERE ep_code = 'source_ep_code' AND gp_code = 'all'; ``` #### 5.2.2 카테고리 복사 ```sql -- 게시판 카테고리 복사 (cg_code 재생성 필수!) INSERT INTO iw_category (cg_name, cg_code, ep_code, gp_code, state_sort, ...) SELECT cg_name, CONCAT('cg', MD5(RAND())) as cg_code, 'new_ep_code' as ep_code, 'all' as gp_code, state_sort, ... FROM iw_category WHERE ep_code = 'source_ep_code' AND state_sort = 'mcb'; ``` #### 5.2.3 ID 재생성이 필요한 테이블 목록 ``` 필드 이름 패턴으로 식별: - {module}_code (cg_code, ad_code, hm_code, etc) - {module}_no (자동 증가 ID) 각 테이블별: - iw_category: cg_code 재생성 (INT 자동 증가) - iw_home_menu: hm_code 재생성 (문자열 고유키) - iw_about_data: ad_code 재생성 - iw_shop_*: 고유 번호 재생성 - [기타...] ⚠️ 외래키 관계 주의: - iw_home_menu → iw_category (cg_code) - iw_home_menu → iw_about_data (cg_code) ``` ### 5.3 트랜잭션 처리 #### 5.3.1 트랜잭션 구조 ```php // MySQL InnoDB 트랜잭션 (MyISAM 미지원) @mysql_query("START TRANSACTION"); try { // 모든 작업 수행 // 체크포인트 if ($error_occurred) { throw new Exception("오류 발생"); } // 커밋 @mysql_query("COMMIT"); } catch (Exception $e) { // 모두 롤백 @mysql_query("ROLLBACK"); // 파일도 롤백 (롤백 불가 → 수동 삭제) cleanup_directories($new_ep_nick); } ``` #### 5.3.2 롤백 시 정리 ```php function cleanup_on_failure($new_ep_nick) { global $iw; // 생성된 디렉토리 삭제 $cleanup_dirs = array( "{$iw['path']}/main/{$new_ep_nick}", "{$iw['path']}/about/{$new_ep_nick}", "{$iw['path']}/mcb/{$new_ep_nick}", // ... 모든 디렉토리 ); foreach ($cleanup_dirs as $dir) { remove_directory($dir); } } function remove_directory($dir) { if (!is_dir($dir)) { return true; } $files = @scandir($dir); if (!$files) { return false; } foreach ($files as $file) { if ($file != '.' && $file != '..') { $path = $dir . '/' . $file; if (is_dir($path)) { remove_directory($path); } else { @unlink($path); } } } return @rmdir($dir); } ``` ### 5.4 보안 고려사항 #### 5.4.1 입력 검증 ```php function validate_clone_input_secure($source_ep_code, $new_ep_nick, $new_ep_domain, $mb_mail, $mb_password) { // 1. SQL Injection 방지 (자동으로 addslashes 적용됨) $source_ep_code = mysql_real_escape_string($source_ep_code); $new_ep_nick = mysql_real_escape_string($new_ep_nick); // 2. 포맷 검증 if (!preg_match('/^[a-z0-9]{3,20}$/', $new_ep_nick)) { return false; // 포워딩ID 형식 오류 } if ($new_ep_domain && !preg_match('/^[a-z0-9\-]{3,20}$/', $new_ep_domain)) { return false; } // 3. 이메일 검증 if (!filter_var($mb_mail, FILTER_VALIDATE_EMAIL)) { return false; } // 4. 비밀번호 최소 길이 if (strlen($mb_password) < 4) { return false; } // 5. 권한 검증 (슈퍼 관리자만) if ($iw['level'] != 'super') { return false; } return true; } ``` #### 5.4.2 권한 검증 ```php // 파일 시작 부분 if (!defined("_INFOWAY_")) exit; // 직접 접근 방지 include_once("_common.php"); include_once("member_super.php"); // 슈퍼 관리자만 허용 if ($iw['level'] != 'super') { alert("잘못된 접근입니다!", ""); } ``` #### 5.4.3 로깅 ```php function log_clone_action($action, $details, $success = true) { global $iw; $log_file = "{$iw['path']}/../logs/clone_" . date("Y-m-d") . ".log"; $log_msg = sprintf("[%s] %s | %s | %s | %s\n", date("Y-m-d H:i:s"), $success ? "SUCCESS" : "FAILURE", $_SESSION['mb_mail'] ?? 'UNKNOWN', $action, json_encode($details) ); @file_put_contents($log_file, $log_msg, FILE_APPEND); } ``` --- ## 6️⃣ 기술적 구현 방안 (계속) ### 6.1 데이터베이스 ID 매핑 전략 복사 중 발생하는 문제: **새로운 ep_code에 맞게 외래키 관계 업데이트** ``` 예시 시나리오: 원본 데이터: - ep_code: ep801870536594c8ad1cd3b5 - iw_category 2개 (cg_code_1, cg_code_2) - iw_home_menu 4개 (cg_code_1, cg_code_1, cg_code_2, "") 새 데이터로 복사 시: 1. 새 cg_code 생성 (cg_new_1, cg_new_2) 2. cg_code_1 → cg_new_1 매핑 3. cg_code_2 → cg_new_2 매핑 4. iw_home_menu에서 외래키 업데이트 ``` **구현**: ```php function clone_database_records_with_mapping($source_ep_code, $new_ep_code, ...) { // ID 매핑 배열 (원본 ID → 새 ID) $code_mapping = array(); // 1. 카테고리 복사 및 ID 매핑 $sql = "SELECT cg_code FROM iw_category WHERE ep_code = '$source_ep_code'"; $result = @mysql_query($sql); while ($row = @mysql_fetch_assoc($result)) { $new_cg_code = "cg".uniqid(rand()); $code_mapping['cg_' . $row['cg_code']] = $new_cg_code; // 복사 및 새 cg_code로 INSERT } // 2. 메뉴 복사 시 외래키 업데이트 $sql = "SELECT hm_code, cg_code FROM iw_home_menu WHERE ep_code = '$source_ep_code'"; $result = @mysql_query($sql); while ($row = @mysql_fetch_assoc($result)) { $new_cg_code = $code_mapping['cg_' . $row['cg_code']] ?? ''; // INSERT with new cg_code } } ``` ### 6.2 성능 최적화 #### 6.2.1 대용량 파일 복사 ```php // 진행률 표시 function clone_files_with_progress($source_ep_code, $new_ep_nick, ...) { $files_to_copy = scan_files($source_path); $total_files = count($files_to_copy); foreach ($files_to_copy as $index => $file) { // 복사 copy_file($file); // AJAX로 진행률 전송 $progress = round(($index + 1) / $total_files * 100); echo json_encode(array('progress' => $progress, 'current_file' => $file)); flush(); } } ``` #### 6.2.2 배치 INSERT ```php function batch_insert($table_name, $records, $batch_size = 100) { $batches = array_chunk($records, $batch_size); foreach ($batches as $batch) { $values = array(); foreach ($batch as $record) { $values[] = "('value1', 'value2', ...)"; } $sql = "INSERT INTO $table_name VALUES " . implode(',', $values); @mysql_query($sql); } } ``` --- ## 7️⃣ 리스크 분석 및 해결책 ### 7.1 식별된 리스크 | # | 리스크 | 심각도 | 해결책 | |---|--------|--------|--------| | 1 | 트랜잭션 중 디스크 부족 | **높음** | 사전 용량 확인, 타임아웃 설정 | | 2 | 외래키 관계 오류 | **높음** | ID 매핑 테이블, 검증 쿼리 | | 3 | 파일 권한 오류 | **중간** | chmod 0707 설정, 재시도 로직 | | 4 | 동시 복제 요청 | **중간** | 뮤텍스 또는 세마포어 | | 5 | 매우 큰 파일 셋 | **중간** | 배경 작업화, 진행률 표시 | | 6 | 권한 부족 | **낮음** | 사전 권한 검증 | | 7 | 데이터 무결성 | **높음** | 트랜잭션, 체크섬 검증 | ### 7.2 구현 전 체크리스트 ``` [ ] 백업 전략 수립 - 원본 데이터 백업 - 트랜잭션 실패 시 롤백 계획 [ ] 데이터베이스 용량 확인 - 저장소 크기 예측 - 최소 여유 공간 확인 [ ] 파일 시스템 권한 확인 - 디렉토리 쓰기 권한 - 실행 권한 [ ] MySQL 버전 확인 - InnoDB 엔진 지원 확인 - 트랜잭션 지원 확인 [ ] PHP 5.3 호환성 테스트 - 문법 검증 - 함수 호환성 검증 [ ] 로깅 시스템 구축 - 에러 로그 - 감사 로그 [ ] 모니터링 설정 - 대기 시간 측정 - 실패율 모니터링 ``` --- ## 8️⃣ 실제 사용 시나리오 ### 시나리오 1: Dlive → Dlive1 복제 ``` [관리자 입력] 원본: Dlive (ep801870536594c8ad1cd3b5) 새 정보: - ep_nick: dlive1 - ep_corporate: D-Live Clone - ep_domain: dlive1 - 관리자 이메일: admin1@example.com - 관리자 비밀번호: **** 복제 옵션: ✓ 디자인/레이아웃 ✓ 콘텐츠 ✓ 게시판 ✓ 파일/이미지 ✗ 상품 ✗ 주문 [처리 단계] 1. 검증: OK 2. 디렉토리 생성: 18개 ✓ 3. DB 레코드 생성: ✓ 4. 데이터 복사: - iw_setting: 1개 ✓ - iw_seo: 1개 ✓ - iw_category: 5개 ✓ - iw_home_menu: 8개 (외래키 업데이트) ✓ - iw_about_data: 2개 ✓ - iw_comment: 15개 ✓ 5. 파일 복사: - _images: 23개 파일 (8.4 MB) ✓ - _board: 45개 파일 (12.3 MB) ✓ - /about: 5개 파일 (2.1 MB) ✓ - /mcb: 0개 파일 ✓ [결과] ✓ 성공 - 새 ep_code: ep7a2b3c4d5e6f - 새 사이트 URL: http://localhost:8000/main/dlive1/ - 총 소요 시간: 2분 34초 - 복제된 데이터량: 22.8 MB ``` ### 시나리오 2: Sample → Test (콘텐츠만 복제) ``` [관리자 입력] 원본: Sample 복제 옵션: ✓ 콘텐츠 (게시판, 메뉴, 프로필) ✓ 파일/이미지 ✗ 디자인 (기본 설정 사용) ✗ 상품, 주문, 회원 [처리] - iw_category: 3개 - iw_home_menu: 4개 - 파일: 123개 (5.2 MB) [결과] ✓ 성공 ``` --- ## 9️⃣ 개발 일정 및 우선순위 ### Phase 1: 핵심 기능 (1주) - [ ] 클론 함수 기본 구조 - [ ] DB 레코드 복사 - [ ] 디렉토리 및 파일 복사 - [ ] 트랜잭션 처리 - [ ] 기본 UI (버튼 → 팝업) ### Phase 2: 고급 기능 (1주) - [ ] 복제 옵션 선택 - [ ] ID 매핑 로직 - [ ] 진행률 표시 - [ ] 에러 처리 - [ ] 롤백 로직 ### Phase 3: 안정화 (1주) - [ ] 보안 감사 - [ ] 성능 최적화 - [ ] 로깅 시스템 - [ ] 테스트 케이스 - [ ] 문서화 --- ## 🔟 결론 및 권장사항 ### 10.1 기술적 타당성 ✅ **매우 높은 가능성 (95% 이상)** **이유**: 1. 멀티테넌트 아키텍처가 잘 구성됨 - ep_code/gp_code 기반 데이터 분리 - 명확한 디렉토리 구조 2. 기존 enterprise_write_ok.php에서 많은 로직 재사용 가능 - 디렉토리 생성 로직 - 기본 DB 레코드 생성 - 초기 설정 값 3. PHP 5.3 호환성 달성 가능 - 기존 코드가 이미 PHP 5.3 기반 - 배열, 함수, SQL 모두 호환 ### 10.2 구현 전 필수 작업 1. **데이터베이스 분석** - 모든 테이블의 ep_code 필터 확인 - 외래키 관계 매핑 - ID 자동 증가 필드 확인 2. **파일 시스템 분석** - 모든 업로드 경로 파악 - 심볼릭 링크 확인 - 권한 설정 확인 3. **테스트 환경 구성** - 로컬 테스트 환경 - 스테이징 환경 - 프로덕션 백업 ### 10.3 최종 권장사항 **"매우 추천함 (9/10)"** ``` 장점: + 기술적으로 완전히 가능 + 기존 코드 기반으로 빠른 구현 + 명확한 아키텍처 + 예측 가능한 복잡도 단점: - 초기 데이터 매핑 작업 필요 - 외래키 관계 처리 복잡 - 성능 모니터링 필요 - 테스트 케이스 많음 권장: 1. 기존 enterprise_write_ok.php 기반 설계 2. 단계별 구현 (핵심 → 고급 → 안정화) 3. 철저한 테스트 및 로깅 4. 운영 환경 배포 전 스테이징 검증 ``` --- **작성자**: Claude Code **분석 대상**: DLIVE-Zinpack-AI 멀티테넌트 시스템 **분석 깊이**: Very Thorough **최종 평가**: 매우 높은 가능성 (95%+)