Serverless статистика для Ghost блога на примере Cloudflare workers + KV за 5 минут

June 13, 2020

Делаем простую статистику и лайки за 5 минут, используя воркеры Cloudflare с применеием Key Value хранилища от них же.

Задача:

После перезда с medium.com на свой собственный блог (в качестве движка которого я использую Ghos 3й версии, так как он мне очень нравится) я не смог найти чего-то удобного для статистики и лайков. Все же хорошей мотивацией является то, что мои статьи кому-то приносят пользу и интересны, так что если не сложно - ставьте лайк :)

И так, готового плагина не нашел, но и писать свой бэкенд как-то не очень-то и хотелось. И тут я подумал, а почему бы не заюзать воркеры для этой задачи + KV от моего любимого Cloudflare?

Первое что нам нужно - завести два неймпсейса - по сути две таблицы:

Тут вроде бы все просто. Далее создаем два воркера. Один воркер считает статистику просмотров, второй воркер обрабатывает "хлопки" (в терминах medium.com :))

На вкладке воркеров создаем два воркера. Код воркеров показан ниже.

Код воркера для обработки лайков. Заходим в редактор:

и пишем код:


addEventListener('fetch',e=>{e.respondWith(handleRequest(e.request))});

const res = json => new Response( JSON.stringify(json), { status: 200, headers: [ [‘Content-Type’, ‘application/json’], [ ‘Access-Control-Allow-Origin’, ’https://tech.geekjob.ru’ ] ] } ) ;

async function handleRequest(req) { let url = new URL(req.url); let key = url.searchParams.get(‘k’);

if (!key)
    return res({ error: true, message: 'Bad key' });

let likes = parseInt(await GJ_BLOG_LIKES.get(key)) || 0;
await GJ_BLOG_LIKES.put(key, ++likes);

return res({ likes })

}

Для удобства я описал функцию res, чтобы было удобно возвращать ответ.

Код простой - обращаемся к нашему хранилищу, читаем данные, приводим к числу, увеличиваем счетчик и возвращаем ответ.

KV поддерживает всего 3 типа:

  • string
  • ReadableStream
  • ArrayBuffer

При этом при чтении вы можете указать какой тип возвращается и там есть возможность сразу преобразовать JSON строку в объект:

NAMESPACE.get(key, type)

The type parameter can be any of:

  • “text”: (default) a string
  • “json”: an object decoded from a JSON string
  • “arrayBuffer”: An ArrayBuffer instance.
  • “stream”: A ReadableStream.

Собственно поэтому мы используем parseInt() для преобразования числа в Number.

Если вы попытаетесь запустить ваш воркер - он выдаст ошибку, так как не знает ничего про ваше хранилище еще.

Подключаем KV хранилище

Для этого нужно сохранить ваш воркер, при желании задать ему адекватное имя и выйти их редактора.

Вы должны пробросить внутрь воркера переменную, которую свяжете с вашим KV неймспейсом.

Вот теперь ваш воркер будет работать.

Пишем код воркера для подсчета просмотров и возврата статистики

Код данного воркера я совместил сразу с отображением статистики по просмотрам и лайкам, чтобы можно было показать на странице.


addEventListener(‘fetch’,e=>{e.respondWith(handleRequest(e.request))});

const res = json => new Response( JSON.stringify(json), { status: 200, headers: [ [‘Content-Type’, ‘application/json’], [ ‘Access-Control-Allow-Origin’, ’https://tech.geekjob.ru’ ] ] } ) ;

async function handleRequest(req) { let url = new URL(req.url); let key = url.searchParams.get(‘k’);

if (!key)
    return res({ error: true, message: 'Bad key' });

let views = parseInt(await GJ_BLOG_STAT.get(key)) || 0;
await GJ_BLOG_STAT.put(key, ++views);
let likes = parseInt(await GJ_BLOG_LIKES.get(key)) || 0;

return res({
    views,
    likes
})

}

Так же не забываем пробросить KV внутрь воркера через настройки.

Теперь в админке Ghost, через блок Code Injections я добавил стили и небольшой JS код.

Site Header. Code here will be injected into the {{ghost_head}} tag on every page of the site
<style>
#statinfo {
cursor: pointer;
z-index: 99999;
background: #fff;
position: absolute;
width: 70px;
top: 69px;
right: 2%;
color: #000;
font-size: 12px;
text-align:center;
padding: 4px;
border-radius: 2px;
opacity: .7;
font-weight: bold;
}
#statinfo hr {
height: 1px;
line-height: 1px;
font-size: 1px;
padding: 0;
margin: 2px 0;
border-top: 1px solid #000;
}
</style>
Site Footer. Code here will be injected into the {{ghost_foot}} tag on every page of the site
<!— Код визуального блока кнопки Like со статистикуой —>
<div id=“statinfo” onclick=“likePost()”>
? <span class=“silikes”>?</span>
<hr>
? <span class=“siviews”>?</span>
</div>
<script>
// Создаем имя страницы - по сути только путь без крайних слешей
var pageid = location.pathname.replace(’/’,”).replace(//$/,”) || ‘main’;

document.addEventListener(‘DOMContentLoaded’, function(){ var $statinfo = document.querySelector(‘#statinfo’), $views = $statinfo.querySelector(‘.siviews’), $likes = $statinfo.querySelector(‘.silikes’) ;

// Обрабатываем Like
window.likePost = function() {
    fetch('https://like-blog.geekjob.workers.dev/?k='+pageid)
        .then(d=&gt;d.json())
        .then(data =&gt; {
            if (data.error) return;
            $likes.innerText = data.likes || 0;
        })
        .catch(console.error);        
};

// Засчитываем просмотр страницы и загружаем статистику
fetch('https://stat-blog.geekjob.workers.dev/?k='+pageid)
    .then(d=&gt;d.json())
    .then(data =&gt; {
        if (data.error) return;
        $views.innerText = data.views || 0;
        $likes.innerText = data.likes || 0;
    })
    .catch(console.error);

}) </script>

Собственно что получилось:

Результат вы можете видеть на этой странице если все отработало штатно, без ошибок.

Поставьте лайк, если не сложно :)


Profile picture

Written by Alexander Mayorov
Full Stack CTO