spring boot + shiro 实现登陆 踢出用户功能 (挤人) 以及UnknownSessionException异常问题 记住我功能

article/2025/9/17 23:45:13

简介:踢出用户功能:就是限制一个账号登陆人数。

                        本文限定一个账号一个用户登陆,并且是挤掉前一个用户


目录

 

首先 pom

然后Shiro配置Bean  ShiroConfigBean

然后配置 ShiroRealm(百度翻译: Realm 领域)

然后sessiondao SessionDAO

然后 配置踢人(挤人)逻辑 KickoutSessionControlFilter

UnknownSessionException异常原因

service

@RestController


首先 pom

        <!-- shiro --><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId><version>1.2.2</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.2.2</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-ehcache</artifactId><version>1.2.2</version></dependency>

然后Shiro配置Bean  ShiroConfigBean


import net.sf.ehcache.CacheException;
import net.sf.ehcache.CacheManager;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.io.ResourceUtils;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import javax.servlet.Filter;
import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;/*** @author liguanhua* @Date: 2019/12/17 11:21* @Description: Shiro配置Bean*/
@Configuration
public class ShiroConfigBean {private static final String NOT_FILTER_STR = "/login|/photoUpload|/img/|/loginOut|/api/login|/importExcel|/api/deviceGis/add" +"|/api/log/uploadLog|/upload|/api/app/update|/api/client/update|/api/device/deviceDataByMDCodeL|/land";private static List<String> NOTFILTER_ARRAY;static {NOTFILTER_ARRAY = Arrays.asList(NOT_FILTER_STR.split("\\|"));}@Beanpublic ShiroFilterFactoryBean shiroFilter(org.apache.shiro.mgt.SecurityManager securityManager) {System.out.println("ShiroConfiguration.shirFilter()");ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();//自定义拦截器 ---------------配置挤人功能呢---------------------- Map<String, Filter> filtersMap = new LinkedHashMap<String, Filter>();
//        限制同一帐号同时在线的个数。filtersMap.put("kickout", kickoutSessionControlFilter());shiroFilterFactoryBean.setFilters(filtersMap);
//        ----------------------------------------------------// 必须设置 SecurityManagershiroFilterFactoryBean.setSecurityManager(securityManager);// 拦截器.Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();// 设置login URLshiroFilterFactoryBean.setLoginUrl("/land");for (String str : NOTFILTER_ARRAY) {shiroFilterFactoryBean.setLoginUrl(str);}// 设置不需要校验的 urlfor (String str : NOTFILTER_ARRAY) {filterChainDefinitionMap.put(str, "anon");}filterChainDefinitionMap.put("/Exception.class", "anon");// 我写的url一般都是xxx.action,根据你的情况自己修改filterChainDefinitionMap.put("/*", "authc");// 退出系统的过滤器filterChainDefinitionMap.put("/loginOut", "logout");// 最后一班都,固定格式//其他资源都需要认证  authc 表示需要认证才能进行访问 user表示配置记住我或认证通过可以访问的地址 kickout 挤人功能配置filterChainDefinitionMap.put("/**", "kickout,authc");shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);return shiroFilterFactoryBean;}/** 凭证匹配器 (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了* 所以我们需要修改下doGetAuthenticationInfo中的代码; )*/@Beanpublic HashedCredentialsMatcher hashedCredentialsMatcher() {HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();hashedCredentialsMatcher.setHashAlgorithmName("md5");// 散列算法:这里使用MD5算法;hashedCredentialsMatcher.setHashIterations(1);// 散列的次数,比如散列两次,相当于md5(md5(""));return hashedCredentialsMatcher;}
//    配置 shiroRealm@Beanpublic ShiroRealm shiroRealm() {ShiroRealm myShiroRealm = new ShiroRealm();myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());return myShiroRealm;}@Beanpublic org.apache.shiro.mgt.SecurityManager securityManager() {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();// 注入自定义的realm;securityManager.setRealm(shiroRealm());// 注入缓存管理器;securityManager.setCacheManager(ehCacheManager());//自定义session管理     添加自定义session管理,解决 异常 sessionId(无效)冲突问题。 挤人功能所需配置securityManager.setSessionManager(sessionManager());return securityManager;}/** 开启shiro aop注解支持 使用代理方式;所以需要开启代码支持;*/@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(org.apache.shiro.mgt.SecurityManager securityManager) {AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);return authorizationAttributeSourceAdvisor;}/*** DefaultAdvisorAutoProxyCreator,Spring的一个bean,由Advisor决定对哪些类的方法进行AOP代理。*/@Bean@ConditionalOnMissingBeanpublic DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();defaultAAP.setProxyTargetClass(true);return defaultAAP;}/** shiro缓存管理器;* 需要注入对应的其它的实体类中-->安全管理器:securityManager可见securityManager是整个shiro的核心;*/@Beanpublic EhCacheManager ehCacheManager() {EhCacheManager ehcache = new EhCacheManager();CacheManager cacheManager = CacheManager.getCacheManager("shiro");if(cacheManager == null){try {cacheManager = CacheManager.create(ResourceUtils.getInputStreamForPath("classpath:ehcache.xml"));} catch (CacheException | IOException e) {e.printStackTrace();}}ehcache.setCacheManager(cacheManager);return ehcache;}/*** 限制同一账号登录同时登录人数控制** @return*/public KickoutSessionControlFilter kickoutSessionControlFilter() {KickoutSessionControlFilter kickoutSessionControlFilter = new KickoutSessionControlFilter();//用于根据会话ID,获取会话进行踢出操作的;kickoutSessionControlFilter.setSessionManager(sessionManager());//使用cacheManager获取相应的cache来缓存用户登录的会话;用于保存用户—会话之间的关系的;kickoutSessionControlFilter.setCacheManager(ehCacheManager());//是否踢出后来登录的,默认是false;即后者登录的用户踢出前者登录的用户;kickoutSessionControlFilter.setKickoutAfter(false);//同一个用户最大的会话数,默认1;比如2的意思是同一个用户允许最多同时两个人登录;kickoutSessionControlFilter.setMaxSession(1);//被踢出后重定向到的地址;kickoutSessionControlFilter.setKickoutUrl("/land?kickout=1");return kickoutSessionControlFilter;}/*** EnterpriseCacheSessionDAO shiro sessionDao层的实现;* 提供了缓存功能的会话维护,默认情况下使用MapCache实现,内部使用ConcurrentHashMap保存缓存的会话。*/@Beanpublic EnterpriseCacheSessionDAO enterCacheSessionDAO() {EnterpriseCacheSessionDAO enterCacheSessionDAO = new EnterpriseCacheSessionDAO();//添加缓存管理器//添加ehcache活跃缓存名称(必须和ehcache缓存名称一致)enterCacheSessionDAO.setActiveSessionsCacheName("shiro-activeSessionCache");return enterCacheSessionDAO;}@Beanpublic SimpleCookie sessionIdCookie() {//DefaultSecurityManagerSimpleCookie simpleCookie = new SimpleCookie();//如果在Cookie中设置了"HttpOnly"属性,那么通过程序(JS脚本、Applet等)将无法读取到Cookie信息,这样能有效的防止XSS攻击。simpleCookie.setHttpOnly(true);
//        用于解决sessionID冲突问题。换个名字simpleCookie.setName("SHRIOSESSIONID");//单位秒simpleCookie.setMaxAge(86400);return simpleCookie;}/*** @描述:sessionManager添加session缓存操作DAO* @return*/@Beanpublic DefaultWebSessionManager sessionManager() {DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();sessionManager.setSessionDAO(enterCacheSessionDAO());sessionManager.setSessionIdCookie(sessionIdCookie());return sessionManager;}}

然后配置 ShiroRealm(百度翻译: Realm 领域)

import com.wulianwang.manage.model.dbentity.system.UserInfo;
import com.wulianwang.manage.service.system.impl.UserService;
import com.wulianwang.manage.utils.util.Utils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;import java.util.HashSet;
import java.util.Set;import static com.wulianwang.manage.utils.base.UserUtil.setUser;/*** @author liguanhua* @Date: 2019/12/17 11:23* @Description:*/
public class ShiroRealm extends AuthorizingRealm {/*** 方面用于加密 参数:AuthenticationToken是从表单穿过来封装好的对象*/@Autowiredprivate UserService userService;@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {System.out.println("doGetAuthenticationInfo:" + token);// 将AuthenticationToken强转为AuthenticationToken对象UsernamePasswordToken upToken = (UsernamePasswordToken) token;// 获得从表单传过来的用户名String userName = upToken.getUsername();UserInfo user = userService.selectUserByUserName(userName);// 如果用户不存在,抛此异常if (!Utils.isNotEmpty(user)) {throw new UnknownAccountException("无此用户名!");}// 认证的实体信息,可以是username,也可以是用户的实体类对象,这里用的用户名Object principal = userName;// 从数据库中查询的密码Object credentials = user.getPassword();// 当前realm对象的名称,调用分类的getName()String realmName = this.getName();// 创建SimpleAuthenticationInfo对象,并且把username和password等信息封装到里面// 用户密码的比对是Shiro帮我们完成的SimpleAuthenticationInfo info = null;info = new SimpleAuthenticationInfo(principal, credentials, realmName);setUser(user);return info;}// 用于授权@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {System.out.println("MyShiroRealm的doGetAuthorizationInfo授权方法执行");// User user=(User)// principals.fromRealm(this.getClass().getName()).iterator().next();//获取session中的用户// System.out.println("在MyShiroRealm中AuthorizationInfo(授权)方法中从session中获取的user对象:"+user);// 从PrincipalCollection中获得用户信息Object principal = principals.getPrimaryPrincipal();System.out.println("ShiroRealm  AuthorizationInfo:" + principal.toString());// 根据用户名来查询数据库赋予用户角色,权限(查数据库)Set<String> roles = new HashSet<>();Set<String> permissions = new HashSet<>();
//		2018.09.14更新//		给用户添加user权限 (没有进行判断、对所有的用户给user权限)
//        if("user".equals(principal)){
//            roles.add("user");
//            permissions.add("user:query");
//        }当用户名为admin时 为用户添加权限admin  两个admin可以理解为连个字段
//        if ("admin".equals(principal)) {
//            roles.add("admin");
//            permissions.add("admin:query");
//        }为用户添加visit游客权限,在url中没有为visit权限,所以,所有的操作都没权限
//        if("visit".equals(principal)){
//            roles.add("visit");
//            permissions.add("visit:query");
//        }
//              更新以上代码SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);//添加权限info.setStringPermissions(permissions);return info;// return null;}
}

然后sessiondao SessionDAO

import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;import java.io.Serializable;
import java.util.Collection;/*** @author liguanhua* @Date: 2019/12/19 10:36* @Description:*/
public interface SessionDAO {/*如DefaultSessionManager在创建完session后会调用该方法;如保存到关系数据库/文件系统/NoSQL数据库;即可以实现会话的持久化;返回会话ID;主要此处返回的ID.equals(session.getId());*/Serializable create(Session session);//根据会话ID获取会话Session readSession(Serializable sessionId) throws UnknownSessionException;//更新会话;如更新会话最后访问时间/停止会话/设置超时时间/设置移除属性等会调用void update(Session session) throws UnknownSessionException;//删除会话;当会话过期/会话停止(如用户退出时)会调用void delete(Session session);//获取当前所有活跃用户,如果用户量多此方法影响性能Collection<Session> getActiveSessions();}

然后 ehcache.xml配置 boot 2.0以上应该是需要加 defaultCache 配置。如果你报错提示 default 错误可以加上这个

<ehcache><diskStore path="java.io.tmpdir"/><!-- name 缓存名称 --><!-- maxElementsInMemory 内存中最大缓存对象数,看着自己的heap大小来搞 --><!-- eternal:true表示对象永不过期,此时会忽略timeToIdleSeconds和timeToLiveSeconds属性,默认为false --><!-- maxElementsOnDisk:硬盘中最大缓存对象数,若是0表示无穷大 --><!-- overflowToDisk:true表示当内存缓存的对象数目达到了maxElementsInMemory界限后,会把溢出的对象写到硬盘缓存中。注意:如果缓存的对象要写入到硬盘中的话,则该对象必须实现了Serializable接口才行。--><!-- diskSpoolBufferSizeMB:磁盘缓存区大小,默认为30MB。每个Cache都应该有自己的一个缓存区。--><!-- diskPersistent:是否缓存虚拟机重启期数据  --><!-- diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认为120秒 --><!--timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。当对象自从最近一次被访问后,如果处于空闲状态的时间超过了timeToIdleSeconds属性值,这个对象就会过期,EHCache将把它从缓存中清空。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。--><!--timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。如果处于缓存中的时间超过了 timeToLiveSeconds属性值,这个对象就会过期,EHCache将把它从缓存中清除。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。--><!-- clearOnFlush:内存数量最大时是否清除 --><!-- memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。--><!-- shiro-activeSessionCache活跃用户session缓存策略 --><defaultCachemaxElementsInMemory="10000"timeToIdleSeconds="120"timeToLiveSeconds="120"maxElementsOnDisk="10000000"diskExpiryThreadIntervalSeconds="120"memoryStoreEvictionPolicy="LRU"><!--<persistence strategy="localTempSwap"/>--></defaultCache><cache name="shiro-activeSessionCache"maxElementsInMemory="10000"timeToIdleSeconds="86400"timeToLiveSeconds="86400"maxElementsOnDisk="10000000"diskExpiryThreadIntervalSeconds="120"memoryStoreEvictionPolicy="LRU"><!-- <persistence strategy="localTempSwap"/>--></cache><!-- 登录记录缓存 锁定2分钟 --><cache name="passwordRetryCache"maxEntriesLocalHeap="10000"eternal="false"timeToIdleSeconds="120"timeToLiveSeconds="0"overflowToDisk="false"statistics="false"></cache>
</ehcache>

然后 配置踢人(挤人)逻辑 KickoutSessionControlFilter


import java.io.Serializable;
import java.util.ArrayDeque;
import java.util.Deque;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.DefaultSessionKey;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/*** @author liguanhua* @Date: 2019/12/18 14:32* @Description: 挤人逻辑*/
public class KickoutSessionControlFilter extends AccessControlFilter {private static final Logger logger = LoggerFactory.getLogger(KickoutSessionControlFilter.class);private static SessionDAO sessionDAO;private String kickoutUrl; // 踢出后到的地址private boolean kickoutAfter = false; // 踢出之前登录的/之后登录的用户 默认false踢出之前登录的用户private int maxSession = 1; // 同一个帐号最大会话数 默认1private SessionManager sessionManager;private Cache<String, Deque<Serializable>> cache;public void setKickoutUrl(String kickoutUrl) {this.kickoutUrl = kickoutUrl;}public void setKickoutAfter(boolean kickoutAfter) {this.kickoutAfter = kickoutAfter;}public void setMaxSession(int maxSession) {this.maxSession = maxSession;}public void setSessionManager(SessionManager sessionManager) {this.sessionManager = sessionManager;}// 设置Cache的key的前缀public void setCacheManager(CacheManager cacheManager) {//必须和ehcache缓存配置中的缓存name一致this.cache = cacheManager.getCache("shiro-activeSessionCache");}@Overrideprotected boolean isAccessAllowed(ServletRequest request,ServletResponse response, Object mappedValue) throws Exception {return false;}@Overrideprotected boolean onAccessDenied(ServletRequest request,ServletResponse response) throws Exception {Subject subject = getSubject(request, response);// 没有登录授权 且没有记住我if (!subject.isAuthenticated() && !subject.isRemembered()) {// 如果没有登录,直接进行之后的流程return true;}Session session = subject.getSession();logger.debug("==session时间设置:" + String.valueOf(session.getTimeout())+ "===========");try {// 当前用户String username = subject.getPrincipal() + "";logger.debug("===当前用户username:==" + username);Serializable sessionId = session.getId();logger.debug("===当前用户sessionId:==" + sessionId);// 读取缓存用户 没有就存入Deque<Serializable> deque = cache.get(username);logger.debug("===当前deque:==" + deque);if (deque == null) {// 初始化队列deque = new ArrayDeque<Serializable>();}// 如果队列里没有此sessionId,且用户没有被踢出;放入队列if (!deque.contains(sessionId)&& session.getAttribute("kickout") == null) {// 将sessionId存入队列deque.push(sessionId);// 将用户的sessionId队列缓存cache.put(username, deque);}// 如果队列里的sessionId数超出最大会话数,开始踢人while (deque.size() > maxSession) {logger.debug("===deque队列长度:==" + deque.size());Serializable kickoutSessionId = null;// 是否踢出后来登录的,默认是false;即后者登录的用户踢出前者登录的用户;if (kickoutAfter) { // 如果踢出后者kickoutSessionId = deque.removeFirst();} else { // 否则踢出前者kickoutSessionId = deque.removeLast();}// 踢出后再更新下缓存队列cache.put(username, deque);try {// 获取被踢出的sessionId的session对象Session kickoutSession = sessionManager.getSession(new DefaultSessionKey(kickoutSessionId));if (kickoutSession != null) {// 设置会话的kickout属性表示踢出了kickoutSession.setAttribute("kickout", true);}} catch (Exception e) {// ignore exceptione.printStackTrace();}}// 如果被踢出了,(前者或后者)直接退出,重定向到踢出后的地址if ((Boolean) session.getAttribute("kickout") != null&& (Boolean) session.getAttribute("kickout") == true) {// 会话被踢出了try {// 退出登录subject.logout();} catch (Exception e) { // ignore}saveRequest(request);logger.debug("==踢出后用户重定向的路径kickoutUrl:" + kickoutUrl);// 重定向WebUtils.issueRedirect(request, response, kickoutUrl);return false;}return true;} catch (Exception e) { // ignore//重定向到登录界面WebUtils.issueRedirect(request, response, "/land");return false;}}
}

UnknownSessionException异常原因

只所以出现这个问题是因为在shiro的DefaultWebSessionManager类中,默认Cookie名称是JSESSIONID,这样的话与servlet容器名冲突, 如jetty, tomcat等默认JSESSIONID, 当跳出shiro servlet时如error-page容器会为JSESSIONID重新分配值导致登录会话丢失!

我们只需要自己指定一个与项目运行容器不冲突的sessionID就好了

上面配置sessionManager(sessionIdCookie)就是为了解决这个问题。使用了SHRIOSESSIONID

service

UserService

