회원가입 | 고객센터 |
DESIGNONEX
디자인원엑스
About
Service
Q&A
PR리그
자유게시판N
갤러리
포인트게임
공지사항N
통계
로그인 회원가입
고객센터
4.2 관리자 시스템 구조

관리자 라우팅

A Administrator
2026.04.21 01:08(수정됨) 103 0

1. 관리자 라우팅 개요

DXCMS 관리자 시스템은 단일 레이아웃 파일(admin/index.php)과 모듈별 콘텐츠 파일(admin/{action}/index.php)로 구성됩니다. 모든 관리자 URL은 /admin/{action}/{sub} 패턴을 따르며, 일반 사용자는 접근 자체가 차단됩니다.


1.1 관리자 라우팅 전체 흐름

HTTP Request: GET /admin/boards/5
    │
    ▼ index.php — 단일 진입점
[STEP 1~4] 인프라 초기화 (DB•세션•Auth)
    │
    ▼
[Router::resolve()]
    $first = 'admin'  →  TYPE_ADMIN
    current['action'] = 'boards'     ← $second
    current['sub']    = '5'          ← $third
    │
    ▼
[Dispatcher::dispatchAdmin()]
    ① Auth::isAdmin() 확인
       → false: dx_redirect(/auth/login?redirect=...)
       → true:  계속 진행
    ② $GLOBALS['dx_admin_action'] = 'boards'
       $GLOBALS['dx_admin_sub']    = '5'
    ③ require admin/index.php  ← 레이아웃 파일
    │
    ▼
[admin/index.php — 레이아웃]
    $adminAction = 'boards'   (from $GLOBALS)
    $adminSub    = '5'        (from $GLOBALS)
    $actionFile  = DX_ROOT . '/admin/boards/index.php'
    // 파일 없으면 → admin/dashboard/index.php 폴백
    │
    ▼ HTML 렌더링
    [사이드바 + 상단바 출력]
    dx_run_hook('dx_admin_top', ['action'=>'boards'])
    require admin/boards/index.php  ← 모듈 콘텐츠
    dx_run_hook('dx_admin_bottom', ['action'=>'boards'])
    │
    ▼ HTTP Response (HTML)


2. 라우터(Router) — 관리자 URL 파싱

Router::parse()는 URL 세그먼트의 첫 번째 값이 'admin'인지 확인하고 TYPE_ADMIN으로 분류합니다. 두 번째 세그먼트가 action, 세 번째 세그먼트가 sub가 됩니다.


2.1 URL 세그먼트 파싱

// core/router/Router.php — parse() 내 관리자 처리
if ($first === 'admin') {
    $this->current['type']   = self::TYPE_ADMIN;  // 'admin'
    $this->current['slug']   = 'admin';
    $this->current['action'] = $second ? $second : 'dashboard';
    $this->current['sub']    = $third;
    return;
}

// URL 세그먼트 예시
// /admin           → action='dashboard', sub=''
// /admin/boards    → action='boards',    sub=''
// /admin/boards/5  → action='boards',    sub='5'
// /admin/members/add → action='members', sub='add'
// /admin/themes/clean → action='themes', sub='clean'


2.2 URL 패턴 — 액션/서브 조합

URL action sub 처리 내용
/admin dashboard 대시보드 (action 없으면 dashboard 기본값)
/admin/boards boards 게시판 목록
/admin/boards/5 boards 5 게시판 ID=5 편집 폼 표시 (adminSub='5')
/admin/members members 회원 목록
/admin/members/add members add 회원 추가 폼 표시 (adminSub='add')
/admin/members/123 members 123 회원 ID=123 편집
/admin/members/123?tab=monitor members 123 회원 모니터링 탭
/admin/pages pages 페이지 목록 (GET: ?new=1 새페이지)
/admin/categories categories 카테고리 관리 (GET: ?board_id=X 필터)
/admin/menus menus 메뉴 관리 (GET: ?site=domain.com 멀티사이트)
/admin/menus/10 menus 10 메뉴 ID=10 편집
/admin/themes themes 테마 목록
/admin/themes/clean themes clean 'clean' 테마 옵션 편집
/admin/settings settings 사이트 설정
/admin/plugins plugins 플러그인 관리
/admin/market market DX 마켓
/admin/socket socket 실시간 소켓 관리
/admin/statistics statistics 방문자 통계


3. Dispatcher — 관리자 디스패치 처리

Dispatcher::dispatchAdmin()은 관리자 라우팅의 핵심입니다. 인증 확인 → 전역 변수 주입 → 레이아웃 파일 실행의 세 단계로 처리됩니다.


3.1 dispatchAdmin() 코드 분석

// core/router/Dispatcher.php
private function dispatchAdmin()
{
    $auth = Auth::getInstance();

    // ① 관리자 권한 확인 — role='admin' 필수
    if (!$auth->isAdmin()) {
        dx_redirect(
            dx_base_url('auth/login')
            . '?redirect=' . urlencode(dx_current_url())
        );
        return;
    }

    // ② 전역 변수 주입 — 레이아웃/모듈이 참조
    $GLOBALS['dx_admin_action'] = $this->route['action']
                                  ? $this->route['action']
                                  : 'dashboard';
    $GLOBALS['dx_admin_sub']    = isset($this->route['sub'])
                                  ? $this->route['sub'] : '';

    // ③ 레이아웃 파일 실행
    $adminFile = DX_ROOT . '/admin/index.php';
    if (!file_exists($adminFile)) { $this->dispatch404(); return; }
    require $adminFile;
}


3.2 인증 차단 흐름

비관리자 접근 시 로그인 페이지로 리다이렉트되며, 로그인 완료 후 원래 요청 URL로 돌아옵니다. 이는 ?redirect= 쿼리스트링을 통해 구현됩니다.
 
// 비관리자 접근 시도 흐름

GET /admin/boards
    │
    ▼ Auth::isAdmin() → false
    │
    ▼ dx_redirect(
          '/auth/login?redirect=%2Fadmin%2Fboards'
      )
    │
    ▼ 로그인 성공 → Auth::login()
    │
    ▼ dx_redirect($_GET['redirect'])  → /admin/boards
    │
    ▼ dispatchAdmin() 재실행 → Auth::isAdmin() → true → 정상 접근


4. 관리자 레이아웃 — admin/index.php

admin/index.php는 관리자 UI의 공통 레이아웃을 담당합니다. 사이드바 내비게이션, 상단바, 모듈 콘텐츠 영역, 소켓 연동, 세션 감시 스크립트를 포함합니다.


4.1 레이아웃 구조

// admin/index.php 실행 흐름

$adminAction = $GLOBALS['dx_admin_action'];  // 'boards'
$adminSub    = $GLOBALS['dx_admin_sub'];     // '5'
$actionFile  = DX_ROOT . '/admin/' . $adminAction . '/index.php';

// 파일 없으면 대시보드로 폴백
if (!file_exists($actionFile))
    $actionFile = DX_ROOT . '/admin/dashboard/index.php';

// $currentPath: 사이드바 active 상태 결정에 사용
$currentPath = strtok(dx_request_uri(), '?');  // ?쿼리 제거

// HTML 출력 순서
// 1) <head>: CSS(style.css, dxb-css.js), jQuery, 사이드바 JS
// 2) <aside>: 사이드바 (프로필 + 내비 + 하단)
// 3) <header>: 상단바 (햄버거 + 브레드크럼 + 날짜)
// 4) <main>:
//    dx_run_hook('dx_admin_top', ['action' => $adminAction])
//    require $actionFile  ← 실제 모듈 콘텐츠
//    dx_run_hook('dx_admin_bottom', ['action' => $adminAction])
// 5) <footer>: 버전 정보
// 6) <script>: dx.js, 세션가드, 다크모드 엔진


4.2 사이드바 내비게이션 구조

사이드바는 adm_nav() 헬퍼 함수로 각 메뉴 링크를 출력합니다. 현재 URL과 메뉴 경로를 비교하여 active 클래스를 자동으로 적용합니다.
 
// adm_nav() — 사이드바 링크 출력 헬퍼
function adm_nav($label, $path, $icon, $cur) {
    // active 판정: 현재 URL이 이 경로로 시작하면 active
    $active = (
        rtrim($cur,'/') === rtrim($path,'/')
        || strpos($cur, $path.'/') === 0
    );
    $base = dx_base_url(ltrim($path,'/'));
    $cls  = 'adm-sb-link' . ($active ? ' active' : '');
    echo '<a href="'.$base.'#adm-nav-pos" class="'.$cls.'"'
       . ' data-nav-link="1">'
       . '<span class="icon">'.$icon.'</span>'
       . '<span>'.$label.'</span></a>';
}

// adm_section() — 섹션 구분자
function adm_section($label) {
    echo '<div class="adm-sb-section">'.$label.'</div>';
}


4.3 사이드바 섹션 구성

