diff --git a/pom.xml b/pom.xml index 39f97ab43..0f26eb5fc 100644 --- a/pom.xml +++ b/pom.xml @@ -53,7 +53,6 @@ 1.16.14 1.4.1.Final - 0.9.1 5.5.6 2.2.7 @@ -177,12 +176,6 @@ ${lombok.version} - - io.jsonwebtoken - jjwt - ${jjwt.version} - - org.projectlombok lombok diff --git a/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java b/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java deleted file mode 100644 index e3c56ee54..000000000 --- a/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.ruoyi; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; - -/** - * 启动程序 - * - * @author ruoyi - */ -@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class }) -public class RuoYiApplication -{ - public static void main(String[] args) - { - // System.setProperty("spring.devtools.restart.enabled", "false"); - SpringApplication.run(RuoYiApplication.class, args); - System.out.println("(♥◠‿◠)ノ゙ 若依启动成功 ლ(´ڡ`ლ)゙ \n" + - " .-------. ____ __ \n" + - " | _ _ \\ \\ \\ / / \n" + - " | ( ' ) | \\ _. / ' \n" + - " |(_ o _) / _( )_ .' \n" + - " | (_,_).' __ ___(_ o _)' \n" + - " | |\\ \\ | || |(_,_)' \n" + - " | | \\ `' /| `-' / \n" + - " | | \\ / \\ / \n" + - " ''-' `'-' `-..-' "); - } -} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnlineController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnlineController.java deleted file mode 100644 index 4ca306d70..000000000 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnlineController.java +++ /dev/null @@ -1,92 +0,0 @@ -package com.ruoyi.web.controller.monitor; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import com.ruoyi.common.annotation.Log; -import com.ruoyi.common.constant.Constants; -import com.ruoyi.common.core.controller.BaseController; -import com.ruoyi.common.core.domain.AjaxResult; -import com.ruoyi.common.core.domain.model.LoginUser; -import com.ruoyi.common.core.page.TableDataInfo; -import com.ruoyi.common.core.redis.RedisCache; -import com.ruoyi.common.enums.BusinessType; -import com.ruoyi.common.utils.StringUtils; -import com.ruoyi.system.domain.SysUserOnline; -import com.ruoyi.system.service.ISysUserOnlineService; - -/** - * 在线用户监控 - * - * @author ruoyi - */ -@RestController -@RequestMapping("/monitor/online") -public class SysUserOnlineController extends BaseController -{ - @Autowired - private ISysUserOnlineService userOnlineService; - - @Autowired - private RedisCache redisCache; - - @PreAuthorize("@ss.hasPermi('monitor:online:list')") - @GetMapping("/list") - public TableDataInfo list(String ipaddr, String userName) - { - Collection keys = redisCache.keys(Constants.LOGIN_TOKEN_KEY + "*"); - List userOnlineList = new ArrayList(); - for (String key : keys) - { - LoginUser user = redisCache.getCacheObject(key); - if (StringUtils.isNotEmpty(ipaddr) && StringUtils.isNotEmpty(userName)) - { - if (StringUtils.equals(ipaddr, user.getIpaddr()) && StringUtils.equals(userName, user.getUsername())) - { - userOnlineList.add(userOnlineService.selectOnlineByInfo(ipaddr, userName, user)); - } - } - else if (StringUtils.isNotEmpty(ipaddr)) - { - if (StringUtils.equals(ipaddr, user.getIpaddr())) - { - userOnlineList.add(userOnlineService.selectOnlineByIpaddr(ipaddr, user)); - } - } - else if (StringUtils.isNotEmpty(userName) && StringUtils.isNotNull(user.getUser())) - { - if (StringUtils.equals(userName, user.getUsername())) - { - userOnlineList.add(userOnlineService.selectOnlineByUserName(userName, user)); - } - } - else - { - userOnlineList.add(userOnlineService.loginUserToUserOnline(user)); - } - } - Collections.reverse(userOnlineList); - userOnlineList.removeAll(Collections.singleton(null)); - return getDataTable(userOnlineList); - } - - /** - * 强退用户 - */ - @PreAuthorize("@ss.hasPermi('monitor:online:forceLogout')") - @Log(title = "在线用户", businessType = BusinessType.FORCE) - @DeleteMapping("/{tokenId}") - public AjaxResult forceLogout(@PathVariable String tokenId) - { - redisCache.deleteObject(Constants.LOGIN_TOKEN_KEY + tokenId); - return AjaxResult.success(); - } -} diff --git a/ruoyi-admin/src/main/resources/META-INF/spring-devtools.properties b/ruoyi-admin/src/main/resources/META-INF/spring-devtools.properties deleted file mode 100644 index 2b23f85a3..000000000 --- a/ruoyi-admin/src/main/resources/META-INF/spring-devtools.properties +++ /dev/null @@ -1 +0,0 @@ -restart.include.json=/com.alibaba.fastjson.*.jar \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index afb292bcf..e2c99b186 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -34,20 +34,6 @@ logging: com.ruoyi: debug org.springframework: warn -# Spring配置 -spring: - # 资源信息 - messages: - # 国际化资源文件路径 - basename: i18n/messages - profiles: - active: druid - # 服务模块 - devtools: - restart: - # 热部署开关 - enabled: true - # 防止XSS攻击 xss: # 过滤开关 diff --git a/src/main/java/cn/iocoder/dashboard/framework/mybatis/core/dataobject/BaseDO.java b/src/main/java/cn/iocoder/dashboard/framework/mybatis/core/dataobject/BaseDO.java index c0fcda207..c65eaf6dd 100644 --- a/src/main/java/cn/iocoder/dashboard/framework/mybatis/core/dataobject/BaseDO.java +++ b/src/main/java/cn/iocoder/dashboard/framework/mybatis/core/dataobject/BaseDO.java @@ -34,7 +34,4 @@ public class BaseDO implements Serializable { @TableLogic private Integer deleted; -// /** 备注 */ TODO 思考下,怎么解决 -// private String remark; - } diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/SysAuthController.java b/src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/SysAuthController.java index 71810f447..026fa2aed 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/SysAuthController.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/SysAuthController.java @@ -28,6 +28,8 @@ import java.util.List; import static cn.iocoder.dashboard.common.pojo.CommonResult.success; import static cn.iocoder.dashboard.framework.security.core.util.SecurityUtils.getLoginUserId; import static cn.iocoder.dashboard.framework.security.core.util.SecurityUtils.getLoginUserRoleIds; +import static cn.iocoder.dashboard.util.servlet.ServletUtils.getClientIP; +import static cn.iocoder.dashboard.util.servlet.ServletUtils.getUserAgent; @Api("认证 API") @RestController @@ -47,7 +49,7 @@ public class SysAuthController { @PostMapping("/login") @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 public CommonResult login(@RequestBody @Valid SysAuthLoginReqVO reqVO) { - String token = authService.login(reqVO.getUsername(), reqVO.getPassword(), reqVO.getUuid(), reqVO.getCode()); + String token = authService.login(reqVO, getClientIP(), getUserAgent()); // 返回结果 return success(SysAuthLoginRespVO.builder().token(token).build()); } diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/SysUserSessionController.java b/src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/SysUserSessionController.java new file mode 100644 index 000000000..f366e0d29 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/SysUserSessionController.java @@ -0,0 +1,49 @@ +package cn.iocoder.dashboard.modules.system.controller.auth; + +import cn.iocoder.dashboard.common.pojo.CommonResult; +import cn.iocoder.dashboard.common.pojo.PageResult; +import cn.iocoder.dashboard.modules.system.controller.auth.vo.session.SysUserSessionPageItemRespVO; +import cn.iocoder.dashboard.modules.system.controller.auth.vo.session.SysUserSessionPageReqVO; +import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.auth.SysUserSessionDO; +import cn.iocoder.dashboard.modules.system.service.auth.SysUserSessionService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; + +import static cn.iocoder.dashboard.common.pojo.CommonResult.success; + +@Api("用户 Session API") +@RestController +@RequestMapping("/user-session") +public class SysUserSessionController { + + @Resource + private SysUserSessionService userSessionService; + + @ApiOperation("获得 Session 分页列表") + @PreAuthorize("@ss.hasPermission('system:user-session:page')") + @GetMapping("/page") + public CommonResult> getUserSessionPage(@Validated SysUserSessionPageReqVO reqVO) { + // 获得 Session 分页 + PageResult sessionPage = userSessionService.getUserSessionPage(reqVO); + + // + return null; + } + + @ApiOperation("删除 Session") + @PreAuthorize("@ss.hasPermission('system:user-session:delete')") + @DeleteMapping("/delete") + @ApiImplicitParam(name = "id", value = "Session 编号", required = true, dataTypeClass = String.class, + example = "fe50b9f6-d177-44b1-8da9-72ea34f63db7") + public CommonResult delete(@RequestParam("id") String id) { + userSessionService.deleteUserSession(id); + return success(true); + } + +} diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/vo/session/SysUserSessionPageItemRespVO.java b/src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/vo/session/SysUserSessionPageItemRespVO.java new file mode 100644 index 000000000..adf1f9459 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/vo/session/SysUserSessionPageItemRespVO.java @@ -0,0 +1,36 @@ +package cn.iocoder.dashboard.modules.system.controller.auth.vo.session; + +import cn.iocoder.dashboard.common.pojo.PageParam; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@ApiModel(value = "用户在线 Session Response VO", description = "相比用户基本信息来说,会多部门、用户账号等信息") +@Data +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class SysUserSessionPageItemRespVO extends PageParam { + + @ApiModelProperty(value = "Session 编号", required = true, example = "fe50b9f6-d177-44b1-8da9-72ea34f63db7") + private String id; + + @ApiModelProperty(value = "用户 IP", required = true, example = "127.0.0.1") + private String userIp; + + @ApiModelProperty(value = "浏览器 UserAgent", required = true, example = "Mozilla/5.0") + private String userAgent; + + @ApiModelProperty(value = "登陆时间", required = true) + private String createTime; + + @ApiModelProperty(value = "用户账号", required = true, example = "yudao") + private String username; + + @ApiModelProperty(value = "部门名称", example = "研发部") + private String deptName; + +} diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/vo/session/SysUserSessionPageReqVO.java b/src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/vo/session/SysUserSessionPageReqVO.java index e91198877..2b27b867f 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/vo/session/SysUserSessionPageReqVO.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/vo/session/SysUserSessionPageReqVO.java @@ -2,11 +2,22 @@ package cn.iocoder.dashboard.modules.system.controller.auth.vo.session; import cn.iocoder.dashboard.common.pojo.PageParam; import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.EqualsAndHashCode; +import javax.validation.constraints.NotEmpty; + @ApiModel("在线用户 Session 分页 Request VO") @Data @EqualsAndHashCode(callSuper = true) public class SysUserSessionPageReqVO extends PageParam { + + @ApiModelProperty(value = "用户 IP", example = "127.0.0.1", notes = "模糊匹配") + @NotEmpty(message = "用户 IP 不能为空") + private String userIp; + + @ApiModelProperty(value = "用户账号", example = "yudao", notes = "模糊匹配") + private String username; + } diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/auth/SysUserOnlineMapper.java b/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/auth/SysUserSessionMapper.java similarity index 61% rename from src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/auth/SysUserOnlineMapper.java rename to src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/auth/SysUserSessionMapper.java index fdc317a39..51aed9dbb 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/auth/SysUserOnlineMapper.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/auth/SysUserSessionMapper.java @@ -2,6 +2,8 @@ package cn.iocoder.dashboard.modules.system.dal.mysql.dao.auth; import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.auth.SysUserSessionDO; import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; -public interface SysUserOnlineMapper extends BaseMapper { +@Mapper +public interface SysUserSessionMapper extends BaseMapper { } diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dataobject/auth/SysUserSessionDO.java b/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dataobject/auth/SysUserSessionDO.java index 3964ef9c6..1efda9b85 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dataobject/auth/SysUserSessionDO.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dataobject/auth/SysUserSessionDO.java @@ -1,17 +1,26 @@ package cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.auth; import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.dashboard.framework.security.core.LoginUser; import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.user.SysUserDO; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; /** * 在线用户表 + * + * 我们已经将 {@link LoginUser} 缓存在 Redis 当中。 + * 这里额外存储在线用户到 MySQL 中,目的是为了方便管理界面可以灵活查询。 + * 同时,通过定时轮询 SysUserSessionDO 表,可以主动删除 Redis 的缓存,因为 Redis 的过期删除是延迟的。 + * + * @author 芋道源码 */ @TableName(value = "sys_user_session", autoResultMap = true) @Data +@Builder @EqualsAndHashCode(callSuper = true) public class SysUserSessionDO extends BaseDO { diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/auth/SysAuthService.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/auth/SysAuthService.java index d1c0580e4..04eb6d968 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/service/auth/SysAuthService.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/service/auth/SysAuthService.java @@ -1,6 +1,7 @@ package cn.iocoder.dashboard.modules.system.service.auth; import cn.iocoder.dashboard.framework.security.core.service.SecurityAuthFrameworkService; +import cn.iocoder.dashboard.modules.system.controller.auth.vo.auth.SysAuthLoginReqVO; /** * 认证 Service 接口 @@ -11,6 +12,14 @@ import cn.iocoder.dashboard.framework.security.core.service.SecurityAuthFramewor */ public interface SysAuthService extends SecurityAuthFrameworkService { - String login(String username, String password, String captchaUUID, String captchaCode); + /** + * 登陆用户 + * + * @param reqVO 登陆信息 + * @param userIp 用户 IP + * @param userAgent 用户 UA + * @return 身份令牌,使用 JWT 方式 + */ + String login(SysAuthLoginReqVO reqVO, String userIp, String userAgent); } diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/auth/SysTokenService.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/auth/SysTokenService.java deleted file mode 100644 index c1f8c8d91..000000000 --- a/src/main/java/cn/iocoder/dashboard/modules/system/service/auth/SysTokenService.java +++ /dev/null @@ -1,30 +0,0 @@ -package cn.iocoder.dashboard.modules.system.service.auth; - -import io.jsonwebtoken.Claims; - -import java.util.Map; - -/** - * Token Service 接口 - * - * 提供访问 Token 令牌,目前基于 JWT 实现 - */ -public interface SysTokenService { - - /** - * 创建 Token - * - * @param subject 主体 - * @return Token 字符串 - */ - String createToken(String subject); - - /** - * 解析 Token,返回 claims 数据声明 - * - * @param token Token - * @return claims - */ - Claims parseToken(String token); - -} diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/auth/SysUserOnlineService.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/auth/SysUserOnlineService.java deleted file mode 100644 index 62198809a..000000000 --- a/src/main/java/cn/iocoder/dashboard/modules/system/service/auth/SysUserOnlineService.java +++ /dev/null @@ -1,40 +0,0 @@ -package cn.iocoder.dashboard.modules.system.service.auth; - -import cn.iocoder.dashboard.common.pojo.PageResult; -import cn.iocoder.dashboard.modules.system.controller.auth.vo.session.SysUserSessionPageReqVO; -import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.auth.SysUserSessionDO; - -import java.util.Date; - -/** - * 在线用户 Session Service 接口 - */ -public interface SysUserOnlineService { - - /** - * 创建在线用户 Session - * - * @param sessionId Session 编号 - * @param userId 用户编号 - * @param userIp 用户 IP - * @param userAgent 用户 UA - */ - void createUserOnline(String sessionId, Long userId, String userIp, String userAgent); - - /** - * 更新在线用户 Session 的更新时间 - * - * @param sessionId Session 编号 - * @param updateTime 更新时间 - */ - void updateUserOnlineUpdateTime(String sessionId, Date updateTime); - - /** - * 获得在线用户分页列表 - * - * @param reqVO 分页条件 - * @return 份额与列表 - */ - PageResult getUserSessionPage(SysUserSessionPageReqVO reqVO); - -} diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/auth/SysUserSessionService.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/auth/SysUserSessionService.java new file mode 100644 index 000000000..f22152a3a --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/modules/system/service/auth/SysUserSessionService.java @@ -0,0 +1,63 @@ +package cn.iocoder.dashboard.modules.system.service.auth; + +import cn.iocoder.dashboard.common.pojo.PageResult; +import cn.iocoder.dashboard.framework.security.core.LoginUser; +import cn.iocoder.dashboard.modules.system.controller.auth.vo.session.SysUserSessionPageReqVO; +import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.auth.SysUserSessionDO; + +/** + * 在线用户 Session Service 接口 + * + * @author 芋道源码 + */ +public interface SysUserSessionService { + + /** + * 创建在线用户 Session + * + * @param loginUser 登陆用户 + * @param userIp 用户 IP + * @param userAgent 用户 UA + * @return Session 编号 + */ + String createUserSession(LoginUser loginUser, String userIp, String userAgent); + + /** + * 刷新在线用户 Session 的更新时间 + * + * @param sessionId Session 编号 + * @param loginUser 登陆用户 + */ + void refreshUserSession(String sessionId, LoginUser loginUser); + + /** + * 删除在线用户 Session + * + * @param sessionId Session 编号 + */ + void deleteUserSession(String sessionId); + + /** + * 获得 Session 编号对应的在线用户 + * + * @param sessionId Session 编号 + * @return 在线用户 + */ + LoginUser getLoginUser(String sessionId); + + /** + * 获得 Session 超时时间,单位:毫秒 + * + * @return 超时时间 + */ + Long getSessionTimeoutMillis(); + + /** + * 获得在线用户分页列表 + * + * @param reqVO 分页条件 + * @return 份额与列表 + */ + PageResult getUserSessionPage(SysUserSessionPageReqVO reqVO); + +} diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/auth/impl/SysAuthServiceImpl.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/auth/impl/SysAuthServiceImpl.java index ce44b0a1e..e86af8c87 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/service/auth/impl/SysAuthServiceImpl.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/service/auth/impl/SysAuthServiceImpl.java @@ -1,28 +1,22 @@ package cn.iocoder.dashboard.modules.system.service.auth.impl; -import cn.hutool.core.util.IdUtil; -import cn.hutool.core.util.StrUtil; import cn.iocoder.dashboard.common.enums.CommonStatusEnum; import cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil; -import cn.iocoder.dashboard.framework.security.config.SecurityProperties; import cn.iocoder.dashboard.framework.security.core.LoginUser; import cn.iocoder.dashboard.framework.tracer.core.util.TracerUtils; +import cn.iocoder.dashboard.modules.system.controller.auth.vo.auth.SysAuthLoginReqVO; import cn.iocoder.dashboard.modules.system.controller.logger.vo.loginlog.SysLoginLogCreateReqVO; import cn.iocoder.dashboard.modules.system.convert.auth.SysAuthConvert; import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.user.SysUserDO; -import cn.iocoder.dashboard.modules.system.dal.redis.dao.auth.SysLoginUserRedisDAO; import cn.iocoder.dashboard.modules.system.enums.logger.SysLoginLogTypeEnum; import cn.iocoder.dashboard.modules.system.enums.logger.SysLoginResultEnum; import cn.iocoder.dashboard.modules.system.service.auth.SysAuthService; -import cn.iocoder.dashboard.modules.system.service.auth.SysTokenService; +import cn.iocoder.dashboard.modules.system.service.auth.SysUserSessionService; import cn.iocoder.dashboard.modules.system.service.common.SysCaptchaService; import cn.iocoder.dashboard.modules.system.service.logger.SysLoginLogService; import cn.iocoder.dashboard.modules.system.service.permission.SysPermissionService; import cn.iocoder.dashboard.modules.system.service.user.SysUserService; -import cn.iocoder.dashboard.util.date.DateUtils; import cn.iocoder.dashboard.util.servlet.ServletUtils; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.JwtException; import lombok.extern.slf4j.Slf4j; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; @@ -37,7 +31,6 @@ import org.springframework.util.Assert; import javax.annotation.Resource; import java.util.Collections; -import java.util.Date; import java.util.Set; import static cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil.exception; @@ -52,11 +45,6 @@ import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*; @Slf4j public class SysAuthServiceImpl implements SysAuthService { - @Resource - private SecurityProperties securityProperties; - - @Resource - private SysTokenService tokenService; @Resource private AuthenticationManager authenticationManager; @Resource @@ -67,9 +55,8 @@ public class SysAuthServiceImpl implements SysAuthService { private SysCaptchaService captchaService; @Resource private SysLoginLogService loginLogService; - @Resource - private SysLoginUserRedisDAO loginUserRedisDAO; + private SysUserSessionService userSessionService; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { @@ -91,27 +78,21 @@ public class SysAuthServiceImpl implements SysAuthService { } // 创建 LoginUser 对象 LoginUser loginUser = SysAuthConvert.INSTANCE.convert(user); - loginUser.setUpdateTime(new Date()); - loginUser.setRoleIds(this.getUserRoleIds(loginUser.getId())); + loginUser.setRoleIds(this.getUserRoleIds(loginUser.getId())); // 获取用户角色列表 return loginUser; } @Override - public String login(String username, String password, String captchaUUID, String captchaCode) { + public String login(SysAuthLoginReqVO reqVO, String userIp, String userAgent) { // 判断验证码是否正确 - this.verifyCaptcha(username, captchaUUID, captchaCode); + this.verifyCaptcha(reqVO.getUsername(), reqVO.getUuid(), reqVO.getCode()); // 使用账号密码,进行登陆。 - LoginUser loginUser = this.login0(username, password); - // 缓存登陆用户到 Redis 中 - String sessionId = IdUtil.fastSimpleUUID(); - loginUser.setUpdateTime(new Date()); - loginUser.setRoleIds(this.getUserRoleIds(loginUser.getId())); - loginUserRedisDAO.set(sessionId, loginUser); + LoginUser loginUser = this.login0(reqVO.getUsername(), reqVO.getPassword()); + loginUser.setRoleIds(this.getUserRoleIds(loginUser.getId())); // 获取用户角色列表 - // 创建 Token - // 我们在返回给前端的 JWT 中,使用 sessionId 作为 subject 主体,标识当前 User 用户 - return tokenService.createToken(sessionId); + // 缓存登陆用户到 Redis 中,返回 sessionId 编号 + return userSessionService.createUserSession(loginUser, userIp, userAgent); } private void verifyCaptcha(String username, String captchaUUID, String captchaCode) { @@ -182,42 +163,20 @@ public class SysAuthServiceImpl implements SysAuthService { @Override public LoginUser verifyTokenAndRefresh(String token) { - // 验证 token 的有效性 - String sessionId = this.verifyToken(token); // 获得 LoginUser - LoginUser loginUser = loginUserRedisDAO.get(sessionId); + LoginUser loginUser = userSessionService.getLoginUser(token); if (loginUser == null) { return null; } // 刷新 LoginUser 缓存 - this.refreshLoginUserCache(sessionId, loginUser); + this.refreshLoginUserCache(token, loginUser); return loginUser; } - private String verifyToken(String token) { - Claims claims; - try { - claims = tokenService.parseToken(token); - } catch (JwtException jwtException) { - log.warn("[verifyToken][token({}) 解析发生异常]", token); - return null; - } - // token 已经过期 - if (DateUtils.isExpired(claims.getExpiration())) { - return null; - } - // 判断 sessionId 是否存在 - String sessionId = claims.getSubject(); - if (StrUtil.isBlank(sessionId)) { - return null; - } - return sessionId; - } - - private void refreshLoginUserCache(String sessionId, LoginUser loginUser) { + private void refreshLoginUserCache(String token, LoginUser loginUser) { // 每 1/3 的 Session 超时时间,刷新 LoginUser 缓存 if (System.currentTimeMillis() - loginUser.getUpdateTime().getTime() < - securityProperties.getSessionTimeout().toMillis() / 3) { + userSessionService.getSessionTimeoutMillis() / 3) { return; } @@ -229,9 +188,8 @@ public class SysAuthServiceImpl implements SysAuthService { // 刷新 LoginUser 缓存 loginUser.setDeptId(user.getDeptId()); - loginUser.setUpdateTime(new Date()); loginUser.setRoleIds(this.getUserRoleIds(loginUser.getId())); - loginUserRedisDAO.set(sessionId, loginUser); + userSessionService.refreshUserSession(token, loginUser); } } diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/auth/impl/SysTokenServiceImpl.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/auth/impl/SysTokenServiceImpl.java deleted file mode 100644 index 3b3112f60..000000000 --- a/src/main/java/cn/iocoder/dashboard/modules/system/service/auth/impl/SysTokenServiceImpl.java +++ /dev/null @@ -1,58 +0,0 @@ -package cn.iocoder.dashboard.modules.system.service.auth.impl; - -import cn.iocoder.dashboard.framework.security.config.SecurityProperties; -import cn.iocoder.dashboard.modules.system.service.auth.SysTokenService; -import cn.iocoder.dashboard.util.date.DateUtils; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import org.springframework.stereotype.Service; - -import javax.annotation.Resource; -import java.util.HashMap; -import java.util.Map; - -/** - * Token Service 实现类 - * - * @author 芋道源码 - */ -@Service -public class SysTokenServiceImpl implements SysTokenService { - - @Resource - private SecurityProperties securityProperties; - - @Override - public String createToken(String subject) { - return Jwts.builder() - .signWith(SignatureAlgorithm.HS512, securityProperties.getTokenSecret()) - .setExpiration(DateUtils.addTime(securityProperties.getTokenTimeout())) - .setSubject(subject) - .compact(); - } - - @Override - public Claims parseToken(String token) { - return Jwts.parser() - .setSigningKey(securityProperties.getTokenSecret()) - .parseClaimsJws(token) - .getBody(); - } - - public static void main(String[] args) { - String secret = "abcdefghijklmnopqrstuvwxyz"; - Map map = new HashMap<>(); - map.put("key1", "value1"); - System.out.println(Jwts.builder() - .signWith(SignatureAlgorithm.HS512, secret) - .setClaims(map) - .compact()); - - System.out.println(Jwts.parser() - .setSigningKey(secret) - .parseClaimsJws("qyJhbGciOiJIUzUxMiJ9.eyJrZXkxIjoidmFsdWUxIn0.AHWncLRBlJkqrKaoWHZmMgbqYIT7rfLs8KCp9LuC0mdNfnx1xEMm1N9bgcD-0lc5sjySqsKiWzqJ3rpoyUSh0g") - .getBody()); - } - -} diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/auth/impl/SysUserSessionServiceImpl.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/auth/impl/SysUserSessionServiceImpl.java new file mode 100644 index 000000000..6dda624c4 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/modules/system/service/auth/impl/SysUserSessionServiceImpl.java @@ -0,0 +1,89 @@ +package cn.iocoder.dashboard.modules.system.service.auth.impl; + +import cn.hutool.core.util.IdUtil; +import cn.iocoder.dashboard.common.pojo.PageResult; +import cn.iocoder.dashboard.framework.security.config.SecurityProperties; +import cn.iocoder.dashboard.framework.security.core.LoginUser; +import cn.iocoder.dashboard.modules.system.controller.auth.vo.session.SysUserSessionPageReqVO; +import cn.iocoder.dashboard.modules.system.dal.mysql.dao.auth.SysUserSessionMapper; +import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.auth.SysUserSessionDO; +import cn.iocoder.dashboard.modules.system.dal.redis.dao.auth.SysLoginUserRedisDAO; +import cn.iocoder.dashboard.modules.system.service.auth.SysUserSessionService; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.Date; + +/** + * 在线用户 Session Service 实现类 + * + * @author 芋道源码 + */ +@Service +public class SysUserSessionServiceImpl implements SysUserSessionService { + + @Resource + private SecurityProperties securityProperties; + + @Resource + private SysLoginUserRedisDAO loginUserRedisDAO; + + @Resource + private SysUserSessionMapper userSessionMapper; + + @Override + public String createUserSession(LoginUser loginUser, String userIp, String userAgent) { + // 生成 Session 编号 + String sessionId = generateSessionId(); + // 写入 Redis 缓存 + loginUser.setUpdateTime(new Date()); + loginUserRedisDAO.set(sessionId, loginUser); + // 写入 DB 中 + SysUserSessionDO userSession = SysUserSessionDO.builder().userId(loginUser.getId()) + .userIp(userIp).userAgent(userAgent).build(); + userSessionMapper.insert(userSession); + // 返回 Session 编号 + return sessionId; + } + + @Override + public void refreshUserSession(String sessionId, LoginUser loginUser) { + // 写入 Redis 缓存 + loginUser.setUpdateTime(new Date()); + loginUserRedisDAO.set(sessionId, loginUser); + // 更新 DB 中 + SysUserSessionDO updateObj = SysUserSessionDO.builder().id(sessionId).build(); + updateObj.setUpdateTime(new Date()); + userSessionMapper.updateById(updateObj); + } + + @Override + public void deleteUserSession(String sessionId) { + + } + + @Override + public LoginUser getLoginUser(String sessionId) { + return loginUserRedisDAO.get(sessionId); + } + + @Override + public Long getSessionTimeoutMillis() { + return securityProperties.getSessionTimeout().toMillis(); + } + + @Override + public PageResult getUserSessionPage(SysUserSessionPageReqVO reqVO) { + return null; + } + + /** + * 生成 Session 编号,目前采用 UUID 算法 + * + * @return Session 编号 + */ + private static String generateSessionId() { + return IdUtil.fastSimpleUUID(); + } + +} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index ff060b164..e313d6e47 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -41,6 +41,7 @@ spring: # 芋道配置项,设置当前项目所有自定义的配置 yudao: + version: 1.0.0 web: api-prefix: /api controller-package: cn.iocoder.dashboard diff --git a/ruoyi-admin/src/main/resources/banner.txt b/src/main/resources/banner.txt similarity index 95% rename from ruoyi-admin/src/main/resources/banner.txt rename to src/main/resources/banner.txt index 94662592f..d4e3b433b 100644 --- a/ruoyi-admin/src/main/resources/banner.txt +++ b/src/main/resources/banner.txt @@ -1,24 +1,25 @@ -Application Version: ${ruoyi.version} -Spring Boot Version: ${spring-boot.version} -//////////////////////////////////////////////////////////////////// -// _ooOoo_ // -// o8888888o // -// 88" . "88 // -// (| ^_^ |) // -// O\ = /O // -// ____/`---'\____ // -// .' \\| |// `. // -// / \\||| : |||// \ // -// / _||||| -:- |||||- \ // -// | | \\\ - /// | | // -// | \_| ''\---/'' | | // -// \ .-\__ `-` ___/-. / // -// ___`. .' /--.--\ `. . ___ // -// ."" '< `.___\_<|>_/___.' >'"". // -// | | : `- \`.;`\ _ /`;.`/ - ` : | | // -// \ \ `-. \_ __\ /__ _/ .-` / / // -// ========`-.____`-.___\_____/___.-`____.-'======== // -// `=---=' // -// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // -// 佛祖保佑 永不宕机 永无BUG // -//////////////////////////////////////////////////////////////////// \ No newline at end of file +芋道源码 http://www.iocoder.cn +Application Version: ${yudao.version} +Spring Boot Version: ${spring-boot.version} +//////////////////////////////////////////////////////////////////// +// _ooOoo_ // +// o8888888o // +// 88" . "88 // +// (| ^_^ |) // +// O\ = /O // +// ____/`---'\____ // +// .' \\| |// `. // +// / \\||| : |||// \ // +// / _||||| -:- |||||- \ // +// | | \\\ - /// | | // +// | \_| ''\---/'' | | // +// \ .-\__ `-` ___/-. / // +// ___`. .' /--.--\ `. . ___ // +// ."" '< `.___\_<|>_/___.' >'"". // +// | | : `- \`.;`\ _ /`;.`/ - ` : | | // +// \ \ `-. \_ __\ /__ _/ .-` / / // +// ========`-.____`-.___\_____/___.-`____.-'======== // +// `=---=' // +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // +// 佛祖保佑 永不宕机 永无BUG // +////////////////////////////////////////////////////////////////////