    /**
             * 根据用户名获取用户信息
             * @param userName
            * @return
          */
          public UserInfo selectUserByUserName(String userName){
              UserInfo user = userMapper.selectUserByUserName(userName);
              return user;
           }

@RestController

@PostMapping(value = "/login")public Message userLogin(@RequestParam Map<String, String> param, HttpSession session) {// 获得当前SubjectSubject currentUser = SecurityUtils.getSubject();String userName = param.get("userName");String password = param.get("password");// 验证用户是否验证,即是否登录
//        if (!currentUser.isAuthenticated()) {String msg = "";// 把用户名和密码封装为 UsernamePasswordToken 对象UsernamePasswordToken token = new UsernamePasswordToken(userName, password);// remembermMe记住密码 关闭浏览器后再次打开浏览器。系统将Cookie 存到本地。可以不用登陆直接进入token.setRememberMe(true);try {// 执行登录.currentUser.login(token);return renderSuccess(map);} catch (IncorrectCredentialsException e) {return renderError(Global.NAME_OR_PWD_ERROR);} catch (ExcessiveAttemptsException e) {msg = "登录失败次数过多";return renderError(msg);} catch (LockedAccountException e) {msg = "帐号已被锁定";return renderError(msg);} catch (DisabledAccountException e) {return renderError(Global.DISABLED_MESSAGE);} catch (ExpiredCredentialsException e) {msg = "帐号已过期";return renderError(msg);} catch (UnknownAccountException e) {msg = "帐号不存在";return renderError(msg);} catch (UnauthorizedException e) {msg = "您没有得到相应的授权";return renderError(msg);} catch (Exception e) {return renderError(msg);}
//        }
//        return renderSuccess("您已登陆");}

 记住我

其他详细权限等介绍可看 https://blog.csdn.net/qq_32786139/article/details/82658197


http://chatgpt.dhexx.cn/article/qxXjFPtH.shtml

相关文章

java怎么实现踢掉在线用户_Java多人在线聊天室(3)— 踢人下线功能

作为一个聊天室&#xff0c;肯定会有很多人在里面聊天&#xff0c;那么要是有人里面捣乱怎么办&#xff1f;直接踢人不解释。 我们在创建数据库的时候就有这样的考虑&#xff0c;所以我们的账户有两种&#xff0c;一种是管理员&#xff0c;一种是普通用户。 打开的数据库&#…

Shiro实现session限制登录数量踢人下线

Shiro实现session限制登录数量踢人下线 前言实现■ 架构准备ShiroConfig ■ redis内的存储分布■ 代码修改修改 JedisSessionDAO修改 SystemAuthorizingRealm新增 ApiLogoutFilter再次修改 JedisSessionDAO 最后 前言 近年无状态登录兴起&#xff0c;但sessionId方式仍是主流方…

SAP SM04踢人操作(摘录)

管理员在SAP系统中&#xff0c;使用事物码SM04对当前登录在线用户&#xff08;User&#xff09;进行管理&#xff0c;可查看服务器全部客户端&#xff08;Client&#xff09;的用户的在线状态&#xff0c;并可以结束指定用户的会话状态&#xff0c;也就是强制踢出用户。 双击查…

出生率新低!1978-2020中国人口出生率、死亡率及自然增长率变迁

根据国家统计局近日发布的《中国统计年鉴2021》&#xff0c;2020中国人口出生率为8.52‰&#xff0c;再创历史新低。同时&#xff0c;2020中国人口自然增长率仅为1.45‰&#xff0c;勉强维持正增长&#xff0c;同样是历史新低。 下面的视频记录了1978-2020中国人口出生率、死亡…

百度迁徙大数据整理[2020+2019同期]

统筹推进疫情防控和经济社会发展工作是常态化疫情防控阶段促进中国经济恢复的必然选择。基于百度地图迁徙大数据&#xff0c;采用双重差分模型探究不同阶段的疫情防控措施对中国人口流动的影响。结果表明&#xff0c;早期的疫情超常规防控措施有效控制了人口流动&#xff0c;导…

【SQLPlanet】基于迁徙率等指标浅析拍拍贷逾期数据(未完待续)

1、背景介绍 拍拍贷是一家金融科技公司&#xff0c;2007年成立于上海&#xff0c;并在2017年11月10日成功于美国纽交所上市。根据官方消息&#xff0c;截至2018年9月30日&#xff0c;拍拍贷累计成交额已突破1300亿&#xff0c;15-29天及30-59天的逾期率分别为0.83%和1.21%&…

百度迁徙 迁入人口和迁徙规模爬虫

最近做COVID-19相关的课程项目&#xff0c;需要用到省级间人口迁移的数据。笔者参考改进了https://blog.csdn.net/qq_44315987/article/details/104118498 的城市间流动的代码&#xff0c;从百度迁徙爬取了数据&#xff0c;并将数据保存在同一张表内。 迁入人口 # coding:utf…

百度大数据迁徙

我记得&#xff0c;百度地图应该是2018年开始做每年春运出行大数据迁移&#xff0c;2019年加入了国庆的迁移大数据分析。这是纯免费的大数据分析数据&#xff0c;相当有意思&#xff0c;但是不作保存&#xff0c;每年做个记录&#xff0c;应该是件有价值的事。 从整体来看&…

什么是计算迁移

0 1 计算迁移背景 边缘计算将网络边缘上的计算、存储等资源进行有机融合&#xff0c;构建成统一的用户服务平台&#xff0c;按就近服务原则对网络边缘节点任务请求及时响应并有效处理。由于边缘节点能力、资源、带宽、能源等受限&#xff0c;计算迁移便异常重要。计算迁移是边缘…

迁徙数据平台简单介绍

引言 人口迁徙数据是研究人口流动、人口迁徙以及城市发展的重要数据源。近年来&#xff0c;受新冠肺炎疫情影响&#xff0c;以及互联网的快速发展&#xff0c;相关数据受到广泛关注与应用&#xff0c;以人口迁徙数据为基础的科学研究也层出不穷。在这里&#xff0c;就对几种常…

人口迁徙大数据(2019-2020年)

人口迁徙大数据具有极高的研究价值&#xff0c;特别是与城市群、疫情等重要话题结合起来&#xff0c;发表了很多核心期刊&#xff1a; 迁徙数据&#xff08;1&#xff09; 高德地图人口迁徙数据&#xff1a;2020年前6个月的400多万条数据&#xff0c;数据为日数据&#xff0c;…

【迁移学习】分布差异的度量以及迁移学习的统一表征方法

在文本分类中&#xff0c;由于文本数据有其领域特殊性&#xff0c;因此&#xff0c;在一个领域上训练的分类器&#xff0c;不能直接拿来作用到另一个领域上&#xff0c;这就需要用到迁移学习。 迁移学习是机器学习中重要的研究领域&#xff0c;ICML、NIPS、AAAI、ICIR等国际人工…

手把手实操系列|贷后迁徙率模型开发(上篇)

序言&#xff1a; 很多关注番茄风控的老铁们都知道&#xff0c;番茄风控的开篇就是从系统性的贷后评分卡开始的&#xff0c;关于贷后相关的内容&#xff0c;番茄不敢说是元老级别的公众号&#xff0c;但再怎么说也是先行者&#xff0c;之前的文章比如这些经典内容&#xff0c;您…

手把手系列|贷后评分(C)卡模型开发实操(全)

序言&#xff1a; 随着风控精细化的管理&#xff0c;番茄风控也就将现有的内容进一步迭代&#xff0c;更新贷后迁徙率模型的内容&#xff0c;同时也综合了星球社区中同学的一些新需求&#xff0c;给大家梳理了贷后迁徙率模型的文章。 希望对所有的风控人员在贷后相关的模型开发…

催收评分卡(三)迁徙率模型

关注公众号“ 番茄风控大数据”&#xff0c;获取更多数据分析与风控大数据的实用干货。 本文主要介绍迁徙率模型和还款率模型&#xff0c;至于失联模型&#xff0c;其实也不难做&#xff0c;难点是在于每家公司对失联客户的定义&#xff0c;主要是需要把多长失时间内失联的客户…

风险资产常用指标

目录 1.放款本金与本金余额 2.迁徙率 3.Vintage 4.不良率 5.损失率 6.回收率 7.入催率 8.首逾率 1.放款本金与本金余额 放款本金为每个月的放贷金额&#xff0c;本金余额为截止统计时点&#xff0c;所有未收回的本金金额&#xff0c;包括逾期未还本金与未到还款期限的待还本金…

前端数据分页——table表数据分页方法(1)

我们在页面展示一个table表格的时候&#xff0c;当数据量较大时&#xff0c;常常会考虑到数据分页的问题&#xff0c;数据分页一般有三种方式&#xff0c;分别是前端数据分页&#xff0c;后端数据分页&#xff0c;数据库分页。 前端数据分页&#xff1a;是把所有数据加载到前端…

利用vue实现树表格分页

目录 1. 准备工作 2. 动态树 2.1 在配置请求路径 2.2 使用动态数据构建导航菜单 2.2.1 通过接口获取数据 2.2.3 通过后台获取的数据构建菜单导航 2.3 点击菜单实现路由跳转 2.3.1 创建书本管理组件 2.3.2 配置路由 2.3.3 修改LeftAside组件 2.3.4 修改Main组件 3. …

Layui 表格分页控件

分页模块 – layui.laypage layPage 致力于提供极致的分页逻辑,既可轻松胜任异步分页,也可作为页面刷新式分页。 快速使用 laypage 的使用非常简单,指向一个用于存放分页的容器,通过服务端得到一些初始值,即可完成分页渲染: 基础参数选项 通过核心方法:laypage.rende…

ant design pro表格分页

需要用到表格的配置项pagination pagination{{ showQuickJumper:true, showTotal:function(total, range){ return( 共total条 ) } }} 效果&#xff1a; 这样可以选择跳转到某个页面&#xff0c;如果如数的数值大于最后一页&#xff0c;那么将跳转到最后一页&#xf…