SpringBoot が Aop+Redis を組み合わせてインターフェイスの繰り返し送信を防ぐ方法
実際の開発プロジェクトでは、外部に公開されたインターフェイスが多くのリクエストに直面することがよくあります。冪等性の概念を説明しましょう: 複数の実行の影響は 1 回の実行の影響と同じです。この意味によると、最終的な意味は、データベースへの影響は 1 回限りであり、繰り返し処理することはできないということです。べき等性を確認する方法には通常、次の方法が含まれます:
1. データベース内に一意のインデックスを確立して、最終的に 1 つのデータのみがデータベースに挿入されるようにします。
2. トークンのメカニズム. 各インターフェイス リクエストの前にトークンを取得し、次回リクエストのヘッダー本文にこのトークンを追加します. 検証はバックグラウンドで実行されます. 検証に合格すると、トークンは次へ リクエストごとに再度トークンの判定を行います。
3. 悲観的ロックまたは楽観的ロック. 悲観的ロックを使用すると、他の SQL が更新のたびにデータを更新できないようにできます (データベース エンジンが innodb の場合、テーブル全体がロックされないように、選択条件は一意のインデックスである必要があります)。 )
4. 最初にクエリを実行してから判断します。まず、データベースにクエリを実行してデータが存在するかどうかを確認します。データが存在する場合は、リクエストが行われたことが証明され、リクエストは直接拒否されます。存在しない場合は初入荷であることを証明し、そのまま公開します。
インターフェイスの繰り返しの送信を防止する必要があるのはなぜですか?
新しいデータ インターフェイスや支払いインターフェイスなど、一部の機密性の高い操作インターフェイスでは、ユーザーが送信ボタンを複数回誤ってクリックすると、これらのインターフェイスが複数回要求され、最終的にシステム例外が発生する可能性があります。
フロントエンドはどのように制御できますか?
フロントエンドは js を通じて制御できます。ユーザーが送信ボタンをクリックすると、##1. ボタンが数秒間クリックできないように設定されます。##2. ボタンがクリックされた後、読み込みが行われます。プロンプト ボックスがポップアップ表示され、インターフェイス リクエストが返されるまで再度クリックする必要はありません。その後
3. ボタンをクリックして新しいページにジャンプします。
ただし、ユーザーの動作を決して信用しないことを覚えておいてください。ユーザーがどのような奇妙な操作を行うかはわかりません。そのため、最も重要なことは、バックエンドで処理する必要があるということです。
インターセプト処理に aop redis を使用する
1. アスペクト クラスのRepeatSubmitAspectを作成します実装プロセス: インターフェイス リクエストの後、トークン リクエスト パスをキー値として使用して、redis からデータを読み取ります。キーが見つかると、キーが繰り返し送信されたことが証明され、その逆も同様です。繰り返し送信されない場合は、直接解放され、キーが Redis に書き込まれ、一定期間内に期限切れになるように設定されます (ここでは有効期限を 5 秒に設定しています)
#従来の Web プロジェクトでは、繰り返しの送信を防ぐために、バックエンドで一意の送信トークン (uuid) を生成し、サーバーに保存します。ページがリクエストを開始すると、そのトークンが送信されます。バックエンドは、リクエストの一意性を保証するためにリクエストを検証した後にトークンを削除します。
アイデア
1. 注釈 @NoRepeatSubmit をカスタマイズして、コントローラーで送信されたすべてのリクエストをマークします
3. ビジネス メソッドを実行します前に、現在のユーザーのトークンまたは JSessionId の現在のリクエストアドレスを一意のキーとして取得し、redis 分散ロックを取得します。このとき同時取得を行うと 1 つのスレッドのみが取得できます。
4. ビジネスが実行されたら、ロックを解放します。
Redis 分散ロックについて
Redis を使用するのは負荷分散デプロイメントのためです。スタンドアロン プロジェクトの場合は、ローカルのスレッドセーフ キャッシュが使用されます。 Redis の代わりに使用できます
Code
カスタム アノテーション
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @ClassName NoRepeatSubmit
* @Description 这里描述
* @Author admin
* @Date 2021/3/2 16:16
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NoRepeatSubmit {
/**
* 设置请求锁定时间
*
* @return
*/
int lockTime() default 10;
}
ログイン後にコピー
AOP
package com.hongkun.aop;
/**
* @ClassName RepeatSubmitAspect
* @Description 这里描述
* @Author admin
* @Date 2021/3/2 16:15
*/
import com.hongkun.until.ApiResult;
import com.hongkun.until.Result;
import com.hongkun.until.RedisLock;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* @author liucheng
* @since 2020/01/15
* 防止接口重复提交
*/
@Aspect
@Component
public class RepeatSubmitAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(RepeatSubmitAspect.class);
@Autowired
private RedisLock redisLock;
@Pointcut("@annotation(noRepeatSubmit)")
public void pointCut(NoRepeatSubmit noRepeatSubmit) {
}
@Around("pointCut(noRepeatSubmit)")
public Object around(ProceedingJoinPoint pjp, NoRepeatSubmit noRepeatSubmit) throws Throwable {
int lockSeconds = noRepeatSubmit.lockTime();
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes) ra;
HttpServletRequest request = sra.getRequest();
Assert.notNull(request, "request can not null");
// 此处可以用token或者JSessionId
String token = request.getHeader("token");
String path = request.getServletPath();
String key = getKey(token, path);
String clientId = getClientId();
boolean isSuccess = redisLock.lock(key, clientId, lockSeconds,TimeUnit.SECONDS);
LOGGER.info("tryLock key = [{}], clientId = [{}]", key, clientId);
if (isSuccess) {
LOGGER.info("tryLock success, key = [{}], clientId = [{}]", key, clientId);
// 获取锁成功
Object result;
try {
// 执行进程
result = pjp.proceed();
} finally {
// 解锁
redisLock.unlock(key, clientId);
LOGGER.info("releaseLock success, key = [{}], clientId = [{}]", key, clientId);
}
return result;
} else {
// 获取锁失败,认为是重复提交的请求
LOGGER.info("tryLock fail, key = [{}]", key);
return ApiResult.success(200, "重复请求,请稍后再试", null);
}
}
private String getKey(String token, String path) {
return "00000"+":"+token + path;
}
private String getClientId() {
return UUID.randomUUID().toString();
}
}
ログイン後にコピー
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @ClassName NoRepeatSubmit * @Description 这里描述 * @Author admin * @Date 2021/3/2 16:16 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface NoRepeatSubmit { /** * 设置请求锁定时间 * * @return */ int lockTime() default 10; }
package com.hongkun.aop; /** * @ClassName RepeatSubmitAspect * @Description 这里描述 * @Author admin * @Date 2021/3/2 16:15 */ import com.hongkun.until.ApiResult; import com.hongkun.until.Result; import com.hongkun.until.RedisLock; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.Assert; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.util.UUID; import java.util.concurrent.TimeUnit; /** * @author liucheng * @since 2020/01/15 * 防止接口重复提交 */ @Aspect @Component public class RepeatSubmitAspect { private static final Logger LOGGER = LoggerFactory.getLogger(RepeatSubmitAspect.class); @Autowired private RedisLock redisLock; @Pointcut("@annotation(noRepeatSubmit)") public void pointCut(NoRepeatSubmit noRepeatSubmit) { } @Around("pointCut(noRepeatSubmit)") public Object around(ProceedingJoinPoint pjp, NoRepeatSubmit noRepeatSubmit) throws Throwable { int lockSeconds = noRepeatSubmit.lockTime(); RequestAttributes ra = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes sra = (ServletRequestAttributes) ra; HttpServletRequest request = sra.getRequest(); Assert.notNull(request, "request can not null"); // 此处可以用token或者JSessionId String token = request.getHeader("token"); String path = request.getServletPath(); String key = getKey(token, path); String clientId = getClientId(); boolean isSuccess = redisLock.lock(key, clientId, lockSeconds,TimeUnit.SECONDS); LOGGER.info("tryLock key = [{}], clientId = [{}]", key, clientId); if (isSuccess) { LOGGER.info("tryLock success, key = [{}], clientId = [{}]", key, clientId); // 获取锁成功 Object result; try { // 执行进程 result = pjp.proceed(); } finally { // 解锁 redisLock.unlock(key, clientId); LOGGER.info("releaseLock success, key = [{}], clientId = [{}]", key, clientId); } return result; } else { // 获取锁失败,认为是重复提交的请求 LOGGER.info("tryLock fail, key = [{}]", key); return ApiResult.success(200, "重复请求,请稍后再试", null); } } private String getKey(String token, String path) { return "00000"+":"+token + path; } private String getClientId() { return UUID.randomUUID().toString(); } }
以上がSpringBoot が Aop+Redis を組み合わせてインターフェイスの繰り返し送信を防ぐ方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

AI Hentai Generator
AIヘンタイを無料で生成します。

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中国語版
中国語版、とても使いやすい

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

ホットトピック











1. [スタート]メニューを起動し、[cmd]と入力し、[コマンドプロンプト]を右クリックし、[管理者として実行]を選択します。 2. 次のコマンドを順番に入力します (注意してコピーして貼り付けてください): SCconfigwuauservstart=auto、Enter キーを押す SCconfigbitsstart=auto、Enter キーを押す SCconfigcryptsvcstart=auto、Enter キーを押す SCconfigtrustedinstallerstart=auto、Enter キーを押す SCconfigwuauservtype=share、Enter キーを押す netstopwuauserv 、enter netstopcryptS を押す

PHP 関数のボトルネックはパフォーマンスの低下につながります。これは、ボトルネック関数を特定し、パフォーマンス分析ツールを使用するという手順で解決できます。結果をキャッシュして再計算を減らします。タスクを並列処理して実行効率を向上させます。文字列の連結を最適化し、代わりに組み込み関数を使用します。カスタム関数の代わりに組み込み関数を使用します。

GolangAPI のキャッシュ戦略により、パフォーマンスが向上し、サーバーの負荷が軽減されます。一般的に使用される戦略は、LRU、LFU、FIFO、TTL です。最適化手法には、適切なキャッシュ ストレージの選択、階層型キャッシュ、無効化管理、監視とチューニングが含まれます。実際には、データベースからユーザー情報を取得する API を最適化するために LRU キャッシュが使用されます。それ以外の場合は、データベースからデータを取得した後にキャッシュを更新できます。

PHP 開発では、キャッシュ メカニズムにより、頻繁にアクセスされるデータがメモリまたはディスクに一時的に保存され、データベース アクセスの数が削減され、パフォーマンスが向上します。キャッシュの種類には主にメモリ、ファイル、データベース キャッシュが含まれます。キャッシュは、組み込み関数またはサードパーティのライブラリ (cache_get() や Memcache など) を使用して PHP に実装できます。一般的な実用的なアプリケーションには、データベース クエリ結果をキャッシュしてクエリ パフォーマンスを最適化したり、ページ出力をキャッシュしてレンダリングを高速化したりすることが含まれます。キャッシュ メカニズムにより、Web サイトの応答速度が効果的に向上し、ユーザー エクスペリエンスが向上し、サーバーの負荷が軽減されます。

Redis キャッシュを使用すると、PHP 配列ページングのパフォーマンスを大幅に最適化できます。これは、次の手順で実現できます。 Redis クライアントをインストールします。 Redisサーバーに接続します。キャッシュ データを作成し、データの各ページをキー「page:{page_number}」を持つ Redis ハッシュに保存します。キャッシュからデータを取得し、大規模な配列での高コストの操作を回避します。

まず、システム言語を簡体字中国語表示に設定して再起動する必要があります。もちろん、以前に表示言語を簡体字中国語に変更したことがある場合は、この手順をスキップできます。次に、レジストリ regedit.exe の操作を開始し、左側のナビゲーション バーまたは上部のアドレス バーで HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlNlsLanguage に直接移動し、InstallLanguage キーの値と Default キーの値を 0804 に変更します (英語に変更する場合)。まずシステムの表示言語を en-us に設定し、システムを再起動してから、すべてを 0409 に変更します) この時点でシステムを再起動する必要があります。

はい、Navicat は Redis に接続できます。これにより、ユーザーはキーの管理、値の表示、コマンドの実行、アクティビティの監視、問題の診断が可能になります。 Redis に接続するには、Navicat で「Redis」接続タイプを選択し、サーバーの詳細を入力します。

1. まず、デスクトップ上の[このPC]アイコンをダブルクリックして開きます。 2. 次に、マウスの左ボタンをダブルクリックして [C ドライブ] に入ります。システム ファイルは通常、自動的に C ドライブに保存されます。 3. 次に、C ドライブで [windows] フォルダーを見つけ、ダブルクリックしてに入ります。 4. [windows]フォルダーに入ったら、[SoftwareDistribution]フォルダーを見つけます。 5. 入力後、win11 のダウンロード ファイルとアップデート ファイルがすべて含まれている [ダウンロード] フォルダーを見つけます。 6. これらのファイルを削除したい場合は、このフォルダー内で直接削除してください。