섹션 메뉴 URL 경로
대시보드 대시보드 /admin
콘텐츠 페이지 관리 /admin/pages
  팝업 관리 /admin/popup
  전체 공지 /admin/global_notices
  게시판 관리 /admin/boards
  게시판 그룹 /admin/board_groups
  카테고리 /admin/categories
  인기글 /admin/popular
  메뉴 관리 /admin/menus
회원 회원 목록 /admin/members
  메일 보내기 /admin/sendmail
  문자 보내기 /admin/sendsms
  포인트 관리 /admin/points
  레벨 관리 /admin/levels
  포인트샵 /admin/shop
사이트 회원 랭킹 /admin/ranking
  통계 /admin/statistics
  다운로드 통계 /admin/downloads
  실시간 소켓 /admin/socket
  플러그인 /admin/plugins
  테마 /admin/themes
  멀티사이트 /admin/sites
  소셜 로그인 /admin/social
  사이트 설정 /admin/settings
마켓 DX 마켓 /admin/market


4.4 브레드크럼 자동 생성

상단바의 브레드크럼은 $_admActionLabels 배열에서 현재 action에 해당하는 한글 레이블을 찾아 표시합니다. adminSub가 있으면 두 번째 뎁스로 추가됩니다.
 
// admin/index.php — 브레드크럼 렌더링
$_admActionLabels = [
    'dashboard'      => '대시보드',
    'boards'         => '게시판 관리',
    'members'        => '회원 목록',
    'pages'          => '페이지 관리',
    'settings'       => '사이트 설정',
    'plugins'        => '플러그인',
    'themes'         => '테마',
    'socket'         => '실시간 소켓',
    // ... 25개 action 레이블
];

// 브레드크럼 HTML 출력
// 🏠 홈 아이콘 → 현재 action 레이블 [→ adminSub (있을 때만)]

// 예시: /admin/boards/5 접근 시
// 🏠 > 게시판 관리 > 5

// 예시: /admin/members/add 접근 시
// 🏠 > 회원 목록 > add


4.5 보안 기능 — .htaccess 직접 접근 차단

admin/ 폴더의 .htaccess는 PHP 파일에 대한 모든 직접 HTTP 접근을 차단합니다. 반드시 index.php(단일 진입점)를 통해서만 접근 가능합니다.
 
# admin/.htaccess
# admin/ 폴더 — PHP 직접 실행 차단, POST는 상위 index.php로
<FilesMatch "\.php$">
    <IfModule mod_authz_core.c>
        Require all denied
    </IfModule>
    <IfModule !mod_authz_core.c>
        Order Allow,Deny
        Deny from all
    </IfModule>
</FilesMatch>
Options -Indexes

// 결과:
// GET  https://example.com/admin/boards/index.php → 403 Forbidden
// GET  https://example.com/admin/boards           → 정상 (index.php 경유)
// 디렉터리 목록 열람도 차단 (Options -Indexes)


5. 모듈 시스템 — admin/{action}/index.php

각 관리자 기능은 admin/{action}/index.php 파일 하나에 구현됩니다. GET(목록/편집폼)과 POST(저장/삭제)를 같은 파일에서 처리하며, adminSub를 통해 세부 뷰를 분기합니다.


5.1 모듈 파일 처리 패턴

// admin/{module}/index.php 공통 처리 패턴

if (!defined('DX_CMS')) exit('Direct access not allowed.');

$db       = Database::getInstance();
$adminSub = isset($GLOBALS['dx_admin_sub']) ? $GLOBALS['dx_admin_sub'] : '';
$msg      = ['type' => '', 'text' => ''];

// ── POST 처리 (저장/삭제) ──────────────────────────
if (dx_method('POST')) {
    dx_csrf_check();  // CSRF 토큰 검증 필수
    $act = dx_post('act');

    if ($act === 'add') { /* 추가 */ }
    elseif ($act === 'edit') { /* 수정 */ }
    elseif ($act === 'delete') { /* 삭제 */ }

    dx_redirect(dx_base_url('admin/' . $module));
    exit;
}

// ── GET 처리 (목록/편집폼) ─────────────────────────
if ($adminSub && is_numeric($adminSub)) {
    $editItem = $db->find($table, ['id' => (int)$adminSub]);
}

// ── HTML 출력 ────────────────────────────────────────
// (admin/index.php가 require로 포함하므로 별도 HTML 헤더 불필요)
?>
<div class="adm-card"> ... </div>


5.2 adminSub 활용 패턴 — 뷰 분기

adminSub는 모듈 내에서 세부 뷰를 분기하는 데 사용됩니다. 숫자이면 해당 ID의 레코드를 편집, 특수 문자열(add 등)이면 해당 뷰를 표시합니다.
 
