このようなニーズによく遭遇します: 赤い封筒は 5 分以内に 2 回受け取ることができない、1 分以内に 2 回以上の抽選はできない、または特定のインターフェイスには IP ごとに 50 回しかアクセスできない日、この要件はアクセス頻度制限です。
PHP を使用する友人は APC に精通しているはずです。APC はオペコードのキャッシュに使用できるだけでなく、共有メモリとしても使用できます。 APC の 2 番目の機能を使用すると、NGINX の limit_req モジュールと同様のアクセス頻度制限機能を実行できます。
アクセス制限のポイントは、各アクセスのリクエスト時刻を把握し、現在時刻から繰り上げた時間帯に何回のアクセスが繰り返されているかを確認することです。 APC を使用すると、ttl の特性を設定できます。これは非常に簡単に行うことができます。
apc_store($uniqid, 1, $period); // $period为限制的时间,以“1分钟只能抽两次奖”这个需求为例,$period = 60
ここで、uniqid 変数は、アクセスが制限されたオブジェクトの一意の識別子です。ユーザーのID、携帯端末番号、IPなど
上記のコードにはいくつかの問題があります。$period 期間内に複数回のアクセスが許可されている場合、$key を変更しないことはできません。そうしないと、後続のアクセスで $key セットが変更されます。有効期限を置き換えたとしても、$period の期間内に何回のアクセスがあったかを判断することはできません。
$key は置き換えることができないため、アクセスごとに異なる $key を生成することを考えるしかありません。これは、microtime 関数と mt_rand 関数を使用して簡単に実行できます。
$key = sprintf('%s.%s%s', %uniqid, $microtime(true), mt_rand(0, 99999));app_store($key, 1, $period);
PHP 関数マニュアルによると、apc_* 関数はキー接頭辞を介してすべてのキーを取得するメソッドを提供していません。そのようなメソッドが存在しない場合、上記の考えは再び行き詰まります。 APC は APCIterator というクラスも提供します。それは私たちのニーズを解決するためのパズルの最も重要なピースです。
公式ドキュメントによると、このクラス コンストラクターの $search パラメーターは必要な機能を提供します。通常のメソッドによるキーのマッチングです。
$iter = new APCIterator('user', sprintf('/^%s\./', preg_quote($uniqid)));
ドキュメントより、 APCIterator には getTotalCount メソッドがあることがわかります。このメソッドの名前に騙されないでください。また、このメソッドによって返される数値には、実際には期限切れのキーが含まれています。 PHP の公式ドキュメントはほとんど役に立たず、この方法は非常にわかりにくいです。
もう 1 つ: APCIterator には getHitsCount というメソッドもあります。ここでのヒット数は、$search が一致するかどうかではなく、apc_fetch を使用したときに現在のスクリプトがヒットするかどうかを参照します。実際、この方法は非常に簡単ですが、ドキュメントには何も書かれていないため、自分で実験して推測する必要があり、残念です。
このイテレーターを走査するために foreach を直接使用します。走査の結果が実際に正しく (なぜそう言えるのか...)、有効期間内のすべてのキーが存在することがわかります。有効期間内にないキーは、まさに私たちが望んでいた効果ではありません。 「誰もが 1 分以内に 2 つのドローしか引けない」という要件に対する答えがついに見つかりました:
if (iterator_count($iter) >= $count) { // 例子里$count = 2 header('status: 429 Too Many Requests'); die();}
もちろん、まだ改善の余地はあります。期間の長さと異なるアクセス時間は異なる制限戦略に属する必要があるため、$key は期間の長さとアクセス時間を反映する必要があります:
$key = sprintf('%s.%s.%s', $uniqid, $period, $count);
コードは少し分散していますが、それがアイデアです。誰もがより明確に理解できるように、コードを関数に合成しました。
function ratelimit($uniqid, $period, $count){ // ...省去参数检查的代码 $key = sprintf('%s.%s.%s',