瀏覽代碼

Merge branch 'system-zjy' of uskycloud/usky-cloud into master

gez 1 天之前
父節點
當前提交
7fe68c11ee

+ 7 - 0
base-modules/service-system/service-system-api/src/main/java/com/usky/system/domain/SysOperLogVO.java

@@ -47,6 +47,9 @@ public class SysOperLogVO extends BaseEntity
     /** 操作地址 */
     private String operIp;
 
+    /** 操作地点 */
+    private String operLocation;
+
     /** 请求参数 */
     private String operParam;
 
@@ -195,6 +198,10 @@ public class SysOperLogVO extends BaseEntity
         this.operIp = operIp;
     }
 
+    public String getOperLocation() { return operLocation; }
+
+    public void setOperLocation(String operLocation) { this.operLocation = operLocation; }
+
     public String getOperParam()
     {
         return operParam;

+ 0 - 6
base-modules/service-system/service-system-biz/pom.xml

@@ -59,12 +59,6 @@
             <artifactId>hutool-all</artifactId>
         </dependency>
 
-        <dependency>
-            <groupId>org.json</groupId>
-            <artifactId>json</artifactId>
-            <version>20210307</version>
-        </dependency>
-
         <!-- 监控服务器资源状态 -->
         <dependency>
             <groupId>com.github.oshi</groupId>

+ 2 - 0
base-modules/service-system/service-system-biz/src/main/java/com/usky/system/service/impl/SysUserOnlineServiceImpl.java

@@ -90,6 +90,8 @@ public class SysUserOnlineServiceImpl implements ISysUserOnlineService
         sysUserOnline.setIpaddr(user.getIpaddr());
         sysUserOnline.setLoginTime(user.getLoginTime());
         sysUserOnline.setExpireTime(user.getExpireTime());
+        sysUserOnline.setOs(user.getOs());
+        sysUserOnline.setBrowser(user.getBrowser());
         return sysUserOnline;
     }
 }

+ 71 - 5
base-modules/service-system/service-system-biz/src/main/java/com/usky/system/service/util/AsyncFactory.java

@@ -3,24 +3,25 @@ package com.usky.system.service.util;
 
 import com.alibaba.fastjson.JSON;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.ruoyi.common.core.constant.CacheConstants;
 import com.ruoyi.common.core.constant.Constants;
-import com.ruoyi.common.core.utils.ServletUtils;
 import com.ruoyi.common.core.utils.StringUtils;
-import com.ruoyi.common.core.utils.ip.IpUtils;
 import com.usky.common.core.util.SpringContextUtils;
+import com.usky.common.log.aspect.AddressUtils;
+import com.usky.common.redis.core.RedisService;
 import com.usky.system.domain.MceRequestVO;
 import com.usky.system.domain.SysLogininfor;
 import com.usky.system.domain.SysUser;
 import com.usky.system.mapper.SysUserMapper;
+import com.usky.system.model.LoginUser;
 import com.usky.system.service.ISysLogininforService;
-import com.usky.system.service.ISysUserService;
 import com.usky.system.service.MceReceiveService;
 import eu.bitwalker.useragentutils.UserAgent;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import javax.servlet.http.HttpServletRequest;
-import java.util.Collections;
+import java.util.*;
 
 import static com.usky.common.core.utils.ip.IpUtils.getIpAddr;
 
@@ -80,9 +81,11 @@ public class AsyncFactory
         if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER)) {
             logininfor.setStatus(String.valueOf(Constants.LOGIN_SUCCESS_STATUS)); // 使用String.valueOf进行转换
 
-        // 如果登录成功,发送微信公众号消息
+        // 如果登录成功,发送微信公众号消息,删除超出规定历史token
             if (Constants.LOGIN_SUCCESS.equals(status)) {
                 sendWeChatMessage(logininfor);
+                // 保留用户相同OS和Browser的最新两条token,删除其他所有token
+                retainLatestToken(username);
             }
         } else if (Constants.LOGIN_FAIL.equals(status)) {
             logininfor.setStatus(String.valueOf(Constants.LOGIN_FAIL_STATUS)); // 使用String.valueOf进行转换
@@ -122,4 +125,67 @@ public class AsyncFactory
         mceRequestVO.setIpAddress(logininfor.getIpaddr());
         mceReceiveService.addMceReceive(mceRequestVO);
     }
+
+    /**
+     * 保留用户相同OS和Browser的最新两条token,删除其他历史token
+     *
+     * @param username 用户名
+     */
+    private static void retainLatestToken(String username) {
+        RedisService redisService = SpringContextUtils.getBean(RedisService.class);
+
+        try {
+            // 获取所有与该用户相关的token键
+            Collection<String> keys = redisService.keys(CacheConstants.LOGIN_TOKEN_KEY + "*");
+            if (keys == null || keys.isEmpty()) {
+                sys_user_logger.warn("Redis密钥为null或为空");
+                return;
+            }
+
+            // 存储该用户的token及其对应的LoginUser对象
+            Map<String, LoginUser> userTokens = new HashMap<>();
+
+            for (String key : keys) {
+                // 检查键是否存在并且未过期
+                if (redisService.hasKey(key)) {
+                    LoginUser user = redisService.getCacheObject(key);
+                    if (user != null && username.equals(user.getUsername())) {
+                        userTokens.put(key, user);
+                    }
+                }
+            }
+
+            if (userTokens.isEmpty()) {
+                sys_user_logger.warn("未找到用户 {} 的任何token", username);
+                return;
+            }
+
+            // 找到最新的token
+            Map<String, List<LoginUser>> groupedTokens = new HashMap<>();
+            for (Map.Entry<String, LoginUser> entry : userTokens.entrySet()) {
+                String osBrowserKey = entry.getValue().getOs() + "-" + entry.getValue().getBrowser();
+                List<LoginUser> tokenList = groupedTokens.computeIfAbsent(osBrowserKey, k -> new ArrayList<>());
+                tokenList.add(entry.getValue());
+            }
+
+            // OS和Browser组合,保留最新的两条token
+            List<String> tokensToDelete = new ArrayList<>();
+            for (List<LoginUser> tokenList : groupedTokens.values()) {
+                Collections.sort(tokenList, Comparator.comparing(LoginUser::getLoginTime).reversed());
+                for (int i = 2; i < tokenList.size(); i++) {
+                    tokensToDelete.add(redisService.getCacheObject(tokenList.get(i).getToken()));
+                }
+            }
+
+            // 删除其他token
+            for (String tokenKey : tokensToDelete) {
+                boolean deleted = redisService.deleteObject(tokenKey);
+                System.out.println("Deleted Token Key: " + tokenKey + ", Result: " + deleted);
+            }
+
+            sys_user_logger.info("用户 {} 的token已清理,仅保留最新的两条token: {}", username, tokensToDelete.size());
+        } catch (Exception ex) {
+            sys_user_logger.error("清理用户 {} 的token时发生错误", username, ex);
+        }
+    }
 }

+ 0 - 2
base-modules/service-system/service-system-biz/src/main/java/com/usky/system/service/util/AsyncManager.java

@@ -3,8 +3,6 @@ package com.usky.system.service.util;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Component;
-import org.springframework.web.context.request.RequestContextHolder;
-import org.springframework.web.context.request.ServletRequestAttributes;
 
 import javax.annotation.PreDestroy;
 import javax.servlet.http.HttpServletRequest;

+ 4 - 0
usky-common/usky-common-core/src/main/java/com/usky/common/core/constants/SecurityConstants.java

@@ -43,4 +43,8 @@ public class SecurityConstants
     public static final String LOGIN_USER = "login_user";
 
     public static final String DETAILS_TENANT_ID = "";
+
+    public static final String DETAILS_OS =  "osInfo";
+
+    public static final String DETAILS_BROWSER = "browserInfo";
 }

+ 9 - 0
usky-common/usky-common-log/pom.xml

@@ -24,6 +24,15 @@
             <groupId>com.usky</groupId>
             <artifactId>usky-common-security</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.usky</groupId>
+            <artifactId>ruoyi-common-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.json</groupId>
+            <artifactId>json</artifactId>
+            <version>20210307</version>
+        </dependency>
 
     </dependencies>
 </project>

+ 3 - 5
base-modules/service-system/service-system-biz/src/main/java/com/usky/system/service/util/AddressUtils.java → usky-common/usky-common-log/src/main/java/com/usky/common/log/aspect/AddressUtils.java

@@ -1,14 +1,12 @@
-package com.usky.system.service.util;
+package com.usky.common.log.aspect;
 
 import cn.hutool.http.HttpUtil;
-import com.alibaba.nacos.shaded.com.google.common.base.Strings;
 import com.ruoyi.common.core.utils.ip.IpUtils;
-import com.usky.system.domain.WjConfig;
+import com.usky.common.log.service.WjConfig;
 import org.json.JSONException;
+import org.json.JSONObject;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.util.StringUtils;
-import org.json.JSONObject;
 
 
 /*

+ 3 - 0
usky-common/usky-common-log/src/main/java/com/usky/common/log/aspect/LogAspect.java

@@ -103,6 +103,9 @@ public class LogAspect {
             operLog.setConsumingTime(consumingTime); // 存储耗时
             operLog.setConsumingTimeWithUnit(consumingTimeWithUnit); // 存储带有单位的字符串
 
+            String operLocation = AddressUtils.getRealAddressByIP(ip);
+            operLog.setOperLocation(operLocation);
+
             // 保存数据库
             asyncLogService.saveSysLog(operLog);
         } catch (Exception exp) {

+ 1 - 1
base-modules/service-system/service-system-biz/src/main/java/com/usky/system/domain/WjConfig.java → usky-common/usky-common-log/src/main/java/com/usky/common/log/service/WjConfig.java

@@ -1,4 +1,4 @@
-package com.usky.system.domain;
+package com.usky.common.log.service;
 
 import lombok.Data;
 import org.springframework.boot.context.properties.ConfigurationProperties;

+ 6 - 0
usky-common/usky-common-security/pom.xml

@@ -36,6 +36,12 @@
             <groupId>com.usky</groupId>
             <artifactId>usky-common-redis</artifactId>
         </dependency>
+        <dependency>
+            <groupId>eu.bitwalker</groupId>
+            <artifactId>UserAgentUtils</artifactId>
+            <version>1.21</version>
+            <scope>compile</scope>
+        </dependency>
 
     </dependencies>
 

+ 11 - 0
usky-common/usky-common-security/src/main/java/com/usky/common/security/service/TokenService.java

@@ -6,6 +6,7 @@ import com.usky.common.core.util.*;
 import com.usky.common.redis.core.RedisHelper;
 import com.usky.common.security.utils.SecurityUtils;
 import com.usky.system.model.LoginUser;
+import eu.bitwalker.useragentutils.UserAgent;
 import org.springframework.cloud.commons.util.IdUtils;
 import org.springframework.stereotype.Component;
 
@@ -50,6 +51,14 @@ public class TokenService {
         loginUser.setUserid(userId);
         loginUser.setUsername(userName);
         loginUser.setIpaddr(IpUtils.getIpAddr(ServletUtils.getRequest()));
+        // 获取操作系统信息
+        String userAgentString = ServletUtils.getRequest().getHeader("User-Agent");
+        UserAgent userAgent = UserAgent.parseUserAgentString(userAgentString);
+        String osInfo = userAgent.getOperatingSystem().getName();
+        String browserInfo = userAgent.getBrowser().getName();
+        loginUser.setOs(osInfo);
+        loginUser.setBrowser(browserInfo);
+
         refreshToken(loginUser);
 
         // Jwt存储信息
@@ -58,6 +67,8 @@ public class TokenService {
         claimsMap.put(SecurityConstants.DETAILS_USER_ID, userId);
         claimsMap.put(SecurityConstants.DETAILS_USERNAME, userName);
         claimsMap.put(SecurityConstants.DETAILS_TENANT_ID, tenantId);
+        claimsMap.put(SecurityConstants.DETAILS_OS, osInfo);
+        claimsMap.put(SecurityConstants.DETAILS_BROWSER, browserInfo);
 
         // 接口返回信息
         Map<String, Object> rspMap = new HashMap<String, Object>();