모듈 adminSub 처리 내용
boards (빈값) 게시판 목록 출력
boards 5 (숫자) $editBoard = $db->find('boards', ['id'=>5]) → 편집 폼 표시
members (빈값) 회원 목록 출력 (GET: ?s= 검색, ?page= 페이지)
members add 회원 추가 폼 표시
members 123 (숫자) $editMember = $db->find('members', ['id'=>123]) → 편집 폼
members 123?tab=monitor 회원 모니터링 탭 표시
themes (빈값) 테마 목록 출력
themes clean (테마명) 'clean' 테마 옵션 편집 폼 표시
menus (빈값) 메뉴 목록 (GET: ?site=domain.com 멀티사이트 필터)
menus 10 (숫자) $editMenu = $db->find('menus', ['id'=>10]) → 편집 폼
pages (빈값) 페이지 목록 (GET: ?new=1 새 페이지 폼)


6. 관리자 모듈 전체 목록 및 기능

DXCMS v8.1.0은 25개의 관리자 모듈을 제공합니다. 각 모듈은 admin/{module}/index.php 단일 파일로 구현됩니다.


6.1 콘텐츠 관리 모듈

URL 파일 크기 주요 기능
/admin/dashboard 23.3KB KPI 카드(게시글·회원·게시판·댓글 수), 최근 게시글·회원·댓글, 인기글, 검색어, 방문자 통계, 소켓 실시간 위젯(훅 연동)
/admin/boards 69.8KB 게시판 CRUD, 스킨 선택, 카테고리 스킨, 에디터 선택, 업로드 설정, 게시판 그룹 연결, 멀티사이트 site_domain, 썸네일 설정, 이동/복사
/admin/board_groups 14.1KB 게시판 그룹 CRUD, 그룹별 게시판 목록 연결, 정렬 순서
/admin/categories 35.2KB 카테고리 CRUD, 무한 계층(parent_id), 배지 색상, list/view 표시 on/off, 슬러그 자동 생성, 마이그레이션 전 폴백
/admin/pages 60.5KB 페이지 CRUD, editor/file 타입, global/theme 위치, 홈 지정(is_home), standalone, 멀티사이트 site_domain, 에디터 콘텐츠 → DX_PAGES/ 파일 저장
/admin/popup 29.9KB 팝업 CRUD, 노출 기간(start_at/end_at), 노출 조건(전체/회원/비회원), Z-index, 크기·위치 설정
/admin/global_notices 32KB 전체 공지 CRUD, 노출 기간, 상태(active/inactive), 게시판 목록 상단 자동 표시
/admin/menus 50.2KB 메뉴 CRUD, 드래그&드롭 정렬(AJAX JSON), 무한 계층, 멀티사이트별 독립 메뉴, 게시판·페이지 빠른 선택 UI
/admin/popular 27.6KB 인기글 관리, popular_score 기반 순위, 게시판별 필터, 수동 순위 조정


6.2 회원 관리 모듈

URL 파일 크기 주요 기능
/admin/members 39.8KB 회원 CRUD, 검색(이름/아이디/이메일), 역할(admin/member) 변경, 상태 변경, 포인트/경험치 직접 수정, 모니터링 탭(last_seen, 접속 이력)
/admin/sendmail 34.5KB 전체 회원/특정 회원/레벨별 이메일 대량 발송, SMTP/Sendmail 드라이버, 발송 이력
/admin/sendsms 43.4KB SMS 대량 발송, 알리고/NCP/CoolSMS/Twilio 드라이버, 수신자 그룹 필터
/admin/points 15.1KB 포인트 로그 조회, 회원별 포인트 내역, 수동 포인트 지급/차감
/admin/levels 28.5KB 레벨 설정 CRUD(경험치 기준, 레벨명), DB dx_level_config 테이블 관리
/admin/shop 26.7KB 포인트샵 상품 CRUD, 가격(포인트), 재고, 구매 이력


6.3 사이트 관리 모듈

