diff --git a/sql/dm/README.md b/sql/dm/README.md
new file mode 100644
index 000000000..2289eea7c
--- /dev/null
+++ b/sql/dm/README.md
@@ -0,0 +1 @@
+暂未适配国产 DM 数据库,如果你有需要,可以微信联系 wangwenbin-server 一起建设。
diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/pom.xml b/yudao-framework/yudao-spring-boot-starter-mybatis/pom.xml
index 2b5915e32..62b616a33 100644
--- a/yudao-framework/yudao-spring-boot-starter-mybatis/pom.xml
+++ b/yudao-framework/yudao-spring-boot-starter-mybatis/pom.xml
@@ -46,6 +46,10 @@
org.postgresql
postgresql
+
+ com.microsoft.sqlserver
+ mssql-jdbc
+
com.alibaba
diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java
index d5953823a..1db4797d4 100644
--- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java
+++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java
@@ -3,7 +3,7 @@ package cn.iocoder.yudao.framework.security.config;
import cn.iocoder.yudao.framework.security.core.aop.PreAuthenticatedAspect;
import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider;
import cn.iocoder.yudao.framework.security.core.context.TransmittableThreadLocalSecurityContextHolderStrategy;
-import cn.iocoder.yudao.framework.security.core.filter.JWTAuthenticationTokenFilter;
+import cn.iocoder.yudao.framework.security.core.filter.TokenAuthenticationFilter;
import cn.iocoder.yudao.framework.security.core.handler.AccessDeniedHandlerImpl;
import cn.iocoder.yudao.framework.security.core.handler.AuthenticationEntryPointImpl;
import cn.iocoder.yudao.framework.security.core.handler.LogoutSuccessHandlerImpl;
@@ -86,9 +86,9 @@ public class YudaoSecurityAutoConfiguration {
* Token 认证过滤器 Bean
*/
@Bean
- public JWTAuthenticationTokenFilter authenticationTokenFilter(MultiUserDetailsAuthenticationProvider authenticationProvider,
- GlobalExceptionHandler globalExceptionHandler) {
- return new JWTAuthenticationTokenFilter(securityProperties, authenticationProvider, globalExceptionHandler);
+ public TokenAuthenticationFilter authenticationTokenFilter(MultiUserDetailsAuthenticationProvider authenticationProvider,
+ GlobalExceptionHandler globalExceptionHandler) {
+ return new TokenAuthenticationFilter(securityProperties, authenticationProvider, globalExceptionHandler);
}
/**
diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java
index 037494475..0c0dd3278 100644
--- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java
+++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java
@@ -2,7 +2,7 @@ package cn.iocoder.yudao.framework.security.config;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider;
-import cn.iocoder.yudao.framework.security.core.filter.JWTAuthenticationTokenFilter;
+import cn.iocoder.yudao.framework.security.core.filter.TokenAuthenticationFilter;
import cn.iocoder.yudao.framework.web.config.WebProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
@@ -55,7 +55,7 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
* Token 认证过滤器 Bean
*/
@Resource
- private JWTAuthenticationTokenFilter authenticationTokenFilter;
+ private TokenAuthenticationFilter authenticationTokenFilter;
/**
* 自定义的权限映射 Bean 们
diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/JWTAuthenticationTokenFilter.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java
similarity index 94%
rename from yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/JWTAuthenticationTokenFilter.java
rename to yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java
index 57344adb8..0908560c8 100644
--- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/JWTAuthenticationTokenFilter.java
+++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java
@@ -18,13 +18,13 @@ import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
- * JWT 过滤器,验证 token 的有效性
+ * Token 过滤器,验证 token 的有效性
* 验证通过后,获得 {@link LoginUser} 信息,并加入到 Spring Security 上下文
*
* @author 芋道源码
*/
@RequiredArgsConstructor
-public class JWTAuthenticationTokenFilter extends OncePerRequestFilter {
+public class TokenAuthenticationFilter extends OncePerRequestFilter {
private final SecurityProperties securityProperties;
@@ -43,7 +43,7 @@ public class JWTAuthenticationTokenFilter extends OncePerRequestFilter {
LoginUser loginUser = authenticationProvider.verifyTokenAndRefresh(request, token);
// 模拟 Login 功能,方便日常开发调试
if (loginUser == null) {
- loginUser = this.mockLoginUser(request, token);
+ loginUser = mockLoginUser(request, token);
}
// 设置当前用户
if (loginUser != null) {
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/DefaultDatabaseQueryTest.java b/yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/DefaultDatabaseQueryTest.java
index 56acb4dd3..0f1a9dcce 100644
--- a/yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/DefaultDatabaseQueryTest.java
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/DefaultDatabaseQueryTest.java
@@ -27,7 +27,8 @@ public class DefaultDatabaseQueryTest {
if (StrUtil.startWithAny(tableInfo.getName().toLowerCase(), "act_", "flw_", "qrtz_")) {
continue;
}
- System.out.println(String.format("CREATE SEQUENCE %s_seq MINVALUE 0;", tableInfo.getName()));
+// System.out.println(String.format("CREATE SEQUENCE %s_seq MINVALUE 0;", tableInfo.getName()));
+ System.out.println(String.format("DELETE FROM %s WHERE deleted = '1';", tableInfo.getName()));
}
System.out.println(tableInfos.size());
System.out.println(System.currentTimeMillis() - time);
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java
index ac0fce407..c09004526 100644
--- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java
@@ -8,8 +8,6 @@ import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.security.core.authentication.MultiUsernamePasswordAuthenticationToken;
import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
-import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserBindReqVO;
-import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO;
import cn.iocoder.yudao.module.member.convert.auth.AuthConvert;
import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper;
@@ -88,7 +86,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
// 使用手机 + 密码,进行登录。
LoginUser loginUser = this.login0(reqVO.getMobile(), reqVO.getPassword());
- // 缓存登录用户到 Redis 中,返回 sessionId 编号
+ // 缓存登录用户到 Redis 中,返回 Token 令牌
return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_USERNAME, userIp, userAgent);
}
@@ -105,7 +103,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
// 执行登陆
LoginUser loginUser = AuthConvert.INSTANCE.convert(user);
- // 缓存登录用户到 Redis 中,返回 sessionId 编号
+ // 缓存登录用户到 Redis 中,返回 Token 令牌
return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_SMS, userIp, userAgent);
}
@@ -127,7 +125,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
// 创建 LoginUser 对象
LoginUser loginUser = AuthConvert.INSTANCE.convert(user);
- // 缓存登录用户到 Redis 中,返回 sessionId 编号
+ // 缓存登录用户到 Redis 中,返回 Token 令牌
return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_SOCIAL, userIp, userAgent);
}
@@ -136,18 +134,18 @@ public class MemberAuthServiceImpl implements MemberAuthService {
// 使用手机号、手机验证码登录
AppAuthSmsLoginReqVO loginReqVO = AppAuthSmsLoginReqVO.builder()
.mobile(reqVO.getMobile()).code(reqVO.getSmsCode()).build();
- String sessionId = this.smsLogin(loginReqVO, userIp, userAgent);
- LoginUser loginUser = userSessionApi.getLoginUser(sessionId);
+ String token = this.smsLogin(loginReqVO, userIp, userAgent);
+ LoginUser loginUser = userSessionApi.getLoginUser(token);
// 绑定社交用户
socialUserApi.bindSocialUser(AuthConvert.INSTANCE.convert(loginUser.getId(), getUserType().getValue(), reqVO));
- return sessionId;
+ return token;
}
private String createUserSessionAfterLoginSuccess(LoginUser loginUser, LoginLogTypeEnum logType, String userIp, String userAgent) {
// 插入登陆日志
createLoginLog(loginUser.getUsername(), logType, LoginResultEnum.SUCCESS);
- // 缓存登录用户到 Redis 中,返回 sessionId 编号
+ // 缓存登录用户到 Redis 中,返回 Token 令牌
return userSessionApi.createUserSession(loginUser, userIp, userAgent);
}
diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApi.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApi.java
index a35dfae38..e7fdcb982 100644
--- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApi.java
+++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApi.java
@@ -18,33 +18,33 @@ public interface UserSessionApi {
* @param loginUser 登录用户
* @param userIp 用户 IP
* @param userAgent 用户 UA
- * @return Session 编号
+ * @return Token 令牌
*/
String createUserSession(@NotNull(message = "登录用户不能为空") LoginUser loginUser, String userIp, String userAgent);
/**
* 刷新在线用户 Session 的更新时间
*
- * @param sessionId Session 编号
+ * @param token Token 令牌
* @param loginUser 登录用户
*/
- void refreshUserSession(@NotEmpty(message = "Session编号不能为空") String sessionId,
+ void refreshUserSession(@NotEmpty(message = "Token 令牌不能为空") String token,
@NotNull(message = "登录用户不能为空") LoginUser loginUser);
/**
* 删除在线用户 Session
*
- * @param sessionId Session 编号
+ * @param token Token 令牌
*/
- void deleteUserSession(String sessionId);
+ void deleteUserSession(String token);
/**
- * 获得 Session 编号对应的在线用户
+ * 获得 Token 令牌对应的在线用户
*
- * @param sessionId Session 编号
+ * @param token Token 令牌
* @return 在线用户
*/
- LoginUser getLoginUser(String sessionId);
+ LoginUser getLoginUser(String token);
/**
* 获得 Session 超时时间,单位:毫秒
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApiImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApiImpl.java
index f8ab2a169..63226c125 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApiImpl.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApiImpl.java
@@ -25,18 +25,18 @@ public class UserSessionApiImpl implements UserSessionApi {
}
@Override
- public void refreshUserSession(String sessionId, LoginUser loginUser) {
- userSessionService.refreshUserSession(sessionId, loginUser);
+ public void refreshUserSession(String token, LoginUser loginUser) {
+ userSessionService.refreshUserSession(token, loginUser);
}
@Override
- public void deleteUserSession(String sessionId) {
- userSessionService.deleteUserSession(sessionId);
+ public void deleteUserSession(String token) {
+ userSessionService.deleteUserSession(token);
}
@Override
- public LoginUser getLoginUser(String sessionId) {
- return userSessionService.getLoginUser(sessionId);
+ public LoginUser getLoginUser(String token) {
+ return userSessionService.getLoginUser(token);
}
@Override
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/UserSessionController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/UserSessionController.java
index 00150fd8b..023532e08 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/UserSessionController.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/UserSessionController.java
@@ -69,10 +69,9 @@ public class UserSessionController {
@DeleteMapping("/delete")
@ApiOperation("删除 Session")
- @ApiImplicitParam(name = "id", value = "Session 编号", required = true, dataTypeClass = String.class,
- example = "fe50b9f6-d177-44b1-8da9-72ea34f63db7")
+ @ApiImplicitParam(name = "id", value = "Session 编号", required = true, dataTypeClass = Long.class, example = "1024")
@PreAuthorize("@ss.hasPermission('system:user-session:delete')")
- public CommonResult deleteUserSession(@RequestParam("id") String id) {
+ public CommonResult deleteUserSession(@RequestParam("id") Long id) {
userSessionService.deleteUserSession(id);
return success(true);
}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/UserSessionDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/UserSessionDO.java
index cb02043b3..6a0649354 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/UserSessionDO.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/UserSessionDO.java
@@ -3,10 +3,7 @@ package cn.iocoder.yudao.module.system.dal.dataobject.auth;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.security.core.LoginUser;
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.KeySequence;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.*;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -22,18 +19,21 @@ import java.util.Date;
*
* @author 芋道源码
*/
-@TableName(value = "system_user_session", autoResultMap = true)
-@KeySequence("system_user_session_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@TableName(value = "system_user_session")
@Data
@Builder
@EqualsAndHashCode(callSuper = true)
public class UserSessionDO extends BaseDO {
/**
- * 会话编号, 即 sessionId
+ * 会话编号
*/
- @TableId(type = IdType.INPUT)
- private String id;
+ private Long id;
+ /**
+ * 令牌
+ */
+ private String token;
+
/**
* 用户编号
*
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/user/AdminUserDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/user/AdminUserDO.java
index c1900cc7c..93e74ddb4 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/user/AdminUserDO.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/user/AdminUserDO.java
@@ -19,7 +19,7 @@ import java.util.Set;
*
* @author 芋道源码
*/
-@TableName(value = "system_user", autoResultMap = true)
+@TableName(value = "system_users", autoResultMap = true) // 由于 SQL Server 的 system_user 是关键字,所以使用 system_users
@KeySequence("system_user_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/UserSessionMapper.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/UserSessionMapper.java
index 1b7f31be3..dea0d4792 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/UserSessionMapper.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/UserSessionMapper.java
@@ -1,10 +1,10 @@
package cn.iocoder.yudao.module.system.dal.mysql.auth;
-import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageReqVO;
-import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
-import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageReqVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
@@ -15,13 +15,23 @@ import java.util.List;
public interface UserSessionMapper extends BaseMapperX {
default PageResult selectPage(UserSessionPageReqVO reqVO, Collection userIds) {
- return selectPage(reqVO, new QueryWrapperX()
- .inIfPresent("user_id", userIds)
- .likeIfPresent("user_ip", reqVO.getUserIp()));
+ return selectPage(reqVO, new LambdaQueryWrapperX()
+ .inIfPresent(UserSessionDO::getUserId, userIds)
+ .likeIfPresent(UserSessionDO::getUserIp, reqVO.getUserIp()));
}
default List selectListBySessionTimoutLt() {
- return selectList(new QueryWrapperX().lt("session_timeout",new Date()));
+ return selectList(new LambdaQueryWrapperX()
+ .lt(UserSessionDO::getSessionTimeout, new Date()));
+ }
+
+ default void updateByToken(String token, UserSessionDO updateObj) {
+ update(updateObj, new LambdaQueryWrapperX()
+ .eq(UserSessionDO::getToken, token));
+ }
+
+ default void deleteByToken(String token) {
+ delete(new LambdaQueryWrapperX().eq(UserSessionDO::getToken, token));
}
}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java
index 170cb53d1..b17c6fb4d 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java
@@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.system.dal.redis;
import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
import cn.iocoder.yudao.framework.security.core.LoginUser;
-import me.zhyd.oauth.model.AuthUser;
import java.time.Duration;
@@ -20,7 +19,7 @@ public interface RedisKeyConstants {
STRING, String.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);
RedisKeyDefine LOGIN_USER = new RedisKeyDefine("登录用户的缓存",
- "login_user:%s", // 参数为 sessionId
+ "login_user:%s", // 参数为 token 令牌
STRING, LoginUser.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);
RedisKeyDefine SOCIAL_AUTH_STATE = new RedisKeyDefine("社交登陆的 state", // 注意,它是被 JustAuth 的 justauth.type.prefix 使用到
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/auth/LoginUserRedisDAO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/auth/LoginUserRedisDAO.java
index 735015917..8af701f71 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/auth/LoginUserRedisDAO.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/auth/LoginUserRedisDAO.java
@@ -24,24 +24,29 @@ public class LoginUserRedisDAO {
@Resource
private SecurityProperties securityProperties;
- public LoginUser get(String sessionId) {
- String redisKey = formatKey(sessionId);
+ public LoginUser get(String token) {
+ String redisKey = formatKey(token);
return JsonUtils.parseObject(stringRedisTemplate.opsForValue().get(redisKey), LoginUser.class);
}
- public void set(String sessionId, LoginUser loginUser) {
- String redisKey = formatKey(sessionId);
+ public Boolean exists(String token) {
+ String redisKey = formatKey(token);
+ return stringRedisTemplate.hasKey(redisKey);
+ }
+
+ public void set(String token, LoginUser loginUser) {
+ String redisKey = formatKey(token);
stringRedisTemplate.opsForValue().set(redisKey, JsonUtils.toJsonString(loginUser),
securityProperties.getSessionTimeout());
}
- public void delete(String sessionId) {
- String redisKey = formatKey(sessionId);
+ public void delete(String token) {
+ String redisKey = formatKey(token);
stringRedisTemplate.delete(redisKey);
}
- private static String formatKey(String sessionId) {
- return LOGIN_USER.formatKey(sessionId);
+ private static String formatKey(String token) {
+ return LOGIN_USER.formatKey(token);
}
}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/job/auth/UserSessionTimeoutJob.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/job/auth/UserSessionTimeoutJob.java
index 04b58b89f..b2bb523e3 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/job/auth/UserSessionTimeoutJob.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/job/auth/UserSessionTimeoutJob.java
@@ -24,7 +24,7 @@ public class UserSessionTimeoutJob implements JobHandler {
@Override
public String execute(String param) throws Exception {
// 执行过期
- Long timeoutCount = userSessionService.clearSessionTimeout();
+ Long timeoutCount = userSessionService.deleteTimeoutSession();
// 返回结果,记录每次的超时数量
return String.format("移除在线会话数量为 %s 个", timeoutCount);
}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java
index 2c054b9dc..105687cec 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java
@@ -104,7 +104,7 @@ public class AdminAuthServiceImpl implements AdminAuthService {
// 使用账号密码,进行登录
LoginUser loginUser = login0(reqVO.getUsername(), reqVO.getPassword());
- // 缓存登陆用户到 Redis 中,返回 sessionId 编号
+ // 缓存登陆用户到 Redis 中,返回 Token 令牌
return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_USERNAME, userIp, userAgent);
}
@@ -207,7 +207,7 @@ public class AdminAuthServiceImpl implements AdminAuthService {
// 创建 LoginUser 对象
LoginUser loginUser = buildLoginUser(user);
- // 缓存登录用户到 Redis 中,返回 sessionId 编号
+ // 缓存登录用户到 Redis 中,返回 Token 令牌
return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_SOCIAL, userIp, userAgent);
}
@@ -219,14 +219,14 @@ public class AdminAuthServiceImpl implements AdminAuthService {
// 绑定社交用户
socialUserService.bindSocialUser(AuthConvert.INSTANCE.convert(loginUser.getId(), getUserType().getValue(), reqVO));
- // 缓存登录用户到 Redis 中,返回 sessionId 编号
+ // 缓存登录用户到 Redis 中,返回 Token 令牌
return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_SOCIAL, userIp, userAgent);
}
private String createUserSessionAfterLoginSuccess(LoginUser loginUser, LoginLogTypeEnum logType, String userIp, String userAgent) {
// 插入登陆日志
createLoginLog(loginUser.getUsername(), logType, LoginResultEnum.SUCCESS);
- // 缓存登录用户到 Redis 中,返回 sessionId 编号
+ // 缓存登录用户到 Redis 中,返回 Token 令牌
return userSessionService.createUserSession(loginUser, userIp, userAgent);
}
@@ -240,7 +240,7 @@ public class AdminAuthServiceImpl implements AdminAuthService {
// 删除 session
userSessionService.deleteUserSession(token);
// 记录登出日志
- this.createLogoutLog(loginUser.getId(), loginUser.getUsername());
+ createLogoutLog(loginUser.getId(), loginUser.getUsername());
}
@Override
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionService.java
index 779f33a47..844cef250 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionService.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionService.java
@@ -25,7 +25,7 @@ public interface UserSessionService {
*
* @return {@link Long } 移出的超时用户数量
**/
- long clearSessionTimeout();
+ long deleteTimeoutSession();
/**
* 创建在线用户 Session
@@ -33,32 +33,39 @@ public interface UserSessionService {
* @param loginUser 登录用户
* @param userIp 用户 IP
* @param userAgent 用户 UA
- * @return Session 编号
+ * @return Token 令牌
*/
String createUserSession(LoginUser loginUser, String userIp, String userAgent);
/**
* 刷新在线用户 Session 的更新时间
*
- * @param sessionId Session 编号
+ * @param token 令牌
* @param loginUser 登录用户
*/
- void refreshUserSession(String sessionId, LoginUser loginUser);
+ void refreshUserSession(String token, LoginUser loginUser);
/**
* 删除在线用户 Session
*
- * @param sessionId Session 编号
+ * @param token token 令牌
*/
- void deleteUserSession(String sessionId);
+ void deleteUserSession(String token);
/**
- * 获得 Session 编号对应的在线用户
+ * 删除在线用户 Session
*
- * @param sessionId Session 编号
+ * @param id 编号
+ */
+ void deleteUserSession(Long id);
+
+ /**
+ * 获得 Token 对应的在线用户
+ *
+ * @param token 令牌
* @return 在线用户
*/
- LoginUser getLoginUser(String sessionId);
+ LoginUser getLoginUser(String token);
/**
* 获得 Session 超时时间,单位:毫秒
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImpl.java
index a1118958a..333e54c69 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImpl.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImpl.java
@@ -3,28 +3,28 @@ package cn.iocoder.yudao.module.system.service.auth;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
import cn.iocoder.yudao.framework.security.config.SecurityProperties;
import cn.iocoder.yudao.framework.security.core.LoginUser;
+import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO;
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import cn.iocoder.yudao.module.system.dal.mysql.auth.UserSessionMapper;
+import cn.iocoder.yudao.module.system.dal.redis.auth.LoginUserRedisDAO;
import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
import cn.iocoder.yudao.module.system.service.logger.LoginLogService;
import cn.iocoder.yudao.module.system.service.user.AdminUserService;
-import cn.iocoder.yudao.module.system.dal.redis.auth.LoginUserRedisDAO;
-import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
-import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.time.Duration;
-import java.util.*;
-import java.util.stream.Collectors;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.addTime;
@@ -65,82 +65,99 @@ public class UserSessionServiceImpl implements UserSessionService {
return userSessionMapper.selectPage(reqVO, userIds);
}
- // TODO @芋艿:优化下该方法
@Override
- public long clearSessionTimeout() {
- // 获取db里已经超时的用户列表
- List sessionTimeoutDOS = userSessionMapper.selectListBySessionTimoutLt();
- Map timeoutSessionDOMap = sessionTimeoutDOS
- .stream()
- .filter(sessionDO -> loginUserRedisDAO.get(sessionDO.getId()) == null)
- .collect(Collectors.toMap(UserSessionDO::getId, o -> o));
- // 确认已经超时,按批次移出在线用户列表
- if (CollUtil.isNotEmpty(timeoutSessionDOMap)) {
- Lists.partition(new ArrayList<>(timeoutSessionDOMap.keySet()), 100)
- .forEach(userSessionMapper::deleteBatchIds);
- // 记录用户超时退出日志
- createTimeoutLogoutLog(timeoutSessionDOMap.values());
+ public long deleteTimeoutSession() {
+ // 获取 db 里已经超时的用户列表
+ List timeoutSessions = userSessionMapper.selectListBySessionTimoutLt();
+ if (CollUtil.isEmpty(timeoutSessions)) {
+ return 0L;
}
- return timeoutSessionDOMap.size();
+
+ // 由于过期的用户一般不多,所以顺序遍历,进行清理
+ int count = 0;
+ for (UserSessionDO session : timeoutSessions) {
+ // 基于 Redis 二次判断,同时也保证 Redis Key 的立即过期,避免延迟导致浪费内存空间
+ if (loginUserRedisDAO.exists(session.getToken())) {
+ continue;
+ }
+ userSessionMapper.deleteById(session.getId());
+ // 记录退出日志
+ createLogoutLog(session, LoginLogTypeEnum.LOGOUT_TIMEOUT);
+ count++;
+ }
+ return count;
}
- private void createTimeoutLogoutLog(Collection timeoutSessionDOS) {
- for (UserSessionDO timeoutSessionDO : timeoutSessionDOS) {
- LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
- reqDTO.setLogType(LoginLogTypeEnum.LOGOUT_TIMEOUT.getType());
- reqDTO.setTraceId(TracerUtils.getTraceId());
- reqDTO.setUserId(timeoutSessionDO.getUserId());
- reqDTO.setUserType(timeoutSessionDO.getUserType());
- reqDTO.setUsername(timeoutSessionDO.getUsername());
- reqDTO.setUserAgent(timeoutSessionDO.getUserAgent());
- reqDTO.setUserIp(timeoutSessionDO.getUserIp());
- reqDTO.setResult(LoginResultEnum.SUCCESS.getResult());
- loginLogService.createLoginLog(reqDTO);
- }
+ private void createLogoutLog(UserSessionDO session, LoginLogTypeEnum type) {
+ LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
+ reqDTO.setLogType(type.getType());
+ reqDTO.setTraceId(TracerUtils.getTraceId());
+ reqDTO.setUserId(session.getUserId());
+ reqDTO.setUserType(session.getUserType());
+ reqDTO.setUsername(session.getUsername());
+ reqDTO.setUserAgent(session.getUserAgent());
+ reqDTO.setUserIp(session.getUserIp());
+ reqDTO.setResult(LoginResultEnum.SUCCESS.getResult());
+ loginLogService.createLoginLog(reqDTO);
}
@Override
public String createUserSession(LoginUser loginUser, String userIp, String userAgent) {
// 生成 Session 编号
- String sessionId = generateSessionId();
+ String token = generateToken();
// 写入 Redis 缓存
loginUser.setUpdateTime(new Date());
- loginUserRedisDAO.set(sessionId, loginUser);
+ loginUserRedisDAO.set(token, loginUser);
// 写入 DB 中
- UserSessionDO userSession = UserSessionDO.builder().id(sessionId)
+ UserSessionDO userSession = UserSessionDO.builder().token(token)
.userId(loginUser.getId()).userType(loginUser.getUserType())
.userIp(userIp).userAgent(userAgent).username(loginUser.getUsername())
.sessionTimeout(addTime(Duration.ofMillis(getSessionTimeoutMillis())))
.build();
userSessionMapper.insert(userSession);
- // 返回 Session 编号
- return sessionId;
+ // 返回 Token 令牌
+ return token;
}
@Override
- public void refreshUserSession(String sessionId, LoginUser loginUser) {
+ public void refreshUserSession(String token, LoginUser loginUser) {
// 写入 Redis 缓存
loginUser.setUpdateTime(new Date());
- loginUserRedisDAO.set(sessionId, loginUser);
+ loginUserRedisDAO.set(token, loginUser);
// 更新 DB 中
- UserSessionDO updateObj = UserSessionDO.builder().id(sessionId).build();
+ UserSessionDO updateObj = UserSessionDO.builder().build();
updateObj.setUsername(loginUser.getUsername());
updateObj.setUpdateTime(new Date());
updateObj.setSessionTimeout(addTime(Duration.ofMillis(getSessionTimeoutMillis())));
- userSessionMapper.updateById(updateObj);
+ userSessionMapper.updateByToken(token, updateObj);
}
@Override
- public void deleteUserSession(String sessionId) {
+ public void deleteUserSession(String token) {
// 删除 Redis 缓存
- loginUserRedisDAO.delete(sessionId);
+ loginUserRedisDAO.delete(token);
// 删除 DB 记录
- userSessionMapper.deleteById(sessionId);
+ userSessionMapper.deleteByToken(token);
+ // 无需记录日志,因为退出那已经记录
}
@Override
- public LoginUser getLoginUser(String sessionId) {
- return loginUserRedisDAO.get(sessionId);
+ public void deleteUserSession(Long id) {
+ UserSessionDO session = userSessionMapper.selectById(id);
+ if (session == null) {
+ return;
+ }
+ // 删除 Redis 缓存
+ loginUserRedisDAO.delete(session.getToken());
+ // 删除 DB 记录
+ userSessionMapper.deleteById(id);
+ // 记录退出日志
+ createLogoutLog(session, LoginLogTypeEnum.LOGOUT_DELETE);
+ }
+
+ @Override
+ public LoginUser getLoginUser(String token) {
+ return loginUserRedisDAO.get(token);
}
@Override
@@ -149,11 +166,11 @@ public class UserSessionServiceImpl implements UserSessionService {
}
/**
- * 生成 Session 编号,目前采用 UUID 算法
+ * 生成 Token 令牌,目前采用 UUID 算法
*
* @return Session 编号
*/
- private static String generateSessionId() {
+ private static String generateToken() {
return IdUtil.fastSimpleUUID();
}
diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AuthServiceImplTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AuthServiceImplTest.java
index 20814d321..5856e629a 100644
--- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AuthServiceImplTest.java
+++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AuthServiceImplTest.java
@@ -134,6 +134,7 @@ public class AuthServiceImplTest extends BaseDbUnitTest {
AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class);
String userIp = randomString();
String userAgent = randomString();
+
// 调用, 并断言异常
assertServiceException(() -> authService.login(reqVO, userIp, userAgent), AUTH_LOGIN_CAPTCHA_NOT_FOUND);
// 校验调用参数
@@ -148,10 +149,12 @@ public class AuthServiceImplTest extends BaseDbUnitTest {
// 准备参数
String userIp = randomString();
String userAgent = randomString();
- String code = randomString();
AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class);
+
// mock 验证码不正确
+ String code = randomString();
when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(code);
+
// 调用, 并断言异常
assertServiceException(() -> authService.login(reqVO, userIp, userAgent), AUTH_LOGIN_CAPTCHA_CODE_ERROR);
// 校验调用参数
@@ -172,6 +175,7 @@ public class AuthServiceImplTest extends BaseDbUnitTest {
// mock 抛出异常
when(authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(reqVO.getUsername(), reqVO.getPassword())))
.thenThrow(new BadCredentialsException("测试账号或密码不正确"));
+
// 调用, 并断言异常
assertServiceException(() -> authService.login(reqVO, userIp, userAgent), AUTH_LOGIN_BAD_CREDENTIALS);
// 校验调用参数
@@ -188,11 +192,13 @@ public class AuthServiceImplTest extends BaseDbUnitTest {
String userIp = randomString();
String userAgent = randomString();
AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class);
+
// mock 验证码正确
when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(reqVO.getCode());
// mock 抛出异常
when(authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(reqVO.getUsername(), reqVO.getPassword())))
.thenThrow(new DisabledException("测试用户被禁用"));
+
// 调用, 并断言异常
assertServiceException(() -> authService.login(reqVO, userIp, userAgent), AUTH_LOGIN_USER_DISABLED);
// 校验调用参数
@@ -214,6 +220,7 @@ public class AuthServiceImplTest extends BaseDbUnitTest {
// mock 抛出异常
when(authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(reqVO.getUsername(), reqVO.getPassword())))
.thenThrow(new AuthenticationException("测试未知异常") {});
+
// 调用, 并断言异常
assertServiceException(() -> authService.login(reqVO, userIp, userAgent), AUTH_LOGIN_FAIL_UNKNOWN);
// 校验调用参数
@@ -229,27 +236,29 @@ public class AuthServiceImplTest extends BaseDbUnitTest {
// 准备参数
String userIp = randomString();
String userAgent = randomString();
+ AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class);
+
+ // mock 验证码正确
+ when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(reqVO.getCode());
+ // mock authentication
Long userId = randomLongId();
Set userRoleIds = randomSet(Long.class);
- String sessionId = randomString();
- AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class);
LoginUser loginUser = randomPojo(LoginUser.class, o -> {
o.setId(userId);
o.setRoleIds(userRoleIds);
});
- // mock 验证码正确
- when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(reqVO.getCode());
- // mock authentication
when(authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(reqVO.getUsername(), reqVO.getPassword())))
.thenReturn(authentication);
when(authentication.getPrincipal()).thenReturn(loginUser);
// mock 获得 User 拥有的角色编号数组
when(permissionService.getUserRoleIds(userId, singleton(CommonStatusEnum.ENABLE.getStatus()))).thenReturn(userRoleIds);
// mock 缓存登录用户到 Redis
- when(userSessionService.createUserSession(loginUser, userIp, userAgent)).thenReturn(sessionId);
+ String token = randomString();
+ when(userSessionService.createUserSession(loginUser, userIp, userAgent)).thenReturn(token);
+
// 调用, 并断言异常
String login = authService.login(reqVO, userIp, userAgent);
- assertEquals(sessionId, login);
+ assertEquals(token, login);
// 校验调用参数
verify(captchaService, times(1)).deleteCaptchaCode(reqVO.getUuid());
verify(loginLogService, times(1)).createLoginLog(
diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImplTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImplTest.java
index 357f16673..ab739be79 100644
--- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImplTest.java
+++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImplTest.java
@@ -1,31 +1,31 @@
package cn.iocoder.yudao.module.system.service.auth;
-import cn.hutool.core.date.DateUtil;
-import cn.iocoder.yudao.framework.security.config.SecurityProperties;
-import cn.iocoder.yudao.framework.security.core.LoginUser;
-import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageReqVO;
-import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
-import cn.iocoder.yudao.module.system.dal.mysql.auth.UserSessionMapper;
-import cn.iocoder.yudao.module.system.service.logger.LoginLogService;
-import cn.iocoder.yudao.module.system.service.user.AdminUserService;
-import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO;
-import cn.iocoder.yudao.module.system.dal.redis.auth.LoginUserRedisDAO;
-import cn.iocoder.yudao.module.system.enums.common.SexEnum;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
+import cn.iocoder.yudao.framework.security.config.SecurityProperties;
+import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest;
+import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageReqVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
+import cn.iocoder.yudao.module.system.dal.mysql.auth.UserSessionMapper;
+import cn.iocoder.yudao.module.system.dal.redis.auth.LoginUserRedisDAO;
+import cn.iocoder.yudao.module.system.enums.common.SexEnum;
+import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
+import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
+import cn.iocoder.yudao.module.system.service.logger.LoginLogService;
+import cn.iocoder.yudao.module.system.service.user.AdminUserService;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource;
import java.time.Duration;
-import java.util.Date;
-import java.util.List;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
+import java.util.Calendar;
import static cn.hutool.core.util.RandomUtil.randomEle;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.addTime;
@@ -33,8 +33,9 @@ import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEq
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
import static java.util.Collections.singletonList;
import static org.junit.jupiter.api.Assertions.*;
-import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
@@ -61,6 +62,11 @@ public class UserSessionServiceImplTest extends BaseDbAndRedisUnitTest {
@MockBean
private SecurityProperties securityProperties;
+ @BeforeEach
+ public void setUp() {
+ when(securityProperties.getSessionTimeout()).thenReturn(Duration.ofDays(1L));
+ }
+
@Test
public void testGetUserSessionPage_success() {
// mock 数据
@@ -78,15 +84,9 @@ public class UserSessionServiceImplTest extends BaseDbAndRedisUnitTest {
});
userSessionMapper.insert(dbSession);
// 测试 username 不匹配
- userSessionMapper.insert(ObjectUtils.cloneIgnoreId(dbSession, o -> {
- o.setId(randomString());
- o.setUserId(123456L);
- }));
+ userSessionMapper.insert(ObjectUtils.cloneIgnoreId(dbSession, o -> o.setUserId(123456L)));
// 测试 userIp 不匹配
- userSessionMapper.insert(ObjectUtils.cloneIgnoreId(dbSession, o -> {
- o.setId(randomString());
- o.setUserIp("testUserIp");
- }));
+ userSessionMapper.insert(ObjectUtils.cloneIgnoreId(dbSession, o -> o.setUserIp("testUserIp")));
// 准备参数
UserSessionPageReqVO reqVO = new UserSessionPageReqVO();
reqVO.setUsername(dbUser.getUsername());
@@ -100,35 +100,60 @@ public class UserSessionServiceImplTest extends BaseDbAndRedisUnitTest {
assertPojoEquals(dbSession, pageResult.getList().get(0));
}
- // TODO 芋艿:单测写的有问题
+ @Test
+ public void testClearSessionTimeout_none() {
+ // mock db 数据
+ UserSessionDO userSession = randomPojo(UserSessionDO.class, o -> {
+ o.setUserType(randomEle(UserTypeEnum.values()).getValue());
+ o.setSessionTimeout(addTime(Duration.ofDays(1)));
+ });
+ userSessionMapper.insert(userSession);
+
+ // 调用
+ long count = userSessionService.deleteTimeoutSession();
+ // 断言
+ assertEquals(0, count);
+ assertPojoEquals(userSession, userSessionMapper.selectById(userSession.getId())); // 未删除
+ }
+
+ @Test // Redis 还存在的情况
+ public void testClearSessionTimeout_exists() {
+ // mock db 数据
+ UserSessionDO userSession = randomPojo(UserSessionDO.class, o -> {
+ o.setUserType(randomEle(UserTypeEnum.values()).getValue());
+ o.setSessionTimeout(DateUtils.addDate(Calendar.DAY_OF_YEAR, -1));
+ });
+ userSessionMapper.insert(userSession);
+ // mock redis 数据
+ loginUserRedisDAO.set(userSession.getToken(), new LoginUser());
+
+ // 调用
+ long count = userSessionService.deleteTimeoutSession();
+ // 断言
+ assertEquals(0, count);
+ assertPojoEquals(userSession, userSessionMapper.selectById(userSession.getId())); // 未删除
+ }
+
@Test
public void testClearSessionTimeout_success() {
- // 准备超时数据 120 条, 在线用户 1 条
- int expectedTimeoutCount = 120, expectedTotal = 1;
-
- // 准备数据
- List prepareData = Stream
- .iterate(0, i -> i)
- .limit(expectedTimeoutCount)
- .map(i -> randomPojo(UserSessionDO.class, o -> {
- o.setUserType(randomEle(UserTypeEnum.values()).getValue());
- o.setSessionTimeout(DateUtil.offsetSecond(new Date(), -1));
- }))
- .collect(Collectors.toList());
- UserSessionDO sessionDO = randomPojo(UserSessionDO.class, o -> {
+ // mock db 数据
+ UserSessionDO userSession = randomPojo(UserSessionDO.class, o -> {
o.setUserType(randomEle(UserTypeEnum.values()).getValue());
- o.setSessionTimeout(DateUtil.offsetMinute(new Date(), 30));
+ o.setSessionTimeout(DateUtils.addDate(Calendar.DAY_OF_YEAR, -1));
});
- prepareData.add(sessionDO);
- prepareData.forEach(userSessionMapper::insert);
+ userSessionMapper.insert(userSession);
// 清空超时数据
- long actualTimeoutCount = userSessionService.clearSessionTimeout();
+ long count = userSessionService.deleteTimeoutSession();
// 校验
- assertEquals(expectedTimeoutCount, actualTimeoutCount);
- List userSessionDOS = userSessionMapper.selectList();
- assertEquals(expectedTotal, userSessionDOS.size());
- assertPojoEquals(sessionDO, userSessionDOS.get(0), "updateTime");
+ assertEquals(1, count);
+ assertNull(userSessionMapper.selectById(userSession.getId())); // 已删除
+ verify(loginLogService).createLoginLog(argThat(loginLog -> {
+ assertPojoEquals(userSession, loginLog);
+ assertEquals(LoginLogTypeEnum.LOGOUT_TIMEOUT.getType(), loginLog.getLogType());
+ assertEquals(LoginResultEnum.SUCCESS.getResult(), loginLog.getResult());
+ return true;
+ }));
}
@Test
@@ -140,80 +165,86 @@ public class UserSessionServiceImplTest extends BaseDbAndRedisUnitTest {
o.setUserType(randomEle(UserTypeEnum.values()).getValue());
o.setTenantId(0L); // 租户设置为 0,因为暂未启用多租户组件
});
- // mock 方法
- when(securityProperties.getSessionTimeout()).thenReturn(Duration.ofDays(1));
// 调用
- String sessionId = userSessionService.createUserSession(loginUser, userIp, userAgent);
+ String token = userSessionService.createUserSession(loginUser, userIp, userAgent);
// 校验 UserSessionDO 记录
- UserSessionDO userSessionDO = userSessionMapper.selectById(sessionId);
+ UserSessionDO userSessionDO = userSessionMapper.selectOne(UserSessionDO::getToken, token);
assertPojoEquals(loginUser, userSessionDO, "id", "updateTime");
- assertEquals(sessionId, userSessionDO.getId());
+ assertEquals(token, userSessionDO.getToken());
assertEquals(userIp, userSessionDO.getUserIp());
assertEquals(userAgent, userSessionDO.getUserAgent());
// 校验 LoginUser 缓存
- LoginUser redisLoginUser = loginUserRedisDAO.get(sessionId);
+ LoginUser redisLoginUser = loginUserRedisDAO.get(token);
assertPojoEquals(loginUser, redisLoginUser, "username", "password");
}
@Test
- public void testCreateRefreshUserSession_success() {
+ public void testCreateRefreshUserSession() {
// 准备参数
- String sessionId = randomString();
- String userIp = randomString();
- String userAgent = randomString();
- long timeLong = randomLongId();
- String userName = randomString();
- Date date = randomDate();
+ String token = randomString();
+
+ // mock redis 数据
LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setUserType(randomEle(UserTypeEnum.values()).getValue()));
- // mock 方法
- when(securityProperties.getSessionTimeout()).thenReturn(Duration.ofDays(1));
- // mock 数据
- loginUser.setUpdateTime(date);
- loginUserRedisDAO.set(sessionId, loginUser);
- UserSessionDO userSession = UserSessionDO.builder().id(sessionId)
- .userId(loginUser.getId()).userType(loginUser.getUserType())
- .userIp(userIp).userAgent(userAgent).username(userName)
- .sessionTimeout(addTime(Duration.ofMillis(timeLong)))
- .build();
+ loginUserRedisDAO.set(token, loginUser);
+ // mock db 数据
+ UserSessionDO userSession = randomPojo(UserSessionDO.class, o -> {
+ o.setUserType(randomEle(UserTypeEnum.values()).getValue());
+ o.setToken(token);
+ });
userSessionMapper.insert(userSession);
// 调用
- userSessionService.refreshUserSession(sessionId, loginUser);
+ userSessionService.refreshUserSession(token, loginUser);
// 校验 LoginUser 缓存
- LoginUser redisLoginUser = loginUserRedisDAO.get(sessionId);
- assertNotEquals(redisLoginUser.getUpdateTime(), date);
+ LoginUser redisLoginUser = loginUserRedisDAO.get(token);
+ assertPojoEquals(redisLoginUser, loginUser, "username", "password");
// 校验 UserSessionDO 记录
- UserSessionDO updateDO = userSessionMapper.selectById(sessionId);
+ UserSessionDO updateDO = userSessionMapper.selectOne(UserSessionDO::getToken, token);
assertEquals(updateDO.getUsername(), loginUser.getUsername());
- assertNotEquals(updateDO.getUpdateTime(), userSession.getUpdateTime());
- assertNotEquals(updateDO.getSessionTimeout(), addTime(Duration.ofMillis(timeLong)));
+ assertNotNull(userSession.getUpdateTime());
+ assertNotNull(userSession.getSessionTimeout());
}
@Test
- public void testDeleteUserSession_success() {
+ public void testDeleteUserSession_Token() {
// 准备参数
- String sessionId = randomString();
- String userIp = randomString();
- String userAgent = randomString();
- Long timeLong = randomLongId();
- LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setUserType(randomEle(UserTypeEnum.values()).getValue()));
- // mock 存入 Redis
- when(securityProperties.getSessionTimeout()).thenReturn(Duration.ofDays(1));
- // mock 数据
- loginUserRedisDAO.set(sessionId, loginUser);
- UserSessionDO userSession = UserSessionDO.builder().id(sessionId)
- .userId(loginUser.getId()).userType(loginUser.getUserType())
- .userIp(userIp).userAgent(userAgent).username(loginUser.getUsername())
- .sessionTimeout(addTime(Duration.ofMillis(timeLong)))
- .build();
+ String token = randomString();
+
+ // mock redis 数据
+ loginUserRedisDAO.set(token, new LoginUser());
+ // mock db 数据
+ UserSessionDO userSession = randomPojo(UserSessionDO.class, o -> {
+ o.setUserType(randomEle(UserTypeEnum.values()).getValue());
+ o.setToken(token);
+ });
userSessionMapper.insert(userSession);
// 调用
- userSessionService.deleteUserSession(sessionId);
+ userSessionService.deleteUserSession(token);
// 校验数据不存在了
- assertNull(loginUserRedisDAO.get(sessionId));
- assertNull(userSessionMapper.selectById(sessionId));
+ assertNull(loginUserRedisDAO.get(token));
+ assertNull(userSessionMapper.selectOne(UserSessionDO::getToken, token));
+ }
+
+ @Test
+ public void testDeleteUserSession_Id() {
+ // mock db 数据
+ UserSessionDO userSession = randomPojo(UserSessionDO.class, o -> {
+ o.setUserType(randomEle(UserTypeEnum.values()).getValue());
+ });
+ userSessionMapper.insert(userSession);
+ // mock redis 数据
+ loginUserRedisDAO.set(userSession.getToken(), new LoginUser());
+
+ // 准备参数
+ Long id = userSession.getId();
+
+ // 调用
+ userSessionService.deleteUserSession(id);
+ // 校验数据不存在了
+ assertNull(loginUserRedisDAO.get(userSession.getToken()));
+ assertNull(userSessionMapper.selectById(id));
}
}
diff --git a/yudao-module-system/yudao-module-system-biz/src/test/resources/sql/create_tables.sql b/yudao-module-system/yudao-module-system-biz/src/test/resources/sql/create_tables.sql
index 723c85406..31f2f7e09 100644
--- a/yudao-module-system/yudao-module-system-biz/src/test/resources/sql/create_tables.sql
+++ b/yudao-module-system/yudao-module-system-biz/src/test/resources/sql/create_tables.sql
@@ -115,7 +115,8 @@ CREATE TABLE IF NOT EXISTS "system_dict_type" (
) COMMENT '字典类型表';
CREATE TABLE IF NOT EXISTS `system_user_session` (
- `id` varchar(32) NOT NULL,
+ "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+ `token` varchar(32) NOT NULL,
`user_id` bigint DEFAULT NULL,
"user_type" tinyint NOT NULL,
`username` varchar(50) NOT NULL DEFAULT '',
diff --git a/yudao-server/pom.xml b/yudao-server/pom.xml
index 06c5750cf..fb212e578 100644
--- a/yudao-server/pom.xml
+++ b/yudao-server/pom.xml
@@ -21,7 +21,6 @@
https://github.com/YunaiV/ruoyi-vue-pro
-
cn.iocoder.boot
yudao-module-member-biz
@@ -43,11 +42,11 @@
${revision}
-
- cn.iocoder.boot
- yudao-module-bpm-biz-flowable
- ${revision}
-
+
+
+
+
+
diff --git a/yudao-server/src/main/resources/application-local.yaml b/yudao-server/src/main/resources/application-local.yaml
index 4ee5835ee..5083e865a 100644
--- a/yudao-server/src/main/resources/application-local.yaml
+++ b/yudao-server/src/main/resources/application-local.yaml
@@ -49,6 +49,9 @@ spring:
# url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例
username: root
password: 123456
+# url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.master.name} # SQLServer 连接的示例
+# username: sa
+# password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W
slave: # 模拟从库,可根据自己需要修改
name: ruoyi-vue-pro
url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL 连接的示例
@@ -56,6 +59,9 @@ spring:
# url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例
username: root
password: 123456
+# url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.slave.name} # SQLServer 连接的示例
+# username: sa
+# password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W
# Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
redis: