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

댓글 및 답글 구조

A Administrator
2026.05.01 01:32(수정됨) 84 0

1장. 댓글 및 답글 시스템 개요

DXCMS의 댓글 시스템은 REST 방식의 JSON API와 PHP 서버사이드 렌더링을 결합한 하이브리드 구조입니다. 댓글 등록•수정•삭제는 AJAX(fetch) 요청으로 처리되고, 결과는 페이지 새로고침을 통해 서버 렌더링된 HTML로 표시됩니다. 대댓글은 parent_id + depth 트리 구조로 무한 계층을 지원하며, 실시간 소켓 알림과 연동됩니다.


1.1 핵심 설계 원칙

  • 하이브리드 처리 — AJAX API로 데이터 처리 → 완료 후 페이지 reload (서버 렌더링 품질 유지)
  • 무한 계층 트리 — parent_id + depth 구조로 댓글→대댓글→대대댓글 무제한 지원
  • CSRF 토큰 순환 — 댓글 등록/수정/삭제 시마다 새 CSRF 토큰을 응답에 포함, JS가 자동 갱신
  • 이중 입력 지원 — AJAX(fetch) 요청과 일반 form POST 모두 처리 (JavaScript 비활성 환경 대응)
  • 에디터 통합 — 플러그인으로 등록된 에디터(CKEditor4 등)를 댓글 폼에도 선택적으로 적용
  • hard delete — 댓글 삭제 시 DB에서 완전 삭제 (soft delete 없음), 좋아요도 함께 삭제
  • 인기점수 연동 — 댓글 등록/삭제 시 게시글 popular_score 자동 재계산


1.2 관련 파일 구성

파일 경로 역할
core/api/comment.php 댓글 등록·수정·삭제·단건조회 API (메인 처리)
core/api/comment_delete.php 레거시 삭제 API (comment.php로 통합 권장)
themes/default/board/view.php 댓글 목록 렌더링 + JS 댓글 동작 전체
boards/skins/gallery/view.php 갤러리 스킨의 댓글 뷰 (동일 구조)
core/DxNotification.php 댓글 등록 시 실시간 알림 발송
install/schema.sql dx_comments, dx_likes 테이블 정의


2장. DB 스키마 상세


2.1 dx_comments — 댓글 테이블

모든 게시판의 댓글과 대댓글이 이 하나의 테이블에 저장됩니다. 계층 구조는 parent_id와 depth 두 컬럼으로 표현됩니다.
 
컬럼명 타입 기본값 설명
id BIGINT UNSIGNED 밀리초ID PK — 밀리초 타임스탬프로 자동 생성
post_id BIGINT UNSIGNED - 소속 게시글 ID (dx_posts.id 참조)
parent_id BIGINT UNSIGNED 0 부모 댓글 ID. 0이면 최상위 댓글, >0이면 대댓글
member_id INT UNSIGNED 0 작성 회원 ID. 0이면 비회원
author_name VARCHAR(100) NULL 비회원 작성자명 (회원이면 members.name 조인)
author_pass VARCHAR(255) NULL 비회원 비밀번호 (bcrypt 해시, 미사용 시 NULL)
content TEXT - 댓글 본문 (일반 텍스트 또는 에디터 HTML)
ip VARCHAR(45) NULL 작성자 IP (IPv4/IPv6 모두 지원)
depth TINYINT(3) 0 계층 깊이. 0=최상위 댓글, 1=대댓글, 2=대대댓글 ...
status TINYINT(1) 1 상태. 1=정상, 0=삭제(현재 hard delete 방식)
created_at DATETIME - 작성일시

💡 밀리초 타임스탬프 ID
id는 PHP insertWithMicrotimeId() 함수로 생성됩니다.
형식: microtime(true) × 1000 + 랜덤 2자리 (예: 1746887234562_47)
이 방식으로 ID 순서 = 시간 순서가 보장되어 ORDER BY id ASC로 시간순 정렬이 됩니다.


2.2 dx_likes — 좋아요 테이블