URL 파일 크기 주요 기능
/admin/ranking 26.1KB 회원 랭킹, 포인트/경험치/레벨 순위, 기간별 필터
/admin/statistics 48.5KB 방문자 통계(일별/주별/월별), 페이지별 조회수, 유입 경로, 검색어 통계
/admin/downloads 23.3KB 파일 다운로드 통계, 게시판/파일별 집계
/admin/socket 55.5KB 소켓 서버 상태(온라인/오프라인), 실시간 접속자, 기능별 ON/OFF, 멀티도메인 소켓 설정
/admin/plugins 39.3KB 플러그인 활성화/비활성화, PluginRegistry 타입별 선택, 플러그인별 설정 필드
/admin/themes 23.5KB 테마 활성화, theme.json 메타 표시, 테마 옵션 편집
/admin/sites 17.5KB 멀티사이트 CRUD, 도메인별 테마·메뉴그룹·언어·시간대 설정
/admin/social 14.9KB 소셜 로그인 설정(카카오/네이버/구글/GitHub), OAuth 키 관리
/admin/settings 40.9KB 사이트명·URL·테마·업로드·CAPTCHA·SEO·Analytics·robots.txt·푸터 등 전체 설정, 캐시 초기화, 세션 청소
/admin/market 34.5KB DX 마켓 플러그인/테마 탐색·설치(SHA-256 검증), 개발자 등록, 아이템 등록


7. 모듈 내부 요청 처리 패턴

각 관리자 모듈은 GET(목록/폼 표시)과 POST(데이터 처리)를 같은 파일에서 처리합니다. POST 처리 후에는 항상 PRG(Post-Redirect-Get) 패턴을 사용합니다.


7.1 PRG 패턴 (Post-Redirect-Get)

// POST 처리 후 Redirect → 새로고침 시 중복 제출 방지

if (dx_method('POST')) {
    dx_csrf_check();
    $act = dx_post('act');

    if ($act === 'add') {
        // 게시판 추가
        $newId = $db->insertRow('boards', $data);
        // 저장 성공 메시지를 URL 파라미터로 전달
        dx_redirect(dx_base_url('admin/boards/' . $newId) . '?saved=1');
        exit;
    }

    if ($act === 'edit') {
        $db->updateRow('boards', $data, ['id' => $id]);
        dx_redirect(dx_base_url('admin/boards/' . $id) . '?saved=1');
        exit;
    }

    if ($act === 'delete') {
        $db->updateRow('boards', ['status' => 0], ['id' => $id]);
        dx_redirect(dx_base_url('admin/boards'));
        exit;
    }
}

// GET 처리 (목록 또는 편집 폼)
if ($adminSub && is_numeric($adminSub)) {
    $editBoard = $db->find('boards', ['id' => (int)$adminSub]);
}
$boards = $db->rows("SELECT * FROM dx_boards ORDER BY sort_order");


7.2 AJAX/JSON POST 처리 패턴

메뉴 관리처럼 드래그&드롭 정렬이 필요한 모듈은 JSON 형식의 AJAX 요청을 처리합니다. Content-Type: application/json 헤더로 구분합니다.
 
// admin/menus/index.php — JSON AJAX 처리
if (
    dx_method('POST') &&
    !empty($_SERVER['CONTENT_TYPE']) &&
    strpos($_SERVER['CONTENT_TYPE'], 'application/json') !== false
) {
    header('Content-Type: application/json; charset=utf-8');
    $raw  = file_get_contents('php://input');
    $data = @json_decode($raw, true);

    // CSRF 검증 — JSON body의 _csrf를 세션 토큰과 비교
    $ajaxCsrf    = isset($data['_csrf']) ? $data['_csrf'] : '';
    $sessionCsrf = Secure::getInstance()->csrfToken();
    if (!$ajaxCsrf || $ajaxCsrf !== $sessionCsrf) {
        echo json_encode(['success'=>false, 'error'=>'CSRF token mismatch']);
        exit;
    }

    if ($data['act'] === 'sort_reorder') {
        foreach ($data['items'] as $item) {
            $db->updateRow('menus',
                ['sort_order'=>$item['sort_order'], 'parent_id'=>$item['parent_id']],
                ['id'=> (int)$item['id']]
            );
        }
        echo json_encode(['success'=>true]);
        exit;
    }
}


7.3 설정 저장 패턴 — DB + 캐시 무효화

관리자 설정 저장은 INSERT ON DUPLICATE KEY UPDATE 패턴으로 누락된 설정도 자동 생성합니다. 저장 후 즉시 캐시를 무효화하고 현재 요청의 $dx_config도 갱신합니다.
 
// admin/settings/index.php — 설정 저장 패턴
foreach ($fields as $key) {
    $val = dx_post($key, '');

    // INSERT IGNORE + ON DUPLICATE KEY UPDATE
    // → 행이 없으면 INSERT, 있으면 UPDATE
    $db->query(
        "INSERT INTO dx_settings (setting_key, setting_value, updated_at)"
        . " VALUES (?, ?, NOW())"
        . " ON DUPLICATE KEY UPDATE"
        . " setting_value=VALUES(setting_value), updated_at=NOW()",
        [$key, $val]
    );

    // ① 현재 요청 $dx_config 즉시 갱신 (이번 페이지 렌더링에 반영)
    dx_set_config($key, $val);
}

// ② 캐시 무효화 (다음 요청부터 DB에서 새 값 로드)
if (class_exists('DxCache')) {
    DxCache::delete('dx_settings');
}

// 설정 로드는 항상 DB 직접 조회 (캐시와 독립)
// → 저장 직후 화면에서 바로 저장된 값이 보임
$settings = _adm_load_settings($db);

// 캐시 전체 초기화 (수동 요청 시)
if (dx_post('cache_flush') === '1') {
    DxCache::flush();  // 모든 캐시 삭제
}


7.4 마이그레이션 자동 대응 패턴

각 모듈은 DB 스키마가 구버전과 다를 수 있음을 고려하여 컬럼 존재 여부를 확인하고 없으면 자동으로 추가합니다.
 
// admin/pages/index.php — 컬럼 자동 추가
$_pgAllCols = array_column(
    $db->rows("SHOW COLUMNS FROM dx_pages"),
    'Field'
);

foreach ([
    'page_type'     => "VARCHAR(10) NOT NULL DEFAULT 'editor'",
    'page_location' => "VARCHAR(10) NOT NULL DEFAULT 'global'",
    'site_domain'   => "VARCHAR(191) NOT NULL DEFAULT ''",
    'is_standalone' => "TINYINT(1) NOT NULL DEFAULT 0",
] as $col => $def) {
    if (!in_array($col, $_pgAllCols)) {
        try {
            $db->query("ALTER TABLE dx_pages ADD COLUMN `{$col}` {$def}");
            $_pgAllCols[] = $col;
        } catch (Exception $e) {}
    }
}

// admin/categories/index.php — 테이블 없으면 migrate 안내 표시
try {
    $db->rows("SELECT 1 FROM dx_categories LIMIT 1");
    $_tblOk = true;
} catch (Exception $e) { $_tblOk = false; }

if (!$_tblOk) {
    echo 'migrate.php 실행이 필요합니다.';
    return;
}


8. 관리자 훅 시스템

관리자 시스템은 HookManager를 통해 외부 코드(플러그인, extend/)가 관리자 UI를 확장할 수 있도록 훅 포인트를 제공합니다.


8.1 관리자 훅 포인트

훅 이름 발생 위치 활용 예시
dx_admin_top admin/index.php 본문 상단 관리자 페이지 공통 상단 알림, 점검 공지, 커스텀 버튼 추가
dx_admin_bottom admin/index.php 본문 하단 관리자 페이지 공통 하단 스크립트, 로그 뷰어 추가
dx_admin_dashboard_widgets 대시보드 위젯 영역 dx-socket 플러그인이 실시간 접속 위젯을 대시보드에 삽입
 
// admin/index.php — 훅 실행 위치
<main>
    <?php dx_run_hook('dx_admin_top', ['action' => $adminAction]); ?>
    <?php require $actionFile; ?>  // 모듈 콘텐츠
    <?php dx_run_hook('dx_admin_bottom', ['action' => $adminAction]); ?>
</main>

// 플러그인에서 관리자 UI 확장 예시
dx_add_hook('dx_admin_top', function($args) {
    if ($args['action'] === 'dashboard') {
        echo '<div class="adm-card">실시간 접속자 위젯</div>';
    }
}, 10);

// action별 분기 가능
dx_add_hook('dx_admin_top', function($args) {
    if ($args['action'] === 'boards') {
        echo '<div class="alert">게시판 설정 주의사항...</div>';
    }
}, 5);


8.2 소켓 플러그인 관리자 연동

dx-socket 플러그인은 dx_admin_dashboard_widgets 훅을 통해 대시보드에 실시간 접속 위젯을 삽입하고, /admin/socket 모듈로 소켓 서버를 관리합니다.
 
// plugins/dx-socket/admin/widget.php
// dx_admin_dashboard_widgets 훅에서 include됨

// 소켓 연결 정보를 JavaScript에 주입
$_admSockHtml = '<script>var DX_SOCKET=DX_SOCKET||{};' 
    . 'DX_SOCKET.wsUrl="' . dx_config('plugin_dx-socket_ws_url','...') . '"' 
    . 'DX_SOCKET.isAdmin=true;</script>';

