オンラインでクラスタリングする場合、セッション共有の問題が発生します。
Tomcat はセッション コピーの機能を提供しますが、欠点は明らかです。
1: Tomcat が多数ある場合、多数のセッションを複数のクラスターに同期する必要があり、イントラネット帯域幅を占有します。
2: 同じユーザー セッションが複数の Tomcat に存在する必要があり、メモリ スペースを無駄にします。
Tomcat のセッション共有を置き換える場合は、代替手段が次の条件を満たす必要があります。
1: データ共有
2: メモリストレージ
3: キー\値構造
Redis に基づく共有セッション ログイン
この記事は Kaige Java (gz#h: kaigejava)、個人ブログ: www#kaigejava#.com によって書かれました。CSDN で公開
セッションに検証コードを保存するビジネス プロセスを確認してみましょう
セッションに保存するものは次のとおりです: session.setAttribute("code", code); セッションの特性により、各アクセスは新しい sessionId です。コードをキーとして直接使用できます。 Redis に置き換えられましたが、コードも同様に使用できますか?
ユーザー情報をセッションに保存するプロセス:
ユーザー情報はセッションに保存されます: session.setAttribute("user", user); 同じことを考えてください: では、Redis に切り替えても、引き続き user をユーザーとして使用できますか?
コードとユーザー情報を Redis に保存するプロセスは次のとおりです。
検証コードのデータ構造は次のとおりです: 文字列型
ユーザー オブジェクトのデータ型は次のとおりです: ハッシュ型
上記の分析に従って、元のコードを変更します。
考慮事項: Redis キーのルール、有効期限
1: 認証コードを送信する際、Redis に認証コードを保存する場合は、有効期限を考慮する必要があります。そのコアコードは次のとおりです。
stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + 電話番号、コード、LOGIN_CODE_TTL、TimeUnit.MINUTES);
2: ユーザーがログインすると、検証コードとユーザー情報を Redis に保存し、トークンを返します
考慮すべき点:
1: トークンを繰り返すことはできません
2: ユーザーの有効期限
3: ログインに成功したら、トークンをフロントエンドに返す必要があります。
4: ユーザーがアクセスしている限り、Redis の有効期限は延長されます - インターセプターで処理されます
ユーザーログインのコアコードの変更:
//2.1:校验验证码是否正确
//String code = (String) session.getAttribute("code");
String code = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
if (StringUtils.isEmpty(code) || !code.equals(loginForm.getCode())) {
return Result.fail("验证码错误!");
}
//2.2:根据手机号查询,如果不存在,创建新用户
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.select("id", "phone", "nick_name");
queryWrapper.eq("phone", phone);
User user = this.getOne(queryWrapper);
if (Objects.isNull(user)) {
log.info("新建用户");
//新建用户
user = createUserWithPhone(phone);
}
//2.3:保存用户到session中
UserDTO userDTO = new UserDTO();
userDTO.setId(user.getId());
userDTO.setIcon(user.getIcon());
userDTO.setNickName(user.getNickName());
//session.setAttribute("user", userDTO);
//2.3.1:获取随机的token,作为用户登录的令牌
String token = UUID.randomUUID().toString(true);
//2.3.2:将用户以hash类型存放到Redis中==》将user对象转换成map
//user对象里有非string类型的字段,用这个方法会报错的
// Map<String,Object> userMap = BeanUtil.beanToMap(userDTO);
Map<String,Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>()
, CopyOptions.create()
.setIgnoreNullValue(true)
.setFieldValueEditor((fieldName,fieldValue)->fieldValue.toString()));
stringRedisTemplate.opsForHash().putAll(LOGIN_USER_TOKEN_KEY+token,userMap);
//LOGIN_USER_TOKEN_TTL
stringRedisTemplate.expire(LOGIN_USER_TOKEN_KEY+token,LOGIN_USER_TOKEN_TTL,TimeUnit.MINUTES);
//2.3.3: 将token返回
return Result.ok(token);
注意が必要です:
stringRedisTemplate を使用してハッシュ オブジェクトを保存する場合、オブジェクト内のすべてのキーは文字列型のみにすることができ、非文字列型がある場合はエラーが報告されます。したがって、ここでは hootool の BeanUtil ツール クラスが使用されます。
Map<String,Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>()
, CopyOptions.create()
.setIgnoreNullValue(true)
.setFieldValueEditor((fieldName,fieldValue)->fieldValue.toString()));
インターセプタ変更コード:
インターセプターは弊社でカスタマイズしたものであるため、Springコンテナで管理することができず、RedisTemplateを自動的にインジェクトすることができません。パラメーター化されたコンストラクターを使用して、以下を渡します。
public class LoginRedisInterceptor implements HandlerInterceptor {
private StringRedisTemplate stringRedisTemplate;
/**
* 因为这个类不能被spring管理,所以不能直接注入RedisTemplate对象。通过构造函数传递
* @param stringRedisTemplate
*/
public LoginRedisInterceptor(StringRedisTemplate stringRedisTemplate){
this.stringRedisTemplate = stringRedisTemplate;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1:从请求中获取到token
String token = request.getHeader("authorization");
if(StringUtils.isEmpty(token)){
response.setStatus(401);
return false;
}
//2:基于token获取redis中用户对象
String key = LOGIN_USER_TOKEN_KEY+token;
Map<Object,Object> userMap = stringRedisTemplate.opsForHash().entries(key);
//3:判断
if(userMap.isEmpty()){
response.setStatus(401);
return false;
}
//将map转对象
UserDTO user = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
UserHolder.saveUser(user);
//刷新token的过期时间
stringRedisTemplate.expire(key,LOGIN_USER_TOKEN_TTL, TimeUnit.MINUTES);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
UserHolder.removeUser();
}
}
要約:
Redis を使用してセッションを置き換える場合、考慮すべき問題は次のとおりです。
1: 適切なデータ構造を選択する
2: 適切なキーを選択する
3: 適切なストレージ粒度を選択します