シリーズ記事ナビゲーション
「インターフェイス設計の最適化のアイデア」 シリーズ:第 1 回 - インターフェイス パラメータの紆余曲折
「インターフェイス設計の最適化のアイデア」 シリーズ:第 2 回 - インターフェイス ユーザー コンテキストの設計と実装
「インターフェイス設計の最適化のアイデア」 》連載:第3回 ― インターフェースにユーザー呼び出しの痕跡を残す
序文
こんにちは、みんな!最前線のローレベルコードファーマーのSum Moです。普段はテクノロジー関連のことを勉強したり考えたりして記事にまとめるのが好きです。あくまで自分のレベルに限定したものです。文中に不適切な表現があった場合は、記事またはコードについて、お気軽に教えてください。
6 年間働いているベテランのプログラマーとして、私の仕事は主に、さまざまな管理バックエンドやアプレットを含むバックエンド Java ビジネス システムを開発することです。これらのプロジェクトでは、シングル/マルチテナント システムを設計し、多くのオープン プラットフォームとインターフェイスし、メッセージ センターなどのより複雑なアプリケーションに取り組んできましたが、幸いなことに、コード A のクラッシュによる資本の損失によるオンライン システムにはまだ遭遇したことがありません。損失。その理由は3つあり、1つ目は業務システム自体が複雑ではないこと、2つ目は常に大手メーカーのコード仕様に従い、開発段階ではできる限り仕様通りにコードを書いてきたこと、3つ目は年月が経ったことです。開発経験を積み、実践的なスキルを身につけたジャーニーマンになりました。
文章がつまらないので先に写真を載せておきます
Spring Boot では、デフォルトで、各リクエストが到着すると処理用に別のスレッドが割り当てられ、リクエストの開始者は必ずしも同じ人であるとは限らないため、リクエストはユーザー コンテキストと要件、つまり異なるスレッドに対応します线程隔离
。ユーザー コンテキストは相互に影響を及ぼさないため、最終的にスレッドの終了時にユーザー コンテキストを削除する必要があります。
この記事では、ユーザー コンテキストの構築方法、使用方法、削除方法の 3 つの側面から、インターフェイス ユーザー コンテキストの設計と実装について説明します。
1. インターフェースユーザーコンテキストの構築、使用、クリア
1. フィルターを使用してすべてのリクエストをインターセプトする
インターフェースはさまざまなコントローラーに分散しており、ほとんどのインターフェースはこのユーザー コンテキストを必要とするため (注: ユーザー コンテキストを必要としないインターフェースの存在は除外されません)、作成と破棄には統一された入り口が必要です。AOPを使用して実装できるようです
が、ここではSpringBootに付属のフィルタ[javax.servlet.Filter]を使用して実装する、より適切な解決策を示します。
実装は非常に簡単です。ここでは WebFilter をカスタマイズしました。コードは次のとおりです。
WebFilter.java
package com.summo.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.summo.context.GlobalUserContext;
import com.summo.context.UserContext;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class WebFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
try {
//获取本次接口的唯一码
String token = java.util.UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();
MDC.put("requestId", token);
//获取请求头
HttpServletRequest httpServletRequest = (HttpServletRequest)servletRequest;
HttpServletResponse httpServletResponse = (HttpServletResponse)servletResponse;
log.info("当前请求链接为:[{}]", httpServletRequest.getRequestURL());
//设置用户上下文
UserContext userContext = new UserContext();
userContext.setUserId(1L);
GlobalUserContext.setUserContext(userContext);
//执行doFilter,这行一定要加,否则程序会中断掉
filterChain.doFilter(httpServletRequest, httpServletResponse);
} catch (Exception e) {
log.error("do doFilter exception", e);
} finally {
GlobalUserContext.clear();
MDC.remove("requestId");
}
}
@Override
public void destroy() {
Filter.super.destroy();
}
}
このコードのコア メソッドは次のとおりです:
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
このメソッドで ServletRequest と ServletResponse を取得できます。これら 2 つのクラスを取得できるということは、リクエスト プロセス全体を操作できることを意味します。現在のリクエストのユーザーを特定するにはどうすればよいですか? 参考までにフローチャートを示します。
もう 1 つのアプローチは、JWT をユーザー トークンとして使用することです。JWT 自体に何らかの情報を保存できるため、ユーザー情報をキャッシュする必要はありません。JWT を直接解析できます。このアプローチは分散アプリケーションでは非常に一般的です。
2. 現在のリクエストのスレッドを取得します。
上記でユーザー情報が取得できたので、ユーザー情報をユーザーコンテキストに入れる必要がありますが、** リクエストの発信者は必ずしも同一人物とは限らないため、1 つのリクエストは 1 つのユーザーコンテキスト、つまり 1 つのユーザーコンテキストに対応します。スレッドはコンテキストを設定します。**その後、現在のスレッドを取得してコンテキストを設定する必要があります。
阿里巴巴开源的TTL框架(TransmittableThreadLocal)
現在のスレッドを取得するにはさまざまな方法があります。強力で使いやすいので、ここで使用することをお勧めします。
導入方法は以下の通りです。
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.11.1</version>
</dependency>
それの使い方:
private static final TransmittableThreadLocal<UserContext> USER_HOLDER = new TransmittableThreadLocal<>();
新しいオブジェクトだけで十分であり、ジェネリックもサポートされています。
3. ユーザーコンテキストのライフサイクル管理
ユーザー コンテキストのライフサイクル管理では、次の 3 つのメソッドを定義する必要があります。
- コンテキストに応じたユーザー情報を設定します。
- コンテキストに応じたユーザー情報を取得する
- コンテキストに応じたユーザー情報のクリア
上記のメソッドはすべて静的メソッドです。
簡単な例を次に示します:
GlobalUserContext.java
package com.summo.context;
import com.alibaba.ttl.TransmittableThreadLocal;
public class GlobalUserContext {
private static final TransmittableThreadLocal<UserContext> USER_HOLDER = new TransmittableThreadLocal<>();
/**
* 设置上下文用户信息
*
* @param user 用户信息
*/
public static void setUserContext(UserContext user) {
USER_HOLDER.set(user);
}
/**
* 获取上下文用户信息
*/
public static UserContext getUserContext() {
return USER_HOLDER.get();
}
/**
* 清除上下文用户信息
*/
public static void clear() {
USER_HOLDER.remove();
}
}
ユーザーコンテキスト.java
package com.summo.context;
import lombok.Data;
@Data
public class UserContext {
/**
* 用户ID
*/
private Long userId;
}
呼び出し方法は以下の通りです。
コンテキスト ユーザー情報の設定: GlobalUserContext.setUserContext(userContext);
コンテキスト ユーザー情報の取得: GlobalUserContext.getUserContext();
コンテキスト ユーザー情報のクリア: GlobalUserContext.clear();
4. ユーザーコンテキストの使用
ユーザーコンテキストは呼び出すだけで取得できるのでとても便利ですが、GlobalUserContext.getUserContext();
ここではユーザーコンテキストの利用シーンを中心に説明します。
a. 本人認証
ユーザーの ID 認証情報 (ユーザー名、パスワード、権限など) をユーザー コンテキストに保存し、認証が必要な場合に検証できます。
b. ユーザーのログ記録
「インターフェイス設計を最適化するアイデア」シリーズの第 3 回 - ユーザーがシステムを使用したときに痕跡を残す方法 3 と同様です。
c. インターフェースデータが権限を超えないようにする
たとえば、一部の企業では、現在ログインしているユーザー、現在ログインしているユーザーのコレクション、および現在ログインしているユーザーの閲覧履歴の情報を取得する必要がありますが、そのようなインターフェイスでは常に userId をインターフェイスにアップロードできるわけではありません。本当にそんなことをしたら警備員に叱られることになるでしょう。。。
ユーザー コンテキストを使用する場合、インターフェイスはパラメーターを渡さずに現在のユーザーの userId を取得して、ニーズを実現できます。
d. サービス間通話
分散システムでは、ユーザーの一貫性と一貫性を維持するために、ユーザーのコンテキスト情報を他のサービスに渡すことができます。
e. モニタリングと統計
ユーザーコンテキストの情報は、リクエストの処理時間、リクエストの数など、システムの監視と統計に使用できます。
5. ユーザーコンテキストの削除
GlobalUserContext.clear();
削除は呼び出すだけなので非常に簡単です詳細はWebFilter.javaの内容を参照してください。
2. ユーザーのログインと認証
上記では主に、インターフェイスによって要求されたユーザーを取得する方法とユーザー コンテキストを設定する方法について説明していますが、ユーザー ID がいつ、どのように確認されるかについては説明されていません。
ユーザー情報を確認する場合、ユーザーのログインと認証が必要になります。ログイン方法は数多くあります。簡単なものでは、アカウントとパスワードによるログイン、携帯電話認証コードによるログイン、より複雑なものでは、シングル サインオンや 3 つのログインなどがあります。 -WeChatスキャン、QRコード、Alipayスキャンコードなどの当事者認証ログイン 方法はたくさんありますが、結果は同じです确认当前用户身份
。
現在のユーザーの身元が確認された後、システムは通常、現在のユーザーの情報に基づいて一意で時間に敏感なトークンを生成し、次のリクエストのためにそれを Cookie に入れます。次のリクエストが来ると、Cookie からトークンを取得し、このトークンを使用してユーザーの情報を取得できます。
ユーザー認証の場面が多すぎるため、ここではコードを掲載しませんが、上記はアカウントとパスワードによるログインユーザー認証のシーケンス図です。