// admin/index.php에서 조건부 출력
if (dx_config('plugin_dx-socket_enabled','1')=='1'
    && function_exists('_dx_sock_group')) {
    echo $_admSockHtml;
}


9. 관리자 보안 구조

관리자 시스템은 다층 보안 구조를 갖습니다. .htaccess 직접 접근 차단, Auth 인증 확인, CSRF 토큰 검증, 세션 감시 스크립트가 협력합니다.


9.1 보안 계층

계층 메커니즘 상세
1계층 (웹서버) admin/.htaccess .php 파일 직접 접근 403 차단. Options -Indexes로 디렉터리 목록 차단
2계층 (PHP) DX_CMS 상수 확인 모든 모듈 파일 첫 줄: if (!defined('DX_CMS')) exit('Direct access not allowed.')
3계층 (인증) Auth::isAdmin() Dispatcher::dispatchAdmin()에서 role='admin' 확인. 실패 시 로그인 페이지로 리다이렉트
4계층 (CSRF) dx_csrf_check() 모든 POST 처리 첫 번째 줄. 세션 토큰과 POST _csrf 필드 비교 (hash_equals)
5계층 (세션감시) SESSION GUARD JS dx-session-guard.js가 30초마다 세션 유효성 확인. 만료 시 로그인 페이지로 자동 리다이렉트


9.2 세션 감시 스크립트 (SESSION GUARD)

// admin/index.php — 세션 감시 스크립트
window.DX_SESSION_GUARD = {
    isLogin  : true,
    baseUrl  : 'https://example.com',
    loginUrl : 'https://example.com/auth/login'
};

// dx-session-guard.js 동작 원리
// 30초마다 /api/session_check 요청 → 세션 유효성 확인
// 응답: {logged_in: false} → 로그인 페이지로 자동 이동
// 응답: {logged_in: true}  → 계속 유지

// 관리자 탭을 방치해도 세션 만료 시 자동 로그아웃
// 보안 강화: 오래된 세션으로 관리자 작업 방지


9.3 CSRF 이중 검증 — 폼/AJAX 공통

// ① 일반 HTML 폼 POST
// 폼에 hidden 필드 포함
echo dx_csrf_field();
// → <input type="hidden" name="_csrf" value="abc123...">

// 서버에서 검증
dx_csrf_check();  // POST _csrf와 세션 토큰 비교

// ② JSON AJAX POST
// JavaScript에서 토큰을 JSON body에 포함
const csrfToken = document.querySelector('[name=_csrf]').value;
fetch('/admin/menus', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({act: 'sort_reorder', _csrf: csrfToken, items: [...]})
});

// 서버에서 JSON body의 _csrf 직접 검증
$ajaxCsrf    = $data['_csrf'] ?? '';
$sessionCsrf = Secure::getInstance()->csrfToken();
if (!hash_equals($sessionCsrf, $ajaxCsrf)) { ... 403 ... }


10. 관리자 프론트엔드 구조

관리자 UI는 dxb-css(Tailwind 호환 런타임 CSS 엔진) + Pretendard 폰트 + Font Awesome 6 아이콘으로 구성됩니다. 별도 빌드 도구 없이 동작합니다.


10.1 CSS/JS 로드 구조

리소스 역할 및 로드 이유
Pretendard (CDN) 한국어 최적화 폰트. 가독성 높은 관리자 UI
Space Grotesk (Google Fonts) 숫자·영문 디스플레이 폰트. KPI 카드 수치 표시
Font Awesome 6 (CDN) 아이콘 라이브러리. 사이드바 메뉴 아이콘
admin/style.css (로컬) 최우선 로드. 정적 pre-compile 유틸리티 CSS. dxb-css.js보다 먼저 로드해야 첫 paint 전에 스타일 적용
assets/js/dxb-css.js 동적 CSS 엔진. style.css에 없는 동적 클래스를 JS 런타임에 생성. MutationObserver로 새 클래스 자동 감지
jQuery 3.7.1 (CDN) 사이드바 토글, 이벤트 핸들링
assets/js/dx.js 공통 JS. CSRF 토큰 관리, 플래시 메시지 등
dx-session-guard.js 세션 만료 감지 및 자동 로그아웃
dx-darkmode-engine.js 다크모드 토글 엔진


10.2 사이드바 스크롤 상태 유지

관리자 메뉴를 클릭하면 페이지가 이동되면서 사이드바 스크롤 위치가 초기화되는 문제를 sessionStorage로 해결합니다.
 
// admin/index.php <head> 인라인 스크립트

var NAV_KEY = 'adm_nav_scroll';

