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: