• ruoyi后台管理系统分析(二)------framework包


    二、framework包

    --aspectj包

    DataScopeAspect.java-------数据过滤处理
    package com.ruoyi.framework.aspectj;
    
    import java.lang.reflect.Method;
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.Signature;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.springframework.stereotype.Component;
    import com.ruoyi.common.annotation.DataScope;
    import com.ruoyi.common.base.BaseEntity;
    import com.ruoyi.common.utils.StringUtils;
    import com.ruoyi.framework.util.ShiroUtils;
    import com.ruoyi.system.domain.SysRole;
    import com.ruoyi.system.domain.SysUser;
    
    /**
     * 数据过滤处理
     * 
     * @author ruoyi
     */
    @Aspect
    @Component
    public class DataScopeAspect
    {
        /**
         * 全部数据权限
         */
        public static final String DATA_SCOPE_ALL = "1";
    
        /**
         * 自定数据权限
         */
        public static final String DATA_SCOPE_CUSTOM = "2";
    
        /**
         * 数据权限过滤关键字
         */
        public static final String DATA_SCOPE = "dataScope";
    
        // 配置织入点
        @Pointcut("@annotation(com.ruoyi.common.annotation.DataScope)")
        public void dataScopePointCut()
        {
        }
    
        @Before("dataScopePointCut()")
        public void doBefore(JoinPoint point) throws Throwable
        {
            handleDataScope(point);
        }
    
        protected void handleDataScope(final JoinPoint joinPoint)
        {
            // 获得注解
            DataScope controllerDataScope = getAnnotationLog(joinPoint);
            if (controllerDataScope == null)
            {
                return;
            }
            // 获取当前的用户
            SysUser currentUser = ShiroUtils.getUser();
            if (currentUser != null)
            {
                // 如果是超级管理员,则不过滤数据
                if (!currentUser.isAdmin())
                {
                    dataScopeFilter(joinPoint, currentUser, controllerDataScope.tableAlias());
                }
            }
        }
    
        /**
         * 数据范围过滤
         * 
         * @param da 部门表别名
         * @return 标准连接条件对象
         */
        public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String alias)
        {
            StringBuilder sqlString = new StringBuilder();
    
            for (SysRole role : user.getRoles())
            {
                String dataScope = role.getDataScope();
                if (DATA_SCOPE_ALL.equals(dataScope))
                {
                    sqlString = new StringBuilder();
                    break;
                }
                else if (DATA_SCOPE_CUSTOM.equals(dataScope))
                {
                    sqlString.append(StringUtils.format(
                            " OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", alias,
                            role.getRoleId()));
                }
            }
    
            if (StringUtils.isNotBlank(sqlString.toString()))
            {
                BaseEntity baseEntity = (BaseEntity) joinPoint.getArgs()[0];
                baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
            }
        }
    
        /**
         * 是否存在注解,如果存在就获取
         */
        private DataScope getAnnotationLog(JoinPoint joinPoint)
        {
            Signature signature = joinPoint.getSignature();
            MethodSignature methodSignature = (MethodSignature) signature;
            Method method = methodSignature.getMethod();
    
            if (method != null)
            {
                return method.getAnnotation(DataScope.class);
            }
            return null;
        }
    }
    View Code
    DataSourceAspect.java---------多源数据处理
    package com.ruoyi.framework.aspectj;
    
    import java.lang.reflect.Method;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    import com.ruoyi.common.annotation.DataSource;
    import com.ruoyi.common.utils.StringUtils;
    import com.ruoyi.framework.datasource.DynamicDataSourceContextHolder;
    
    /**
     * 多数据源处理
     * 
     * @author ruoyi
     */
    @Aspect
    @Order(1)
    @Component
    public class DataSourceAspect
    {
        protected Logger logger = LoggerFactory.getLogger(getClass());
    
        @Pointcut("@annotation(com.ruoyi.common.annotation.DataSource)")
        public void dsPointCut()
        {
    
        }
    
        @Around("dsPointCut()")
        public Object around(ProceedingJoinPoint point) throws Throwable
        {
            MethodSignature signature = (MethodSignature) point.getSignature();
    
            Method method = signature.getMethod();
    
            DataSource dataSource = method.getAnnotation(DataSource.class);
    
            if (StringUtils.isNotNull(dataSource))
            {
                DynamicDataSourceContextHolder.setDateSoureType(dataSource.value().name());
            }
    
            try
            {
                return point.proceed();
            }
            finally
            {
                // 销毁数据源 在执行方法之后
                DynamicDataSourceContextHolder.clearDateSoureType();
            }
        }
    }
    View Code
    LogAspect.java-------操作日志记录处理
    package com.ruoyi.framework.aspectj;
    
    import java.lang.reflect.Method;
    import java.util.Map;
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.Signature;
    import org.aspectj.lang.annotation.AfterReturning;
    import org.aspectj.lang.annotation.AfterThrowing;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    import com.ruoyi.common.annotation.Log;
    import com.ruoyi.common.enums.BusinessStatus;
    import com.ruoyi.common.json.JSON;
    import com.ruoyi.common.utils.StringUtils;
    import com.ruoyi.framework.manager.AsyncManager;
    import com.ruoyi.framework.manager.factory.AsyncFactory;
    import com.ruoyi.framework.util.ServletUtils;
    import com.ruoyi.framework.util.ShiroUtils;
    import com.ruoyi.system.domain.SysOperLog;
    import com.ruoyi.system.domain.SysUser;
    
    /**
     * 操作日志记录处理
     * 
     * @author ruoyi
     */
    @Aspect
    @Component
    public class LogAspect
    {
        private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
    
        // 配置织入点
        @Pointcut("@annotation(com.ruoyi.common.annotation.Log)")
        public void logPointCut()
        {
        }
    
        /**
         * 前置通知 用于拦截操作
         *
         * @param joinPoint 切点
         */
        @AfterReturning(pointcut = "logPointCut()")
        public void doBefore(JoinPoint joinPoint)
        {
            handleLog(joinPoint, null);
        }
    
        /**
         * 拦截异常操作
         * 
         * @param joinPoint
         * @param e
         */
        @AfterThrowing(value = "logPointCut()", throwing = "e")
        public void doAfter(JoinPoint joinPoint, Exception e)
        {
            handleLog(joinPoint, e);
        }
    
        protected void handleLog(final JoinPoint joinPoint, final Exception e)
        {
            try
            {
                // 获得注解
                Log controllerLog = getAnnotationLog(joinPoint);
                if (controllerLog == null)
                {
                    return;
                }
    
                // 获取当前的用户
                SysUser currentUser = ShiroUtils.getUser();
    
                // *========数据库日志=========*//
                SysOperLog operLog = new SysOperLog();
                operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
                // 请求的地址
                String ip = ShiroUtils.getIp();
                operLog.setOperIp(ip);
    
                operLog.setOperUrl(ServletUtils.getRequest().getRequestURI());
                if (currentUser != null)
                {
                    operLog.setOperName(currentUser.getLoginName());
                    if (StringUtils.isNotNull(currentUser.getDept())
                            && StringUtils.isNotEmpty(currentUser.getDept().getDeptName()))
                    {
                        operLog.setDeptName(currentUser.getDept().getDeptName());
                    }
                }
    
                if (e != null)
                {
                    operLog.setStatus(BusinessStatus.FAIL.ordinal());
                    operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
                }
                // 设置方法名称
                String className = joinPoint.getTarget().getClass().getName();
                String methodName = joinPoint.getSignature().getName();
                operLog.setMethod(className + "." + methodName + "()");
                // 处理设置注解上的参数
                getControllerMethodDescription(controllerLog, operLog);
                // 保存数据库
                AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
            }
            catch (Exception exp)
            {
                // 记录本地异常日志
                log.error("==前置通知异常==");
                log.error("异常信息:{}", exp.getMessage());
                exp.printStackTrace();
            }
        }
    
        /**
         * 获取注解中对方法的描述信息 用于Controller层注解
         * 
         * @param joinPoint 切点
         * @return 方法描述
         * @throws Exception
         */
        public void getControllerMethodDescription(Log log, SysOperLog operLog) throws Exception
        {
            // 设置action动作
            operLog.setBusinessType(log.businessType().ordinal());
            // 设置标题
            operLog.setTitle(log.title());
            // 设置操作人类别
            operLog.setOperatorType(log.operatorType().ordinal());
            // 是否需要保存request,参数和值
            if (log.isSaveRequestData())
            {
                // 获取参数的信息,传入到数据库中。
                setRequestValue(operLog);
            }
        }
    
        /**
         * 获取请求的参数,放到log中
         * 
         * @param operLog 操作日志
         * @throws Exception 异常
         */
        private void setRequestValue(SysOperLog operLog) throws Exception
        {
            Map<String, String[]> map = ServletUtils.getRequest().getParameterMap();
            String params = JSON.marshal(map);
            operLog.setOperParam(StringUtils.substring(params, 0, 255));
        }
    
        /**
         * 是否存在注解,如果存在就获取
         */
        private Log getAnnotationLog(JoinPoint joinPoint) throws Exception
        {
            Signature signature = joinPoint.getSignature();
            MethodSignature methodSignature = (MethodSignature) signature;
            Method method = methodSignature.getMethod();
    
            if (method != null)
            {
                return method.getAnnotation(Log.class);
            }
            return null;
        }
    }
    View Code
    --config包
    CaptchaConfig.java-------验证码配置
    package com.ruoyi.framework.config;
    
    import java.util.Properties;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import com.google.code.kaptcha.impl.DefaultKaptcha;
    import com.google.code.kaptcha.util.Config;
    
    /**
     * 验证码配置
     * 
     * @author ruoyi
     */
    @Configuration
    public class CaptchaConfig
    {
        @Bean(name = "captchaProducer")
        public DefaultKaptcha getKaptchaBean()
        {
            DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
            Properties properties = new Properties();
            properties.setProperty("kaptcha.border", "yes");
            properties.setProperty("kaptcha.border.color", "105,179,90");
            properties.setProperty("kaptcha.textproducer.font.color", "blue");
            properties.setProperty("kaptcha.image.width", "160");
            properties.setProperty("kaptcha.image.height", "60");
            properties.setProperty("kaptcha.textproducer.font.size", "28");
            properties.setProperty("kaptcha.session.key", "kaptchaCode");
            properties.setProperty("kaptcha.textproducer.char.spac", "35");
            properties.setProperty("kaptcha.textproducer.char.length", "5");
            properties.setProperty("kaptcha.textproducer.font.names", "Arial,Courier");
            properties.setProperty("kaptcha.noise.color", "white");
            Config config = new Config(properties);
            defaultKaptcha.setConfig(config);
            return defaultKaptcha;
        }
    
        @Bean(name = "captchaProducerMath")
        public DefaultKaptcha getKaptchaBeanMath()
        {
            DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
            Properties properties = new Properties();
            properties.setProperty("kaptcha.border", "yes");
            properties.setProperty("kaptcha.border.color", "105,179,90");
            properties.setProperty("kaptcha.textproducer.font.color", "blue");
            properties.setProperty("kaptcha.image.width", "160");
            properties.setProperty("kaptcha.image.height", "60");
            properties.setProperty("kaptcha.textproducer.font.size", "38");
            properties.setProperty("kaptcha.session.key", "kaptchaCodeMath");
            properties.setProperty("kaptcha.textproducer.impl", "com.ruoyi.framework.config.KaptchaTextCreator");
            properties.setProperty("kaptcha.textproducer.char.spac", "5");
            properties.setProperty("kaptcha.textproducer.char.length", "6");
            properties.setProperty("kaptcha.textproducer.font.names", "Arial,Courier");
            properties.setProperty("kaptcha.noise.color", "white");
            properties.setProperty("kaptcha.noise.impl", "com.google.code.kaptcha.impl.NoNoise");
            properties.setProperty("kaptcha.obscurificator.impl", "com.google.code.kaptcha.impl.ShadowGimpy");
            Config config = new Config(properties);
            defaultKaptcha.setConfig(config);
            return defaultKaptcha;
        }
    }
    View Code
    DruidConfig.java-------配置多个数据源
    package com.ruoyi.framework.config;
    
    import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
    import com.ruoyi.common.enums.DataSourceType;
    import com.ruoyi.framework.datasource.DynamicDataSource;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Primary;
    
    import javax.sql.DataSource;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * druid 配置多数据源
     * 
     * @author ruoyi
     */
    @Configuration
        public class DruidConfig
    {
        @Bean
        @ConfigurationProperties("spring.datasource.druid.master")
        public DataSource masterDataSource()
        {
            return DruidDataSourceBuilder.create().build();
        }
    
        @Bean
        @ConfigurationProperties("spring.datasource.druid.slave")
        @ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
        public DataSource slaveDataSource()
        {
            return DruidDataSourceBuilder.create().build();
        }
    
        @Bean(name = "dynamicDataSource")
        @Primary
        public DynamicDataSource dataSource(DataSource masterDataSource, DataSource slaveDataSource)
        {
            Map<Object, Object> targetDataSources = new HashMap<>();
            targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
            targetDataSources.put(DataSourceType.SLAVE.name(), slaveDataSource);
            return new DynamicDataSource(masterDataSource, targetDataSources);
        }
    }
    View Code
    FilterConfig.java---------过滤器配置
    package com.ruoyi.framework.config;
    
    import java.util.HashMap;
    import java.util.Map;
    import javax.servlet.DispatcherType;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.web.servlet.FilterRegistrationBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import com.ruoyi.common.utils.StringUtils;
    import com.ruoyi.common.xss.XssFilter;
    
    /**
     * Filter配置
     *
     * @author ruoyi
     */
    @Configuration
    public class FilterConfig
    {
        @Value("${xss.enabled}")
        private String enabled;
    
        @Value("${xss.excludes}")
        private String excludes;
    
        @Value("${xss.urlPatterns}")
        private String urlPatterns;
    
        @SuppressWarnings({ "rawtypes", "unchecked" })
        @Bean
        public FilterRegistrationBean xssFilterRegistration()
        {
            FilterRegistrationBean registration = new FilterRegistrationBean();
            registration.setDispatcherTypes(DispatcherType.REQUEST);
            registration.setFilter(new XssFilter());
            registration.addUrlPatterns(StringUtils.split(urlPatterns, ","));
            registration.setName("xssFilter");
            registration.setOrder(Integer.MAX_VALUE);
            Map<String, String> initParameters = new HashMap<String, String>();
            initParameters.put("excludes", excludes);
            initParameters.put("enabled", enabled);
            registration.setInitParameters(initParameters);
            return registration;
        }
    }
    View Code
    GenConfig.java----------读取代码生成相关配置
    package com.ruoyi.framework.config;
    
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.stereotype.Component;
    
    /**
     * 读取代码生成相关配置
     * 
     * @author ruoyi
     */
    @Component
    @ConfigurationProperties(prefix = "gen")
    public class GenConfig
    {
        /** 作者 */
        public static String author;
        /** 生成包路径 */
        public static String packageName;
        /** 自动去除表前缀,默认是true */
        public static String autoRemovePre;
        /** 表前缀(类名不会包含表前缀) */
        public static String tablePrefix;
    
        public static String getAuthor()
        {
            return author;
        }
    
        public void setAuthor(String author)
        {
            GenConfig.author = author;
        }
    
        public static String getPackageName()
        {
            return packageName;
        }
    
        public void setPackageName(String packageName)
        {
            GenConfig.packageName = packageName;
        }
    
        public static String getAutoRemovePre()
        {
            return autoRemovePre;
        }
    
        public void setAutoRemovePre(String autoRemovePre)
        {
            GenConfig.autoRemovePre = autoRemovePre;
        }
    
        public static String getTablePrefix()
        {
            return tablePrefix;
        }
    
        public void setTablePrefix(String tablePrefix)
        {
            GenConfig.tablePrefix = tablePrefix;
        }
    }
    View Code
    I18nConfig.java---------资源文件配置加载
    package com.ruoyi.framework.config;
    
    import java.util.Locale;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.LocaleResolver;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
    import org.springframework.web.servlet.i18n.SessionLocaleResolver;
    
    /**
     * 资源文件配置加载
     * 
     * @author ruoyi
     */
    @Configuration
    public class I18nConfig implements WebMvcConfigurer
    {
        @Bean
        public LocaleResolver localeResolver()
        {
            SessionLocaleResolver slr = new SessionLocaleResolver();
            // 默认语言
            slr.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
            return slr;
        }
    
        @Bean
        public LocaleChangeInterceptor localeChangeInterceptor()
        {
            LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
            // 参数名
            lci.setParamName("lang");
            return lci;
        }
    
        @Override
        public void addInterceptors(InterceptorRegistry registry)
        {
            registry.addInterceptor(localeChangeInterceptor());
        }
    }
    View Code
     KaptchaTextCreator.java----------验证码文本生成器
    package com.ruoyi.framework.config;
    
    import java.util.Random;
    import com.google.code.kaptcha.text.impl.DefaultTextCreator;
    
    /**
     * 验证码文本生成器
     * 
     * @author ruoyi
     */
    public class KaptchaTextCreator extends DefaultTextCreator
    {
        private static final String[] CNUMBERS = "0,1,2,3,4,5,6,7,8,9,10".split(",");
    
        @Override
        public String getText()
        {
            Integer result = 0;
            Random random = new Random();
            int x = random.nextInt(10);
            int y = random.nextInt(10);
            StringBuilder suChinese = new StringBuilder();
            int randomoperands = (int) Math.round(Math.random() * 2);
            if (randomoperands == 0)
            {
                result = x * y;
                suChinese.append(CNUMBERS[x]);
                suChinese.append("*");
                suChinese.append(CNUMBERS[y]);
            }
            else if (randomoperands == 1)
            {
                if (!(x == 0) && y % x == 0)
                {
                    result = y / x;
                    suChinese.append(CNUMBERS[y]);
                    suChinese.append("/");
                    suChinese.append(CNUMBERS[x]);
                }
                else
                {
                    result = x + y;
                    suChinese.append(CNUMBERS[x]);
                    suChinese.append("+");
                    suChinese.append(CNUMBERS[y]);
                }
            }
            else if (randomoperands == 2)
            {
                if (x >= y)
                {
                    result = x - y;
                    suChinese.append(CNUMBERS[x]);
                    suChinese.append("-");
                    suChinese.append(CNUMBERS[y]);
                }
                else
                {
                    result = y - x;
                    suChinese.append(CNUMBERS[y]);
                    suChinese.append("-");
                    suChinese.append(CNUMBERS[x]);
                }
            }
            else
            {
                result = x + y;
                suChinese.append(CNUMBERS[x]);
                suChinese.append("+");
                suChinese.append(CNUMBERS[y]);
            }
            suChinese.append("=?@" + result);
            return suChinese.toString();
        }
    }
    View Code
    ResourcesConfig.java---------通用配置
    package com.ruoyi.framework.config;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
    import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    import com.ruoyi.common.config.Global;
    
    /**
     * 通用配置
     * 
     * @author ruoyi
     */
    @Configuration
    public class ResourcesConfig implements WebMvcConfigurer
    {
        /**
         * 首页地址
         */
        @Value("${shiro.user.indexUrl}")
        private String indexUrl;
    
        /**
         * 默认首页的设置,当输入域名是可以自动跳转到默认指定的网页
         */
        @Override
        public void addViewControllers(ViewControllerRegistry registry)
        {
            registry.addViewController("/").setViewName("forward:" + indexUrl);
        }
    
        @Override
        public void addResourceHandlers(ResourceHandlerRegistry registry)
        {
            /** 文件上传路径 */
            registry.addResourceHandler("/profile/**").addResourceLocations("file:" + Global.getProfile());
    
            /** swagger配置 */
            registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
            registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
        }
    }
    View Code
    ShiroConfig.java-----------权限配置加载
    package com.ruoyi.framework.config;
    
    import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
    import com.ruoyi.common.utils.StringUtils;
    import com.ruoyi.framework.shiro.realm.UserRealm;
    import com.ruoyi.framework.shiro.session.OnlineSessionDAO;
    import com.ruoyi.framework.shiro.session.OnlineSessionFactory;
    import com.ruoyi.framework.shiro.web.filter.LogoutFilter;
    import com.ruoyi.framework.shiro.web.filter.captcha.CaptchaValidateFilter;
    import com.ruoyi.framework.shiro.web.filter.online.OnlineSessionFilter;
    import com.ruoyi.framework.shiro.web.filter.sync.SyncOnlineSessionFilter;
    import com.ruoyi.framework.shiro.web.session.OnlineWebSessionManager;
    import com.ruoyi.framework.shiro.web.session.SpringSessionValidationScheduler;
    import org.apache.shiro.cache.ehcache.EhCacheManager;
    import org.apache.shiro.codec.Base64;
    import org.apache.shiro.mgt.SecurityManager;
    import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
    import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
    import org.apache.shiro.web.mgt.CookieRememberMeManager;
    import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
    import org.apache.shiro.web.servlet.SimpleCookie;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import javax.servlet.Filter;
    import java.util.LinkedHashMap;
    import java.util.Map;
    
    /**
     * 权限配置加载
     * 
     * @author ruoyi
     */
    @Configuration
        public class ShiroConfig
    {
        public static final String PREMISSION_STRING = "perms["{0}"]";
    
        // Session超时时间,单位为毫秒(默认30分钟)
        @Value("${shiro.session.expireTime}")
        private int expireTime;
    
        // 相隔多久检查一次session的有效性,单位毫秒,默认就是10分钟
        @Value("${shiro.session.validationInterval}")
        private int validationInterval;
    
        // 验证码开关
        @Value("${shiro.user.captchaEnabled}")
        private boolean captchaEnabled;
    
        // 验证码类型
        @Value("${shiro.user.captchaType}")
        private String captchaType;
    
        // 设置Cookie的域名
        @Value("${shiro.cookie.domain}")
        private String domain;
    
        // 设置cookie的有效访问路径
        @Value("${shiro.cookie.path}")
        private String path;
    
        // 设置HttpOnly属性
        @Value("${shiro.cookie.httpOnly}")
        private boolean httpOnly;
    
        // 设置Cookie的过期时间,秒为单位
        @Value("${shiro.cookie.maxAge}")
        private int maxAge;
    
        // 登录地址
        @Value("${shiro.user.loginUrl}")
        private String loginUrl;
    
        // 权限认证失败地址
        @Value("${shiro.user.unauthorizedUrl}")
        private String unauthorizedUrl;
    
        /**
         * 缓存管理器 使用Ehcache实现
         */
        @Bean
        public EhCacheManager getEhCacheManager()
        {
            net.sf.ehcache.CacheManager cacheManager = net.sf.ehcache.CacheManager.getCacheManager("ruoyi");
            EhCacheManager em = new EhCacheManager();
            if (StringUtils.isNull(cacheManager))
            {
                em.setCacheManagerConfigFile("classpath:ehcache/ehcache-shiro.xml");
                return em;
            }
            else
            {
                em.setCacheManager(cacheManager);
                return em;
            }
        }
    
        /**
         * 自定义Realm
         */
        @Bean
        public UserRealm userRealm(EhCacheManager cacheManager)
        {
            UserRealm userRealm = new UserRealm();
            userRealm.setCacheManager(cacheManager);
            return userRealm;
        }
    
        /**
         * 自定义sessionDAO会话
         */
        @Bean
        public OnlineSessionDAO sessionDAO()
        {
            OnlineSessionDAO sessionDAO = new OnlineSessionDAO();
            return sessionDAO;
        }
    
        /**
         * 自定义sessionFactory会话
         */
        @Bean
        public OnlineSessionFactory sessionFactory()
        {
            OnlineSessionFactory sessionFactory = new OnlineSessionFactory();
            return sessionFactory;
        }
    
        /**
         * 自定义sessionFactory调度器
         */
        @Bean
        public SpringSessionValidationScheduler sessionValidationScheduler()
        {
            SpringSessionValidationScheduler sessionValidationScheduler = new SpringSessionValidationScheduler();
            // 相隔多久检查一次session的有效性,单位毫秒,默认就是10分钟
            sessionValidationScheduler.setSessionValidationInterval(validationInterval * 60 * 1000);
            // 设置会话验证调度器进行会话验证时的会话管理器
            sessionValidationScheduler.setSessionManager(sessionValidationManager());
            return sessionValidationScheduler;
        }
    
        /**
         * 会话管理器
         */
        @Bean
        public OnlineWebSessionManager sessionValidationManager()
        {
            OnlineWebSessionManager manager = new OnlineWebSessionManager();
            // 加入缓存管理器
            manager.setCacheManager(getEhCacheManager());
            // 删除过期的session
            manager.setDeleteInvalidSessions(true);
            // 设置全局session超时时间
            manager.setGlobalSessionTimeout(expireTime * 60 * 1000);
            // 去掉 JSESSIONID
            manager.setSessionIdUrlRewritingEnabled(false);
            // 是否定时检查session
            manager.setSessionValidationSchedulerEnabled(true);
            // 自定义SessionDao
            manager.setSessionDAO(sessionDAO());
            // 自定义sessionFactory
            manager.setSessionFactory(sessionFactory());
            return manager;
        }
    
        /**
         * 会话管理器
         */
        @Bean
        public OnlineWebSessionManager sessionManager()
        {
            OnlineWebSessionManager manager = new OnlineWebSessionManager();
            // 加入缓存管理器
            manager.setCacheManager(getEhCacheManager());
            // 删除过期的session
            manager.setDeleteInvalidSessions(true);
            // 设置全局session超时时间
            manager.setGlobalSessionTimeout(expireTime * 60 * 1000);
            // 去掉 JSESSIONID
            manager.setSessionIdUrlRewritingEnabled(false);
            // 定义要使用的无效的Session定时调度器
            manager.setSessionValidationScheduler(sessionValidationScheduler());
            // 是否定时检查session
            manager.setSessionValidationSchedulerEnabled(true);
            // 自定义SessionDao
            manager.setSessionDAO(sessionDAO());
            // 自定义sessionFactory
            manager.setSessionFactory(sessionFactory());
            return manager;
        }
    
        /**
         * 安全管理器
         */
        @Bean
        public SecurityManager securityManager(UserRealm userRealm)
        {
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            // 设置realm.
            securityManager.setRealm(userRealm);
            // 记住我
            securityManager.setRememberMeManager(rememberMeManager());
            // 注入缓存管理器;
            securityManager.setCacheManager(getEhCacheManager());
            // session管理器
            securityManager.setSessionManager(sessionManager());
            return securityManager;
        }
    
        /**
         * 退出过滤器
         */
        public LogoutFilter logoutFilter()
        {
            LogoutFilter logoutFilter = new LogoutFilter();
            logoutFilter.setLoginUrl(loginUrl);
            return logoutFilter;
        }
    
        /**
         * Shiro过滤器配置
         */
        @Bean
        public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager)
        {
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            // Shiro的核心安全接口,这个属性是必须的
            shiroFilterFactoryBean.setSecurityManager(securityManager);
            // 身份认证失败,则跳转到登录页面的配置
            shiroFilterFactoryBean.setLoginUrl(loginUrl);
            // 权限认证失败,则跳转到指定页面
            shiroFilterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);
            // Shiro连接约束配置,即过滤链的定义
            LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
            // 对静态资源设置匿名访问
    
            filterChainDefinitionMap.put("/favicon.ico**", "anon");
            // uflo工作流不拦截
            filterChainDefinitionMap.put("/uflo/**", "anon");
            filterChainDefinitionMap.put("/ruoyi.png**", "anon");
            filterChainDefinitionMap.put("/css/**", "anon");
            filterChainDefinitionMap.put("/docs/**", "anon");
            filterChainDefinitionMap.put("/fonts/**", "anon");
            filterChainDefinitionMap.put("/img/**", "anon");
            filterChainDefinitionMap.put("/ajax/**", "anon");
            filterChainDefinitionMap.put("/js/**", "anon");
            filterChainDefinitionMap.put("/ruoyi/**", "anon");
            filterChainDefinitionMap.put("/druid/**", "anon");
            filterChainDefinitionMap.put("/captcha/captchaImage**", "anon");
            // 退出 logout地址,shiro去清除session
            filterChainDefinitionMap.put("/logout", "logout");
            // 不需要拦截的访问
            filterChainDefinitionMap.put("/login", "anon,captchaValidate");
            // 系统权限列表
            // filterChainDefinitionMap.putAll(SpringUtils.getBean(IMenuService.class).selectPermsAll());
    
            Map<String, Filter> filters = new LinkedHashMap<>();
            filters.put("onlineSession", onlineSessionFilter());
            filters.put("syncOnlineSession", syncOnlineSessionFilter());
            filters.put("captchaValidate", captchaValidateFilter());
            // 注销成功,则跳转到指定页面
            filters.put("logout", logoutFilter());
            shiroFilterFactoryBean.setFilters(filters);
    
            // 所有请求需要认证
            filterChainDefinitionMap.put("/**", "user,onlineSession,syncOnlineSession");
            shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
    
            return shiroFilterFactoryBean;
        }
    
        /**
         * 自定义在线用户处理过滤器
         */
        @Bean
        public OnlineSessionFilter onlineSessionFilter()
        {
            OnlineSessionFilter onlineSessionFilter = new OnlineSessionFilter();
            onlineSessionFilter.setLoginUrl(loginUrl);
            return onlineSessionFilter;
        }
    
        /**
         * 自定义在线用户同步过滤器
         */
        @Bean
        public SyncOnlineSessionFilter syncOnlineSessionFilter()
        {
            SyncOnlineSessionFilter syncOnlineSessionFilter = new SyncOnlineSessionFilter();
            return syncOnlineSessionFilter;
        }
    
        /**
         * 自定义验证码过滤器
         */
        @Bean
        public CaptchaValidateFilter captchaValidateFilter()
        {
            CaptchaValidateFilter captchaValidateFilter = new CaptchaValidateFilter();
            captchaValidateFilter.setCaptchaEnabled(captchaEnabled);
            captchaValidateFilter.setCaptchaType(captchaType);
            return captchaValidateFilter;
        }
    
        /**
         * cookie 属性设置
         */
        public SimpleCookie rememberMeCookie()
        {
            SimpleCookie cookie = new SimpleCookie("rememberMe");
            cookie.setDomain(domain);
            cookie.setPath(path);
            cookie.setHttpOnly(httpOnly);
            cookie.setMaxAge(maxAge * 24 * 60 * 60);
            return cookie;
        }
    
        /**
         * 记住我
         */
        public CookieRememberMeManager rememberMeManager()
        {
            CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
            cookieRememberMeManager.setCookie(rememberMeCookie());
            cookieRememberMeManager.setCipherKey(Base64.decode("fCq+/xW488hMTCD+cmJ3aQ=="));
            return cookieRememberMeManager;
        }
    
        /**
         * thymeleaf模板引擎和shiro框架的整合
         */
        @Bean
        public ShiroDialect shiroDialect()
        {
            return new ShiroDialect();
        }
    
        /**
         * 开启Shiro注解通知器
         */
        @Bean
        public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
                @Qualifier("securityManager") SecurityManager securityManager)
        {
            AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
            authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
            return authorizationAttributeSourceAdvisor;
        }
    }
    View Code

    -----datasource包

    DynamicDataSource.java----------动态数据源
    package com.ruoyi.framework.datasource;
    
    import java.util.Map;
    import javax.sql.DataSource;
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    
    /**
     * 动态数据源
     * 
     * @author ruoyi
     */
        public class DynamicDataSource extends AbstractRoutingDataSource
    {
        public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources)
        {
            super.setDefaultTargetDataSource(defaultTargetDataSource);
            super.setTargetDataSources(targetDataSources);
            super.afterPropertiesSet();
        }
    
        @Override
        protected Object determineCurrentLookupKey()
        {
            return DynamicDataSourceContextHolder.getDateSoureType();
        }
    }
    View Code
    DynamicDataSourceContextHolder.java-----------动态数据源切换处理
    package com.ruoyi.framework.datasource;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    /**
     * 数据源切换处理
     * 
     * @author ruoyi
     */
    public class DynamicDataSourceContextHolder
    {
        public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
    
        /**
         * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
         *  所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
         */
        private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
    
        /**
         * 设置数据源的变量
         */
        public static void setDateSoureType(String dsType)
        {
            log.info("切换到{}数据源", dsType);
            CONTEXT_HOLDER.set(dsType);
        }
    
        /**
         * 获得数据源的变量
         */
        public static String getDateSoureType()
        {
            return CONTEXT_HOLDER.get();
        }
    
        /**
         * 清空数据源变量
         */
        public static void clearDateSoureType()
        {
            CONTEXT_HOLDER.remove();
        }
    }
    View Code

    --manager包

    -----factory包

    AsyncFactory.java--------异步工厂(产生任务时用到)
    package com.ruoyi.framework.manager.factory;
    
    import java.util.TimerTask;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import com.ruoyi.common.constant.Constants;
    import com.ruoyi.common.utils.AddressUtils;
    import com.ruoyi.framework.shiro.session.OnlineSession;
    import com.ruoyi.framework.util.LogUtils;
    import com.ruoyi.framework.util.ServletUtils;
    import com.ruoyi.framework.util.ShiroUtils;
    import com.ruoyi.framework.util.SpringUtils;
    import com.ruoyi.system.domain.SysLogininfor;
    import com.ruoyi.system.domain.SysOperLog;
    import com.ruoyi.system.domain.SysUserOnline;
    import com.ruoyi.system.service.ISysOperLogService;
    import com.ruoyi.system.service.impl.SysLogininforServiceImpl;
    import com.ruoyi.system.service.impl.SysUserOnlineServiceImpl;
    import eu.bitwalker.useragentutils.UserAgent;
    
    /**
     * 异步工厂(产生任务用)
     * 
     * @author liuhulu
     *
     */
    public class AsyncFactory
    {
        private static final Logger sys_user_logger = LoggerFactory.getLogger("sys-user");
    
        /**
         * 同步session到数据库
         * 
         * @param session 在线用户会话
         * @return 任务task
         */
        public static TimerTask syncSessionToDb(final OnlineSession session)
        {
            return new TimerTask()
            {
                @Override
                public void run()
                {
                    SysUserOnline online = new SysUserOnline();
                    online.setSessionId(String.valueOf(session.getId()));
                    online.setDeptName(session.getDeptName());
                    online.setLoginName(session.getLoginName());
                    online.setStartTimestamp(session.getStartTimestamp());
                    online.setLastAccessTime(session.getLastAccessTime());
                    online.setExpireTime(session.getTimeout());
                    online.setIpaddr(session.getHost());
                    online.setLoginLocation(AddressUtils.getRealAddressByIP(session.getHost()));
                    online.setBrowser(session.getBrowser());
                    online.setOs(session.getOs());
                    online.setStatus(session.getStatus());
                    SpringUtils.getBean(SysUserOnlineServiceImpl.class).saveOnline(online);
    
                }
            };
        }
    
        /**
         * 操作日志记录
         * 
         * @param operLog 操作日志信息
         * @return 任务task
         */
        public static TimerTask recordOper(final SysOperLog operLog)
        {
            return new TimerTask()
            {
                @Override
                public void run()
                {
                    // 远程查询操作地点
                    operLog.setOperLocation(AddressUtils.getRealAddressByIP(operLog.getOperIp()));
                    SpringUtils.getBean(ISysOperLogService.class).insertOperlog(operLog);
                }
            };
        }
    
        /**
         * 记录登陆信息
         * 
         * @param username 用户名
         * @param status 状态
         * @param message 消息
         * @param args 列表
         * @return 任务task
         */
        public static TimerTask recordLogininfor(final String username, final String status, final String message, final Object... args)
        {
            final UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
            final String ip = ShiroUtils.getIp();
            return new TimerTask()
            {
                @Override
                public void run()
                {
                    StringBuilder s = new StringBuilder();
                    s.append(LogUtils.getBlock(ip));
                    s.append(AddressUtils.getRealAddressByIP(ip));
                    s.append(LogUtils.getBlock(username));
                    s.append(LogUtils.getBlock(status));
                    s.append(LogUtils.getBlock(message));
                    // 打印信息到日志
                    sys_user_logger.info(s.toString(), args);
                    // 获取客户端操作系统
                    String os = userAgent.getOperatingSystem().getName();
                    // 获取客户端浏览器
                    String browser = userAgent.getBrowser().getName();
                    // 封装对象
                    SysLogininfor logininfor = new SysLogininfor();
                    logininfor.setLoginName(username);
                    logininfor.setIpaddr(ip);
                    logininfor.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
                    logininfor.setBrowser(browser);
                    logininfor.setOs(os);
                    logininfor.setMsg(message);
                    // 日志状态
                    if (Constants.LOGIN_SUCCESS.equals(status) || Constants.LOGOUT.equals(status))
                    {
                        logininfor.setStatus(Constants.SUCCESS);
                    }
                    else if (Constants.LOGIN_FAIL.equals(status))
                    {
                        logininfor.setStatus(Constants.FAIL);
                    }
                    // 插入数据
                    SpringUtils.getBean(SysLogininforServiceImpl.class).insertLogininfor(logininfor);
                }
            };
        }
    }
    View Code
    ---------------------
    AsyncManager.java---------异步任务管理器
        package com.ruoyi.framework.manager;
    
        import java.util.TimerTask;
        import java.util.concurrent.ScheduledThreadPoolExecutor;
        import java.util.concurrent.TimeUnit;
    
        /**
         * 异步任务管理器
         *
         * @author liuhulu
         */
            public class AsyncManager
        {
            /**
             * 操作延迟10毫秒
             */
            private final int OPERATE_DELAY_TIME = 10;
    
            /**
             * 异步操作任务调度线程池
             */
            private ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(5);
    
            /**
             * 单例模式
             */
            private static AsyncManager me = new AsyncManager();
    
            public static AsyncManager me()
            {
                return me;
            }
    
            /**
             * 执行任务
             *
             * @param 任务task
             */
            public void execute(TimerTask task)
            {
                executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS);
            }
        }
    View Code

     --shiro包

    -------realm包

    UserRealm.java----------自定义realm处理登陆权限
    package com.ruoyi.framework.shiro.realm;
    
    import java.util.HashSet;
    import java.util.Set;
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.AuthenticationInfo;
    import org.apache.shiro.authc.AuthenticationToken;
    import org.apache.shiro.authc.ExcessiveAttemptsException;
    import org.apache.shiro.authc.IncorrectCredentialsException;
    import org.apache.shiro.authc.LockedAccountException;
    import org.apache.shiro.authc.SimpleAuthenticationInfo;
    import org.apache.shiro.authc.UnknownAccountException;
    import org.apache.shiro.authc.UsernamePasswordToken;
    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.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import com.ruoyi.framework.shiro.service.LoginService;
    import com.ruoyi.framework.util.ShiroUtils;
    import com.ruoyi.framework.web.exception.user.CaptchaException;
    import com.ruoyi.framework.web.exception.user.RoleBlockedException;
    import com.ruoyi.framework.web.exception.user.UserBlockedException;
    import com.ruoyi.framework.web.exception.user.UserNotExistsException;
    import com.ruoyi.framework.web.exception.user.UserPasswordNotMatchException;
    import com.ruoyi.framework.web.exception.user.UserPasswordRetryLimitExceedException;
    import com.ruoyi.system.domain.SysUser;
    import com.ruoyi.system.service.ISysMenuService;
    import com.ruoyi.system.service.ISysRoleService;
    
    /**
     * 自定义Realm 处理登录 权限
     * 
     * @author ruoyi
     */
    public class UserRealm extends AuthorizingRealm
    {
        private static final Logger log = LoggerFactory.getLogger(UserRealm.class);
    
        @Autowired
        private ISysMenuService menuService;
    
        @Autowired
        private ISysRoleService roleService;
    
        @Autowired
        private LoginService loginService;
    
        /**
         * 授权
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0)
        {
            SysUser user = ShiroUtils.getUser();
            // 角色列表
            Set<String> roles = new HashSet<String>();
            // 功能列表
            Set<String> menus = new HashSet<String>();
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            // 管理员拥有所有权限
            if (user.isAdmin())
            {
                info.addRole("admin");
                info.addStringPermission("*:*:*");
            }
            else
            {
                roles = roleService.selectRoleKeys(user.getUserId());
                menus = menuService.selectPermsByUserId(user.getUserId());
                // 角色加入AuthorizationInfo认证对象
                info.setRoles(roles);
                // 权限加入AuthorizationInfo认证对象
                info.setStringPermissions(menus);
            }
            return info;
        }
    
        /**
         * 登录认证
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException
        {
            UsernamePasswordToken upToken = (UsernamePasswordToken) token;
            String username = upToken.getUsername();
            String password = "";
            if (upToken.getPassword() != null)
            {
                password = new String(upToken.getPassword());
            }
    
            SysUser user = null;
            try
            {
                user = loginService.login(username, password);
            }
            catch (CaptchaException e)
            {
                throw new AuthenticationException(e.getMessage(), e);
            }
            catch (UserNotExistsException e)
            {
                throw new UnknownAccountException(e.getMessage(), e);
            }
            catch (UserPasswordNotMatchException e)
            {
                throw new IncorrectCredentialsException(e.getMessage(), e);
            }
            catch (UserPasswordRetryLimitExceedException e)
            {
                throw new ExcessiveAttemptsException(e.getMessage(), e);
            }
            catch (UserBlockedException e)
            {
                throw new LockedAccountException(e.getMessage(), e);
            }
            catch (RoleBlockedException e)
            {
                throw new LockedAccountException(e.getMessage(), e);
            }
            catch (Exception e)
            {
                log.info("对用户[" + username + "]进行登录验证..验证未通过{}", e.getMessage());
                throw new AuthenticationException(e.getMessage(), e);
            }
            SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName());
            return info;
        }
    
        /**
         * 清理缓存权限
         */
        public void clearCachedAuthorizationInfo()
        {
            this.clearCachedAuthorizationInfo(SecurityUtils.getSubject().getPrincipals());
        }
    }
    View Code

    -------service包

    LoginService.java---------登陆校验方法
    package com.ruoyi.framework.shiro.service;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.springframework.util.StringUtils;
    import com.ruoyi.common.constant.Constants;
    import com.ruoyi.common.constant.ShiroConstants;
    import com.ruoyi.common.constant.UserConstants;
    import com.ruoyi.common.enums.UserStatus;
    import com.ruoyi.common.utils.DateUtils;
    import com.ruoyi.framework.manager.AsyncManager;
    import com.ruoyi.framework.manager.factory.AsyncFactory;
    import com.ruoyi.framework.util.MessageUtils;
    import com.ruoyi.framework.util.ServletUtils;
    import com.ruoyi.framework.util.ShiroUtils;
    import com.ruoyi.framework.web.exception.user.CaptchaException;
    import com.ruoyi.framework.web.exception.user.UserBlockedException;
    import com.ruoyi.framework.web.exception.user.UserDeleteException;
    import com.ruoyi.framework.web.exception.user.UserNotExistsException;
    import com.ruoyi.framework.web.exception.user.UserPasswordNotMatchException;
    import com.ruoyi.system.domain.SysUser;
    import com.ruoyi.system.service.ISysUserService;
    
    /**
     * 登录校验方法
     * 
     * @author ruoyi
     */
    @Component  
    public class LoginService
    {
        @Autowired
        private PasswordService passwordService;
    
        @Autowired
        private ISysUserService userService;
    
        /**
         * 登录
         */
        public SysUser login(String username, String password)
        {
            // 验证码校验
            if (!StringUtils.isEmpty(ServletUtils.getRequest().getAttribute(ShiroConstants.CURRENT_CAPTCHA)))
            {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
                throw new CaptchaException();
            }
            // 用户名或密码为空 错误
            if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password))
            {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("not.null")));
                throw new UserNotExistsException();
            }
            // 密码如果不在指定范围内 错误
            if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
                    || password.length() > UserConstants.PASSWORD_MAX_LENGTH)
            {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
                throw new UserPasswordNotMatchException();
            }
    
            // 用户名不在指定范围内 错误
            if (username.length() < UserConstants.USERNAME_MIN_LENGTH
                    || username.length() > UserConstants.USERNAME_MAX_LENGTH)
            {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
                throw new UserPasswordNotMatchException();
            }
    
            // 查询用户信息
            SysUser user = userService.selectUserByLoginName(username);
    
            if (user == null && maybeMobilePhoneNumber(username))
            {
                user = userService.selectUserByPhoneNumber(username);
            }
    
            if (user == null && maybeEmail(username))
            {
                user = userService.selectUserByEmail(username);
            }
    
            if (user == null)
            {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.not.exists")));
                throw new UserNotExistsException();
            }
            
            if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
            {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.delete")));
                throw new UserDeleteException();
            }
            
            if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
            {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.blocked", user.getRemark())));
                throw new UserBlockedException(user.getRemark());
            }
    
            passwordService.validate(user, password);
    
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
            recordLoginInfo(user);
            return user;
        }
    
        private boolean maybeEmail(String username)
        {
            if (!username.matches(UserConstants.EMAIL_PATTERN))
            {
                return false;
            }
            return true;
        }
    
        private boolean maybeMobilePhoneNumber(String username)
        {
            if (!username.matches(UserConstants.MOBILE_PHONE_NUMBER_PATTERN))
            {
                return false;
            }
            return true;
        }
    
        /**
         * 记录登录信息
         */
        public void recordLoginInfo(SysUser user)
        {
            user.setLoginIp(ShiroUtils.getIp());
            user.setLoginDate(DateUtils.getNowDate());
            userService.updateUserInfo(user);
        }
    }
    View Code
    PasswordService.java----------登陆密码方法
    package com.ruoyi.framework.shiro.service;
    
    import java.util.concurrent.atomic.AtomicInteger;
    import javax.annotation.PostConstruct;
    import org.apache.shiro.cache.Cache;
    import org.apache.shiro.cache.CacheManager;
    import org.apache.shiro.crypto.hash.Md5Hash;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    import com.ruoyi.common.constant.Constants;
    import com.ruoyi.framework.manager.AsyncManager;
    import com.ruoyi.framework.manager.factory.AsyncFactory;
    import com.ruoyi.framework.util.MessageUtils;
    import com.ruoyi.framework.web.exception.user.UserPasswordNotMatchException;
    import com.ruoyi.framework.web.exception.user.UserPasswordRetryLimitExceedException;
    import com.ruoyi.system.domain.SysUser;
    
    /**
     * 登录密码方法
     * 
     * @author ruoyi
     */
    @Component
    public class PasswordService
    {
        @Autowired
        private CacheManager cacheManager;
    
        private Cache<String, AtomicInteger> loginRecordCache;
    
        @Value(value = "${user.password.maxRetryCount}")
        private String maxRetryCount;
    
        @PostConstruct
        public void init()
        {
            loginRecordCache = cacheManager.getCache("loginRecordCache");
        }
    
        public void validate(SysUser user, String password)
        {
            String loginName = user.getLoginName();
    
            AtomicInteger retryCount = loginRecordCache.get(loginName);
    
            if (retryCount == null)
            {
                retryCount = new AtomicInteger(0);
                loginRecordCache.put(loginName, retryCount);
            }
            if (retryCount.incrementAndGet() > Integer.valueOf(maxRetryCount).intValue())
            {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(loginName, Constants.LOGIN_FAIL, MessageUtils.message("user.password.retry.limit.exceed", maxRetryCount)));
                throw new UserPasswordRetryLimitExceedException(Integer.valueOf(maxRetryCount).intValue());
            }
    
            if (!matches(user, password))
            {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(loginName, Constants.LOGIN_FAIL, MessageUtils.message("user.password.retry.limit.count", retryCount)));
                loginRecordCache.put(loginName, retryCount);
                throw new UserPasswordNotMatchException();
            }
            else
            {
                clearLoginRecordCache(loginName);
            }
        }
    
        public boolean matches(SysUser user, String newPassword)
        {
            return user.getPassword().equals(encryptPassword(user.getLoginName(), newPassword, user.getSalt()));
        }
    
        public void clearLoginRecordCache(String username)
        {
            loginRecordCache.remove(username);
        }
    
        public String encryptPassword(String username, String password, String salt)
        {
            return new Md5Hash(username + password + salt).toHex().toString();
        }
    
        public static void main(String[] args)
        {
            System.out.println(new PasswordService().encryptPassword("admin", "admin123", "111111"));
            System.out.println(new PasswordService().encryptPassword("ry", "admin123", "222222"));
        }
    }
    View Code

    -------session包

    package com.ruoyi.framework.shiro.session;
    
    import org.apache.shiro.session.mgt.SimpleSession;
    import com.ruoyi.common.enums.OnlineStatus;
    
    /**
     * 在线用户会话属性
     * 
     * @author ruoyi
     */
    public class OnlineSession extends SimpleSession
    {
        private static final long serialVersionUID = 1L;
    
        /** 用户ID */
        private Long userId;
    
        /** 用户名称 */
        private String loginName;
    
        /** 部门名称 */
        private String deptName;
    
        /** 登录IP地址 */
        private String host;
    
        /** 浏览器类型 */
        private String browser;
    
        /** 操作系统 */
        private String os;
    
        /** 在线状态 */
        private OnlineStatus status = OnlineStatus.on_line;
    
        /** 属性是否改变 优化session数据同步 */
        private transient boolean attributeChanged = false;
    
        @Override
        public String getHost()
        {
            return host;
        }
    
        @Override
        public void setHost(String host)
        {
            this.host = host;
        }
    
        public String getBrowser()
        {
            return browser;
        }
    
        public void setBrowser(String browser)
        {
            this.browser = browser;
        }
    
        public String getOs()
        {
            return os;
        }
    
        public void setOs(String os)
        {
            this.os = os;
        }
    
        public Long getUserId()
        {
            return userId;
        }
    
        public void setUserId(Long userId)
        {
            this.userId = userId;
        }
    
        public String getLoginName()
        {
            return loginName;
        }
    
        public void setLoginName(String loginName)
        {
            this.loginName = loginName;
        }
    
        public String getDeptName()
        {
            return deptName;
        }
    
        public void setDeptName(String deptName)
        {
            this.deptName = deptName;
        }
    
        public OnlineStatus getStatus()
        {
            return status;
        }
    
        public void setStatus(OnlineStatus status)
        {
            this.status = status;
        }
    
        public void markAttributeChanged()
        {
            this.attributeChanged = true;
        }
    
        public void resetAttributeChanged()
        {
            this.attributeChanged = false;
        }
    
        public boolean isAttributeChanged()
        {
            return attributeChanged;
        }
    
        @Override
        public void setAttribute(Object key, Object value)
        {
            super.setAttribute(key, value);
        }
    
        @Override
        public Object removeAttribute(Object key)
        {
            return super.removeAttribute(key);
        }
    }
    View Code
    OnlineSessionDAO.java------------在线用户会话属性
    package com.ruoyi.framework.shiro.session;
    
    import java.io.Serializable;
    import java.util.Date;
    import org.apache.shiro.session.Session;
    import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import com.ruoyi.common.enums.OnlineStatus;
    import com.ruoyi.framework.manager.AsyncManager;
    import com.ruoyi.framework.manager.factory.AsyncFactory;
    import com.ruoyi.system.domain.SysUserOnline;
    import com.ruoyi.system.service.impl.SysUserOnlineServiceImpl;
    
    /**
     * 针对自定义的ShiroSession的db操作
     * 
     * @author ruoyi
     */
    public class OnlineSessionDAO extends EnterpriseCacheSessionDAO
    {
        /**
         * 同步session到数据库的周期 单位为毫秒(默认1分钟)
         */
        @Value("${shiro.session.dbSyncPeriod}")
        private int dbSyncPeriod;
    
        /**
         * 上次同步数据库的时间戳
         */
        private static final String LAST_SYNC_DB_TIMESTAMP = OnlineSessionDAO.class.getName() + "LAST_SYNC_DB_TIMESTAMP";
    
        @Autowired
        private SysUserOnlineServiceImpl onlineService;
    
        public OnlineSessionDAO()
        {
            super();
        }
    
        public OnlineSessionDAO(long expireTime)
        {
            super();
        }
    
        /**
         * 根据会话ID获取会话
         *
         * @param sessionId 会话ID
         * @return ShiroSession
         */
        @Override
        protected Session doReadSession(Serializable sessionId)
        {
            SysUserOnline userOnline = onlineService.selectOnlineById(String.valueOf(sessionId));
            if (userOnline == null)
            {
                return null;
            }
            return super.doReadSession(sessionId);
        }
    
        /**
         * 更新会话;如更新会话最后访问时间/停止会话/设置超时时间/设置移除属性等会调用
         */
        public void syncToDb(OnlineSession onlineSession)
        {
            Date lastSyncTimestamp = (Date) onlineSession.getAttribute(LAST_SYNC_DB_TIMESTAMP);
            if (lastSyncTimestamp != null)
            {
                boolean needSync = true;
                long deltaTime = onlineSession.getLastAccessTime().getTime() - lastSyncTimestamp.getTime();
                if (deltaTime < dbSyncPeriod * 60 * 1000)
                {
                    // 时间差不足 无需同步
                    needSync = false;
                }
                boolean isGuest = onlineSession.getUserId() == null || onlineSession.getUserId() == 0L;
    
                // session 数据变更了 同步
                if (isGuest == false && onlineSession.isAttributeChanged())
                {
                    needSync = true;
                }
    
                if (needSync == false)
                {
                    return;
                }
            }
            onlineSession.setAttribute(LAST_SYNC_DB_TIMESTAMP, onlineSession.getLastAccessTime());
            // 更新完后 重置标识
            if (onlineSession.isAttributeChanged())
            {
                onlineSession.resetAttributeChanged();
            }
            AsyncManager.me().execute(AsyncFactory.syncSessionToDb(onlineSession));
        }
    
        /**
         * 当会话过期/停止(如用户退出时)属性等会调用
         */
        @Override
        protected void doDelete(Session session)
        {
            OnlineSession onlineSession = (OnlineSession) session;
            if (null == onlineSession)
            {
                return;
            }
            onlineSession.setStatus(OnlineStatus.off_line);
            onlineService.deleteOnlineById(String.valueOf(onlineSession.getId()));
        }
    }
    View Code
    OnlineSessionFactory.java------------自定义session对话
    package com.ruoyi.framework.shiro.session;
    
    import javax.servlet.http.HttpServletRequest;
    import org.apache.shiro.session.Session;
    import org.apache.shiro.session.mgt.SessionContext;
    import org.apache.shiro.session.mgt.SessionFactory;
    import org.apache.shiro.web.session.mgt.WebSessionContext;
    import org.springframework.stereotype.Component;
    import com.ruoyi.common.utils.IpUtils;
    import com.ruoyi.framework.util.ServletUtils;
    import eu.bitwalker.useragentutils.UserAgent;
    
    /**
     * 自定义sessionFactory会话
     * 
     * @author ruoyi
     */
    @Component
    public class OnlineSessionFactory implements SessionFactory
    {
        @Override
        public Session createSession(SessionContext initData)
        {
            OnlineSession session = new OnlineSession();
            if (initData != null && initData instanceof WebSessionContext)
            {
                WebSessionContext sessionContext = (WebSessionContext) initData;
                HttpServletRequest request = (HttpServletRequest) sessionContext.getServletRequest();
                if (request != null)
                {
                    UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
                    // 获取客户端操作系统
                    String os = userAgent.getOperatingSystem().getName();
                    // 获取客户端浏览器
                    String browser = userAgent.getBrowser().getName();
                    session.setHost(IpUtils.getIpAddr(request));
                    session.setBrowser(browser);
                    session.setOs(os);
                }
            }
            return session;
        }
    }
    View Code

    --------web包

    -----------filter包

    --------------captcha包

    CaptchaValidateFilter.java----------验证码过滤器
    package com.ruoyi.framework.shiro.web.filter.captcha;
    
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    import org.apache.shiro.web.filter.AccessControlFilter;
    import com.google.code.kaptcha.Constants;
    import com.ruoyi.common.constant.ShiroConstants;
    import com.ruoyi.common.utils.StringUtils;
    import com.ruoyi.framework.util.ShiroUtils;
    
    /**
     * 验证码过滤器
     * 
     * @author ruoyi
     */
    public class CaptchaValidateFilter extends AccessControlFilter
    {
        /**
         * 是否开启验证码
         */
        private boolean captchaEnabled = true;
    
        /**
         * 验证码类型
         */
        private String captchaType = "math";
    
        public void setCaptchaEnabled(boolean captchaEnabled)
        {
            this.captchaEnabled = captchaEnabled;
        }
    
        public void setCaptchaType(String captchaType)
        {
            this.captchaType = captchaType;
        }
    
        @Override
        public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception
        {
            request.setAttribute(ShiroConstants.CURRENT_ENABLED, captchaEnabled);
            request.setAttribute(ShiroConstants.CURRENT_TYPE, captchaType);
            return super.onPreHandle(request, response, mappedValue);
        }
    
        @Override
        protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)
                throws Exception
        {
            HttpServletRequest httpServletRequest = (HttpServletRequest) request;
            // 验证码禁用 或不是表单提交 允许访问
            if (captchaEnabled == false || !"post".equals(httpServletRequest.getMethod().toLowerCase()))
            {
                return true;
            }
            return validateResponse(httpServletRequest, httpServletRequest.getParameter(ShiroConstants.CURRENT_VALIDATECODE));
        }
    
        public boolean validateResponse(HttpServletRequest request, String validateCode)
        {
            Object obj = ShiroUtils.getSession().getAttribute(Constants.KAPTCHA_SESSION_KEY);
            String code = String.valueOf(obj != null ? obj : "");
            if (StringUtils.isEmpty(validateCode) || !validateCode.equalsIgnoreCase(code))
            {
                return false;
            }
            return true;
        }
    
        @Override
        protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception
        {
            request.setAttribute(ShiroConstants.CURRENT_CAPTCHA, ShiroConstants.CAPTCHA_ERROR);
            return true;
        }
    }
    View Code

    ---------------online包

    OnlineSessionFilter.java--------自定义访问控制
    package com.ruoyi.framework.shiro.web.filter.online;
    
    import java.io.IOException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import org.apache.shiro.session.Session;
    import org.apache.shiro.subject.Subject;
    import org.apache.shiro.web.filter.AccessControlFilter;
    import org.apache.shiro.web.util.WebUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import com.ruoyi.common.constant.ShiroConstants;
    import com.ruoyi.common.enums.OnlineStatus;
    import com.ruoyi.framework.shiro.session.OnlineSession;
    import com.ruoyi.framework.shiro.session.OnlineSessionDAO;
    import com.ruoyi.framework.util.ShiroUtils;
    import com.ruoyi.system.domain.SysUser;
    
    /**
     * 自定义访问控制
     * 
     * @author ruoyi
     */
    public class OnlineSessionFilter extends AccessControlFilter
    {
        /**
         * 强制退出后重定向的地址
         */
        @Value("${shiro.user.loginUrl}")
        private String loginUrl;
    
        @Autowired
        private OnlineSessionDAO onlineSessionDAO;
    
        /**
         * 表示是否允许访问;mappedValue就是[urls]配置中拦截器参数部分,如果允许访问返回true,否则false;
         */
        @Override
        protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)
                throws Exception
        {
            Subject subject = getSubject(request, response);
            if (subject == null || subject.getSession() == null)
            {
                return true;
            }
            Session session = onlineSessionDAO.readSession(subject.getSession().getId());
            if (session != null && session instanceof OnlineSession)
            {
                OnlineSession onlineSession = (OnlineSession) session;
                request.setAttribute(ShiroConstants.ONLINE_SESSION, onlineSession);
                // 把user对象设置进去
                boolean isGuest = onlineSession.getUserId() == null || onlineSession.getUserId() == 0L;
                if (isGuest == true)
                {
                    SysUser user = ShiroUtils.getUser();
                    if (user != null)
                    {
                        onlineSession.setUserId(user.getUserId());
                        onlineSession.setLoginName(user.getLoginName());
                        onlineSession.setDeptName(user.getDept().getDeptName());
                        onlineSession.markAttributeChanged();
                    }
                }
    
                if (onlineSession.getStatus() == OnlineStatus.off_line)
                {
                    return false;
                }
            }
            return true;
        }
    
        /**
         * 表示当访问拒绝时是否已经处理了;如果返回true表示需要继续处理;如果返回false表示该拦截器实例已经处理了,将直接返回即可。
         */
        @Override
        protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception
        {
            Subject subject = getSubject(request, response);
            if (subject != null)
            {
                subject.logout();
            }
            saveRequestAndRedirectToLogin(request, response);
            return true;
        }
    
        // 跳转到登录页
        @Override
        protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException
        {
            WebUtils.issueRedirect(request, response, loginUrl);
        }
    }
    View Code

    ---------------sysnc包

    SyncOnlineSessionFilter.java-------同步session数据到数据库
    package com.ruoyi.framework.shiro.web.filter.sync;
    
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import org.apache.shiro.web.filter.PathMatchingFilter;
    import org.springframework.beans.factory.annotation.Autowired;
    import com.ruoyi.common.constant.ShiroConstants;
    import com.ruoyi.framework.shiro.session.OnlineSession;
    import com.ruoyi.framework.shiro.session.OnlineSessionDAO;
    
    /**
     * 同步Session数据到Db
     * 
     * @author ruoyi
     */
    public class SyncOnlineSessionFilter extends PathMatchingFilter
    {
        @Autowired
        private OnlineSessionDAO onlineSessionDAO;
    
        /**
         * 同步会话数据到DB 一次请求最多同步一次 防止过多处理 需要放到Shiro过滤器之前
         *
         * @param request
         * @param response
         * @return
         * @throws Exception
         */
        @Override
        protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception
        {
            OnlineSession session = (OnlineSession) request.getAttribute(ShiroConstants.ONLINE_SESSION);
            // 如果session stop了 也不同步
            // session停止时间,如果stopTimestamp不为null,则代表已停止
            if (session != null && session.getUserId() != null && session.getStopTimestamp() == null)
            {
                onlineSessionDAO.syncToDb(session);
            }
            return true;
        }
    }
    View Code
    -----------------------
    LogoutFilter.java--------退出过滤器
    package com.ruoyi.framework.shiro.web.filter;
    
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import org.apache.shiro.session.SessionException;
    import org.apache.shiro.subject.Subject;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import com.ruoyi.common.constant.Constants;
    import com.ruoyi.common.utils.StringUtils;
    import com.ruoyi.framework.manager.AsyncManager;
    import com.ruoyi.framework.manager.factory.AsyncFactory;
    import com.ruoyi.framework.util.MessageUtils;
    import com.ruoyi.framework.util.ShiroUtils;
    import com.ruoyi.system.domain.SysUser;
    
    /**
     * 退出过滤器
     * 
     * @author ruoyi
     */
    public class LogoutFilter extends org.apache.shiro.web.filter.authc.LogoutFilter
    {
        private static final Logger log = LoggerFactory.getLogger(LogoutFilter.class);
    
        /**
         * 退出后重定向的地址
         */
        private String loginUrl;
    
        public String getLoginUrl()
        {
            return loginUrl;
        }
    
        public void setLoginUrl(String loginUrl)
        {
            this.loginUrl = loginUrl;
        }
    
        @Override
        protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception
        {
            try
            {
                Subject subject = getSubject(request, response);
                String redirectUrl = getRedirectUrl(request, response, subject);
                try
                {
                    SysUser user = ShiroUtils.getUser();
                    if (StringUtils.isNotNull(user))
                    {
                        String loginName = user.getLoginName();
                        // 记录用户退出日志
                        AsyncManager.me().execute(AsyncFactory.recordLogininfor(loginName, Constants.LOGOUT, MessageUtils.message("user.logout.success")));
                    }
                    // 退出登录
                    subject.logout();
                }
                catch (SessionException ise)
                {
                    log.error("logout fail.", ise);
                }
                issueRedirect(request, response, redirectUrl);
            }
            catch (Exception e)
            {
                log.error("Encountered session exception during logout.  This can generally safely be ignored.", e);
            }
            return false;
        }
    
        /**
         * 退出跳转URL
         */
        @Override
        protected String getRedirectUrl(ServletRequest request, ServletResponse response, Subject subject)
        {
            String url = getLoginUrl();
            if (StringUtils.isNotEmpty(url))
            {
                return url;
            }
            return super.getRedirectUrl(request, response, subject);
        }
    }
    View Code

    -----------session包

    OnlineWebSessionManager.java------标识会话修改
    package com.ruoyi.framework.shiro.web.session;
    
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.Date;
    import java.util.List;
    import org.apache.commons.lang3.time.DateUtils;
    import org.apache.shiro.session.ExpiredSessionException;
    import org.apache.shiro.session.InvalidSessionException;
    import org.apache.shiro.session.Session;
    import org.apache.shiro.session.mgt.DefaultSessionKey;
    import org.apache.shiro.session.mgt.SessionKey;
    import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import com.ruoyi.common.constant.ShiroConstants;
    import com.ruoyi.framework.shiro.session.OnlineSession;
    import com.ruoyi.framework.util.SpringUtils;
    import com.ruoyi.system.domain.SysUserOnline;
    import com.ruoyi.system.service.impl.SysUserOnlineServiceImpl;
    
    /**
     * 主要是在此如果会话的属性修改了 就标识下其修改了 然后方便 OnlineSessionDao同步
     * 
     * @author ruoyi
     */
    public class OnlineWebSessionManager extends DefaultWebSessionManager
    {
        private static final Logger log = LoggerFactory.getLogger(OnlineWebSessionManager.class);
        
        @Override
        public void setAttribute(SessionKey sessionKey, Object attributeKey, Object value) throws InvalidSessionException
        {
            super.setAttribute(sessionKey, attributeKey, value);
            if (value != null && needMarkAttributeChanged(attributeKey))
            {
                OnlineSession s = (OnlineSession) doGetSession(sessionKey);
                s.markAttributeChanged();
            }
        }
    
        private boolean needMarkAttributeChanged(Object attributeKey)
        {
            if (attributeKey == null)
            {
                return false;
            }
            String attributeKeyStr = attributeKey.toString();
            // 优化 flash属性没必要持久化
            if (attributeKeyStr.startsWith("org.springframework"))
            {
                return false;
            }
            if (attributeKeyStr.startsWith("javax.servlet"))
            {
                return false;
            }
            if (attributeKeyStr.equals(ShiroConstants.CURRENT_USERNAME))
            {
                return false;
            }
            return true;
        }
    
        @Override
        public Object removeAttribute(SessionKey sessionKey, Object attributeKey) throws InvalidSessionException
        {
            Object removed = super.removeAttribute(sessionKey, attributeKey);
            if (removed != null)
            {
                OnlineSession s = (OnlineSession) doGetSession(sessionKey);
                s.markAttributeChanged();
            }
    
            return removed;
        }
    
        /**
         * 验证session是否有效 用于删除过期session
         */
        @Override
        public void validateSessions()
        {
            if (log.isInfoEnabled())
            {
                log.info("invalidation sessions...");
            }
    
            int invalidCount = 0;
    
            int timeout = (int) this.getGlobalSessionTimeout();
            Date expiredDate = DateUtils.addMilliseconds(new Date(), 0 - timeout);
            SysUserOnlineServiceImpl userOnlineService = SpringUtils.getBean(SysUserOnlineServiceImpl.class);
            List<SysUserOnline> userOnlineList = userOnlineService.selectOnlineByExpired(expiredDate);
            // 批量过期删除
            List<String> needOfflineIdList = new ArrayList<String>();
            for (SysUserOnline userOnline : userOnlineList)
            {
                try
                {
                    SessionKey key = new DefaultSessionKey(userOnline.getSessionId());
                    Session session = retrieveSession(key);
                    if (session != null)
                    {
                        throw new InvalidSessionException();
                    }
                }
                catch (InvalidSessionException e)
                {
                    if (log.isDebugEnabled())
                    {
                        boolean expired = (e instanceof ExpiredSessionException);
                        String msg = "Invalidated session with id [" + userOnline.getSessionId() + "]"
                                + (expired ? " (expired)" : " (stopped)");
                        log.debug(msg);
                    }
                    invalidCount++;
                    needOfflineIdList.add(userOnline.getSessionId());
                }
    
            }
            if (needOfflineIdList.size() > 0)
            {
                try
                {
                    userOnlineService.batchDeleteOnline(needOfflineIdList);
                }
                catch (Exception e)
                {
                    log.error("batch delete db session error.", e);
                }
            }
    
            if (log.isInfoEnabled())
            {
                String msg = "Finished invalidation session.";
                if (invalidCount > 0)
                {
                    msg += " [" + invalidCount + "] sessions were stopped.";
                }
                else
                {
                    msg += " No sessions were stopped.";
                }
                log.info(msg);
            }
    
        }
    
        @Override
        protected Collection<Session> getActiveSessions()
        {
            throw new UnsupportedOperationException("getActiveSessions method not supported");
        }
    }
    View Code
    SpringSessionValidationScheduler.java------自定义任务调度器
    package com.ruoyi.framework.shiro.web.session;
    
    import java.util.concurrent.Executors;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.TimeUnit;
    import org.apache.shiro.session.mgt.DefaultSessionManager;
    import org.apache.shiro.session.mgt.SessionValidationScheduler;
    import org.apache.shiro.session.mgt.ValidatingSessionManager;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    /**
     * 自定义任务调度器完成
     * 
     * @author ruoyi
     */
    public class SpringSessionValidationScheduler implements SessionValidationScheduler
    {
        private static final Logger log = LoggerFactory.getLogger(SpringSessionValidationScheduler.class);
        
        public static final long DEFAULT_SESSION_VALIDATION_INTERVAL = DefaultSessionManager.DEFAULT_SESSION_VALIDATION_INTERVAL;
    
        /**
         * 定时器,用于处理超时的挂起请求,也用于连接断开时的重连。
         */
        private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
    
        private volatile boolean enabled = false;
    
        /**
         * The session manager used to validate sessions.
         */
        private ValidatingSessionManager sessionManager;
    
        /**
         * The session validation interval in milliseconds.
         */
        private long sessionValidationInterval = DEFAULT_SESSION_VALIDATION_INTERVAL;
    
        /**
         * Default constructor.
         */
        public SpringSessionValidationScheduler()
        {
        }
    
        /**
         * Constructor that specifies the session manager that should be used for validating sessions.
         *
         * @param sessionManager the <tt>SessionManager</tt> that should be used to validate sessions.
         */
        public SpringSessionValidationScheduler(ValidatingSessionManager sessionManager)
        {
            this.sessionManager = sessionManager;
        }
    
        public void setSessionManager(ValidatingSessionManager sessionManager)
        {
            this.sessionManager = sessionManager;
        }
    
        @Override
        public boolean isEnabled()
        {
            return this.enabled;
        }
    
        /**
         * Specifies how frequently (in milliseconds) this Scheduler will call the
         * {@link org.apache.shiro.session.mgt.ValidatingSessionManager#validateSessions()
         * ValidatingSessionManager#validateSessions()} method.
         *
         * <p>
         * Unless this method is called, the default value is {@link #DEFAULT_SESSION_VALIDATION_INTERVAL}.
         *
         * @param sessionValidationInterval
         */
        public void setSessionValidationInterval(long sessionValidationInterval)
        {
            this.sessionValidationInterval = sessionValidationInterval;
        }
    
        /**
         * Starts session validation by creating a spring PeriodicTrigger.
         */
        @Override
        public void enableSessionValidation()
        {
    
            enabled = true;
    
            if (log.isDebugEnabled())
            {
                log.debug("Scheduling session validation job using Spring Scheduler with "
                        + "session validation interval of [" + sessionValidationInterval + "]ms...");
            }
    
            try
            {
                executorService.scheduleAtFixedRate(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        if (enabled)
                        {
                            sessionManager.validateSessions();
                        }
                    }
                }, 1000, sessionValidationInterval, TimeUnit.MILLISECONDS);
    
                this.enabled = true;
    
                if (log.isDebugEnabled())
                {
                    log.debug("Session validation job successfully scheduled with Spring Scheduler.");
                }
    
            }
            catch (Exception e)
            {
                if (log.isErrorEnabled())
                {
                    log.error("Error starting the Spring Scheduler session validation job.  Session validation may not occur.", e);
                }
            }
        }
    
        @Override
        public void disableSessionValidation()
        {
            if (log.isDebugEnabled())
            {
                log.debug("Stopping Spring Scheduler session validation job...");
            }
    
            this.enabled = false;
        }
    }
    View Code

    --web包

    ------exception包

    --------base包

    BaseException.java------基础异常
    package com.ruoyi.framework.web.exception.base;
    
    import com.ruoyi.common.utils.StringUtils;
    import com.ruoyi.framework.util.MessageUtils;
    
    /**
     * 基础异常
     * 
     * @author ruoyi
     */
        public class BaseException extends RuntimeException
    {
        private static final long serialVersionUID = 1L;
    
        /**
         * 所属模块
         */
        private String module;
    
        /**
         * 错误码
         */
        private String code;
    
        /**
         * 错误码对应的参数
         */
        private Object[] args;
    
        /**
         * 错误消息
         */
        private String defaultMessage;
    
        public BaseException(String module, String code, Object[] args, String defaultMessage)
        {
            this.module = module;
            this.code = code;
            this.args = args;
            this.defaultMessage = defaultMessage;
        }
    
        public BaseException(String module, String code, Object[] args)
        {
            this(module, code, args, null);
        }
    
        public BaseException(String module, String defaultMessage)
        {
            this(module, null, null, defaultMessage);
        }
    
        public BaseException(String code, Object[] args)
        {
            this(null, code, args, null);
        }
    
        public BaseException(String defaultMessage)
        {
            this(null, null, null, defaultMessage);
        }
    
        @Override
        public String getMessage()
        {
            String message = null;
            if (!StringUtils.isEmpty(code))
            {
                message = MessageUtils.message(code, args);
            }
            if (message == null)
            {
                message = defaultMessage;
            }
            return message;
        }
    
        public String getModule()
        {
            return module;
        }
    
        public String getCode()
        {
            return code;
        }
    
        public Object[] getArgs()
        {
            return args;
        }
    
        public String getDefaultMessage()
        {
            return defaultMessage;
        }
    }
    View Code

    --------user包

    CaptchaException.java------验证码错误异常类
    package com.ruoyi.framework.web.exception.user;
    
    /**
     * 验证码错误异常类
     * 
     * @author ruoyi
     */
    public class CaptchaException extends UserException
    {
        private static final long serialVersionUID = 1L;
    
        public CaptchaException()
        {
            super("user.jcaptcha.error", null);
        }
    }
    View Code
    RoleBlockedException.java-------角色锁定异常类
    package com.ruoyi.framework.web.exception.user;
    
    /**
     * 角色锁定异常类
     * 
     * @author ruoyi
     */
    public class RoleBlockedException extends UserException
    {
        private static final long serialVersionUID = 1L;
    
        public RoleBlockedException(String reason)
        {
            super("role.blocked", new Object[] { reason });
        }
    }
    View Code
    UserBlockedException.java-------用户锁定异常类
    package com.ruoyi.framework.web.exception.user;
    
    /**
     * 用户锁定异常类
     * 
     * @author ruoyi
     */
    public class UserBlockedException extends UserException
    {
        private static final long serialVersionUID = 1L;
    
        public UserBlockedException(String reason)
        {
            super("user.blocked", new Object[] { reason });
        }
    }
    View Code
    UserDeleteException.java--------用户账号删除异常
    package com.ruoyi.framework.web.exception.user;
    
    /**
     * 用户账号已被删除
     * 
     * @author ruoyi
     */
    public class UserDeleteException extends UserException
    {
        private static final long serialVersionUID = 1L;
    
        public UserDeleteException()
        {
            super("user.password.delete", null);
        }
    }
    View Code
    UserException.java-------用户信息异常
    package com.ruoyi.framework.web.exception.user;
    
    import com.ruoyi.framework.web.exception.base.BaseException;
    
    /**
     * 用户信息异常类
     * 
     * @author ruoyi
     */
    public class UserException extends BaseException
    {
        private static final long serialVersionUID = 1L;
    
        public UserException(String code, Object[] args)
        {
            super("user", code, args, null);
        }
    }
    View Code
    UserNotExistsException.java-------用户不存在异常类
    package com.ruoyi.framework.web.exception.user;
    
    /**
     * 用户不存在异常类
     * 
     * @author ruoyi
     */
    public class UserNotExistsException extends UserException
    {
        private static final long serialVersionUID = 1L;
    
        public UserNotExistsException()
        {
            super("user.not.exists", null);
        }
    }
    View Code
    UserPasswordNotMatchException.java-------用户密码不正确或不规范异常
    package com.ruoyi.framework.web.exception.user;
    
    /**
     * 用户密码不正确或不符合规范异常类
     * 
     * @author ruoyi
     */
    public class UserPasswordNotMatchException extends UserException
    {
        private static final long serialVersionUID = 1L;
    
        public UserPasswordNotMatchException()
        {
            super("user.password.not.match", null);
        }
    }
    View Code
    UserPasswordRetryLimitCountException.java--------用户错误记录异常
    package com.ruoyi.framework.web.exception.user;
    
    /**
     * 用户错误记数异常类
     * 
     * @author ruoyi
     */
    public class UserPasswordRetryLimitCountException extends UserException
    {
        private static final long serialVersionUID = 1L;
    
        public UserPasswordRetryLimitCountException(int retryLimitCount)
        {
            super("user.password.retry.limit.count", new Object[] { retryLimitCount });
        }
    }
    View Code
    UserPasswordRetryLimitExceedException.java-------用户错误最大次数异常
    package com.ruoyi.framework.web.exception.user;
    
    /**
     * 用户错误最大次数异常类
     * 
     * @author ruoyi
     */
    public class UserPasswordRetryLimitExceedException extends UserException
    {
        private static final long serialVersionUID = 1L;
    
        public UserPasswordRetryLimitExceedException(int retryLimitCount)
        {
            super("user.password.retry.limit.exceed", new Object[] { retryLimitCount });
        }
    }
    View Code

    -----------------

    DefaultExceptionHandler.java---------自定义异常处理器
    package com.ruoyi.framework.web.exception;
    
    import org.apache.shiro.authz.AuthorizationException;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.web.HttpRequestMethodNotSupportedException;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.RestControllerAdvice;
    import com.ruoyi.common.base.AjaxResult;
    import com.ruoyi.common.exception.DemoModeException;
    import com.ruoyi.framework.util.PermissionUtils;
    
    /**
     * 自定义异常处理器
     * 
     * @author ruoyi
     */
    @RestControllerAdvice
    public class DefaultExceptionHandler
    {
        private static final Logger log = LoggerFactory.getLogger(DefaultExceptionHandler.class);
        
        /**
         * 权限校验失败
         */
        @ExceptionHandler(AuthorizationException.class)
        public AjaxResult handleAuthorizationException(AuthorizationException e)
        {
            log.error(e.getMessage(), e);
            return AjaxResult.error(PermissionUtils.getMsg(e.getMessage()));
        }
    
        /**
         * 请求方式不支持
         */
        @ExceptionHandler({ HttpRequestMethodNotSupportedException.class })
        public AjaxResult handleException(HttpRequestMethodNotSupportedException e)
        {
            log.error(e.getMessage(), e);
            return AjaxResult.error("不支持' " + e.getMethod() + "'请求");
        }
    
        /**
         * 拦截未知的运行时异常
         */
        @ExceptionHandler(RuntimeException.class)
        public AjaxResult notFount(RuntimeException e)
        {
            log.error("运行时异常:", e);
            return AjaxResult.error("运行时异常:" + e.getMessage());
        }
    
        /**
         * 系统异常
         */
        @ExceptionHandler(Exception.class)
        public AjaxResult handleException(Exception e)
        {
            log.error(e.getMessage(), e);
            return AjaxResult.error("服务器错误,请联系管理员");
        }
        
        /**
         * 演示模式异常
         */
        @ExceptionHandler(DemoModeException.class)
        public AjaxResult demoModeException(DemoModeException e)
        {
            return AjaxResult.error("演示模式,不允许操作");
        }
    }
    View Code

    ------page包

    PageDomain.java-------分页数据
    package com.ruoyi.framework.web.page;
    
    import com.ruoyi.common.utils.StringUtils;
    
    /**
     * 分页数据
     * 
     * @author ruoyi
     */
    public class PageDomain
    {
        /** 当前记录起始索引 */
        private Integer pageNum;
    
        /** 每页显示记录数 */
        private Integer pageSize;
    
        /** 排序列 */
        private String orderByColumn;
        /** 排序的方向 "desc" 或者 "asc". */
    
        private String isAsc;
    
        public String getOrderBy()
        {
            if (StringUtils.isEmpty(orderByColumn))
            {
                return "";
            }
            return StringUtils.toUnderScoreCase(orderByColumn) + " " + isAsc;
        }
    
        public Integer getPageNum()
        {
            return pageNum;
        }
    
        public void setPageNum(Integer pageNum)
        {
            this.pageNum = pageNum;
        }
    
        public Integer getPageSize()
        {
            return pageSize;
        }
    
        public void setPageSize(Integer pageSize)
        {
            this.pageSize = pageSize;
        }
    
        public String getOrderByColumn()
        {
            return orderByColumn;
        }
    
        public void setOrderByColumn(String orderByColumn)
        {
            this.orderByColumn = orderByColumn;
        }
    
        public String getIsAsc()
        {
            return isAsc;
        }
    
        public void setIsAsc(String isAsc)
        {
            this.isAsc = isAsc;
        }
    }
    View Code
    TableDataInfo.java------表格分页数据对象
    package com.ruoyi.framework.web.page;
    
    import java.io.Serializable;
    import java.util.List;
    
    /**
     * 表格分页数据对象
     * 
     * @author ruoyi
     */
    public class TableDataInfo implements Serializable
    {
        private static final long serialVersionUID = 1L;
        /** 总记录数 */
        private long total;
        /** 列表数据 */
        private List<?> rows;
        /** 消息状态码 */
        private int code;
    
        /**
         * 表格数据对象
         */
        public TableDataInfo()
        {
        }
    
        /**
         * 分页
         * 
         * @param list 列表数据
         * @param total 总记录数
         */
        public TableDataInfo(List<?> list, int total)
        {
            this.rows = list;
            this.total = total;
        }
    
        public long getTotal()
        {
            return total;
        }
    
        public void setTotal(long total)
        {
            this.total = total;
        }
    
        public List<?> getRows()
        {
            return rows;
        }
    
        public void setRows(List<?> rows)
        {
            this.rows = rows;
        }
    
        public int getCode()
        {
            return code;
        }
    
        public void setCode(int code)
        {
            this.code = code;
        }
    }
    View Code
    TableSupport.java-------表格数据处理
    package com.ruoyi.framework.web.page;
    
    import com.ruoyi.common.constant.Constants;
    import com.ruoyi.framework.util.ServletUtils;
    
    /**
     * 表格数据处理
     * 
     * @author ruoyi
     */
    public class TableSupport
    {
        /**
         * 封装分页对象
         */
        public static PageDomain getPageDomain()
        {
            PageDomain pageDomain = new PageDomain();
            pageDomain.setPageNum(ServletUtils.getParameterToInt(Constants.PAGE_NUM));
            pageDomain.setPageSize(ServletUtils.getParameterToInt(Constants.PAGE_SIZE));
            pageDomain.setOrderByColumn(ServletUtils.getParameter(Constants.ORDER_BY_COLUMN));
            pageDomain.setIsAsc(ServletUtils.getParameter(Constants.IS_ASC));
            return pageDomain;
        }
    
        public static PageDomain buildPageRequest()
        {
            return getPageDomain();
        }
    }
    View Code

    ------service包

    ConfigService.java------html调用 thymeleaf 实现参数管理
    package com.ruoyi.framework.web.service;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import com.ruoyi.system.service.ISysConfigService;
    
    /**
     * RuoYi首创 html调用 thymeleaf 实现参数管理
     * 
     * @author ruoyi
     */
    @Service("config")
    public class ConfigService
    {
        @Autowired
        private ISysConfigService configService;
    
        /**
         * 根据键名查询参数配置信息
         * 
         * @param configName 参数名称
         * @return 参数键值
         */
        public String getKey(String configKey)
        {
            return configService.selectConfigByKey(configKey);
        }
    }
    View Code
    DictService.java----------html调用 thymeleaf 实现字典读取
    package com.ruoyi.framework.web.service;
    
    import java.util.List;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import com.ruoyi.system.domain.SysDictData;
    import com.ruoyi.system.service.ISysDictDataService;
    
    /**
     * RuoYi首创 html调用 thymeleaf 实现字典读取
     * 
     * @author ruoyi
     */
    @Service("dict")
    public class DictService
    {
        @Autowired
        private ISysDictDataService dictDataService;
    
        /**
         * 根据字典类型查询字典数据信息
         * 
         * @param dictType 字典类型
         * @return 参数键值
         */
        public List<SysDictData> getType(String dictType)
        {
            return dictDataService.selectDictDataByType(dictType);
        }
    
        /**
         * 根据字典类型和字典键值查询字典数据信息
         * 
         * @param dictType 字典类型
         * @param dictValue 字典键值
         * @return 字典标签
         */
        public String getLabel(String dictType, String dictValue)
        {
            return dictDataService.selectDictLabel(dictType, dictValue);
        }
    }
    View Code
    PermissionService.java------------js调用 thymeleaf 实现按钮权限可见性
    package com.ruoyi.framework.web.service;
    
    import org.apache.shiro.SecurityUtils;
    import org.springframework.stereotype.Service;
    
    /**
     * RuoYi首创 js调用 thymeleaf 实现按钮权限可见性
     * 
     * @author ruoyi
     */
    @Service("permission")
    public class PermissionService
    {
        public String hasPermi(String permission)
        {
            return isPermittedOperator(permission) ? "" : "hidden";
        }
    
        private boolean isPermittedOperator(String permission)
        {
            return SecurityUtils.getSubject().isPermitted(permission);
        }
    }
    View Code
     
  • 相关阅读:
    (转)c++中NULL与nullptr的区别
    剑指offer:面试题1:赋值运算符函数
    剑指offer开篇
    立flag
    牛客网程序员面试金典:1.2——原串翻转(java实现)
    牛客网程序员面试金典:1.1确定字符互异(java实现)
    剑指Offer:面试题34——丑数(java实现)
    剑指Offer:面试题33——把数组排成最小的数(java实现)(未完待续)
    剑指Offer:面试题32——从1到n整数中1出现的次数(java实现)
    8种网站防盗链手段
  • 原文地址:https://www.cnblogs.com/zhzJAVA11/p/9994654.html
Copyright © 2020-2023  润新知