// 메뉴 클릭 시 현재 스크롤 위치 저장
function saveNavScroll() {
    var nav = document.getElementById('adm_navi_nav');
    if (nav) sessionStorage.setItem(NAV_KEY, nav.scrollTop);
}

// 페이지 로드 시 스크롤 복원
function restoreNavScroll() {
    var nav = document.getElementById('adm_navi_nav');
    var saved = sessionStorage.getItem(NAV_KEY);
    if (saved !== null) nav.scrollTop = parseInt(saved) || 0;
}

// #adm-nav-pos 앵커로 인한 본문 점프 방지
// 링크 href의 #adm-nav-pos를 제거하고 이동
$("[data-nav-link='1']").on("click", function(e) {
    saveNavScroll();
    var href = this.href.replace(/#adm-nav-pos$/, '');
    location.href = href;
    e.preventDefault();
});


11. 관리자 라우팅 전체 흐름 요약

┌───────────────────────────────────────────────────────────────┐
│  HTTP Request: GET /admin/boards/5                           │
└───────────────────────────────────────────────────────────────┘
    │
    ▼ index.php (단일 진입점)
[STEP 1] Secure.php, Database, functions.php, HookManager 로드
[STEP 2] Secure::initSession(), sendSecurityHeaders()
[STEP 3] data/config.php (DB 연결)
[STEP 4] load_plugins(), DxSite, DxTheme, Auth 초기화
    │
    ▼ [STEP 5] 라우팅
DxRouter::dispatch() → 매칭 없음 → Dispatcher 폴백
Router::resolve()
    segments = ['admin', 'boards', '5']
    type   = TYPE_ADMIN
    action = 'boards'
    sub    = '5'
    │
    ▼
Dispatcher::dispatchAdmin()
    ┌─ Auth::isAdmin() → false
    │   → dx_redirect('/auth/login?redirect=%2Fadmin%2Fboards%2F5')
    │   → exit
    │
    └─ Auth::isAdmin() → true
        $GLOBALS['dx_admin_action'] = 'boards'
        $GLOBALS['dx_admin_sub']    = '5'
        require admin/index.php
    │
    ▼ admin/index.php (레이아웃)
$actionFile = DX_ROOT . '/admin/boards/index.php'
// 파일 없으면 admin/dashboard/index.php 폴백
    │
    ▼ HTML 출력 시작
<head> CSS + JS 로드
<aside> 사이드바 (boards 메뉴 active 표시)
<header> 브레드크럼: 🏠 > 게시판 관리 > 5
<main>
    dx_run_hook('dx_admin_top', ['action'=>'boards'])
    │
    ▼ require admin/boards/index.php
    $adminSub = '5' (from $GLOBALS)
    // adminSub가 숫자 → 편집 폼 표시
    $editBoard = $db->find('boards', ['id'=>5])
    // POST 없음(GET) → 편집 폼 HTML 출력
    │
    dx_run_hook('dx_admin_bottom', ['action'=>'boards'])
</main>
<footer> 버전 정보
<script> dx.js, 세션가드, 다크모드
    │
    ▼
┌───────────────────────────────────────────────────────────────┐
│  HTTP Response: HTML (게시판 편집 폼)                        │
└───────────────────────────────────────────────────────────────┘

✅  관리자 라우팅 핵심 원칙 요약

  1) 단일 레이아웃: admin/index.php가 모든 모듈의 공통 프레임 제공
  2) 모듈 분리: admin/{action}/index.php 한 파일로 GET/POST 모두 처리
  3) sub 파라미터: URL 세 번째 세그먼트 → 편집할 레코드 ID 또는 특수 문자열
  4) 보안 5계층: .htaccess → DX_CMS → isAdmin() → CSRF → 세션가드
  5) PRG 패턴: POST 처리 후 반드시 dx_redirect()로 이동 (중복 제출 방지)
  6) 훅 확장: dx_admin_top/bottom으로 플러그인이 관리자 UI 확장 가능
  7) 마이그레이션 자동 대응: SHOW COLUMNS로 컬럼 확인 후 ALTER TABLE
  8) 폴백 안전성: 모듈 파일 없으면 dashboard로 자동 폴백

댓글0

로그인 후 댓글을 작성할 수 있습니다.
4.2 관리자 시스템 구조 관리자 UI 구조 2026.04.21 4.2 관리자 시스템 구조 관리자 라우팅 2026.04.21
30
전체 회원
269
전체 게시글
144
전체 댓글
181
오늘 방문
28,530
전체 방문
1
현재 접속
인기글 7일 이내
최신글
최신댓글
목록