网站开启页面缓存或 CDN 后,文章阅读数容易出现一个问题:页面已经被静态缓存,PHP 中的阅读数不会随着访问实时更新。
解决方案是将文章页面和阅读数分开处理:
- 首页/列表页:REST API 批量读取阅读数,不增加计数;
- 文章详情页:通过 POST 请求使阅读数 +1;
- 首页一次请求获取所有文章的阅读数;
- AJAX 加载更多后,自动读取新加载文章的阅读数。
一、直接读取数据库中的阅读数
Sakura 主题原来的 get_post_views() 会经过 restyle_text() 格式化阅读数。
例如数据库中的实际数据:
22492
通过 get_post_views() 可能得到:
22
因此 REST API 中直接使用:
get_post_meta($post_id, 'views', true)
获取原始数据。
二、创建 REST API
将下面代码加入 functions.php:
// ======================================================
// Sakura 文章阅读数 REST API
//
// GET /wp-json/sakura/v1/views?ids=1,2,3
// 批量读取阅读数,不增加
//
// POST /wp-json/sakura/v1/views/738
// 单篇文章阅读数 +1
// ======================================================
add_action('rest_api_init', function () {
// 批量读取阅读数
register_rest_route('sakura/v1', '/views', array(
'methods' => 'GET',
'callback' => function ($request) {
$ids = $request->get_param('ids');
if (!$ids) {
return new WP_Error(
'missing_ids',
'缺少文章ID',
array('status' => 400)
);
}
$ids = explode(',', $ids);
$result = array();
foreach ($ids as $id) {
$post_id = intval($id);
if (!$post_id) {
continue;
}
if (get_post_status($post_id) !== 'publish') {
continue;
}
$views = intval(
get_post_meta(
$post_id,
'views',
true
)
);
$result[$post_id] = $views;
}
return array(
'views' => $result
);
},
'permission_callback' => '__return_true'
));
// 单篇文章阅读数 +1
register_rest_route(
'sakura/v1',
'/views/(?P<id>\d+)',
array(
'methods' => 'POST',
'callback' => function ($request) {
$post_id = intval($request['id']);
if (
!$post_id ||
get_post_status($post_id) !== 'publish'
) {
return new WP_Error(
'invalid_post',
'文章不存在',
array('status' => 404)
);
}
$views = intval(
get_post_meta(
$post_id,
'views',
true
)
);
$views++;
update_post_meta(
$post_id,
'views',
$views
);
return array(
'post_id' => $post_id,
'views' => $views
);
},
'permission_callback' => '__return_true'
)
);
});
例如批量读取:
/wp-json/sakura/v1/views?ids=738,739,740
返回:
{
"views": {
"738": 22499,
"739": 1256,
"740": 86
}
}
而文章详情页:
/wp-json/sakura/v1/views/738
使用 POST 请求即可让文章阅读数 +1。
三、修改文章模板
原来的阅读数不要直接使用 get_post_views(),改成占位元素:
<span
class="post-views"
data-post-id="<?php the_ID(); ?>"
>
Loading...
</span>
通过 data-post-id 保存文章 ID,JavaScript 根据这个 ID 获取阅读数。
四、前端 JavaScript
首页通过一个 GET 请求批量获取所有文章的阅读数:
document.addEventListener('DOMContentLoaded', function () {
function formatViews(views) {
views = parseInt(views, 10) || 0;
if (views >= 1000) {
return (views / 1000).toFixed(1) + 'k';
}
return views;
}
var isSinglePost =
document.querySelector('.entry-census') !== null;
// ==================================================
// 文章详情页:POST +1
// ==================================================
if (isSinglePost) {
document.querySelectorAll('.post-views')
.forEach(function (el) {
var postId =
el.getAttribute('data-post-id');
if (!postId) {
return;
}
fetch(
'/wp-json/sakura/v1/views/' + postId,
{
method: 'POST',
cache: 'no-store'
}
)
.then(function (response) {
return response.json();
})
.then(function (data) {
el.textContent =
formatViews(data.views) +
' 次阅读';
});
});
return;
}
// ==================================================
// 首页 / 列表页:批量 GET
// ==================================================
function loadViews() {
var elements =
document.querySelectorAll(
'.post-views'
);
var ids = [];
elements.forEach(function (el) {
var postId =
el.getAttribute('data-post-id');
if (
postId &&
el.getAttribute(
'data-views-loaded'
) !== '1'
) {
ids.push(postId);
}
});
if (!ids.length) {
return;
}
ids = ids.filter(function (id, index) {
return ids.indexOf(id) === index;
});
var url =
'/wp-json/sakura/v1/views' +
'?ids=' + ids.join(',') +
'&t=' + Date.now();
fetch(url, {
method: 'GET',
cache: 'no-store'
})
.then(function (response) {
return response.json();
})
.then(function (data) {
var views = data.views || {};
elements.forEach(function (el) {
var postId =
el.getAttribute('data-post-id');
if (
Object.prototype.hasOwnProperty.call(
views,
postId
)
) {
el.textContent =
formatViews(views[postId]) +
' 热度';
el.setAttribute(
'data-views-loaded',
'1'
);
}
});
});
}
// 第一次加载
loadViews();
// ==================================================
// 监听 AJAX 加载更多
// ==================================================
var timer = null;
var observer =
new MutationObserver(function (mutations) {
var hasNewViews = false;
mutations.forEach(function (mutation) {
mutation.addedNodes.forEach(
function (node) {
if (node.nodeType !== 1) {
return;
}
if (
(
node.classList &&
node.classList.contains(
'post-views'
)
) ||
(
node.querySelector &&
node.querySelector(
'.post-views'
)
)
) {
hasNewViews = true;
}
}
);
});
if (!hasNewViews) {
return;
}
clearTimeout(timer);
timer = setTimeout(function () {
loadViews();
}, 100);
});
observer.observe(document.body, {
childList: true,
subtree: true
});
});



Comments | NOTHING