게시글과 댓글의 좋아요를 모두 이 테이블에서 관리합니다. target_type으로 대상 유형을 구분합니다.
 
컬럼명 타입 기본값 설명
id INT UNSIGNED AUTO_INCREMENT PK
target_type ENUM('post','comment') - 'post' = 게시글 좋아요, 'comment' = 댓글 좋아요
target_id BIGINT UNSIGNED - 대상 게시글 또는 댓글의 ID
member_id INT UNSIGNED 0 좋아요한 회원 ID
ip VARCHAR(45) NULL 비회원 좋아요 시 IP
created_at DATETIME - 좋아요 일시

UNIQUE KEY uk_like (target_type, target_id, member_id) — 같은 회원이 같은 대상에 중복 좋아요를 누를 수 없습니다.


2.3 dx_notifications — 실시간 알림 테이블

댓글 등록 시 게시글 작성자와 부모 댓글 작성자에게 알림이 생성됩니다.
 
컬럼명 타입 설명
id BIGINT UNSIGNED PK (AUTO_INCREMENT)
to_member_id INT UNSIGNED 알림을 받을 회원 ID
from_member_id INT UNSIGNED 알림을 보낸 회원 ID
type VARCHAR(30) 알림 유형 (예: 'comment', 'reply', 'like')
message VARCHAR(255) 알림 본문 (예: "홍길동님이 댓글을 달았습니다.")
url VARCHAR(500) 클릭 시 이동 URL (게시글 view + #comment-{id} 앵커)
is_read TINYINT(1) 읽음 여부. 0=미읽음, 1=읽음
created_at DATETIME 알림 생성일시


3장. 댓글 트리 구조 (parent_id + depth)

댓글과 대댓글은 부모-자식 관계를 parent_id로 연결하고, 계층 깊이를 depth로 표현합니다. 화면에서 대댓글은 왼쪽에 들여쓰기(depth × 24px)와 파란 세로선으로 시각적으로 구분됩니다.


3.1 데이터 구조 예시

아래는 게시글 하나에 댓글 4개가 달린 경우의 DB 레코드 예시입니다.
 
id post_id parent_id depth 내용
1001 500 0 0 안녕하세요! (최상위 댓글)
1002 500 0 0 저도 궁금합니다. (최상위 댓글)
1003 500 1001 1 1001번 댓글의 대댓글
1004 500 1003 2 1003번의 대대댓글


3.2 화면 렌더링 결과

위 데이터가 화면에서 아래와 같이 렌더링됩니다:
┌─ [댓글 1001] 안녕하세요! (depth=0, 들여쓰기 없음)
│    └─ [댓글 1003] 1001번 댓글의 대댓글 (depth=1, 들여쓰기 24px + 파란선)
│         └─ [댓글 1004] 1003번의 대대댓글 (depth=2, 들여쓰기 48px + 파란선)
└─ [댓글 1002] 저도 궁금합니다. (depth=0, 들여쓰기 없음)


3.3 PHP 트리 렌더링 알고리즘

view.php는 아래 단계로 댓글 목록을 트리 형태로 렌더링합니다:
  1. DB에서 해당 게시글의 모든 댓글을 id 오름차순(작성 순)으로 조회
  2. $_cmtMap[댓글id] = 댓글데이터 — ID로 댓글 데이터에 빠르게 접근
  3. $_cmtChildren[parent_id][] = 댓글id — 부모별 자식 id 목록 구성
  4. parent_id='0'인 최상위 댓글부터 순서대로 _dnx_render_comment() 재귀 호출
  5. 각 댓글 렌더 후 해당 댓글의 자식들을 재귀적으로 렌더링
  6. depth가 5를 초과하면 min(depth, 5)로 최대 120px까지만 들여쓰기


🔍 렌더링 핵심 코드 구조

// 댓글 맵과 자식 목록 구성
foreach ($comments as $_c) {
    $_cmtMap[$_c['id']] = $_c;
    $_cmtChildren[$_c['parent_id']][] = $_c['id'];
}
// 최상위 댓글부터 재귀 렌더
foreach ($_cmtChildren['0'] as $_rootId) {
 _dnx_render_comment($_rootId, $_cmtMap, $_cmtChildren, ...);
}


4장. 댓글 API 상세 (core/api/comment.php)

모든 댓글 작업은 /api/comment 단일 엔드포인트로 처리됩니다. _sub 파라미터로 작업 유형을 구분합니다.
 
HTTP 메서드 + _sub 기능 설명
GET  ?_sub=get&id=N 단건 댓글 조회 실시간 DOM 삽입용. status=1인 댓글만 반환
POST (없음) 댓글 등록 post_id + content + parent_id(0 또는 부모ID) 전송
POST _sub=update 댓글 수정 comment_id + content 전송. 본인 또는 관리자만 가능
POST _sub=delete 댓글 삭제 comment_id 전송. hard delete + 좋아요 삭제


4.1 댓글 등록 처리 흐름

  1. CSRF 토큰 검증 (dx_csrf_check())
  2. post_id, parent_id, content 파라미터 수신 (content 또는 comment_content 둘 다 지원)
  3. XSS 필터링 — 에디터 사용 시 DxSanitizer::editorContent(), 아니면 DxSanitizer::text()
  4. 게시글 존재 확인 (status=1)
  5. 게시판의 use_comment=1 및 comment_level 권한 확인
  6. depth 계산 — parent_id의 댓글 depth + 1 (최상위 댓글은 0)
  7. 비회원 처리 — author_name 필수, author_pass bcrypt 해시 저장
  8. depth 컬럼 존재 여부 확인 (구버전 DB 대응, 없으면 data에서 제거)
  9. insertWithMicrotimeId() 로 밀리초 ID 생성 후 댓글 INSERT
  10. 게시글 comment_count + 1 업데이트
  11. 인기점수(popular_score) 재계산 (조회×1 + 좋아요×5 + 댓글×3 × 시간감쇠)
  12. dx_after_comment 훅 실행
  13. 게시판 목록 캐시 무효화
  14. DxNotification::onComment() 로 글 작성자 + 부모 댓글 작성자에게 알림
  15. AJAX 요청: JSON 반환 (id, depth, csrf_token 포함) / 일반 POST: 원글 페이지로 redirect


4.2 댓글 삭제 처리 흐름

  1. CSRF 토큰 검증
  2. comment_id로 댓글 조회 (status 무관 — 이미 soft-delete된 것도 처리)
  3. 권한 확인 — 본인(member_id 일치) 또는 관리자만 삭제 가능
  4. dx_likes 테이블에서 해당 댓글의 모든 좋아요 hard delete
  5. dx_comments 테이블에서 댓글 hard delete
  6. 게시글 comment_count GREATEST(0, count-1) 감소 (0 이하로 내려가지 않음)
  7. 인기점수(popular_score) 재계산
  8. JSON 응답 반환 — comment_id, post_id, notif_type, 새 CSRF 토큰 포함

⚠️ 주의: 대댓글 삭제 시 고아(orphan) 문제
현재 구현에서 부모 댓글을 삭제해도 자식(대댓글)은 DB에 남아 있습니다.
화면에서는 부모 댓글 DOM이 즉시 제거되지만 자식은 parent_id로 연결되어 있어
다음 페이지 reload 시 _cmtChildren에 등록되나 _cmtMap에 부모가 없어 렌더되지 않습니다.
(안전한 완전 삭제를 원한다면 훅 dx_board_before_delete 활용 권장)


4.3 댓글 수정 처리 흐름

  1. CSRF 토큰 검증
  2. comment_id, content 파라미터 수신 및 유효성 검사
  3. 댓글 조회 (status=1)
  4. 권한 확인 — 본인 또는 관리자
  5. XSS 필터링 (에디터 사용 여부에 따라 다른 새니타이저 적용)
  6. dx_comments 테이블 content 컬럼만 UPDATE
  7. JSON 응답 반환 — comment_id, post_id, content, notif_type, 새 CSRF 토큰 포함


4.4 API 응답 형식

등록 성공 시 JSON 응답 예시:
{
  "success": true,
  "id": "1746887234562",       // 새 댓글 ID (밀리초 타임스탬프)
  "depth": 1,                  // 댓글 계층 깊이
  "message": "댓글이 등록되었습니다.",
  "notify_targets": ["user1"],  // 알림 받을 login_id 배열
  "csrf_token": "abc123..."     // 새 CSRF 토큰 (JS가 자동 갱신)
}


5장. 댓글 권한 체계


5.1 댓글 작성 권한 (comment_level)

게시판의 comment_level 컬럼 값에 따라 댓글 작성 가능 여부가 결정됩니다.
 
comment_level 허용 대상 미충족 시 처리
0 비회원 포함 누구나 제한 없음
1 (기본값) 로그인 회원만 JSON: 403 + 로그인 유도 메시지 반환
9 관리자만 JSON: 403 + 관리자 전용 메시지 반환


5.2 댓글 수정/삭제 권한

조건 결과
관리자 (isAdmin() = true) 모든 댓글 수정/삭제 가능
로그인 회원 + member_id 일치 본인 댓글만 수정/삭제 가능
그 외 (비회원, 타인) 403 Forbidden — "삭제/수정 권한이 없습니다." 반환


5.3 비회원 댓글

comment_level=0인 게시판에서는 비회원도 댓글을 작성할 수 있습니다.
  • author_name 필수 — 이름을 입력하지 않으면 "이름을 입력해주세요." 오류 반환
  • author_pass 선택 — 입력 시 bcrypt 해시로 저장 (삭제 시 비밀번호 확인 용도)
  • member_id = 0 — DB에 0으로 저장, 회원과 구분
  • ip 기록 — 작성자 IP 저장 (관리자 확인용)


6장. JavaScript 동작 상세


6.1 주요 JS 함수 목록

함수명 역할
dxFetch(url, body) fetch 공통 래퍼 — CSRF 헤더 자동 포함, 응답의 csrf_token 자동 갱신
dxSubmitComment(e, postId) 메인 댓글 등록 — 폼 submit 핸들러, parent_id=0으로 전송
dxSubmitReply(e, parentId, postId) 답글 등록 — parent_id=부모댓글ID로 전송
dxDeleteComment(commentId, postId) 댓글 삭제 — confirm 후 API 호출, 성공 시 DOM 제거
dxEditCommentToggle(commentId) 수정 폼 토글 — 보기모드 ↔ 수정모드 전환
dxUpdateComment(commentId, postId) 댓글 수정 저장 — CKEditor 또는 textarea 내용 추출 후 API 전송
dxApplyCommentUpdate(commentId, content) 수정 내용을 DOM에 즉시 반영 (본인 + 소켓 수신 측 공용)
dxApplyCommentDelete(commentId) 댓글 DOM 제거 + 카운트 감소 (본인 + 소켓 수신 측 공용)
dxReplyToggle(id) 답글 폼 열기/닫기, 메인 댓글 폼 숨기기/복원, CKEditor 초기화
dxCmtMenuToggle(id) 드롭다운 메뉴 열기/닫기
dxGetEditorVal(form, name) textarea에서 값 추출 (CKEditor 인스턴스 있으면 getData() 호출)


6.2 댓글 등록 흐름 (JS → API → 화면)

  1. 사용자가 댓글 폼에 내용 입력 후 "댓글 등록" 버튼 클릭
  2. dxSubmitComment() 호출 → dxGetEditorVal()로 내용 추출
  3. submit 버튼 disabled 처리 (중복 제출 방지)
  4. dxFetch("/api/comment", "post_id=...&content=...&parent_id=0") 호출
  5. 성공 시 dxSockNotify(d) 로 소켓 알림 브로드캐스트
  6. 80ms 후 location.href = 현재URL + "#comment-{새ID}" 설정 후 reload
  7. 페이지 reload 후 #comment-{id} 앵커로 해당 댓글로 자동 스크롤
  8. 2초 후 배경 노란색 하이라이트 효과 제거


6.3 답글 폼 동작 (dxReplyToggle)

답글 버튼 클릭 시 아래 동작이 순서대로 실행됩니다:
  • 이미 열린 다른 답글 폼 모두 닫기 (동시에 하나만 열림)
  • 현재 댓글의 답글 폼 열기 (hidden 클래스 토글)
  • 메인 댓글 입력 폼(#dx-cmt-form) 숨기기 → 답글 폼이 열린 동안 메인 폼 비노출
  • 에디터 사용 환경이면 80ms 후 CKEditor를 답글 textarea에 초기화
  • 답글 폼 닫을 때: CKEditor 인스턴스 파괴, 메인 댓글 폼 복원


6.4 댓글 수정 흐름

  1. ⋮ 메뉴에서 "수정" 클릭 → dxEditCommentToggle(commentId) 호출
  2. 댓글 내용 div 숨기기 + 수정 폼 표시
  3. 에디터 사용 환경이면 CKEditor를 수정 textarea에 초기화 (기존 내용 로드)
  4. "저장" 버튼 클릭 → dxUpdateComment(commentId, postId) 호출
  5. CKEditor.getData() 또는 textarea.value로 내용 추출
  6. dxFetch("/api/comment", "_sub=update&comment_id=...&content=...")
  7. 성공 시 CKEditor 인스턴스 파괴, dxApplyCommentUpdate()로 DOM 즉시 반영
  8. 수정 폼 닫기 (보기 모드로 복귀)


6.5 CSRF 토큰 자동 갱신

댓글 API의 모든 응답에는 새 CSRF 토큰이 포함됩니다. dxFetch()가 이를 자동으로 처리합니다:
  • 응답 JSON의 csrf_token 값을 DX_CSRF 전역 변수에 저장
  • <meta name="csrf-token"> 태그의 content 속성 갱신
  • 페이지 내 모든 <input name="_csrf"> hidden 필드 값 동기화
이 방식으로 SPA처럼 단일 페이지에서 연속 댓글 작성 시 CSRF 토큰 만료 문제를 방지합니다.


7장. 댓글 에디터 연동


7.1 에디터 사용 여부 결정

댓글 폼에 에디터를 사용할지 여부는 dx_editor_use_comment() 함수가 결정합니다:
function dx_editor_use_comment() {
    $editorId = dx_active_plugin("editor");
    if (!$editorId) return false;
    $key = "plugin_" . $editorId . "_use_comment_editor";
    return dx_config($key, "0") === "1";
}
관리자 → 플러그인 설정 → 에디터 플러그인 → "댓글 에디터 사용" 옵션을 ON으로 설정하면 활성화됩니다.


7.2 에디터 모드별 동작 차이

구분 에디터 OFF (기본) 에디터 ON (CKEditor4 등)
댓글 입력 UI 일반 textarea (resize 가능) CKEditor 경량 에디터 (toolbar 포함)
내용 새니타이즈 DxSanitizer::text() — 태그 제거 DxSanitizer::editorContent() — 허용 태그 필터
저장 형식 일반 텍스트 HTML (볼드, 링크, 표 등 가능)
화면 출력 nl2br(htmlspecialchars()) — 줄바꿈 → <br> HTML 그대로 출력 (XSS 필터 후)
답글 에디터 없음 (textarea) 폼 열릴 때 CKEditor 동적 초기화
수정 에디터 없음 (textarea) 수정 폼 열릴 때 CKEditor 초기화


7.3 CKEditor 댓글용 설정

댓글 및 답글의 CKEditor는 게시글용보다 경량화된 툴바를 사용합니다:
  • 높이: 150px (게시글은 400px)
  • 툴바: Bold, Italic, Underline, Strike, RemoveFormat, TextColor, BGColor, 목록, 정렬, 링크, 표, 자동번역, 소스
  • enterMode: ENTER_BR (줄바꿈 시 <br> 삽입, 게시글과 동일)
  • 다크모드 자동 동기화: body.classList에 dark 클래스 감지


8장. 실시간 알림 및 소켓 연동


8.1 댓글 알림 대상

새 댓글이 등록되면 아래 두 대상에게 알림이 발송됩니다:
 
알림 수신 대상 조건 및 설명
게시글 작성자 댓글 작성자와 다른 회원일 때만 알림. 비회원 게시글은 알림 없음
부모 댓글 작성자 (대댓글 시) 대댓글 시 부모 댓글 작성자에게 추가 알림. 게시글 작성자와 동일인이면 중복 발송 안함

8.2 알림 발송 흐름

  1. 댓글 등록 완료 후 DxNotification::onComment($newId, $postId, $fromId, $parentId) 호출
  2. 게시글 작성자의 login_id를 DB에서 조회
  3. parent_id가 있으면 부모 댓글 작성자의 login_id도 조회
  4. notify_targets 배열에 login_id 수집 (중복 제거)
  5. dx_notifications 테이블에 알림 레코드 INSERT
  6. JSON 응답에 notify_targets 포함 → JS의 dxSockNotify(d) 호출
  7. WebSocket 서버로 알림 이벤트 브로드캐스트 → 수신자 화면에 실시간 벨 알림 표시


8.3 소켓 이벤트 유형

이벤트 유형 발생 시점 및 동작
comment (신규 댓글) 댓글 등록 후 dxSockNotify() 호출 → 알림 대상 화면에 배지 표시
comment_delete 댓글 삭제 후 dxSockCommentAction() → 같은 글을 보는 다른 사람 화면에서도 해당 댓글 DOM 제거
comment_update 댓글 수정 후 dxSockCommentAction() → 같은 글을 보는 사람 화면의 댓글 내용 즉시 갱신


9장. 댓글 UI 구성 요소


9.1 댓글 섹션 전체 구조

댓글 섹션은 아래 순서로 구성됩니다:
┌ <section> 댓글 섹션 컨테이너 (use_comment=1일 때만 표시)
│  ├ <header> 댓글 수 표시
│  ├ [댓글 목록] — PHP 재귀 렌더링
│  │  ├ 댓글1 (depth=0)
│  │  │  ├ 작성자 아바타 + 이름 + 날짜
│  │  │  ├ ⋮ 드롭다운 (답글/수정/삭제)
│  │  │  ├ 댓글 내용 (보기 모드)
│  │  │  ├ 수정 폼 (숨김, 수정 시 표시)
│  │  │  └ 답글 폼 (숨김, 답글 버튼 클릭 시 표시)
│  │  └ 대댓글 (depth=1, 들여쓰기 + 파란선)
│  └ [댓글 작성 폼] — 권한 충족 시 표시
       └ 미로그인 시: "로그인 후 댓글 작성" 안내
└


9.2 ⋮ 드롭다운 메뉴

각 댓글 우측에는 ⋮(ellipsis) 버튼이 있어 클릭 시 드롭다운 메뉴가 표시됩니다. 메뉴 항목은 권한에 따라 다르게 표시됩니다:
 
메뉴 항목 표시 조건
답글 댓글 작성 권한(canCmt)이 있는 사용자 모두
수정 본인 댓글이거나 관리자
삭제 본인 댓글이거나 관리자 (빨간색으로 표시)

화면 다른 곳 클릭 시 document click 이벤트로 모든 드롭다운 자동 닫힘


9.3 댓글 앵커 및 스크롤

댓글 등록 후 페이지가 reload되면 URL에 #comment-{id} 해시가 포함됩니다. 페이지 로드 후 아래 동작이 수행됩니다:
  • id="comment-{id}" 요소를 scrollIntoView({ behavior: "smooth", block: "center" })
  • 해당 댓글 배경을 노란색(#fffbeb)으로 하이라이트
  • 1.5초 전환 효과(CSS transition) 후 원래 배경색으로 복귀


9.4 댓글 작성 폼 UI

상태 UI 표시
로그인 + 작성 권한 있음 textarea(또는 에디터) + "댓글 등록" 버튼
미로그인 (comment_level=1 이상) 자물쇠 아이콘 + "로그인 후 댓글을 작성할 수 있습니다." + 로그인 링크
use_comment=0 (댓글 비활성화) 댓글 섹션 자체가 표시되지 않음


10장. 게시글 지표 자동 갱신


10.1 comment_count 자동 갱신

댓글 등록/삭제 시 게시글의 comment_count 컬럼이 자동으로 갱신됩니다:
  • 등록 시 — UPDATE posts SET comment_count = comment_count + 1, updated_at = ? WHERE id = ?
  • 삭제 시 — UPDATE posts SET comment_count = GREATEST(0, comment_count - 1) WHERE id = ?
GREATEST(0, ...) 함수로 0 이하로 내려가지 않도록 보호합니다.


10.2 popular_score 재계산

댓글 등록 또는 삭제 후 인기점수를 즉시 재계산합니다. 계산 공식:

기본 점수 = 조회수 × 1 + 좋아요 × 5 + 댓글수 × 3
시간 감쇠 = max(0.1, 1.0 - floor(경과일수 / 7) × 0.1)
popular_score = round(기본점수 × 시간감쇠)

이 점수로 ?sf=popular 정렬 시 최신 인기글이 상위에 노출됩니다.


11장. 댓글 훅(Hook) 포인트

플러그인이나 테마에서 댓글 동작을 커스터마이징할 때 사용하는 훅입니다.
 
훅 이름 발생 시점 및 전달 인자
dx_after_comment 댓글 등록 완료 직후 — comment_id, post_id, board 전달
 
💡 훅 활용 예시 — 댓글 등록 후 외부 알림
// 댓글 등록 시 슬랙 웹훅 발송 예시
dx_add_hook('dx_after_comment', function($args) {
    $commentId = $args['comment_id'];
    $postId    = $args['post_id'];
    // 슬랙 웹훅 또는 외부 알림 처리...
});


12장. 관리자 사용방법


12.1 게시판별 댓글 설정

  1. 관리자 → 게시판 관리 → 해당 게시판 수정
  2. 댓글 사용 여부 — use_comment ON/OFF
  3. 댓글 권한 설정 — comment_level: 0=전체 / 1=회원 / 9=관리자
  4. 저장 후 즉시 적용


12.2 관리자의 댓글 관리

관리자는 게시글 뷰 페이지에서 모든 댓글의 ⋮ 메뉴를 통해 아래 작업을 수행할 수 있습니다:
  • 수정 — 본인 여부와 무관하게 모든 댓글 내용 수정 가능
  • 삭제 — 모든 댓글 삭제 가능 (hard delete, 좋아요도 함께 삭제)
대댓글이 달린 부모 댓글을 삭제해도 대댓글은 그대로 남습니다. 연결된 대댓글까지 모두 정리하려면 직접 삭제해야 합니다.


12.3 댓글 에디터 활성화

  1. 관리자 → 플러그인 관리 → 에디터 플러그인 (CKEditor4 등) 설정
  2. "댓글 에디터 사용" 옵션 ON
  3. 저장 후 게시글 뷰 페이지의 댓글 입력란이 에디터로 교체됨
에디터 ON 상태에서는 댓글에 볼드, 이탤릭, 링크, 표, 이미지 삽입 등이 가능합니다.


12.4 댓글 알림 확인

  1. 상단 알림 벨 아이콘 클릭
  2. 미읽음 알림 목록에서 댓글 알림 확인
  3. 알림 클릭 시 해당 게시글 view + #comment-{id} 앵커로 이동
  4. 알림 읽음 처리 — 클릭 시 is_read = 1 업데이트


13장. 스킨에서 댓글 커스터마이징


13.1 뷰 파일에서 사용 가능한 댓글 변수

변수 설명
$comments 댓글 배열 — dx_comments + 작성자 정보 (LEFT JOIN members). id 오름차순
$board['use_comment'] 댓글 활성화 여부 (1=활성, 0=비활성)
$board['comment_level'] 댓글 작성 권한 (0/1/9)
$_useCommentEditor dx_editor_use_comment() 반환값 — 에디터 사용 여부 (bool)
$post['comment_count'] 게시글의 댓글 수 (DB 캐시 값, 실제 count와 다를 수 있음)


13.2 댓글 목록 데이터 구조

$comments 배열의 각 요소는 아래 필드를 포함합니다:
id           // 댓글 ID
post_id      // 소속 게시글 ID
parent_id    // 부모 댓글 ID (0=최상위)
depth        // 계층 깊이 (0, 1, 2 ...)
member_id    // 작성 회원 ID (0=비회원)
member_name  // 작성 회원 이름 (LEFT JOIN)
member_profile_img  // 프로필 이미지 경로
author_name  // 비회원 작성자명
content      // 댓글 내용
ip           // 작성자 IP
status       // 상태 (1=정상)
created_at   // 작성일시


13.3 커스텀 댓글 렌더링 예시

스킨의 view.php에서 기본 댓글 구조를 직접 구현하는 최소 예시:
<?php
// 트리 구조로 변환
$cmtMap      = array();
$cmtChildren = array();
foreach ($comments as $c) {
    $cmtMap[$c['id']] = $c;
    $cmtChildren[$c['parent_id']][] = $c['id'];
}

// 최상위 댓글부터 재귀 렌더
function renderCmt($id, $map, $children) {
    if (!isset($map[$id])) return;
    $c = $map[$id];
    $indent = (int)$c['depth'] * 20;
    echo "<div style='margin-left:{$indent}px'>";
    echo "<b>" . htmlspecialchars($c['member_name'] ?: $c['author_name']) . "</b>: ";
    echo htmlspecialchars($c['content']);
    echo "</div>";
    foreach ($children[$id] ?? [] as $childId) {
        renderCmt($childId, $map, $children);
    }
}
if (!empty($cmtChildren['0'])) {
    foreach ($cmtChildren['0'] as $rootId) {
        renderCmt($rootId, $cmtMap, $cmtChildren);
    }
}


14장. 오류 처리 및 디버깅


14.1 자주 발생하는 오류와 해결 방법

증상 원인 및 해결
댓글 등록 후 아무 반응 없음 JS 오류 확인 (브라우저 콘솔). CSRF 토큰 만료 또는 content 값이 비어있는 경우
"댓글 내용을 입력하세요." 오류 CKEditor 내용이 비어있거나 comment_content POST 값이 빈 문자열. DX_DEBUG=true로 서버 로그 확인
depth 컬럼 없음 오류 migrate.php 미실행. 오류 발생 시 자동으로 depth 컬럼 없이 저장 (대댓글 들여쓰기 불가)
삭제 후 comment_count 불일치 하드 삭제는 됐으나 count 갱신 실패. data/error.log 확인 후 직접 UPDATE로 수정
대댓글 답글 폼이 열리지 않음 dxReplyToggle() 호출 시 요소 ID 불일치. reply-{id} 요소 존재 여부 DOM 확인


14.2 data/error.log 로그 패턴

댓글 관련 오류는 아래 접두사로 로그에 기록됩니다:
  • [comment_api_delete] — 댓글 삭제 API 오류
  • [Comment] — 댓글 등록/수정 오류
  • [comment_delete] — 레거시 삭제 API 오류

💡 댓글 시스템 디버깅 체크리스트
1. 브라우저 DevTools → Network 탭에서 /api/comment 요청/응답 확인
2. data/error.log 파일에서 [Comment] 또는 [comment_api] 로그 확인
3. DX_DEBUG=true 설정 시 PHP 경고가 화면 하단에 표시됨
4. dx_comments 테이블에 INSERT 여부 직접 DB 쿼리로 확인
5. CSRF 토큰 문제: 브라우저 쿠키/세션 초기화 후 재시도

댓글0

로그인 후 댓글을 작성할 수 있습니다.
6. 게시판 스킨 DX마켓 등록 2026.05.01 6. 게시판 게시판 스킨 제작 2026.05.01 6. 게시판 댓글 및 답글 구조 2026.05.01 6. 게시판 게시판 구조 2026.05.01
30
전체 회원
269
전체 게시글
144
전체 댓글
181
오늘 방문
28,530
전체 방문
1
현재 접속
인기글 7일 이내
최신글
최신댓글
목록