アクセスカウンターと拍手の実装

日付
閲覧数
拍手

個人サイトといえばやはりアクセスカウンターとWeb拍手、これらは承認欲求を満たすための二大要素です。 従前であればCGIを使って実装していたと思いますが、さいきんなのでCloudflare D1で実装しました。 実装はぜんぶClaude Codeにおまかせなので、こういうかんじでっていう設計しかしていません。以下の説明もClaude Codeに書いてもらったのでそういうことでよろしくおねがいします。

このサイトharukasan.devはCloudflare Pagesで構成されておりHonoXを使ってつくられています。 サイト本体はSSGしていますが、Pagesの前段にはWorkerがいるので、動的なエンドポイントだけはWorkerで動かすことができます。

islandで数字を出す

記事のHTMLはSSGされた静的ファイルなので、数字はビルドした時点で固定されてしまいます。そこで数字の部分だけをHonoXのislandにして、ページを開いたあとにブラウザから取りにいきます。日付の隣の閲覧数と拍手数、記事末尾の拍手ボタンがそれぞれislandです。取得できるまでは何も描かないので、数字があとから出てきてもレイアウトは動きません。

かぞえかた

閲覧数はのべ日次ユニーク訪問者数です。同じ人が同じ記事を何度リロードしても、その日は1しか増えません。翌日になるとまた1増えます。「同じ人」は日次ハッシュで見ています。秘密の値・日付・IPアドレス・User-Agentのハッシュで、日付が入っているので日をまたぐと別の値になります。

テーブルは3つです。

-- 記事ごとの累計
CREATE TABLE page_counts (
  path TEXT PRIMARY KEY,
  count INTEGER NOT NULL DEFAULT 0,
  claps INTEGER NOT NULL DEFAULT 0
) WITHOUT ROWID;

-- 閲覧数の重複排除用。visitor は日次ハッシュ
CREATE TABLE page_count_visitors (
  path TEXT NOT NULL,
  visitor TEXT NOT NULL,
  created_at INTEGER NOT NULL DEFAULT (unixepoch()),
  PRIMARY KEY (path, visitor)
) WITHOUT ROWID;

-- 拍手の日次上限用。claps はその訪問者の今日の回数
CREATE TABLE page_clap_visitors (
  path TEXT NOT NULL,
  visitor TEXT NOT NULL,
  claps INTEGER NOT NULL DEFAULT 0,
  created_at INTEGER NOT NULL DEFAULT (unixepoch()),
  PRIMARY KEY (path, visitor)
) WITHOUT ROWID;

累計は page_counts の列に持っているので、表示のたびに数えなおすことはありません。日次ハッシュの行は2日で消します。

加算はページを開いたときにブラウザから送るPOSTの処理でおこないます。SQLはこの2文です。

INSERT INTO page_counts (path, count) VALUES (:path,
  1 - coalesce((SELECT 1 FROM page_count_visitors
                WHERE path = :path AND visitor = :visitor), 0))
ON CONFLICT (path) DO UPDATE SET count = count +
  (1 - coalesce((SELECT 1 FROM page_count_visitors
                 WHERE path = :path AND visitor = :visitor), 0));

INSERT OR IGNORE INTO page_count_visitors (path, visitor)
  VALUES (:path, :visitor);

その訪問者をその日まだ記録していなければ1、記録済みなら0になる式を足しこみます。増える量をSQLのなかで決めるのがポイントで、アプリが読んでから書くと、途中で失敗したときに訪問者の記録だけ残って二度と数えられなくなります。2文は1トランザクションで実行します。

拍手は1人が1記事につき1日10回までです。連打されるので1クリックごとには送らず、クリックが止まって1秒たったらまとめて送ります。送るのは差分ではなく「今日この記事に拍手した累計」で、増える量はこう求めます。

max(:total - coalesce((SELECT claps FROM page_clap_visitors
                       WHERE path = :path AND visitor = :visitor), 0), 0)

同じ数字が何度届いても2回目からは0です。送信が重なってもタブが複数あっても二重になりません。

というわけで、ここまで読んでいただいた方はぜひ拍手ボタンを押していってください。1日10回まで押せます。

0 / 10