Commit 8606f5c6053b0947213f4e4ed336b4cbc17b2502

Authored by YunaiV
1 parent 0f21b55e

优化 UserSession 的实现,将 id 变成自增,额外增加 token 字段

Showing 24 changed files with 322 additions and 235 deletions
sql/dm/README.md 0 → 100644
  1 +暂未适配国产 DM 数据库,如果你有需要,可以微信联系 wangwenbin-server 一起建设。
yudao-framework/yudao-spring-boot-starter-mybatis/pom.xml
@@ -46,6 +46,10 @@ @@ -46,6 +46,10 @@
46 <groupId>org.postgresql</groupId> 46 <groupId>org.postgresql</groupId>
47 <artifactId>postgresql</artifactId> 47 <artifactId>postgresql</artifactId>
48 </dependency> 48 </dependency>
  49 + <dependency>
  50 + <groupId>com.microsoft.sqlserver</groupId>
  51 + <artifactId>mssql-jdbc</artifactId>
  52 + </dependency>
49 53
50 <dependency> 54 <dependency>
51 <groupId>com.alibaba</groupId> 55 <groupId>com.alibaba</groupId>
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; @@ -3,7 +3,7 @@ package cn.iocoder.yudao.framework.security.config;
3 import cn.iocoder.yudao.framework.security.core.aop.PreAuthenticatedAspect; 3 import cn.iocoder.yudao.framework.security.core.aop.PreAuthenticatedAspect;
4 import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider; 4 import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider;
5 import cn.iocoder.yudao.framework.security.core.context.TransmittableThreadLocalSecurityContextHolderStrategy; 5 import cn.iocoder.yudao.framework.security.core.context.TransmittableThreadLocalSecurityContextHolderStrategy;
6 -import cn.iocoder.yudao.framework.security.core.filter.JWTAuthenticationTokenFilter; 6 +import cn.iocoder.yudao.framework.security.core.filter.TokenAuthenticationFilter;
7 import cn.iocoder.yudao.framework.security.core.handler.AccessDeniedHandlerImpl; 7 import cn.iocoder.yudao.framework.security.core.handler.AccessDeniedHandlerImpl;
8 import cn.iocoder.yudao.framework.security.core.handler.AuthenticationEntryPointImpl; 8 import cn.iocoder.yudao.framework.security.core.handler.AuthenticationEntryPointImpl;
9 import cn.iocoder.yudao.framework.security.core.handler.LogoutSuccessHandlerImpl; 9 import cn.iocoder.yudao.framework.security.core.handler.LogoutSuccessHandlerImpl;
@@ -86,9 +86,9 @@ public class YudaoSecurityAutoConfiguration { @@ -86,9 +86,9 @@ public class YudaoSecurityAutoConfiguration {
86 * Token 认证过滤器 Bean 86 * Token 认证过滤器 Bean
87 */ 87 */
88 @Bean 88 @Bean
89 - public JWTAuthenticationTokenFilter authenticationTokenFilter(MultiUserDetailsAuthenticationProvider authenticationProvider,  
90 - GlobalExceptionHandler globalExceptionHandler) {  
91 - return new JWTAuthenticationTokenFilter(securityProperties, authenticationProvider, globalExceptionHandler); 89 + public TokenAuthenticationFilter authenticationTokenFilter(MultiUserDetailsAuthenticationProvider authenticationProvider,
  90 + GlobalExceptionHandler globalExceptionHandler) {
  91 + return new TokenAuthenticationFilter(securityProperties, authenticationProvider, globalExceptionHandler);
92 } 92 }
93 93
94 /** 94 /**
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; @@ -2,7 +2,7 @@ package cn.iocoder.yudao.framework.security.config;
2 2
3 import cn.hutool.core.util.StrUtil; 3 import cn.hutool.core.util.StrUtil;
4 import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider; 4 import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider;
5 -import cn.iocoder.yudao.framework.security.core.filter.JWTAuthenticationTokenFilter; 5 +import cn.iocoder.yudao.framework.security.core.filter.TokenAuthenticationFilter;
6 import cn.iocoder.yudao.framework.web.config.WebProperties; 6 import cn.iocoder.yudao.framework.web.config.WebProperties;
7 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 7 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
8 import org.springframework.context.annotation.Bean; 8 import org.springframework.context.annotation.Bean;
@@ -55,7 +55,7 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap @@ -55,7 +55,7 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
55 * Token 认证过滤器 Bean 55 * Token 认证过滤器 Bean
56 */ 56 */
57 @Resource 57 @Resource
58 - private JWTAuthenticationTokenFilter authenticationTokenFilter; 58 + private TokenAuthenticationFilter authenticationTokenFilter;
59 59
60 /** 60 /**
61 * 自定义的权限映射 Bean 们 61 * 自定义的权限映射 Bean 们
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/JWTAuthenticationTokenFilter.java renamed to 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; @@ -18,13 +18,13 @@ import javax.servlet.http.HttpServletResponse;
18 import java.io.IOException; 18 import java.io.IOException;
19 19
20 /** 20 /**
21 - * JWT 过滤器,验证 token 的有效性 21 + * Token 过滤器,验证 token 的有效性
22 * 验证通过后,获得 {@link LoginUser} 信息,并加入到 Spring Security 上下文 22 * 验证通过后,获得 {@link LoginUser} 信息,并加入到 Spring Security 上下文
23 * 23 *
24 * @author 芋道源码 24 * @author 芋道源码
25 */ 25 */
26 @RequiredArgsConstructor 26 @RequiredArgsConstructor
27 -public class JWTAuthenticationTokenFilter extends OncePerRequestFilter { 27 +public class TokenAuthenticationFilter extends OncePerRequestFilter {
28 28
29 private final SecurityProperties securityProperties; 29 private final SecurityProperties securityProperties;
30 30
@@ -43,7 +43,7 @@ public class JWTAuthenticationTokenFilter extends OncePerRequestFilter { @@ -43,7 +43,7 @@ public class JWTAuthenticationTokenFilter extends OncePerRequestFilter {
43 LoginUser loginUser = authenticationProvider.verifyTokenAndRefresh(request, token); 43 LoginUser loginUser = authenticationProvider.verifyTokenAndRefresh(request, token);
44 // 模拟 Login 功能,方便日常开发调试 44 // 模拟 Login 功能,方便日常开发调试
45 if (loginUser == null) { 45 if (loginUser == null) {
46 - loginUser = this.mockLoginUser(request, token); 46 + loginUser = mockLoginUser(request, token);
47 } 47 }
48 // 设置当前用户 48 // 设置当前用户
49 if (loginUser != null) { 49 if (loginUser != null) {
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 { @@ -27,7 +27,8 @@ public class DefaultDatabaseQueryTest {
27 if (StrUtil.startWithAny(tableInfo.getName().toLowerCase(), "act_", "flw_", "qrtz_")) { 27 if (StrUtil.startWithAny(tableInfo.getName().toLowerCase(), "act_", "flw_", "qrtz_")) {
28 continue; 28 continue;
29 } 29 }
30 - System.out.println(String.format("CREATE SEQUENCE %s_seq MINVALUE 0;", tableInfo.getName())); 30 +// System.out.println(String.format("CREATE SEQUENCE %s_seq MINVALUE 0;", tableInfo.getName()));
  31 + System.out.println(String.format("DELETE FROM %s WHERE deleted = '1';", tableInfo.getName()));
31 } 32 }
32 System.out.println(tableInfos.size()); 33 System.out.println(tableInfos.size());
33 System.out.println(System.currentTimeMillis() - time); 34 System.out.println(System.currentTimeMillis() - time);
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; @@ -8,8 +8,6 @@ import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
8 import cn.iocoder.yudao.framework.security.core.LoginUser; 8 import cn.iocoder.yudao.framework.security.core.LoginUser;
9 import cn.iocoder.yudao.framework.security.core.authentication.MultiUsernamePasswordAuthenticationToken; 9 import cn.iocoder.yudao.framework.security.core.authentication.MultiUsernamePasswordAuthenticationToken;
10 import cn.iocoder.yudao.module.member.controller.app.auth.vo.*; 10 import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
11 -import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserBindReqVO;  
12 -import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO;  
13 import cn.iocoder.yudao.module.member.convert.auth.AuthConvert; 11 import cn.iocoder.yudao.module.member.convert.auth.AuthConvert;
14 import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO; 12 import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
15 import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper; 13 import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper;
@@ -88,7 +86,7 @@ public class MemberAuthServiceImpl implements MemberAuthService { @@ -88,7 +86,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
88 // 使用手机 + 密码,进行登录。 86 // 使用手机 + 密码,进行登录。
89 LoginUser loginUser = this.login0(reqVO.getMobile(), reqVO.getPassword()); 87 LoginUser loginUser = this.login0(reqVO.getMobile(), reqVO.getPassword());
90 88
91 - // 缓存登录用户到 Redis 中,返回 sessionId 编号 89 + // 缓存登录用户到 Redis 中,返回 Token 令牌
92 return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_USERNAME, userIp, userAgent); 90 return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_USERNAME, userIp, userAgent);
93 } 91 }
94 92
@@ -105,7 +103,7 @@ public class MemberAuthServiceImpl implements MemberAuthService { @@ -105,7 +103,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
105 // 执行登陆 103 // 执行登陆
106 LoginUser loginUser = AuthConvert.INSTANCE.convert(user); 104 LoginUser loginUser = AuthConvert.INSTANCE.convert(user);
107 105
108 - // 缓存登录用户到 Redis 中,返回 sessionId 编号 106 + // 缓存登录用户到 Redis 中,返回 Token 令牌
109 return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_SMS, userIp, userAgent); 107 return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_SMS, userIp, userAgent);
110 } 108 }
111 109
@@ -127,7 +125,7 @@ public class MemberAuthServiceImpl implements MemberAuthService { @@ -127,7 +125,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
127 // 创建 LoginUser 对象 125 // 创建 LoginUser 对象
128 LoginUser loginUser = AuthConvert.INSTANCE.convert(user); 126 LoginUser loginUser = AuthConvert.INSTANCE.convert(user);
129 127
130 - // 缓存登录用户到 Redis 中,返回 sessionId 编号 128 + // 缓存登录用户到 Redis 中,返回 Token 令牌
131 return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_SOCIAL, userIp, userAgent); 129 return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_SOCIAL, userIp, userAgent);
132 } 130 }
133 131
@@ -136,18 +134,18 @@ public class MemberAuthServiceImpl implements MemberAuthService { @@ -136,18 +134,18 @@ public class MemberAuthServiceImpl implements MemberAuthService {
136 // 使用手机号、手机验证码登录 134 // 使用手机号、手机验证码登录
137 AppAuthSmsLoginReqVO loginReqVO = AppAuthSmsLoginReqVO.builder() 135 AppAuthSmsLoginReqVO loginReqVO = AppAuthSmsLoginReqVO.builder()
138 .mobile(reqVO.getMobile()).code(reqVO.getSmsCode()).build(); 136 .mobile(reqVO.getMobile()).code(reqVO.getSmsCode()).build();
139 - String sessionId = this.smsLogin(loginReqVO, userIp, userAgent);  
140 - LoginUser loginUser = userSessionApi.getLoginUser(sessionId); 137 + String token = this.smsLogin(loginReqVO, userIp, userAgent);
  138 + LoginUser loginUser = userSessionApi.getLoginUser(token);
141 139
142 // 绑定社交用户 140 // 绑定社交用户
143 socialUserApi.bindSocialUser(AuthConvert.INSTANCE.convert(loginUser.getId(), getUserType().getValue(), reqVO)); 141 socialUserApi.bindSocialUser(AuthConvert.INSTANCE.convert(loginUser.getId(), getUserType().getValue(), reqVO));
144 - return sessionId; 142 + return token;
145 } 143 }
146 144
147 private String createUserSessionAfterLoginSuccess(LoginUser loginUser, LoginLogTypeEnum logType, String userIp, String userAgent) { 145 private String createUserSessionAfterLoginSuccess(LoginUser loginUser, LoginLogTypeEnum logType, String userIp, String userAgent) {
148 // 插入登陆日志 146 // 插入登陆日志
149 createLoginLog(loginUser.getUsername(), logType, LoginResultEnum.SUCCESS); 147 createLoginLog(loginUser.getUsername(), logType, LoginResultEnum.SUCCESS);
150 - // 缓存登录用户到 Redis 中,返回 sessionId 编号 148 + // 缓存登录用户到 Redis 中,返回 Token 令牌
151 return userSessionApi.createUserSession(loginUser, userIp, userAgent); 149 return userSessionApi.createUserSession(loginUser, userIp, userAgent);
152 } 150 }
153 151
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 { @@ -18,33 +18,33 @@ public interface UserSessionApi {
18 * @param loginUser 登录用户 18 * @param loginUser 登录用户
19 * @param userIp 用户 IP 19 * @param userIp 用户 IP
20 * @param userAgent 用户 UA 20 * @param userAgent 用户 UA
21 - * @return Session 编号 21 + * @return Token 令牌
22 */ 22 */
23 String createUserSession(@NotNull(message = "登录用户不能为空") LoginUser loginUser, String userIp, String userAgent); 23 String createUserSession(@NotNull(message = "登录用户不能为空") LoginUser loginUser, String userIp, String userAgent);
24 24
25 /** 25 /**
26 * 刷新在线用户 Session 的更新时间 26 * 刷新在线用户 Session 的更新时间
27 * 27 *
28 - * @param sessionId Session 编号 28 + * @param token Token 令牌
29 * @param loginUser 登录用户 29 * @param loginUser 登录用户
30 */ 30 */
31 - void refreshUserSession(@NotEmpty(message = "Session编号不能为空") String sessionId, 31 + void refreshUserSession(@NotEmpty(message = "Token 令牌不能为空") String token,
32 @NotNull(message = "登录用户不能为空") LoginUser loginUser); 32 @NotNull(message = "登录用户不能为空") LoginUser loginUser);
33 33
34 /** 34 /**
35 * 删除在线用户 Session 35 * 删除在线用户 Session
36 * 36 *
37 - * @param sessionId Session 编号 37 + * @param token Token 令牌
38 */ 38 */
39 - void deleteUserSession(String sessionId); 39 + void deleteUserSession(String token);
40 40
41 /** 41 /**
42 - * 获得 Session 编号对应的在线用户 42 + * 获得 Token 令牌对应的在线用户
43 * 43 *
44 - * @param sessionId Session 编号 44 + * @param token Token 令牌
45 * @return 在线用户 45 * @return 在线用户
46 */ 46 */
47 - LoginUser getLoginUser(String sessionId); 47 + LoginUser getLoginUser(String token);
48 48
49 /** 49 /**
50 * 获得 Session 超时时间,单位:毫秒 50 * 获得 Session 超时时间,单位:毫秒
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 { @@ -25,18 +25,18 @@ public class UserSessionApiImpl implements UserSessionApi {
25 } 25 }
26 26
27 @Override 27 @Override
28 - public void refreshUserSession(String sessionId, LoginUser loginUser) {  
29 - userSessionService.refreshUserSession(sessionId, loginUser); 28 + public void refreshUserSession(String token, LoginUser loginUser) {
  29 + userSessionService.refreshUserSession(token, loginUser);
30 } 30 }
31 31
32 @Override 32 @Override
33 - public void deleteUserSession(String sessionId) {  
34 - userSessionService.deleteUserSession(sessionId); 33 + public void deleteUserSession(String token) {
  34 + userSessionService.deleteUserSession(token);
35 } 35 }
36 36
37 @Override 37 @Override
38 - public LoginUser getLoginUser(String sessionId) {  
39 - return userSessionService.getLoginUser(sessionId); 38 + public LoginUser getLoginUser(String token) {
  39 + return userSessionService.getLoginUser(token);
40 } 40 }
41 41
42 @Override 42 @Override
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 { @@ -69,10 +69,9 @@ public class UserSessionController {
69 69
70 @DeleteMapping("/delete") 70 @DeleteMapping("/delete")
71 @ApiOperation("删除 Session") 71 @ApiOperation("删除 Session")
72 - @ApiImplicitParam(name = "id", value = "Session 编号", required = true, dataTypeClass = String.class,  
73 - example = "fe50b9f6-d177-44b1-8da9-72ea34f63db7") 72 + @ApiImplicitParam(name = "id", value = "Session 编号", required = true, dataTypeClass = Long.class, example = "1024")
74 @PreAuthorize("@ss.hasPermission('system:user-session:delete')") 73 @PreAuthorize("@ss.hasPermission('system:user-session:delete')")
75 - public CommonResult<Boolean> deleteUserSession(@RequestParam("id") String id) { 74 + public CommonResult<Boolean> deleteUserSession(@RequestParam("id") Long id) {
76 userSessionService.deleteUserSession(id); 75 userSessionService.deleteUserSession(id);
77 return success(true); 76 return success(true);
78 } 77 }
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; @@ -3,10 +3,7 @@ package cn.iocoder.yudao.module.system.dal.dataobject.auth;
3 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; 3 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
5 import cn.iocoder.yudao.framework.security.core.LoginUser; 5 import cn.iocoder.yudao.framework.security.core.LoginUser;
6 -import com.baomidou.mybatisplus.annotation.IdType;  
7 -import com.baomidou.mybatisplus.annotation.KeySequence;  
8 -import com.baomidou.mybatisplus.annotation.TableId;  
9 -import com.baomidou.mybatisplus.annotation.TableName; 6 +import com.baomidou.mybatisplus.annotation.*;
10 import lombok.Builder; 7 import lombok.Builder;
11 import lombok.Data; 8 import lombok.Data;
12 import lombok.EqualsAndHashCode; 9 import lombok.EqualsAndHashCode;
@@ -22,18 +19,21 @@ import java.util.Date; @@ -22,18 +19,21 @@ import java.util.Date;
22 * 19 *
23 * @author 芋道源码 20 * @author 芋道源码
24 */ 21 */
25 -@TableName(value = "system_user_session", autoResultMap = true)  
26 -@KeySequence("system_user_session_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 22 +@TableName(value = "system_user_session")
27 @Data 23 @Data
28 @Builder 24 @Builder
29 @EqualsAndHashCode(callSuper = true) 25 @EqualsAndHashCode(callSuper = true)
30 public class UserSessionDO extends BaseDO { 26 public class UserSessionDO extends BaseDO {
31 27
32 /** 28 /**
33 - * 会话编号, 即 sessionId 29 + * 会话编号
34 */ 30 */
35 - @TableId(type = IdType.INPUT)  
36 - private String id; 31 + private Long id;
  32 + /**
  33 + * 令牌
  34 + */
  35 + private String token;
  36 +
37 /** 37 /**
38 * 用户编号 38 * 用户编号
39 * 39 *
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; @@ -19,7 +19,7 @@ import java.util.Set;
19 * 19 *
20 * @author 芋道源码 20 * @author 芋道源码
21 */ 21 */
22 -@TableName(value = "system_user", autoResultMap = true) 22 +@TableName(value = "system_users", autoResultMap = true) // 由于 SQL Server 的 system_user 是关键字,所以使用 system_users
23 @KeySequence("system_user_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 23 @KeySequence("system_user_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
24 @Data 24 @Data
25 @EqualsAndHashCode(callSuper = true) 25 @EqualsAndHashCode(callSuper = true)
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/UserSessionMapper.java
1 package cn.iocoder.yudao.module.system.dal.mysql.auth; 1 package cn.iocoder.yudao.module.system.dal.mysql.auth;
2 2
3 -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageReqVO;  
4 -import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO;  
5 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
7 -import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX; 5 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageReqVO;
  7 +import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO;
8 import org.apache.ibatis.annotations.Mapper; 8 import org.apache.ibatis.annotations.Mapper;
9 9
10 import java.util.Collection; 10 import java.util.Collection;
@@ -15,13 +15,23 @@ import java.util.List; @@ -15,13 +15,23 @@ import java.util.List;
15 public interface UserSessionMapper extends BaseMapperX<UserSessionDO> { 15 public interface UserSessionMapper extends BaseMapperX<UserSessionDO> {
16 16
17 default PageResult<UserSessionDO> selectPage(UserSessionPageReqVO reqVO, Collection<Long> userIds) { 17 default PageResult<UserSessionDO> selectPage(UserSessionPageReqVO reqVO, Collection<Long> userIds) {
18 - return selectPage(reqVO, new QueryWrapperX<UserSessionDO>()  
19 - .inIfPresent("user_id", userIds)  
20 - .likeIfPresent("user_ip", reqVO.getUserIp())); 18 + return selectPage(reqVO, new LambdaQueryWrapperX<UserSessionDO>()
  19 + .inIfPresent(UserSessionDO::getUserId, userIds)
  20 + .likeIfPresent(UserSessionDO::getUserIp, reqVO.getUserIp()));
21 } 21 }
22 22
23 default List<UserSessionDO> selectListBySessionTimoutLt() { 23 default List<UserSessionDO> selectListBySessionTimoutLt() {
24 - return selectList(new QueryWrapperX<UserSessionDO>().lt("session_timeout",new Date())); 24 + return selectList(new LambdaQueryWrapperX<UserSessionDO>()
  25 + .lt(UserSessionDO::getSessionTimeout, new Date()));
  26 + }
  27 +
  28 + default void updateByToken(String token, UserSessionDO updateObj) {
  29 + update(updateObj, new LambdaQueryWrapperX<UserSessionDO>()
  30 + .eq(UserSessionDO::getToken, token));
  31 + }
  32 +
  33 + default void deleteByToken(String token) {
  34 + delete(new LambdaQueryWrapperX<UserSessionDO>().eq(UserSessionDO::getToken, token));
25 } 35 }
26 36
27 } 37 }
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; @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.system.dal.redis;
2 2
3 import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine; 3 import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
4 import cn.iocoder.yudao.framework.security.core.LoginUser; 4 import cn.iocoder.yudao.framework.security.core.LoginUser;
5 -import me.zhyd.oauth.model.AuthUser;  
6 5
7 import java.time.Duration; 6 import java.time.Duration;
8 7
@@ -20,7 +19,7 @@ public interface RedisKeyConstants { @@ -20,7 +19,7 @@ public interface RedisKeyConstants {
20 STRING, String.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); 19 STRING, String.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);
21 20
22 RedisKeyDefine LOGIN_USER = new RedisKeyDefine("登录用户的缓存", 21 RedisKeyDefine LOGIN_USER = new RedisKeyDefine("登录用户的缓存",
23 - "login_user:%s", // 参数为 sessionId 22 + "login_user:%s", // 参数为 token 令牌
24 STRING, LoginUser.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); 23 STRING, LoginUser.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);
25 24
26 RedisKeyDefine SOCIAL_AUTH_STATE = new RedisKeyDefine("社交登陆的 state", // 注意,它是被 JustAuth 的 justauth.type.prefix 使用到 25 RedisKeyDefine SOCIAL_AUTH_STATE = new RedisKeyDefine("社交登陆的 state", // 注意,它是被 JustAuth 的 justauth.type.prefix 使用到
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 { @@ -24,24 +24,29 @@ public class LoginUserRedisDAO {
24 @Resource 24 @Resource
25 private SecurityProperties securityProperties; 25 private SecurityProperties securityProperties;
26 26
27 - public LoginUser get(String sessionId) {  
28 - String redisKey = formatKey(sessionId); 27 + public LoginUser get(String token) {
  28 + String redisKey = formatKey(token);
29 return JsonUtils.parseObject(stringRedisTemplate.opsForValue().get(redisKey), LoginUser.class); 29 return JsonUtils.parseObject(stringRedisTemplate.opsForValue().get(redisKey), LoginUser.class);
30 } 30 }
31 31
32 - public void set(String sessionId, LoginUser loginUser) {  
33 - String redisKey = formatKey(sessionId); 32 + public Boolean exists(String token) {
  33 + String redisKey = formatKey(token);
  34 + return stringRedisTemplate.hasKey(redisKey);
  35 + }
  36 +
  37 + public void set(String token, LoginUser loginUser) {
  38 + String redisKey = formatKey(token);
34 stringRedisTemplate.opsForValue().set(redisKey, JsonUtils.toJsonString(loginUser), 39 stringRedisTemplate.opsForValue().set(redisKey, JsonUtils.toJsonString(loginUser),
35 securityProperties.getSessionTimeout()); 40 securityProperties.getSessionTimeout());
36 } 41 }
37 42
38 - public void delete(String sessionId) {  
39 - String redisKey = formatKey(sessionId); 43 + public void delete(String token) {
  44 + String redisKey = formatKey(token);
40 stringRedisTemplate.delete(redisKey); 45 stringRedisTemplate.delete(redisKey);
41 } 46 }
42 47
43 - private static String formatKey(String sessionId) {  
44 - return LOGIN_USER.formatKey(sessionId); 48 + private static String formatKey(String token) {
  49 + return LOGIN_USER.formatKey(token);
45 } 50 }
46 51
47 } 52 }
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 { @@ -24,7 +24,7 @@ public class UserSessionTimeoutJob implements JobHandler {
24 @Override 24 @Override
25 public String execute(String param) throws Exception { 25 public String execute(String param) throws Exception {
26 // 执行过期 26 // 执行过期
27 - Long timeoutCount = userSessionService.clearSessionTimeout(); 27 + Long timeoutCount = userSessionService.deleteTimeoutSession();
28 // 返回结果,记录每次的超时数量 28 // 返回结果,记录每次的超时数量
29 return String.format("移除在线会话数量为 %s 个", timeoutCount); 29 return String.format("移除在线会话数量为 %s 个", timeoutCount);
30 } 30 }
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 { @@ -104,7 +104,7 @@ public class AdminAuthServiceImpl implements AdminAuthService {
104 // 使用账号密码,进行登录 104 // 使用账号密码,进行登录
105 LoginUser loginUser = login0(reqVO.getUsername(), reqVO.getPassword()); 105 LoginUser loginUser = login0(reqVO.getUsername(), reqVO.getPassword());
106 106
107 - // 缓存登陆用户到 Redis 中,返回 sessionId 编号 107 + // 缓存登陆用户到 Redis 中,返回 Token 令牌
108 return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_USERNAME, userIp, userAgent); 108 return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_USERNAME, userIp, userAgent);
109 } 109 }
110 110
@@ -207,7 +207,7 @@ public class AdminAuthServiceImpl implements AdminAuthService { @@ -207,7 +207,7 @@ public class AdminAuthServiceImpl implements AdminAuthService {
207 // 创建 LoginUser 对象 207 // 创建 LoginUser 对象
208 LoginUser loginUser = buildLoginUser(user); 208 LoginUser loginUser = buildLoginUser(user);
209 209
210 - // 缓存登录用户到 Redis 中,返回 sessionId 编号 210 + // 缓存登录用户到 Redis 中,返回 Token 令牌
211 return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_SOCIAL, userIp, userAgent); 211 return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_SOCIAL, userIp, userAgent);
212 } 212 }
213 213
@@ -219,14 +219,14 @@ public class AdminAuthServiceImpl implements AdminAuthService { @@ -219,14 +219,14 @@ public class AdminAuthServiceImpl implements AdminAuthService {
219 // 绑定社交用户 219 // 绑定社交用户
220 socialUserService.bindSocialUser(AuthConvert.INSTANCE.convert(loginUser.getId(), getUserType().getValue(), reqVO)); 220 socialUserService.bindSocialUser(AuthConvert.INSTANCE.convert(loginUser.getId(), getUserType().getValue(), reqVO));
221 221
222 - // 缓存登录用户到 Redis 中,返回 sessionId 编号 222 + // 缓存登录用户到 Redis 中,返回 Token 令牌
223 return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_SOCIAL, userIp, userAgent); 223 return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_SOCIAL, userIp, userAgent);
224 } 224 }
225 225
226 private String createUserSessionAfterLoginSuccess(LoginUser loginUser, LoginLogTypeEnum logType, String userIp, String userAgent) { 226 private String createUserSessionAfterLoginSuccess(LoginUser loginUser, LoginLogTypeEnum logType, String userIp, String userAgent) {
227 // 插入登陆日志 227 // 插入登陆日志
228 createLoginLog(loginUser.getUsername(), logType, LoginResultEnum.SUCCESS); 228 createLoginLog(loginUser.getUsername(), logType, LoginResultEnum.SUCCESS);
229 - // 缓存登录用户到 Redis 中,返回 sessionId 编号 229 + // 缓存登录用户到 Redis 中,返回 Token 令牌
230 return userSessionService.createUserSession(loginUser, userIp, userAgent); 230 return userSessionService.createUserSession(loginUser, userIp, userAgent);
231 } 231 }
232 232
@@ -240,7 +240,7 @@ public class AdminAuthServiceImpl implements AdminAuthService { @@ -240,7 +240,7 @@ public class AdminAuthServiceImpl implements AdminAuthService {
240 // 删除 session 240 // 删除 session
241 userSessionService.deleteUserSession(token); 241 userSessionService.deleteUserSession(token);
242 // 记录登出日志 242 // 记录登出日志
243 - this.createLogoutLog(loginUser.getId(), loginUser.getUsername()); 243 + createLogoutLog(loginUser.getId(), loginUser.getUsername());
244 } 244 }
245 245
246 @Override 246 @Override
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 { @@ -25,7 +25,7 @@ public interface UserSessionService {
25 * 25 *
26 * @return {@link Long } 移出的超时用户数量 26 * @return {@link Long } 移出的超时用户数量
27 **/ 27 **/
28 - long clearSessionTimeout(); 28 + long deleteTimeoutSession();
29 29
30 /** 30 /**
31 * 创建在线用户 Session 31 * 创建在线用户 Session
@@ -33,32 +33,39 @@ public interface UserSessionService { @@ -33,32 +33,39 @@ public interface UserSessionService {
33 * @param loginUser 登录用户 33 * @param loginUser 登录用户
34 * @param userIp 用户 IP 34 * @param userIp 用户 IP
35 * @param userAgent 用户 UA 35 * @param userAgent 用户 UA
36 - * @return Session 编号 36 + * @return Token 令牌
37 */ 37 */
38 String createUserSession(LoginUser loginUser, String userIp, String userAgent); 38 String createUserSession(LoginUser loginUser, String userIp, String userAgent);
39 39
40 /** 40 /**
41 * 刷新在线用户 Session 的更新时间 41 * 刷新在线用户 Session 的更新时间
42 * 42 *
43 - * @param sessionId Session 编号 43 + * @param token 令牌
44 * @param loginUser 登录用户 44 * @param loginUser 登录用户
45 */ 45 */
46 - void refreshUserSession(String sessionId, LoginUser loginUser); 46 + void refreshUserSession(String token, LoginUser loginUser);
47 47
48 /** 48 /**
49 * 删除在线用户 Session 49 * 删除在线用户 Session
50 * 50 *
51 - * @param sessionId Session 编号 51 + * @param token token 令牌
52 */ 52 */
53 - void deleteUserSession(String sessionId); 53 + void deleteUserSession(String token);
54 54
55 /** 55 /**
56 - * 获得 Session 编号对应的在线用户 56 + * 删除在线用户 Session
  57 + *
  58 + * @param id 编号
  59 + */
  60 + void deleteUserSession(Long id);
  61 +
  62 + /**
  63 + * 获得 Token 对应的在线用户
57 * 64 *
58 - * @param sessionId Session 编号 65 + * @param token 令牌
59 * @return 在线用户 66 * @return 在线用户
60 */ 67 */
61 - LoginUser getLoginUser(String sessionId); 68 + LoginUser getLoginUser(String token);
62 69
63 /** 70 /**
64 * 获得 Session 超时时间,单位:毫秒 71 * 获得 Session 超时时间,单位:毫秒
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; @@ -3,28 +3,28 @@ package cn.iocoder.yudao.module.system.service.auth;
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.util.IdUtil; 4 import cn.hutool.core.util.IdUtil;
5 import cn.hutool.core.util.StrUtil; 5 import cn.hutool.core.util.StrUtil;
  6 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  7 +import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
6 import cn.iocoder.yudao.framework.security.config.SecurityProperties; 8 import cn.iocoder.yudao.framework.security.config.SecurityProperties;
7 import cn.iocoder.yudao.framework.security.core.LoginUser; 9 import cn.iocoder.yudao.framework.security.core.LoginUser;
  10 +import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO;
8 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageReqVO; 11 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageReqVO;
9 import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO; 12 import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO;
10 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; 13 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
11 import cn.iocoder.yudao.module.system.dal.mysql.auth.UserSessionMapper; 14 import cn.iocoder.yudao.module.system.dal.mysql.auth.UserSessionMapper;
  15 +import cn.iocoder.yudao.module.system.dal.redis.auth.LoginUserRedisDAO;
12 import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum; 16 import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
13 import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum; 17 import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
14 import cn.iocoder.yudao.module.system.service.logger.LoginLogService; 18 import cn.iocoder.yudao.module.system.service.logger.LoginLogService;
15 import cn.iocoder.yudao.module.system.service.user.AdminUserService; 19 import cn.iocoder.yudao.module.system.service.user.AdminUserService;
16 -import cn.iocoder.yudao.module.system.dal.redis.auth.LoginUserRedisDAO;  
17 -import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO;  
18 -import cn.iocoder.yudao.framework.common.pojo.PageResult;  
19 -import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;  
20 -import com.google.common.collect.Lists;  
21 import lombok.extern.slf4j.Slf4j; 20 import lombok.extern.slf4j.Slf4j;
22 import org.springframework.stereotype.Service; 21 import org.springframework.stereotype.Service;
23 22
24 import javax.annotation.Resource; 23 import javax.annotation.Resource;
25 import java.time.Duration; 24 import java.time.Duration;
26 -import java.util.*;  
27 -import java.util.stream.Collectors; 25 +import java.util.Collection;
  26 +import java.util.Date;
  27 +import java.util.List;
28 28
29 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; 29 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
30 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.addTime; 30 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.addTime;
@@ -65,82 +65,99 @@ public class UserSessionServiceImpl implements UserSessionService { @@ -65,82 +65,99 @@ public class UserSessionServiceImpl implements UserSessionService {
65 return userSessionMapper.selectPage(reqVO, userIds); 65 return userSessionMapper.selectPage(reqVO, userIds);
66 } 66 }
67 67
68 - // TODO @芋艿:优化下该方法  
69 @Override 68 @Override
70 - public long clearSessionTimeout() {  
71 - // 获取db里已经超时的用户列表  
72 - List<UserSessionDO> sessionTimeoutDOS = userSessionMapper.selectListBySessionTimoutLt();  
73 - Map<String, UserSessionDO> timeoutSessionDOMap = sessionTimeoutDOS  
74 - .stream()  
75 - .filter(sessionDO -> loginUserRedisDAO.get(sessionDO.getId()) == null)  
76 - .collect(Collectors.toMap(UserSessionDO::getId, o -> o));  
77 - // 确认已经超时,按批次移出在线用户列表  
78 - if (CollUtil.isNotEmpty(timeoutSessionDOMap)) {  
79 - Lists.partition(new ArrayList<>(timeoutSessionDOMap.keySet()), 100)  
80 - .forEach(userSessionMapper::deleteBatchIds);  
81 - // 记录用户超时退出日志  
82 - createTimeoutLogoutLog(timeoutSessionDOMap.values()); 69 + public long deleteTimeoutSession() {
  70 + // 获取 db 里已经超时的用户列表
  71 + List<UserSessionDO> timeoutSessions = userSessionMapper.selectListBySessionTimoutLt();
  72 + if (CollUtil.isEmpty(timeoutSessions)) {
  73 + return 0L;
83 } 74 }
84 - return timeoutSessionDOMap.size();  
85 - }  
86 75
87 - private void createTimeoutLogoutLog(Collection<UserSessionDO> timeoutSessionDOS) {  
88 - for (UserSessionDO timeoutSessionDO : timeoutSessionDOS) {  
89 - LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();  
90 - reqDTO.setLogType(LoginLogTypeEnum.LOGOUT_TIMEOUT.getType());  
91 - reqDTO.setTraceId(TracerUtils.getTraceId());  
92 - reqDTO.setUserId(timeoutSessionDO.getUserId());  
93 - reqDTO.setUserType(timeoutSessionDO.getUserType());  
94 - reqDTO.setUsername(timeoutSessionDO.getUsername());  
95 - reqDTO.setUserAgent(timeoutSessionDO.getUserAgent());  
96 - reqDTO.setUserIp(timeoutSessionDO.getUserIp());  
97 - reqDTO.setResult(LoginResultEnum.SUCCESS.getResult());  
98 - loginLogService.createLoginLog(reqDTO); 76 + // 由于过期的用户一般不多,所以顺序遍历,进行清理
  77 + int count = 0;
  78 + for (UserSessionDO session : timeoutSessions) {
  79 + // 基于 Redis 二次判断,同时也保证 Redis Key 的立即过期,避免延迟导致浪费内存空间
  80 + if (loginUserRedisDAO.exists(session.getToken())) {
  81 + continue;
  82 + }
  83 + userSessionMapper.deleteById(session.getId());
  84 + // 记录退出日志
  85 + createLogoutLog(session, LoginLogTypeEnum.LOGOUT_TIMEOUT);
  86 + count++;
99 } 87 }
  88 + return count;
  89 + }
  90 +
  91 + private void createLogoutLog(UserSessionDO session, LoginLogTypeEnum type) {
  92 + LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
  93 + reqDTO.setLogType(type.getType());
  94 + reqDTO.setTraceId(TracerUtils.getTraceId());
  95 + reqDTO.setUserId(session.getUserId());
  96 + reqDTO.setUserType(session.getUserType());
  97 + reqDTO.setUsername(session.getUsername());
  98 + reqDTO.setUserAgent(session.getUserAgent());
  99 + reqDTO.setUserIp(session.getUserIp());
  100 + reqDTO.setResult(LoginResultEnum.SUCCESS.getResult());
  101 + loginLogService.createLoginLog(reqDTO);
100 } 102 }
101 103
102 @Override 104 @Override
103 public String createUserSession(LoginUser loginUser, String userIp, String userAgent) { 105 public String createUserSession(LoginUser loginUser, String userIp, String userAgent) {
104 // 生成 Session 编号 106 // 生成 Session 编号
105 - String sessionId = generateSessionId(); 107 + String token = generateToken();
106 // 写入 Redis 缓存 108 // 写入 Redis 缓存
107 loginUser.setUpdateTime(new Date()); 109 loginUser.setUpdateTime(new Date());
108 - loginUserRedisDAO.set(sessionId, loginUser); 110 + loginUserRedisDAO.set(token, loginUser);
109 // 写入 DB 中 111 // 写入 DB 中
110 - UserSessionDO userSession = UserSessionDO.builder().id(sessionId) 112 + UserSessionDO userSession = UserSessionDO.builder().token(token)
111 .userId(loginUser.getId()).userType(loginUser.getUserType()) 113 .userId(loginUser.getId()).userType(loginUser.getUserType())
112 .userIp(userIp).userAgent(userAgent).username(loginUser.getUsername()) 114 .userIp(userIp).userAgent(userAgent).username(loginUser.getUsername())
113 .sessionTimeout(addTime(Duration.ofMillis(getSessionTimeoutMillis()))) 115 .sessionTimeout(addTime(Duration.ofMillis(getSessionTimeoutMillis())))
114 .build(); 116 .build();
115 userSessionMapper.insert(userSession); 117 userSessionMapper.insert(userSession);
116 - // 返回 Session 编号  
117 - return sessionId; 118 + // 返回 Token 令牌
  119 + return token;
118 } 120 }
119 121
120 @Override 122 @Override
121 - public void refreshUserSession(String sessionId, LoginUser loginUser) { 123 + public void refreshUserSession(String token, LoginUser loginUser) {
122 // 写入 Redis 缓存 124 // 写入 Redis 缓存
123 loginUser.setUpdateTime(new Date()); 125 loginUser.setUpdateTime(new Date());
124 - loginUserRedisDAO.set(sessionId, loginUser); 126 + loginUserRedisDAO.set(token, loginUser);
125 // 更新 DB 中 127 // 更新 DB 中
126 - UserSessionDO updateObj = UserSessionDO.builder().id(sessionId).build(); 128 + UserSessionDO updateObj = UserSessionDO.builder().build();
127 updateObj.setUsername(loginUser.getUsername()); 129 updateObj.setUsername(loginUser.getUsername());
128 updateObj.setUpdateTime(new Date()); 130 updateObj.setUpdateTime(new Date());
129 updateObj.setSessionTimeout(addTime(Duration.ofMillis(getSessionTimeoutMillis()))); 131 updateObj.setSessionTimeout(addTime(Duration.ofMillis(getSessionTimeoutMillis())));
130 - userSessionMapper.updateById(updateObj); 132 + userSessionMapper.updateByToken(token, updateObj);
131 } 133 }
132 134
133 @Override 135 @Override
134 - public void deleteUserSession(String sessionId) { 136 + public void deleteUserSession(String token) {
  137 + // 删除 Redis 缓存
  138 + loginUserRedisDAO.delete(token);
  139 + // 删除 DB 记录
  140 + userSessionMapper.deleteByToken(token);
  141 + // 无需记录日志,因为退出那已经记录
  142 + }
  143 +
  144 + @Override
  145 + public void deleteUserSession(Long id) {
  146 + UserSessionDO session = userSessionMapper.selectById(id);
  147 + if (session == null) {
  148 + return;
  149 + }
135 // 删除 Redis 缓存 150 // 删除 Redis 缓存
136 - loginUserRedisDAO.delete(sessionId); 151 + loginUserRedisDAO.delete(session.getToken());
137 // 删除 DB 记录 152 // 删除 DB 记录
138 - userSessionMapper.deleteById(sessionId); 153 + userSessionMapper.deleteById(id);
  154 + // 记录退出日志
  155 + createLogoutLog(session, LoginLogTypeEnum.LOGOUT_DELETE);
139 } 156 }
140 157
141 @Override 158 @Override
142 - public LoginUser getLoginUser(String sessionId) {  
143 - return loginUserRedisDAO.get(sessionId); 159 + public LoginUser getLoginUser(String token) {
  160 + return loginUserRedisDAO.get(token);
144 } 161 }
145 162
146 @Override 163 @Override
@@ -149,11 +166,11 @@ public class UserSessionServiceImpl implements UserSessionService { @@ -149,11 +166,11 @@ public class UserSessionServiceImpl implements UserSessionService {
149 } 166 }
150 167
151 /** 168 /**
152 - * 生成 Session 编号,目前采用 UUID 算法 169 + * 生成 Token 令牌,目前采用 UUID 算法
153 * 170 *
154 * @return Session 编号 171 * @return Session 编号
155 */ 172 */
156 - private static String generateSessionId() { 173 + private static String generateToken() {
157 return IdUtil.fastSimpleUUID(); 174 return IdUtil.fastSimpleUUID();
158 } 175 }
159 176
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 { @@ -134,6 +134,7 @@ public class AuthServiceImplTest extends BaseDbUnitTest {
134 AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class); 134 AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class);
135 String userIp = randomString(); 135 String userIp = randomString();
136 String userAgent = randomString(); 136 String userAgent = randomString();
  137 +
137 // 调用, 并断言异常 138 // 调用, 并断言异常
138 assertServiceException(() -> authService.login(reqVO, userIp, userAgent), AUTH_LOGIN_CAPTCHA_NOT_FOUND); 139 assertServiceException(() -> authService.login(reqVO, userIp, userAgent), AUTH_LOGIN_CAPTCHA_NOT_FOUND);
139 // 校验调用参数 140 // 校验调用参数
@@ -148,10 +149,12 @@ public class AuthServiceImplTest extends BaseDbUnitTest { @@ -148,10 +149,12 @@ public class AuthServiceImplTest extends BaseDbUnitTest {
148 // 准备参数 149 // 准备参数
149 String userIp = randomString(); 150 String userIp = randomString();
150 String userAgent = randomString(); 151 String userAgent = randomString();
151 - String code = randomString();  
152 AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class); 152 AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class);
  153 +
153 // mock 验证码不正确 154 // mock 验证码不正确
  155 + String code = randomString();
154 when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(code); 156 when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(code);
  157 +
155 // 调用, 并断言异常 158 // 调用, 并断言异常
156 assertServiceException(() -> authService.login(reqVO, userIp, userAgent), AUTH_LOGIN_CAPTCHA_CODE_ERROR); 159 assertServiceException(() -> authService.login(reqVO, userIp, userAgent), AUTH_LOGIN_CAPTCHA_CODE_ERROR);
157 // 校验调用参数 160 // 校验调用参数
@@ -172,6 +175,7 @@ public class AuthServiceImplTest extends BaseDbUnitTest { @@ -172,6 +175,7 @@ public class AuthServiceImplTest extends BaseDbUnitTest {
172 // mock 抛出异常 175 // mock 抛出异常
173 when(authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(reqVO.getUsername(), reqVO.getPassword()))) 176 when(authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(reqVO.getUsername(), reqVO.getPassword())))
174 .thenThrow(new BadCredentialsException("测试账号或密码不正确")); 177 .thenThrow(new BadCredentialsException("测试账号或密码不正确"));
  178 +
175 // 调用, 并断言异常 179 // 调用, 并断言异常
176 assertServiceException(() -> authService.login(reqVO, userIp, userAgent), AUTH_LOGIN_BAD_CREDENTIALS); 180 assertServiceException(() -> authService.login(reqVO, userIp, userAgent), AUTH_LOGIN_BAD_CREDENTIALS);
177 // 校验调用参数 181 // 校验调用参数
@@ -188,11 +192,13 @@ public class AuthServiceImplTest extends BaseDbUnitTest { @@ -188,11 +192,13 @@ public class AuthServiceImplTest extends BaseDbUnitTest {
188 String userIp = randomString(); 192 String userIp = randomString();
189 String userAgent = randomString(); 193 String userAgent = randomString();
190 AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class); 194 AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class);
  195 +
191 // mock 验证码正确 196 // mock 验证码正确
192 when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(reqVO.getCode()); 197 when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(reqVO.getCode());
193 // mock 抛出异常 198 // mock 抛出异常
194 when(authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(reqVO.getUsername(), reqVO.getPassword()))) 199 when(authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(reqVO.getUsername(), reqVO.getPassword())))
195 .thenThrow(new DisabledException("测试用户被禁用")); 200 .thenThrow(new DisabledException("测试用户被禁用"));
  201 +
196 // 调用, 并断言异常 202 // 调用, 并断言异常
197 assertServiceException(() -> authService.login(reqVO, userIp, userAgent), AUTH_LOGIN_USER_DISABLED); 203 assertServiceException(() -> authService.login(reqVO, userIp, userAgent), AUTH_LOGIN_USER_DISABLED);
198 // 校验调用参数 204 // 校验调用参数
@@ -214,6 +220,7 @@ public class AuthServiceImplTest extends BaseDbUnitTest { @@ -214,6 +220,7 @@ public class AuthServiceImplTest extends BaseDbUnitTest {
214 // mock 抛出异常 220 // mock 抛出异常
215 when(authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(reqVO.getUsername(), reqVO.getPassword()))) 221 when(authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(reqVO.getUsername(), reqVO.getPassword())))
216 .thenThrow(new AuthenticationException("测试未知异常") {}); 222 .thenThrow(new AuthenticationException("测试未知异常") {});
  223 +
217 // 调用, 并断言异常 224 // 调用, 并断言异常
218 assertServiceException(() -> authService.login(reqVO, userIp, userAgent), AUTH_LOGIN_FAIL_UNKNOWN); 225 assertServiceException(() -> authService.login(reqVO, userIp, userAgent), AUTH_LOGIN_FAIL_UNKNOWN);
219 // 校验调用参数 226 // 校验调用参数
@@ -229,27 +236,29 @@ public class AuthServiceImplTest extends BaseDbUnitTest { @@ -229,27 +236,29 @@ public class AuthServiceImplTest extends BaseDbUnitTest {
229 // 准备参数 236 // 准备参数
230 String userIp = randomString(); 237 String userIp = randomString();
231 String userAgent = randomString(); 238 String userAgent = randomString();
  239 + AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class);
  240 +
  241 + // mock 验证码正确
  242 + when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(reqVO.getCode());
  243 + // mock authentication
232 Long userId = randomLongId(); 244 Long userId = randomLongId();
233 Set<Long> userRoleIds = randomSet(Long.class); 245 Set<Long> userRoleIds = randomSet(Long.class);
234 - String sessionId = randomString();  
235 - AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class);  
236 LoginUser loginUser = randomPojo(LoginUser.class, o -> { 246 LoginUser loginUser = randomPojo(LoginUser.class, o -> {
237 o.setId(userId); 247 o.setId(userId);
238 o.setRoleIds(userRoleIds); 248 o.setRoleIds(userRoleIds);
239 }); 249 });
240 - // mock 验证码正确  
241 - when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(reqVO.getCode());  
242 - // mock authentication  
243 when(authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(reqVO.getUsername(), reqVO.getPassword()))) 250 when(authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(reqVO.getUsername(), reqVO.getPassword())))
244 .thenReturn(authentication); 251 .thenReturn(authentication);
245 when(authentication.getPrincipal()).thenReturn(loginUser); 252 when(authentication.getPrincipal()).thenReturn(loginUser);
246 // mock 获得 User 拥有的角色编号数组 253 // mock 获得 User 拥有的角色编号数组
247 when(permissionService.getUserRoleIds(userId, singleton(CommonStatusEnum.ENABLE.getStatus()))).thenReturn(userRoleIds); 254 when(permissionService.getUserRoleIds(userId, singleton(CommonStatusEnum.ENABLE.getStatus()))).thenReturn(userRoleIds);
248 // mock 缓存登录用户到 Redis 255 // mock 缓存登录用户到 Redis
249 - when(userSessionService.createUserSession(loginUser, userIp, userAgent)).thenReturn(sessionId); 256 + String token = randomString();
  257 + when(userSessionService.createUserSession(loginUser, userIp, userAgent)).thenReturn(token);
  258 +
250 // 调用, 并断言异常 259 // 调用, 并断言异常
251 String login = authService.login(reqVO, userIp, userAgent); 260 String login = authService.login(reqVO, userIp, userAgent);
252 - assertEquals(sessionId, login); 261 + assertEquals(token, login);
253 // 校验调用参数 262 // 校验调用参数
254 verify(captchaService, times(1)).deleteCaptchaCode(reqVO.getUuid()); 263 verify(captchaService, times(1)).deleteCaptchaCode(reqVO.getUuid());
255 verify(loginLogService, times(1)).createLoginLog( 264 verify(loginLogService, times(1)).createLoginLog(
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImplTest.java
1 package cn.iocoder.yudao.module.system.service.auth; 1 package cn.iocoder.yudao.module.system.service.auth;
2 2
3 -import cn.hutool.core.date.DateUtil; 3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.date.DateUtils;
  7 +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
4 import cn.iocoder.yudao.framework.security.config.SecurityProperties; 8 import cn.iocoder.yudao.framework.security.config.SecurityProperties;
5 import cn.iocoder.yudao.framework.security.core.LoginUser; 9 import cn.iocoder.yudao.framework.security.core.LoginUser;
  10 +import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest;
6 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageReqVO; 11 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageReqVO;
  12 +import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO;
7 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; 13 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
8 import cn.iocoder.yudao.module.system.dal.mysql.auth.UserSessionMapper; 14 import cn.iocoder.yudao.module.system.dal.mysql.auth.UserSessionMapper;
9 -import cn.iocoder.yudao.module.system.service.logger.LoginLogService;  
10 -import cn.iocoder.yudao.module.system.service.user.AdminUserService;  
11 -import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO;  
12 import cn.iocoder.yudao.module.system.dal.redis.auth.LoginUserRedisDAO; 15 import cn.iocoder.yudao.module.system.dal.redis.auth.LoginUserRedisDAO;
13 import cn.iocoder.yudao.module.system.enums.common.SexEnum; 16 import cn.iocoder.yudao.module.system.enums.common.SexEnum;
14 -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;  
15 -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;  
16 -import cn.iocoder.yudao.framework.common.pojo.PageResult;  
17 -import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;  
18 -import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest; 17 +import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
  18 +import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
  19 +import cn.iocoder.yudao.module.system.service.logger.LoginLogService;
  20 +import cn.iocoder.yudao.module.system.service.user.AdminUserService;
  21 +import org.junit.jupiter.api.BeforeEach;
19 import org.junit.jupiter.api.Test; 22 import org.junit.jupiter.api.Test;
20 import org.springframework.boot.test.mock.mockito.MockBean; 23 import org.springframework.boot.test.mock.mockito.MockBean;
21 import org.springframework.context.annotation.Import; 24 import org.springframework.context.annotation.Import;
22 25
23 import javax.annotation.Resource; 26 import javax.annotation.Resource;
24 import java.time.Duration; 27 import java.time.Duration;
25 -import java.util.Date;  
26 -import java.util.List;  
27 -import java.util.stream.Collectors;  
28 -import java.util.stream.Stream; 28 +import java.util.Calendar;
29 29
30 import static cn.hutool.core.util.RandomUtil.randomEle; 30 import static cn.hutool.core.util.RandomUtil.randomEle;
31 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.addTime; 31 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 @@ -33,8 +33,9 @@ import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEq
33 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; 33 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
34 import static java.util.Collections.singletonList; 34 import static java.util.Collections.singletonList;
35 import static org.junit.jupiter.api.Assertions.*; 35 import static org.junit.jupiter.api.Assertions.*;
36 -import static org.junit.jupiter.api.Assertions.assertNull; 36 +import static org.mockito.ArgumentMatchers.argThat;
37 import static org.mockito.ArgumentMatchers.eq; 37 import static org.mockito.ArgumentMatchers.eq;
  38 +import static org.mockito.Mockito.verify;
38 import static org.mockito.Mockito.when; 39 import static org.mockito.Mockito.when;
39 40
40 /** 41 /**
@@ -61,6 +62,11 @@ public class UserSessionServiceImplTest extends BaseDbAndRedisUnitTest { @@ -61,6 +62,11 @@ public class UserSessionServiceImplTest extends BaseDbAndRedisUnitTest {
61 @MockBean 62 @MockBean
62 private SecurityProperties securityProperties; 63 private SecurityProperties securityProperties;
63 64
  65 + @BeforeEach
  66 + public void setUp() {
  67 + when(securityProperties.getSessionTimeout()).thenReturn(Duration.ofDays(1L));
  68 + }
  69 +
64 @Test 70 @Test
65 public void testGetUserSessionPage_success() { 71 public void testGetUserSessionPage_success() {
66 // mock 数据 72 // mock 数据
@@ -78,15 +84,9 @@ public class UserSessionServiceImplTest extends BaseDbAndRedisUnitTest { @@ -78,15 +84,9 @@ public class UserSessionServiceImplTest extends BaseDbAndRedisUnitTest {
78 }); 84 });
79 userSessionMapper.insert(dbSession); 85 userSessionMapper.insert(dbSession);
80 // 测试 username 不匹配 86 // 测试 username 不匹配
81 - userSessionMapper.insert(ObjectUtils.cloneIgnoreId(dbSession, o -> {  
82 - o.setId(randomString());  
83 - o.setUserId(123456L);  
84 - })); 87 + userSessionMapper.insert(ObjectUtils.cloneIgnoreId(dbSession, o -> o.setUserId(123456L)));
85 // 测试 userIp 不匹配 88 // 测试 userIp 不匹配
86 - userSessionMapper.insert(ObjectUtils.cloneIgnoreId(dbSession, o -> {  
87 - o.setId(randomString());  
88 - o.setUserIp("testUserIp");  
89 - })); 89 + userSessionMapper.insert(ObjectUtils.cloneIgnoreId(dbSession, o -> o.setUserIp("testUserIp")));
90 // 准备参数 90 // 准备参数
91 UserSessionPageReqVO reqVO = new UserSessionPageReqVO(); 91 UserSessionPageReqVO reqVO = new UserSessionPageReqVO();
92 reqVO.setUsername(dbUser.getUsername()); 92 reqVO.setUsername(dbUser.getUsername());
@@ -100,35 +100,60 @@ public class UserSessionServiceImplTest extends BaseDbAndRedisUnitTest { @@ -100,35 +100,60 @@ public class UserSessionServiceImplTest extends BaseDbAndRedisUnitTest {
100 assertPojoEquals(dbSession, pageResult.getList().get(0)); 100 assertPojoEquals(dbSession, pageResult.getList().get(0));
101 } 101 }
102 102
103 - // TODO 芋艿:单测写的有问题 103 + @Test
  104 + public void testClearSessionTimeout_none() {
  105 + // mock db 数据
  106 + UserSessionDO userSession = randomPojo(UserSessionDO.class, o -> {
  107 + o.setUserType(randomEle(UserTypeEnum.values()).getValue());
  108 + o.setSessionTimeout(addTime(Duration.ofDays(1)));
  109 + });
  110 + userSessionMapper.insert(userSession);
  111 +
  112 + // 调用
  113 + long count = userSessionService.deleteTimeoutSession();
  114 + // 断言
  115 + assertEquals(0, count);
  116 + assertPojoEquals(userSession, userSessionMapper.selectById(userSession.getId())); // 未删除
  117 + }
  118 +
  119 + @Test // Redis 还存在的情况
  120 + public void testClearSessionTimeout_exists() {
  121 + // mock db 数据
  122 + UserSessionDO userSession = randomPojo(UserSessionDO.class, o -> {
  123 + o.setUserType(randomEle(UserTypeEnum.values()).getValue());
  124 + o.setSessionTimeout(DateUtils.addDate(Calendar.DAY_OF_YEAR, -1));
  125 + });
  126 + userSessionMapper.insert(userSession);
  127 + // mock redis 数据
  128 + loginUserRedisDAO.set(userSession.getToken(), new LoginUser());
  129 +
  130 + // 调用
  131 + long count = userSessionService.deleteTimeoutSession();
  132 + // 断言
  133 + assertEquals(0, count);
  134 + assertPojoEquals(userSession, userSessionMapper.selectById(userSession.getId())); // 未删除
  135 + }
  136 +
104 @Test 137 @Test
105 public void testClearSessionTimeout_success() { 138 public void testClearSessionTimeout_success() {
106 - // 准备超时数据 120 条, 在线用户 1 条  
107 - int expectedTimeoutCount = 120, expectedTotal = 1;  
108 -  
109 - // 准备数据  
110 - List<UserSessionDO> prepareData = Stream  
111 - .iterate(0, i -> i)  
112 - .limit(expectedTimeoutCount)  
113 - .map(i -> randomPojo(UserSessionDO.class, o -> {  
114 - o.setUserType(randomEle(UserTypeEnum.values()).getValue());  
115 - o.setSessionTimeout(DateUtil.offsetSecond(new Date(), -1));  
116 - }))  
117 - .collect(Collectors.toList());  
118 - UserSessionDO sessionDO = randomPojo(UserSessionDO.class, o -> { 139 + // mock db 数据
  140 + UserSessionDO userSession = randomPojo(UserSessionDO.class, o -> {
119 o.setUserType(randomEle(UserTypeEnum.values()).getValue()); 141 o.setUserType(randomEle(UserTypeEnum.values()).getValue());
120 - o.setSessionTimeout(DateUtil.offsetMinute(new Date(), 30)); 142 + o.setSessionTimeout(DateUtils.addDate(Calendar.DAY_OF_YEAR, -1));
121 }); 143 });
122 - prepareData.add(sessionDO);  
123 - prepareData.forEach(userSessionMapper::insert); 144 + userSessionMapper.insert(userSession);
124 145
125 // 清空超时数据 146 // 清空超时数据
126 - long actualTimeoutCount = userSessionService.clearSessionTimeout(); 147 + long count = userSessionService.deleteTimeoutSession();
127 // 校验 148 // 校验
128 - assertEquals(expectedTimeoutCount, actualTimeoutCount);  
129 - List<UserSessionDO> userSessionDOS = userSessionMapper.selectList();  
130 - assertEquals(expectedTotal, userSessionDOS.size());  
131 - assertPojoEquals(sessionDO, userSessionDOS.get(0), "updateTime"); 149 + assertEquals(1, count);
  150 + assertNull(userSessionMapper.selectById(userSession.getId())); // 已删除
  151 + verify(loginLogService).createLoginLog(argThat(loginLog -> {
  152 + assertPojoEquals(userSession, loginLog);
  153 + assertEquals(LoginLogTypeEnum.LOGOUT_TIMEOUT.getType(), loginLog.getLogType());
  154 + assertEquals(LoginResultEnum.SUCCESS.getResult(), loginLog.getResult());
  155 + return true;
  156 + }));
132 } 157 }
133 158
134 @Test 159 @Test
@@ -140,80 +165,86 @@ public class UserSessionServiceImplTest extends BaseDbAndRedisUnitTest { @@ -140,80 +165,86 @@ public class UserSessionServiceImplTest extends BaseDbAndRedisUnitTest {
140 o.setUserType(randomEle(UserTypeEnum.values()).getValue()); 165 o.setUserType(randomEle(UserTypeEnum.values()).getValue());
141 o.setTenantId(0L); // 租户设置为 0,因为暂未启用多租户组件 166 o.setTenantId(0L); // 租户设置为 0,因为暂未启用多租户组件
142 }); 167 });
143 - // mock 方法  
144 - when(securityProperties.getSessionTimeout()).thenReturn(Duration.ofDays(1));  
145 168
146 // 调用 169 // 调用
147 - String sessionId = userSessionService.createUserSession(loginUser, userIp, userAgent); 170 + String token = userSessionService.createUserSession(loginUser, userIp, userAgent);
148 // 校验 UserSessionDO 记录 171 // 校验 UserSessionDO 记录
149 - UserSessionDO userSessionDO = userSessionMapper.selectById(sessionId); 172 + UserSessionDO userSessionDO = userSessionMapper.selectOne(UserSessionDO::getToken, token);
150 assertPojoEquals(loginUser, userSessionDO, "id", "updateTime"); 173 assertPojoEquals(loginUser, userSessionDO, "id", "updateTime");
151 - assertEquals(sessionId, userSessionDO.getId()); 174 + assertEquals(token, userSessionDO.getToken());
152 assertEquals(userIp, userSessionDO.getUserIp()); 175 assertEquals(userIp, userSessionDO.getUserIp());
153 assertEquals(userAgent, userSessionDO.getUserAgent()); 176 assertEquals(userAgent, userSessionDO.getUserAgent());
154 // 校验 LoginUser 缓存 177 // 校验 LoginUser 缓存
155 - LoginUser redisLoginUser = loginUserRedisDAO.get(sessionId); 178 + LoginUser redisLoginUser = loginUserRedisDAO.get(token);
156 assertPojoEquals(loginUser, redisLoginUser, "username", "password"); 179 assertPojoEquals(loginUser, redisLoginUser, "username", "password");
157 } 180 }
158 181
159 @Test 182 @Test
160 - public void testCreateRefreshUserSession_success() { 183 + public void testCreateRefreshUserSession() {
161 // 准备参数 184 // 准备参数
162 - String sessionId = randomString();  
163 - String userIp = randomString();  
164 - String userAgent = randomString();  
165 - long timeLong = randomLongId();  
166 - String userName = randomString();  
167 - Date date = randomDate(); 185 + String token = randomString();
  186 +
  187 + // mock redis 数据
168 LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setUserType(randomEle(UserTypeEnum.values()).getValue())); 188 LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setUserType(randomEle(UserTypeEnum.values()).getValue()));
169 - // mock 方法  
170 - when(securityProperties.getSessionTimeout()).thenReturn(Duration.ofDays(1));  
171 - // mock 数据  
172 - loginUser.setUpdateTime(date);  
173 - loginUserRedisDAO.set(sessionId, loginUser);  
174 - UserSessionDO userSession = UserSessionDO.builder().id(sessionId)  
175 - .userId(loginUser.getId()).userType(loginUser.getUserType())  
176 - .userIp(userIp).userAgent(userAgent).username(userName)  
177 - .sessionTimeout(addTime(Duration.ofMillis(timeLong)))  
178 - .build(); 189 + loginUserRedisDAO.set(token, loginUser);
  190 + // mock db 数据
  191 + UserSessionDO userSession = randomPojo(UserSessionDO.class, o -> {
  192 + o.setUserType(randomEle(UserTypeEnum.values()).getValue());
  193 + o.setToken(token);
  194 + });
179 userSessionMapper.insert(userSession); 195 userSessionMapper.insert(userSession);
180 196
181 // 调用 197 // 调用
182 - userSessionService.refreshUserSession(sessionId, loginUser); 198 + userSessionService.refreshUserSession(token, loginUser);
183 // 校验 LoginUser 缓存 199 // 校验 LoginUser 缓存
184 - LoginUser redisLoginUser = loginUserRedisDAO.get(sessionId);  
185 - assertNotEquals(redisLoginUser.getUpdateTime(), date); 200 + LoginUser redisLoginUser = loginUserRedisDAO.get(token);
  201 + assertPojoEquals(redisLoginUser, loginUser, "username", "password");
186 // 校验 UserSessionDO 记录 202 // 校验 UserSessionDO 记录
187 - UserSessionDO updateDO = userSessionMapper.selectById(sessionId); 203 + UserSessionDO updateDO = userSessionMapper.selectOne(UserSessionDO::getToken, token);
188 assertEquals(updateDO.getUsername(), loginUser.getUsername()); 204 assertEquals(updateDO.getUsername(), loginUser.getUsername());
189 - assertNotEquals(updateDO.getUpdateTime(), userSession.getUpdateTime());  
190 - assertNotEquals(updateDO.getSessionTimeout(), addTime(Duration.ofMillis(timeLong))); 205 + assertNotNull(userSession.getUpdateTime());
  206 + assertNotNull(userSession.getSessionTimeout());
191 } 207 }
192 208
193 @Test 209 @Test
194 - public void testDeleteUserSession_success() { 210 + public void testDeleteUserSession_Token() {
195 // 准备参数 211 // 准备参数
196 - String sessionId = randomString();  
197 - String userIp = randomString();  
198 - String userAgent = randomString();  
199 - Long timeLong = randomLongId();  
200 - LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setUserType(randomEle(UserTypeEnum.values()).getValue()));  
201 - // mock 存入 Redis  
202 - when(securityProperties.getSessionTimeout()).thenReturn(Duration.ofDays(1));  
203 - // mock 数据  
204 - loginUserRedisDAO.set(sessionId, loginUser);  
205 - UserSessionDO userSession = UserSessionDO.builder().id(sessionId)  
206 - .userId(loginUser.getId()).userType(loginUser.getUserType())  
207 - .userIp(userIp).userAgent(userAgent).username(loginUser.getUsername())  
208 - .sessionTimeout(addTime(Duration.ofMillis(timeLong)))  
209 - .build(); 212 + String token = randomString();
  213 +
  214 + // mock redis 数据
  215 + loginUserRedisDAO.set(token, new LoginUser());
  216 + // mock db 数据
  217 + UserSessionDO userSession = randomPojo(UserSessionDO.class, o -> {
  218 + o.setUserType(randomEle(UserTypeEnum.values()).getValue());
  219 + o.setToken(token);
  220 + });
210 userSessionMapper.insert(userSession); 221 userSessionMapper.insert(userSession);
211 222
212 // 调用 223 // 调用
213 - userSessionService.deleteUserSession(sessionId); 224 + userSessionService.deleteUserSession(token);
  225 + // 校验数据不存在了
  226 + assertNull(loginUserRedisDAO.get(token));
  227 + assertNull(userSessionMapper.selectOne(UserSessionDO::getToken, token));
  228 + }
  229 +
  230 + @Test
  231 + public void testDeleteUserSession_Id() {
  232 + // mock db 数据
  233 + UserSessionDO userSession = randomPojo(UserSessionDO.class, o -> {
  234 + o.setUserType(randomEle(UserTypeEnum.values()).getValue());
  235 + });
  236 + userSessionMapper.insert(userSession);
  237 + // mock redis 数据
  238 + loginUserRedisDAO.set(userSession.getToken(), new LoginUser());
  239 +
  240 + // 准备参数
  241 + Long id = userSession.getId();
  242 +
  243 + // 调用
  244 + userSessionService.deleteUserSession(id);
214 // 校验数据不存在了 245 // 校验数据不存在了
215 - assertNull(loginUserRedisDAO.get(sessionId));  
216 - assertNull(userSessionMapper.selectById(sessionId)); 246 + assertNull(loginUserRedisDAO.get(userSession.getToken()));
  247 + assertNull(userSessionMapper.selectById(id));
217 } 248 }
218 249
219 } 250 }
yudao-module-system/yudao-module-system-biz/src/test/resources/sql/create_tables.sql
@@ -115,7 +115,8 @@ CREATE TABLE IF NOT EXISTS &quot;system_dict_type&quot; ( @@ -115,7 +115,8 @@ CREATE TABLE IF NOT EXISTS &quot;system_dict_type&quot; (
115 ) COMMENT '字典类型表'; 115 ) COMMENT '字典类型表';
116 116
117 CREATE TABLE IF NOT EXISTS `system_user_session` ( 117 CREATE TABLE IF NOT EXISTS `system_user_session` (
118 - `id` varchar(32) NOT NULL, 118 + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
  119 + `token` varchar(32) NOT NULL,
119 `user_id` bigint DEFAULT NULL, 120 `user_id` bigint DEFAULT NULL,
120 "user_type" tinyint NOT NULL, 121 "user_type" tinyint NOT NULL,
121 `username` varchar(50) NOT NULL DEFAULT '', 122 `username` varchar(50) NOT NULL DEFAULT '',
yudao-server/pom.xml
@@ -21,7 +21,6 @@ @@ -21,7 +21,6 @@
21 <url>https://github.com/YunaiV/ruoyi-vue-pro</url> 21 <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
22 22
23 <dependencies> 23 <dependencies>
24 - <!-- TODO 芋艿:多模块 -->  
25 <dependency> 24 <dependency>
26 <groupId>cn.iocoder.boot</groupId> 25 <groupId>cn.iocoder.boot</groupId>
27 <artifactId>yudao-module-member-biz</artifactId> 26 <artifactId>yudao-module-member-biz</artifactId>
@@ -43,11 +42,11 @@ @@ -43,11 +42,11 @@
43 <version>${revision}</version> 42 <version>${revision}</version>
44 </dependency> 43 </dependency>
45 <!-- 默认引入 yudao-module-bpm-biz-flowable 实现,可以替换为 yudao-module-bpm-biz-activiti 实现--> 44 <!-- 默认引入 yudao-module-bpm-biz-flowable 实现,可以替换为 yudao-module-bpm-biz-activiti 实现-->
46 - <dependency>  
47 - <groupId>cn.iocoder.boot</groupId>  
48 - <artifactId>yudao-module-bpm-biz-flowable</artifactId>  
49 - <version>${revision}</version>  
50 - </dependency> 45 +<!-- <dependency>-->
  46 +<!-- <groupId>cn.iocoder.boot</groupId>-->
  47 +<!-- <artifactId>yudao-module-bpm-biz-flowable</artifactId>-->
  48 +<!-- <version>${revision}</version>-->
  49 +<!-- </dependency>-->
51 <!-- <dependency>--> 50 <!-- <dependency>-->
52 <!-- <groupId>cn.iocoder.boot</groupId>--> 51 <!-- <groupId>cn.iocoder.boot</groupId>-->
53 <!-- <artifactId>yudao-module-bpm-biz-activiti</artifactId>--> 52 <!-- <artifactId>yudao-module-bpm-biz-activiti</artifactId>-->
yudao-server/src/main/resources/application-local.yaml
@@ -49,6 +49,9 @@ spring: @@ -49,6 +49,9 @@ spring:
49 # url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例 49 # url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例
50 username: root 50 username: root
51 password: 123456 51 password: 123456
  52 +# url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.master.name} # SQLServer 连接的示例
  53 +# username: sa
  54 +# password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W
52 slave: # 模拟从库,可根据自己需要修改 55 slave: # 模拟从库,可根据自己需要修改
53 name: ruoyi-vue-pro 56 name: ruoyi-vue-pro
54 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 连接的示例 57 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: @@ -56,6 +59,9 @@ spring:
56 # url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例 59 # url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例
57 username: root 60 username: root
58 password: 123456 61 password: 123456
  62 +# url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.slave.name} # SQLServer 连接的示例
  63 +# username: sa
  64 +# password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W
59 65
60 # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 66 # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
61 redis: 67 redis: