refactor: 重构获取登录用户信息方式(线程级存储)

This commit is contained in:
2024-10-10 22:33:20 +08:00
parent 8466105a9b
commit 79ea39dd07
27 changed files with 523 additions and 363 deletions

View File

@@ -17,8 +17,7 @@
package top.continew.admin.common.config.mybatis;
import cn.hutool.core.convert.Convert;
import top.continew.admin.common.model.dto.LoginUser;
import top.continew.admin.common.util.helper.LoginHelper;
import top.continew.admin.common.context.UserContextHolder;
import top.continew.starter.extension.datapermission.enums.DataScope;
import top.continew.starter.extension.datapermission.filter.DataPermissionUserContextProvider;
import top.continew.starter.extension.datapermission.model.RoleContext;
@@ -36,17 +35,16 @@ public class DefaultDataPermissionUserContextProvider implements DataPermissionU
@Override
public boolean isFilter() {
LoginUser loginUser = LoginHelper.getLoginUser();
return !loginUser.isAdmin();
return !UserContextHolder.isAdmin();
}
@Override
public UserContext getUserContext() {
LoginUser loginUser = LoginHelper.getLoginUser();
top.continew.admin.common.context.UserContext context = UserContextHolder.getContext();
UserContext userContext = new UserContext();
userContext.setUserId(Convert.toStr(loginUser.getId()));
userContext.setDeptId(Convert.toStr(loginUser.getDeptId()));
userContext.setRoles(loginUser.getRoles()
userContext.setUserId(Convert.toStr(context.getId()));
userContext.setDeptId(Convert.toStr(context.getDeptId()));
userContext.setRoles(context.getRoles()
.stream()
.map(r -> new RoleContext(Convert.toStr(r.getId()), DataScope.valueOf(r.getDataScope().name())))
.collect(Collectors.toSet()));

View File

@@ -19,9 +19,8 @@ package top.continew.admin.common.config.mybatis;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import top.continew.admin.common.util.helper.LoginHelper;
import top.continew.admin.common.context.UserContextHolder;
import top.continew.starter.core.exception.BusinessException;
import top.continew.starter.core.util.ExceptionUtils;
import top.continew.starter.extension.crud.model.entity.BaseDO;
import java.time.LocalDateTime;
@@ -62,7 +61,7 @@ public class MyBatisPlusMetaObjectHandler implements MetaObjectHandler {
if (null == metaObject) {
return;
}
Long createUser = ExceptionUtils.exToNull(LoginHelper::getUserId);
Long createUser = UserContextHolder.getUserId();
LocalDateTime createTime = LocalDateTime.now();
if (metaObject.getOriginalObject() instanceof BaseDO baseDO) {
// 继承了 BaseDO 的类,填充创建信息字段
@@ -89,8 +88,7 @@ public class MyBatisPlusMetaObjectHandler implements MetaObjectHandler {
if (null == metaObject) {
return;
}
Long updateUser = LoginHelper.getUserId();
Long updateUser = UserContextHolder.getUserId();
LocalDateTime updateTime = LocalDateTime.now();
if (metaObject.getOriginalObject() instanceof BaseDO baseDO) {
// 继承了 BaseDO 的类,填充修改信息

View File

@@ -16,11 +16,11 @@
package top.continew.admin.common.config.websocket;
import cn.dev33.satoken.stp.StpUtil;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.stereotype.Component;
import top.continew.admin.common.model.dto.LoginUser;
import top.continew.admin.common.util.helper.LoginHelper;
import top.continew.starter.core.exception.BusinessException;
import top.continew.starter.messaging.websocket.core.WebSocketClientService;
/**
@@ -36,7 +36,9 @@ public class WebSocketClientServiceImpl implements WebSocketClientService {
public String getClientId(ServletServerHttpRequest request) {
HttpServletRequest servletRequest = request.getServletRequest();
String token = servletRequest.getParameter("token");
LoginUser loginUser = LoginHelper.getLoginUser(token);
return loginUser.getToken();
if (null == StpUtil.getLoginIdByToken(token)) {
throw new BusinessException("登录已过期,请重新登录");
}
return token;
}
}

View File

@@ -31,11 +31,6 @@ public class CacheConstants {
*/
public static final String DELIMITER = StringConstants.COLON;
/**
* 登录用户键
*/
public static final String LOGIN_USER_KEY = "LOGIN_USER";
/**
* 验证码键前缀
*/

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package top.continew.admin.common.model.dto;
package top.continew.admin.common.context;
import lombok.Data;
import top.continew.admin.common.enums.DataScopeEnum;
@@ -23,13 +23,13 @@ import java.io.Serial;
import java.io.Serializable;
/**
* 角色信息
* 角色上下文
*
* @author Charles7c
* @since 2023/3/7 22:08
*/
@Data
public class RoleDTO implements Serializable {
public class RoleContext implements Serializable {
@Serial
private static final long serialVersionUID = 1L;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package top.continew.admin.common.model.dto;
package top.continew.admin.common.context;
import cn.hutool.core.collection.CollUtil;
import lombok.Data;
@@ -28,14 +28,14 @@ import java.util.Set;
import java.util.stream.Collectors;
/**
* 登录用户信息
* 用户上下文
*
* @author Charles7c
* @since 2022/12/24 13:01
* @since 2024/10/9 20:29
*/
@Data
@NoArgsConstructor
public class LoginUser implements Serializable {
public class UserContext implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@@ -55,6 +55,16 @@ public class LoginUser implements Serializable {
*/
private Long deptId;
/**
* 最后一次修改密码时间
*/
private LocalDateTime pwdResetTime;
/**
* 登录时系统设置的密码过期天数
*/
private Integer passwordExpirationDays;
/**
* 权限码集合
*/
@@ -68,57 +78,17 @@ public class LoginUser implements Serializable {
/**
* 角色集合
*/
private Set<RoleDTO> roles;
private Set<RoleContext> roles;
/**
* 令牌
*/
private String token;
/**
* IP
*/
private String ip;
/**
* IP 归属地
*/
private String address;
/**
* 浏览器
*/
private String browser;
/**
* 操作系统
*/
private String os;
/**
* 登录时间
*/
private LocalDateTime loginTime;
/**
* 最后一次修改密码时间
*/
private LocalDateTime pwdResetTime;
/**
* 登录时系统设置的密码过期天数
*/
private Integer passwordExpirationDays;
public LoginUser(Set<String> permissions, Set<RoleDTO> roles, Integer passwordExpirationDays) {
public UserContext(Set<String> permissions, Set<RoleContext> roles, Integer passwordExpirationDays) {
this.permissions = permissions;
this.setRoles(roles);
this.passwordExpirationDays = passwordExpirationDays;
}
public void setRoles(Set<RoleDTO> roles) {
public void setRoles(Set<RoleContext> roles) {
this.roles = roles;
this.roleCodes = roles.stream().map(RoleDTO::getCode).collect(Collectors.toSet());
this.roleCodes = roles.stream().map(RoleContext::getCode).collect(Collectors.toSet());
}
/**

View File

@@ -0,0 +1,185 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.common.context;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.extra.spring.SpringUtil;
import top.continew.starter.core.util.ExceptionUtils;
import top.continew.starter.extension.crud.service.CommonUserService;
import java.util.Optional;
/**
* 用户上下文 Holder
*
* @author Charles7c
* @since 2022/12/24 12:58
*/
public class UserContextHolder {
private static final ThreadLocal<UserContext> CONTEXT_HOLDER = new ThreadLocal<>();
private static final ThreadLocal<UserExtraContext> EXTRA_CONTEXT_HOLDER = new ThreadLocal<>();
private UserContextHolder() {
}
/**
* 设置上下文
*
* @param context 上下文
*/
public static void setContext(UserContext context) {
setContext(context, true);
}
/**
* 设置上下文
*
* @param context 上下文
* @param isUpdate 是否更新
*/
public static void setContext(UserContext context, boolean isUpdate) {
CONTEXT_HOLDER.set(context);
if (isUpdate) {
StpUtil.getSessionByLoginId(context.getId()).set(SaSession.USER, context);
}
}
/**
* 获取上下文
*
* @return 上下文
*/
public static UserContext getContext() {
UserContext context = CONTEXT_HOLDER.get();
if (null == context) {
context = StpUtil.getSession().getModel(SaSession.USER, UserContext.class);
CONTEXT_HOLDER.set(context);
}
return context;
}
/**
* 获取指定用户的上下文
*
* @param userId 用户 ID
* @return 上下文
*/
public static UserContext getContext(Long userId) {
SaSession session = StpUtil.getSessionByLoginId(userId, false);
if (null == session) {
return null;
}
return session.getModel(SaSession.USER, UserContext.class);
}
/**
* 设置额外上下文
*
* @param context 额外上下文
*/
public static void setExtraContext(UserExtraContext context) {
EXTRA_CONTEXT_HOLDER.set(context);
}
/**
* 获取额外上下文
*
* @return 额外上下文
*/
public static UserExtraContext getExtraContext() {
UserExtraContext context = EXTRA_CONTEXT_HOLDER.get();
if (null == context) {
context = getExtraContext(StpUtil.getTokenValue());
EXTRA_CONTEXT_HOLDER.set(context);
}
return context;
}
/**
* 获取额外上下文
*
* @param token 令牌
* @return 额外上下文
*/
public static UserExtraContext getExtraContext(String token) {
UserExtraContext context = new UserExtraContext();
context.setIp(Convert.toStr(StpUtil.getExtra(token, "ip")));
context.setAddress(Convert.toStr(StpUtil.getExtra(token, "address")));
context.setBrowser(Convert.toStr(StpUtil.getExtra(token, "browser")));
context.setOs(Convert.toStr(StpUtil.getExtra(token, "os")));
context.setLoginTime(Convert.toLocalDateTime(StpUtil.getExtra(token, "loginTime")));
return context;
}
/**
* 清除上下文
*/
public static void clearContext() {
CONTEXT_HOLDER.remove();
EXTRA_CONTEXT_HOLDER.remove();
}
/**
* 获取用户 ID
*
* @return 用户 ID
*/
public static Long getUserId() {
return Optional.ofNullable(getContext()).map(UserContext::getId).orElse(null);
}
/**
* 获取用户名
*
* @return 用户名
*/
public static String getUsername() {
return Optional.ofNullable(getContext()).map(UserContext::getUsername).orElse(null);
}
/**
* 获取用户昵称
*
* @return 用户昵称
*/
public static String getNickname() {
return getNickname(getUserId());
}
/**
* 获取用户昵称
*
* @param userId 登录用户 ID
* @return 用户昵称
*/
public static String getNickname(Long userId) {
return ExceptionUtils.exToNull(() -> SpringUtil.getBean(CommonUserService.class).getNicknameById(userId));
}
/**
* 是否为管理员
*
* @return 是否为管理员
*/
public static Boolean isAdmin() {
StpUtil.checkLogin();
return getContext().isAdmin();
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.common.context;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.JakartaServletUtil;
import jakarta.servlet.http.HttpServletRequest;
import lombok.Data;
import lombok.NoArgsConstructor;
import top.continew.starter.core.util.ExceptionUtils;
import top.continew.starter.core.util.IpUtils;
import top.continew.starter.web.util.ServletUtils;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 用户额外上下文
*
* @author Charles7c
* @since 2024/10/9 20:29
*/
@Data
@NoArgsConstructor
public class UserExtraContext implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* IP
*/
private String ip;
/**
* IP 归属地
*/
private String address;
/**
* 浏览器
*/
private String browser;
/**
* 操作系统
*/
private String os;
/**
* 登录时间
*/
private LocalDateTime loginTime;
public UserExtraContext(HttpServletRequest request) {
this.ip = JakartaServletUtil.getClientIP(request);
this.address = ExceptionUtils.exToNull(() -> IpUtils.getIpv4Address(this.ip));
this.setBrowser(ServletUtils.getBrowser(request));
this.setLoginTime(LocalDateTime.now());
this.setOs(StrUtil.subBefore(ServletUtils.getOs(request), " or", false));
}
}

View File

@@ -1,152 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.common.util.helper;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.JakartaServletUtil;
import cn.hutool.extra.spring.SpringUtil;
import jakarta.servlet.http.HttpServletRequest;
import top.continew.admin.common.constant.CacheConstants;
import top.continew.admin.common.model.dto.LoginUser;
import top.continew.starter.core.util.ExceptionUtils;
import top.continew.starter.core.util.IpUtils;
import top.continew.starter.extension.crud.service.CommonUserService;
import top.continew.starter.web.util.ServletUtils;
import top.continew.starter.web.util.SpringWebUtils;
import java.time.LocalDateTime;
/**
* 登录助手
*
* @author Charles7c
* @author Lion Li<a href="https://gitee.com/dromara/RuoYi-Vue-Plus">RuoYi-Vue-Plus</a>
* @since 2022/12/24 12:58
*/
public class LoginHelper {
private LoginHelper() {
}
/**
* 用户登录并缓存用户信息
*
* @param loginUser 登录用户信息
* @return 令牌
*/
public static String login(LoginUser loginUser) {
// 记录登录信息
HttpServletRequest request = SpringWebUtils.getRequest();
loginUser.setIp(JakartaServletUtil.getClientIP(request));
loginUser.setAddress(ExceptionUtils.exToNull(() -> IpUtils.getIpv4Address(loginUser.getIp())));
loginUser.setBrowser(ServletUtils.getBrowser(request));
loginUser.setLoginTime(LocalDateTime.now());
loginUser.setOs(StrUtil.subBefore(ServletUtils.getOs(request), " or", false));
// 登录并缓存用户信息
StpUtil.login(loginUser.getId());
SaHolder.getStorage().set(CacheConstants.LOGIN_USER_KEY, loginUser);
String tokenValue = StpUtil.getTokenValue();
loginUser.setToken(tokenValue);
StpUtil.getTokenSession().set(CacheConstants.LOGIN_USER_KEY, loginUser);
return tokenValue;
}
/**
* 更新登录用户信息
*
* @param loginUser
* 登录用户信息
* @param token 令牌
*/
public static void updateLoginUser(LoginUser loginUser, String token) {
SaHolder.getStorage().delete(CacheConstants.LOGIN_USER_KEY);
StpUtil.getTokenSessionByToken(token).set(CacheConstants.LOGIN_USER_KEY, loginUser);
}
/**
* 获取登录用户信息
*
* @return 登录用户信息
* @throws NotLoginException 未登录异常
*/
public static LoginUser getLoginUser() throws NotLoginException {
StpUtil.checkLogin();
LoginUser loginUser = (LoginUser)SaHolder.getStorage().get(CacheConstants.LOGIN_USER_KEY);
if (null != loginUser) {
return loginUser;
}
SaSession tokenSession = StpUtil.getTokenSession();
loginUser = (LoginUser)tokenSession.get(CacheConstants.LOGIN_USER_KEY);
SaHolder.getStorage().set(CacheConstants.LOGIN_USER_KEY, loginUser);
return loginUser;
}
/**
* 根据 Token 获取登录用户信息
*
* @param token 用户 Token
* @return 登录用户信息
*/
public static LoginUser getLoginUser(String token) {
SaSession tokenSession = StpUtil.getStpLogic().getTokenSessionByToken(token, false);
if (null == tokenSession) {
return null;
}
return (LoginUser)tokenSession.get(CacheConstants.LOGIN_USER_KEY);
}
/**
* 获取登录用户 ID
*
* @return 登录用户 ID
*/
public static Long getUserId() {
return getLoginUser().getId();
}
/**
* 获取登录用户名
*
* @return 登录用户名
*/
public static String getUsername() {
return getLoginUser().getUsername();
}
/**
* 获取登录用户昵称
*
* @return 登录用户昵称
*/
public static String getNickname() {
return getNickname(getUserId());
}
/**
* 获取登录用户昵称
*
* @param userId 登录用户 ID
* @return 登录用户昵称
*/
public static String getNickname(Long userId) {
return ExceptionUtils.exToNull(() -> SpringUtil.getBean(CommonUserService.class).getNicknameById(userId));
}
}