오라클 클라우드에 직접 리눅스 서버를 설치하고 워드프레스 블로그를 웹서버와 DB서버로 분리하여 구축하여 운영한지 이제 3년에 접어든다. 그동안 수없이 많은 악성 봇, 스팸댓글과의 싸움이 있었고 테마 커스터머이징을 비롯해 댓글 작성 시 알림 메일 발송 등 필요한 기능들을 직접 구현하기도 했다. 그리고 어제 방문자 카운터를 직접 플러그인으로 제작해 적용했다. 방문자 카운터를 직접 코딩하고 플러그인으로 등록해 사이드바에 표시하는 과정을 기록으로 남긴다.
워드프레스 방문자 카운터 코딩
일단 코딩은 AI의 도움을 받으면 무척 편리하다. VSCode에 제미나이 AI 플러그인을 설치하고 요구사항을 잘 정리해 질문하면 다음과 같이 워드프레스에 플러그인으로 사용할 수 있는 코드를 제공해준다. 먼저 Java Script 파일이다.
// 파일명 : wp-counter.js
jQuery(document).ready(function($) {
// 위젯(숏코드)이 페이지에 존재하는지 확인
if ($('#adc-counter-box').length) {
$.ajax({
url: adc_vars.ajax_url,
type: 'post',
data: {
action: 'adc_increment_counter',
security: adc_vars.nonce
},
success: function(response) {
if (response.success) {
// 서버에서 받은 데이터를 각각의 span에 넣기
$('#adc-today').text(response.data.today);
$('#adc-total').text(response.data.total);
} else {
console.log('Stats Error');
}
}
});
});
이 Java Script는 블로그에 방문한 이용자의 웹 브라우저에서 실행되며 어느 포스트를 조회하고 있는지를 블로그 서버(워드프레스)로 전송해주는 역할을 수행한다.
코드의 윗쪽에 있는 첫 번째 $.ajax 블록이 브라우저에서 방문한 url을 추출해 post 메소드로 adc_increment_counter 액션을 수행하도록 워드프레스에게 요청하고 성공할 경우 받은 응답(방문자 카운터)을 화면에 출력하는 코드다.
그리고 이제 wp-counter.js 가 실행되어 전송한 요청을 처리할 .php 파일이다.
<?php
/**
* Plugin Name: Advanced Daily Visitor Counter
* Description: 워드프레스 DB에 별도 테이블을 생성하여 일일방문자 수 및 해당일까지의 전체방문자를 기록하고 포스트 별 누적 방문자를 기록하는 방문자 카운터 플러그인
* Version: 1.0
* Author: taeho kim
*/
if (!defined('ABSPATH')) {
exit;
}
// 1. 테이블 생성 (워드프레스에서 플러그인 설치 활성화 시 1회 실행)
function adc_create_table() {
global $wpdb;
$table_name = $wpdb->prefix . 'counter_total'; // 실제 전체 방문자수가 저장되는 테이블 이름: wp_counter_total
$table_post_name = $wpdb->prefix . 'counter_post'; // 실제 포스트별 방문자수가 저장되는 테이블 이름: wp_counter_post
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE $table_name (
id bigint(20) NOT NULL AUTO_INCREMENT,
visit_date date NOT NULL,
day_of_week varchar(10) NOT NULL,
daily_count int(11) DEFAULT 0,
total_count bigint(20) DEFAULT 0,
PRIMARY KEY (id),
UNIQUE KEY visit_date (visit_date)
) $charset_collate;";
$sql_post = "CREATE TABLE $table_post_name (
id bigint(20) NOT NULL AUTO_INCREMENT,
post_id bigint(20) NOT NULL DEFAULT 0,
post_path varchar(191) NOT NULL,
visit_count bigint(20) DEFAULT 0,
PRIMARY KEY (id),
KEY post_id (post_id),
UNIQUE KEY post_path (post_path)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
dbDelta($sql_post);
}
register_activation_hook(__FILE__, 'adc_create_table');
// 2. 스크립트 등록
function adc_enqueue_scripts() {
wp_enqueue_script('adc-js', plugin_dir_url(__FILE__) . 'wp-counter.js', array('jquery'), '1.0', true);
wp_localize_script('adc-js', 'adc_vars', array(
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('adc_nonce_security')
));
}
add_action('wp_enqueue_scripts', 'adc_enqueue_scripts');
// 3. 숏코드 등록 [daily_stats]
function adc_shortcode_display() {
return '
<div id="adc-counter-box" class="adc-widget">
<div class="adc-row">오늘 방문자: <span id="adc-today">...</span></div>
<div class="adc-row">전체 방문자: <span id="adc-total">...</span></div>
</div>';
}
add_shortcode('daily_stats', 'adc_shortcode_display');
// 4. AJAX 처리 (방문자 집계 및 조회)
function adc_process_counter() {
check_ajax_referer('adc_nonce_security', 'security');
global $wpdb;
$table_name = $wpdb->prefix . 'counter_total';
$table_post_name = $wpdb->prefix . 'counter_post';
// 워드프레스 설정된 타임존 기준 오늘 날짜 가져오기
$today_date = current_time('Y-m-d');
// 봇 제외 로직 (간단 버전)
$is_bot = preg_match('/bot|crawl|slurp|spider/i', $_SERVER['HTTP_USER_AGENT']);
// 쿠키 확인 (오늘 하루 동안 중복 카운트 방지)
$cookie_name = 'adc_visited_' . $today_date;
$should_count = (!$is_bot && !isset($_COOKIE[$cookie_name]));
if ($should_count) {
// --- [트랜잭션 시작] 카운트 증가 로직 ---
// 1. 오늘 날짜의 데이터가 있는지 확인
$row = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table_name WHERE visit_date = %s", $today_date));
if ($row) {
// A. 오늘 기록이 이미 있으면 -> daily + 1, total + 1
$wpdb->update(
$table_name,
array(
'daily_count' => $row->daily_count + 1,
'total_count' => $row->total_count + 1 // 현재 전체값에 +1
),
array('visit_date' => $today_date)
);
} else {
// B. 오늘 기록이 없으면 (자정이 지나서 첫 방문) -> 새로 삽입
// 어제까지의 가장 큰 전체 방문자 수 가져오기 (없으면 0)
$last_total = $wpdb->get_var("SELECT MAX(total_count) FROM $table_name");
if (!$last_total) $last_total = 0;
$current_total = $last_total + 1;
// 한글 요일 구하기
$days = array('일', '월', '화', '수', '목', '금', '토');
$day_of_week = $days[date('w', current_time('timestamp'))];
$wpdb->insert(
$table_name,
array(
'visit_date' => $today_date,
'day_of_week' => $day_of_week,
'daily_count' => 1,
'total_count' => $current_total
)
);
}
// 쿠키 설정 (자정까지 유효하도록 설정하거나 24시간)
setcookie($cookie_name, 'yes', time() + 86400, COOKIEPATH, COOKIE_DOMAIN);
}
// --- [글별 카운트 로직] ---
// Referer를 통해 현재 페이지 경로 파악 (도메인 제외한 URL)
$referer_url = wp_get_referer();
$post_count = 0;
if ($referer_url) {
$parsed_path = parse_url($referer_url, PHP_URL_PATH);
if (!$parsed_path) $parsed_path = '/';
// URL을 통해 Post ID 가져오기 (wp_posts 테이블과 연결용)
$post_id = url_to_postid($referer_url);
// 봇이 아닐 경우 카운트 증가 (글별 카운트는 '방문할 때마다' 증가하므로 쿠키 체크 제외)
if (!$is_bot) {
$row_post = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table_post_name WHERE post_path = %s", $parsed_path));
if ($row_post) {
$wpdb->update(
$table_post_name,
array(
'visit_count' => $row_post->visit_count + 1,
'post_id' => $post_id
),
array('id' => $row_post->id)
);
$post_count = $row_post->visit_count + 1;
} else {
$wpdb->insert($table_post_name, array(
'post_path' => $parsed_path,
'post_id' => $post_id,
'visit_count' => 1
));
$post_count = 1;
}
} else {
// 봇인 경우 조회만 수행
$post_count = $wpdb->get_var($wpdb->prepare("SELECT visit_count FROM $table_post_name WHERE post_path = %s", $parsed_path));
}
}
// --- [결과 반환] 화면 표시용 데이터 조회 ---
// 다시 한 번 DB에서 최신 값을 조회해서 반환 (정확성 보장)
$latest_data = $wpdb->get_row($wpdb->prepare("SELECT daily_count, total_count FROM $table_name WHERE visit_date = %s", $today_date));
// 만약 조회 시점과 쓰기 시점 차이로 오늘 데이터가 없으면(방금 생성 실패 등), 전체 맥스값이라도 가져옴
if (!$latest_data) {
$total = $wpdb->get_var("SELECT MAX(total_count) FROM $table_name");
$daily = 0;
} else {
$daily = $latest_data->daily_count;
$total = $latest_data->total_count;
}
wp_send_json_success(array(
'today' => number_format($daily),
'total' => number_format($total ? $total : 0),
'post_total' => number_format($post_count ? $post_count : 0)
));
}
add_action('wp_ajax_adc_increment_counter', 'adc_process_counter');
add_action('wp_ajax_nopriv_adc_increment_counter', 'adc_process_counter');
중간에 보면 봇 제외로직도 추가해준다. User-Agent 정보에 bot, craw 등 있을 경우 카운트하지 않는다. bot을 모두 차단할 수는 없다. bot을 모두 차단하면 구글, 네이버 등 검색엔진의 봇도 차단되기 때문이다. 차단할 봇은 .htaccess 등에서 IP와 user-agent 차단 기능을 통해 차단하고 여기에서는 허용된 봇에 대해 방문자 카운트를 하지 않는 정도로 처리해주는 것이 좋다.
이 두 파일이면 플러그인의 코딩은 완료다. 이제 이 두개의 파일을 다음과 같이 서버의 플러그인 이름으로 폴더를 만들고 저장한다.

그리고 폴더의 상위폴더에서 zip 파일로 묶어준다.

워드프레스에 플러그인으로 설치
zip 으로 압축한 파일을 워드프레스의 플러그인 목록 화면에서 등록해주면 된다. 아래 화면같이 설치되어 있는 플러그인 목록화면에서 “플러그인 추가” 버튼을 눌러 플러그인 추가 화면으로 간다.

방문자 카운터 플러그인을 마켓에서 설치할 수도 있지만 직접 제작한 플러그인을 설치해야 하므로 “플러그인 업로드” 버튼을 누른다.

플러그인 파일을 업로드할 수 있는 화면이 표시된다. zip 파일로 압축한 플러그인 파일을 드래그앤드랍 하거나 “파일선택” 버튼을 눌러 압축해둔 파일을 선택하면 된다.

“파일 선택” 버튼을 누르면 표시되는 파일 선택 창에서 zip으로 압축해둔 플러그인 파일을 선택하면 업로드가 되며 다음 화면과 같이 “지금 설치” 버튼이 활성화 된다. 이 버튼을 누르면 플러그인이 설치된다.

설치가 완료되면 다시 설치된 플러그인 목록 화면으로 이동된다. 설치 직후에는 “활성화” 링크가 보이는데 바로 “활성화”를 클릭한다. 그러면 아래 화면처럼 “비활성화”할 수 있는 링크가 보인다. “비활성화” 링크가 보인다면 정상적으로 활성화 된 것이다.

플러그인의 이름과, 설명에 플러그인의 PHP 파일 상단에 있는 플러그인의 이름, 설명, 작성자, 버전이 화면에 표시된다.
방문자 카운터 위젯 추가
플러그인을 설치하면 사이드바 등 원하는 위치에 방문자 카운터를 표시할 수 있다. 필자는 사이드바의 가장 하단에 표시하고자 한다. 워드프레스 관리자 페이지에서 “모양”의 “위젯” 메뉴로 이동한다.

위젯 메뉴에 가면 현재 블로그에 표시되고 있는 사이드바가 표시된다. 사이드바의 아래쪽으로 이동하면 필자의 경우 다음과 같이 최근 댓글이 표시되는 위젯이 있다. 그 아래에 적당한 타이틀을 사용자 정의 HTML 위젯으로 표시해주고 그 아래에 동일하게 “사용자 정의 HTML” 위젯을 추가한 다음 “코드 편집”을 눌러 다음 화면과 같이
를 입력해 준다.
“코드 편집”을 누르면 HTML을 입력하는 창이 보이게 되는데 다음과 같이 치환자
를 입력해주면 된다.
적용된 방문자 카운터
코드에 에러가 없고 플러그인과 위젯이 정상적으로 설정되었다면 이제 다음과 같이 방문자 카운터가 표시된다.

사이드바 하단에 방문자 카운터가 표시된 것을 볼 수 있다. 아마도 그다지 예쁘지 않은 스타일로 표시가 되는데 “모양” > “사용자 정의” > “추가 CSS”에 다음의 CSS 코드를 위 화면과 같이 저장하면 원하는 스타일로 수정할 수 있다.
/* 방문자 카운터 스타일 시작 */
.adc-widget {
border: 1px solid #ddd;
padding: 15px 40px;
background: #f9f9f9;
border-radius: 8px;
font-size: 14px;
width: fit-content;
margin: 0 auto;
text-align: center;
}
.adc-row {
margin-bottom: 5px;
display: flex;
justify-content: center;
align-items: left;
gap: 15px;
white-space: nowrap;
}
.adc-row span {
font-weight: bold;
color: #0073aa;
}
/* 방문자 카운터 스타일 끝 */
#워드프레스플러그인 #워드프레스 #방문자카운터
답글 남기기