删除文件 ruoyi-framework
This commit is contained in:
parent
5579f16a22
commit
653257fd4a
|
|
@ -1,82 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<parent>
|
|
||||||
<artifactId>ruoyi</artifactId>
|
|
||||||
<groupId>com.ruoyi</groupId>
|
|
||||||
<version>4.7.6</version>
|
|
||||||
</parent>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<artifactId>ruoyi-framework</artifactId>
|
|
||||||
|
|
||||||
<description>
|
|
||||||
framework框架核心
|
|
||||||
</description>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
|
|
||||||
<!-- SpringBoot Web容器 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-web</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- SpringBoot 拦截器 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-aop</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- 阿里数据库连接池 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.alibaba</groupId>
|
|
||||||
<artifactId>druid-spring-boot-starter</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- 验证码 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>pro.fessional</groupId>
|
|
||||||
<artifactId>kaptcha</artifactId>
|
|
||||||
<exclusions>
|
|
||||||
<exclusion>
|
|
||||||
<artifactId>javax.servlet-api</artifactId>
|
|
||||||
<groupId>javax.servlet</groupId>
|
|
||||||
</exclusion>
|
|
||||||
</exclusions>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- Shiro使用Spring框架 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.shiro</groupId>
|
|
||||||
<artifactId>shiro-spring</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- thymeleaf模板引擎和shiro框架的整合 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.github.theborakompanioni</groupId>
|
|
||||||
<artifactId>thymeleaf-extras-shiro</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- 解析客户端操作系统、浏览器等 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>eu.bitwalker</groupId>
|
|
||||||
<artifactId>UserAgentUtils</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- 获取系统信息 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.github.oshi</groupId>
|
|
||||||
<artifactId>oshi-core</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- 系统模块-->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.ruoyi</groupId>
|
|
||||||
<artifactId>ruoyi-system</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
</project>
|
|
||||||
|
|
@ -1,165 +0,0 @@
|
||||||
package com.ruoyi.framework.aspectj;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import org.aspectj.lang.JoinPoint;
|
|
||||||
import org.aspectj.lang.annotation.Aspect;
|
|
||||||
import org.aspectj.lang.annotation.Before;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import com.ruoyi.common.annotation.DataScope;
|
|
||||||
import com.ruoyi.common.core.context.PermissionContextHolder;
|
|
||||||
import com.ruoyi.common.core.domain.BaseEntity;
|
|
||||||
import com.ruoyi.common.core.domain.entity.SysRole;
|
|
||||||
import com.ruoyi.common.core.domain.entity.SysUser;
|
|
||||||
import com.ruoyi.common.core.text.Convert;
|
|
||||||
import com.ruoyi.common.utils.ShiroUtils;
|
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 数据过滤处理
|
|
||||||
*
|
|
||||||
* @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_DEPT = "3";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 部门及以下数据权限
|
|
||||||
*/
|
|
||||||
public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 仅本人数据权限
|
|
||||||
*/
|
|
||||||
public static final String DATA_SCOPE_SELF = "5";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 数据权限过滤关键字
|
|
||||||
*/
|
|
||||||
public static final String DATA_SCOPE = "dataScope";
|
|
||||||
|
|
||||||
@Before("@annotation(controllerDataScope)")
|
|
||||||
public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable
|
|
||||||
{
|
|
||||||
clearDataScope(point);
|
|
||||||
handleDataScope(point, controllerDataScope);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope)
|
|
||||||
{
|
|
||||||
// 获取当前的用户
|
|
||||||
SysUser currentUser = ShiroUtils.getSysUser();
|
|
||||||
if (currentUser != null)
|
|
||||||
{
|
|
||||||
// 如果是超级管理员,则不过滤数据
|
|
||||||
if (!currentUser.isAdmin())
|
|
||||||
{
|
|
||||||
String permission = StringUtils.defaultIfEmpty(controllerDataScope.permission(), PermissionContextHolder.getContext());
|
|
||||||
dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),
|
|
||||||
controllerDataScope.userAlias(), permission);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 数据范围过滤
|
|
||||||
*
|
|
||||||
* @param joinPoint 切点
|
|
||||||
* @param user 用户
|
|
||||||
* @param deptAlias 部门别名
|
|
||||||
* @param userAlias 用户别名
|
|
||||||
* @param permission 权限字符
|
|
||||||
*/
|
|
||||||
public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias, String permission)
|
|
||||||
{
|
|
||||||
StringBuilder sqlString = new StringBuilder();
|
|
||||||
List<String> conditions = new ArrayList<String>();
|
|
||||||
|
|
||||||
for (SysRole role : user.getRoles())
|
|
||||||
{
|
|
||||||
String dataScope = role.getDataScope();
|
|
||||||
if (!DATA_SCOPE_CUSTOM.equals(dataScope) && conditions.contains(dataScope))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (StringUtils.isNotEmpty(permission) && StringUtils.isNotEmpty(role.getPermissions())
|
|
||||||
&& !StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission)))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
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 = {} ) ", deptAlias,
|
|
||||||
role.getRoleId()));
|
|
||||||
}
|
|
||||||
else if (DATA_SCOPE_DEPT.equals(dataScope))
|
|
||||||
{
|
|
||||||
sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));
|
|
||||||
}
|
|
||||||
else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope))
|
|
||||||
{
|
|
||||||
sqlString.append(StringUtils.format(
|
|
||||||
" OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )",
|
|
||||||
deptAlias, user.getDeptId(), user.getDeptId()));
|
|
||||||
}
|
|
||||||
else if (DATA_SCOPE_SELF.equals(dataScope))
|
|
||||||
{
|
|
||||||
if (StringUtils.isNotBlank(userAlias))
|
|
||||||
{
|
|
||||||
sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 数据权限为仅本人且没有userAlias别名不查询任何数据
|
|
||||||
sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
conditions.add(dataScope);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (StringUtils.isNotBlank(sqlString.toString()))
|
|
||||||
{
|
|
||||||
Object params = joinPoint.getArgs()[0];
|
|
||||||
if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
|
|
||||||
{
|
|
||||||
BaseEntity baseEntity = (BaseEntity) params;
|
|
||||||
baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 拼接权限sql前先清空params.dataScope参数防止注入
|
|
||||||
*/
|
|
||||||
private void clearDataScope(final JoinPoint joinPoint)
|
|
||||||
{
|
|
||||||
Object params = joinPoint.getArgs()[0];
|
|
||||||
if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
|
|
||||||
{
|
|
||||||
BaseEntity baseEntity = (BaseEntity) params;
|
|
||||||
baseEntity.getParams().put(DATA_SCOPE, "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
package com.ruoyi.framework.aspectj;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
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.AnnotationUtils;
|
|
||||||
import org.springframework.core.annotation.Order;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import com.ruoyi.common.annotation.DataSource;
|
|
||||||
import com.ruoyi.common.config.datasource.DynamicDataSourceContextHolder;
|
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 多数据源处理
|
|
||||||
*
|
|
||||||
* @author ruoyi
|
|
||||||
*/
|
|
||||||
@Aspect
|
|
||||||
@Order(1)
|
|
||||||
@Component
|
|
||||||
public class DataSourceAspect
|
|
||||||
{
|
|
||||||
protected Logger logger = LoggerFactory.getLogger(getClass());
|
|
||||||
|
|
||||||
@Pointcut("@annotation(com.ruoyi.common.annotation.DataSource)"
|
|
||||||
+ "|| @within(com.ruoyi.common.annotation.DataSource)")
|
|
||||||
public void dsPointCut()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Around("dsPointCut()")
|
|
||||||
public Object around(ProceedingJoinPoint point) throws Throwable
|
|
||||||
{
|
|
||||||
DataSource dataSource = getDataSource(point);
|
|
||||||
|
|
||||||
if (StringUtils.isNotNull(dataSource))
|
|
||||||
{
|
|
||||||
DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return point.proceed();
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
// 销毁数据源 在执行方法之后
|
|
||||||
DynamicDataSourceContextHolder.clearDataSourceType();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取需要切换的数据源
|
|
||||||
*/
|
|
||||||
public DataSource getDataSource(ProceedingJoinPoint point)
|
|
||||||
{
|
|
||||||
MethodSignature signature = (MethodSignature) point.getSignature();
|
|
||||||
DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
|
|
||||||
if (Objects.nonNull(dataSource))
|
|
||||||
{
|
|
||||||
return dataSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,242 +0,0 @@
|
||||||
package com.ruoyi.framework.aspectj;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Map;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import org.aspectj.lang.JoinPoint;
|
|
||||||
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.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.validation.BindingResult;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
|
||||||
import com.alibaba.fastjson.support.spring.PropertyPreFilters;
|
|
||||||
import com.ruoyi.common.annotation.Log;
|
|
||||||
import com.ruoyi.common.core.domain.entity.SysUser;
|
|
||||||
import com.ruoyi.common.enums.BusinessStatus;
|
|
||||||
import com.ruoyi.common.utils.ServletUtils;
|
|
||||||
import com.ruoyi.common.utils.ShiroUtils;
|
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
|
||||||
import com.ruoyi.framework.manager.AsyncManager;
|
|
||||||
import com.ruoyi.framework.manager.factory.AsyncFactory;
|
|
||||||
import com.ruoyi.system.domain.SysOperLog;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 操作日志记录处理
|
|
||||||
*
|
|
||||||
* @author ruoyi
|
|
||||||
*/
|
|
||||||
@Aspect
|
|
||||||
@Component
|
|
||||||
public class LogAspect
|
|
||||||
{
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
|
|
||||||
|
|
||||||
/** 排除敏感属性字段 */
|
|
||||||
public static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" };
|
|
||||||
|
|
||||||
// 配置织入点
|
|
||||||
@Pointcut("@annotation(com.ruoyi.common.annotation.Log)")
|
|
||||||
public void logPointCut()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理完请求后执行
|
|
||||||
*
|
|
||||||
* @param joinPoint 切点
|
|
||||||
*/
|
|
||||||
@AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
|
|
||||||
public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult)
|
|
||||||
{
|
|
||||||
handleLog(joinPoint, controllerLog, null, jsonResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 拦截异常操作
|
|
||||||
*
|
|
||||||
* @param joinPoint 切点
|
|
||||||
* @param e 异常
|
|
||||||
*/
|
|
||||||
@AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
|
|
||||||
public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e)
|
|
||||||
{
|
|
||||||
handleLog(joinPoint, controllerLog, e, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 获取当前的用户
|
|
||||||
SysUser currentUser = ShiroUtils.getSysUser();
|
|
||||||
|
|
||||||
// *========数据库日志=========*//
|
|
||||||
SysOperLog operLog = new SysOperLog();
|
|
||||||
operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
|
|
||||||
// 请求的地址
|
|
||||||
String ip = ShiroUtils.getIp();
|
|
||||||
operLog.setOperIp(ip);
|
|
||||||
operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));
|
|
||||||
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 + "()");
|
|
||||||
// 设置请求方式
|
|
||||||
operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
|
|
||||||
// 处理设置注解上的参数
|
|
||||||
getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
|
|
||||||
// 保存数据库
|
|
||||||
AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
|
|
||||||
}
|
|
||||||
catch (Exception exp)
|
|
||||||
{
|
|
||||||
// 记录本地异常日志
|
|
||||||
log.error("==前置通知异常==");
|
|
||||||
log.error("异常信息:{}", exp.getMessage());
|
|
||||||
exp.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取注解中对方法的描述信息 用于Controller层注解
|
|
||||||
*
|
|
||||||
* @param log 日志
|
|
||||||
* @param operLog 操作日志
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog, Object jsonResult) throws Exception
|
|
||||||
{
|
|
||||||
// 设置action动作
|
|
||||||
operLog.setBusinessType(log.businessType().ordinal());
|
|
||||||
// 设置标题
|
|
||||||
operLog.setTitle(log.title());
|
|
||||||
// 设置操作人类别
|
|
||||||
operLog.setOperatorType(log.operatorType().ordinal());
|
|
||||||
// 是否需要保存request,参数和值
|
|
||||||
if (log.isSaveRequestData())
|
|
||||||
{
|
|
||||||
// 获取参数的信息,传入到数据库中。
|
|
||||||
setRequestValue(joinPoint, operLog);
|
|
||||||
}
|
|
||||||
// 是否需要保存response,参数和值
|
|
||||||
if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult))
|
|
||||||
{
|
|
||||||
operLog.setJsonResult(StringUtils.substring(JSONObject.toJSONString(jsonResult), 0, 2000));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取请求的参数,放到log中
|
|
||||||
*
|
|
||||||
* @param operLog 操作日志
|
|
||||||
* @throws Exception 异常
|
|
||||||
*/
|
|
||||||
private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog) throws Exception
|
|
||||||
{
|
|
||||||
Map<String, String[]> map = ServletUtils.getRequest().getParameterMap();
|
|
||||||
if (StringUtils.isNotEmpty(map))
|
|
||||||
{
|
|
||||||
String params = JSONObject.toJSONString(map, excludePropertyPreFilter());
|
|
||||||
operLog.setOperParam(StringUtils.substring(params, 0, 2000));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Object args = joinPoint.getArgs();
|
|
||||||
if (StringUtils.isNotNull(args))
|
|
||||||
{
|
|
||||||
String params = argsArrayToString(joinPoint.getArgs());
|
|
||||||
operLog.setOperParam(StringUtils.substring(params, 0, 2000));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 忽略敏感属性
|
|
||||||
*/
|
|
||||||
public PropertyPreFilters.MySimplePropertyPreFilter excludePropertyPreFilter()
|
|
||||||
{
|
|
||||||
return new PropertyPreFilters().addFilter().addExcludes(EXCLUDE_PROPERTIES);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 参数拼装
|
|
||||||
*/
|
|
||||||
private String argsArrayToString(Object[] paramsArray)
|
|
||||||
{
|
|
||||||
String params = "";
|
|
||||||
if (paramsArray != null && paramsArray.length > 0)
|
|
||||||
{
|
|
||||||
for (Object o : paramsArray)
|
|
||||||
{
|
|
||||||
if (StringUtils.isNotNull(o) && !isFilterObject(o))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Object jsonObj = JSONObject.toJSONString(o, excludePropertyPreFilter());
|
|
||||||
params += jsonObj.toString() + " ";
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return params.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断是否需要过滤的对象。
|
|
||||||
*
|
|
||||||
* @param o 对象信息。
|
|
||||||
* @return 如果是需要过滤的对象,则返回true;否则返回false。
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
public boolean isFilterObject(final Object o)
|
|
||||||
{
|
|
||||||
Class<?> clazz = o.getClass();
|
|
||||||
if (clazz.isArray())
|
|
||||||
{
|
|
||||||
return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
|
|
||||||
}
|
|
||||||
else if (Collection.class.isAssignableFrom(clazz))
|
|
||||||
{
|
|
||||||
Collection collection = (Collection) o;
|
|
||||||
for (Object value : collection)
|
|
||||||
{
|
|
||||||
return value instanceof MultipartFile;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (Map.class.isAssignableFrom(clazz))
|
|
||||||
{
|
|
||||||
Map map = (Map) o;
|
|
||||||
for (Object value : map.entrySet())
|
|
||||||
{
|
|
||||||
Map.Entry entry = (Map.Entry) value;
|
|
||||||
return entry.getValue() instanceof MultipartFile;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
|
|
||||||
|| o instanceof BindingResult;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
package com.ruoyi.framework.aspectj;
|
|
||||||
|
|
||||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
|
||||||
import org.aspectj.lang.JoinPoint;
|
|
||||||
import org.aspectj.lang.annotation.Aspect;
|
|
||||||
import org.aspectj.lang.annotation.Before;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import com.ruoyi.common.core.context.PermissionContextHolder;
|
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 自定义权限拦截器,将权限字符串放到当前请求中以便用于多个角色匹配符合要求的权限
|
|
||||||
*
|
|
||||||
* @author ruoyi
|
|
||||||
*/
|
|
||||||
@Aspect
|
|
||||||
@Component
|
|
||||||
public class PermissionsAspect
|
|
||||||
{
|
|
||||||
@Before("@annotation(controllerRequiresPermissions)")
|
|
||||||
public void doBefore(JoinPoint point, RequiresPermissions controllerRequiresPermissions) throws Throwable
|
|
||||||
{
|
|
||||||
handleRequiresPermissions(point, controllerRequiresPermissions);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void handleRequiresPermissions(final JoinPoint joinPoint, RequiresPermissions requiresPermissions)
|
|
||||||
{
|
|
||||||
PermissionContextHolder.setContext(StringUtils.join(requiresPermissions.value(), ","));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
package com.ruoyi.framework.config;
|
|
||||||
|
|
||||||
import org.mybatis.spring.annotation.MapperScan;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 程序注解配置
|
|
||||||
*
|
|
||||||
* @author ruoyi
|
|
||||||
*/
|
|
||||||
@Configuration
|
|
||||||
// 表示通过aop框架暴露该代理对象,AopContext能够访问
|
|
||||||
@EnableAspectJAutoProxy(exposeProxy = true)
|
|
||||||
// 指定要扫描的Mapper类的包的路径
|
|
||||||
@MapperScan("com.ruoyi.**.mapper")
|
|
||||||
public class ApplicationConfig
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
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;
|
|
||||||
import static com.google.code.kaptcha.Constants.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证码配置
|
|
||||||
*
|
|
||||||
* @author ruoyi
|
|
||||||
*/
|
|
||||||
@Configuration
|
|
||||||
public class CaptchaConfig
|
|
||||||
{
|
|
||||||
@Bean(name = "captchaProducer")
|
|
||||||
public DefaultKaptcha getKaptchaBean()
|
|
||||||
{
|
|
||||||
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
|
|
||||||
Properties properties = new Properties();
|
|
||||||
// 是否有边框 默认为true 我们可以自己设置yes,no
|
|
||||||
properties.setProperty(KAPTCHA_BORDER, "yes");
|
|
||||||
// 验证码文本字符颜色 默认为Color.BLACK
|
|
||||||
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black");
|
|
||||||
// 验证码图片宽度 默认为200
|
|
||||||
properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");
|
|
||||||
// 验证码图片高度 默认为50
|
|
||||||
properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");
|
|
||||||
// 验证码文本字符大小 默认为40
|
|
||||||
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "38");
|
|
||||||
// KAPTCHA_SESSION_KEY
|
|
||||||
properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode");
|
|
||||||
// 验证码文本字符长度 默认为5
|
|
||||||
properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");
|
|
||||||
// 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
|
|
||||||
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
|
|
||||||
// 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy
|
|
||||||
properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
|
|
||||||
Config config = new Config(properties);
|
|
||||||
defaultKaptcha.setConfig(config);
|
|
||||||
return defaultKaptcha;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean(name = "captchaProducerMath")
|
|
||||||
public DefaultKaptcha getKaptchaBeanMath()
|
|
||||||
{
|
|
||||||
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
|
|
||||||
Properties properties = new Properties();
|
|
||||||
// 是否有边框 默认为true 我们可以自己设置yes,no
|
|
||||||
properties.setProperty(KAPTCHA_BORDER, "yes");
|
|
||||||
// 边框颜色 默认为Color.BLACK
|
|
||||||
properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90");
|
|
||||||
// 验证码文本字符颜色 默认为Color.BLACK
|
|
||||||
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue");
|
|
||||||
// 验证码图片宽度 默认为200
|
|
||||||
properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");
|
|
||||||
// 验证码图片高度 默认为50
|
|
||||||
properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");
|
|
||||||
// 验证码文本字符大小 默认为40
|
|
||||||
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35");
|
|
||||||
// KAPTCHA_SESSION_KEY
|
|
||||||
properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath");
|
|
||||||
// 验证码文本生成器
|
|
||||||
properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "com.ruoyi.framework.config.KaptchaTextCreator");
|
|
||||||
// 验证码文本字符间距 默认为2
|
|
||||||
properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3");
|
|
||||||
// 验证码文本字符长度 默认为5
|
|
||||||
properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6");
|
|
||||||
// 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
|
|
||||||
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
|
|
||||||
// 验证码噪点颜色 默认为Color.BLACK
|
|
||||||
properties.setProperty(KAPTCHA_NOISE_COLOR, "white");
|
|
||||||
// 干扰实现类
|
|
||||||
properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise");
|
|
||||||
// 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy
|
|
||||||
properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
|
|
||||||
Config config = new Config(properties);
|
|
||||||
defaultKaptcha.setConfig(config);
|
|
||||||
return defaultKaptcha;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,128 +0,0 @@
|
||||||
package com.ruoyi.framework.config;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import javax.servlet.Filter;
|
|
||||||
import javax.servlet.FilterChain;
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.ServletRequest;
|
|
||||||
import javax.servlet.ServletResponse;
|
|
||||||
import javax.sql.DataSource;
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
|
||||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.context.annotation.Primary;
|
|
||||||
import com.alibaba.druid.pool.DruidDataSource;
|
|
||||||
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
|
|
||||||
import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
|
|
||||||
import com.alibaba.druid.util.Utils;
|
|
||||||
import com.ruoyi.common.enums.DataSourceType;
|
|
||||||
import com.ruoyi.common.utils.spring.SpringUtils;
|
|
||||||
import com.ruoyi.framework.config.properties.DruidProperties;
|
|
||||||
import com.ruoyi.framework.datasource.DynamicDataSource;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* druid 配置多数据源
|
|
||||||
*
|
|
||||||
* @author ruoyi
|
|
||||||
*/
|
|
||||||
@Configuration
|
|
||||||
public class DruidConfig
|
|
||||||
{
|
|
||||||
@Bean
|
|
||||||
@ConfigurationProperties("spring.datasource.druid.master")
|
|
||||||
public DataSource masterDataSource(DruidProperties druidProperties)
|
|
||||||
{
|
|
||||||
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
|
|
||||||
return druidProperties.dataSource(dataSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConfigurationProperties("spring.datasource.druid.slave")
|
|
||||||
@ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
|
|
||||||
public DataSource slaveDataSource(DruidProperties druidProperties)
|
|
||||||
{
|
|
||||||
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
|
|
||||||
return druidProperties.dataSource(dataSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean(name = "dynamicDataSource")
|
|
||||||
@Primary
|
|
||||||
public DynamicDataSource dataSource(DataSource masterDataSource)
|
|
||||||
{
|
|
||||||
Map<Object, Object> targetDataSources = new HashMap<>();
|
|
||||||
targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
|
|
||||||
setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource");
|
|
||||||
return new DynamicDataSource(masterDataSource, targetDataSources);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置数据源
|
|
||||||
*
|
|
||||||
* @param targetDataSources 备选数据源集合
|
|
||||||
* @param sourceName 数据源名称
|
|
||||||
* @param beanName bean名称
|
|
||||||
*/
|
|
||||||
public void setDataSource(Map<Object, Object> targetDataSources, String sourceName, String beanName)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
DataSource dataSource = SpringUtils.getBean(beanName);
|
|
||||||
targetDataSources.put(sourceName, dataSource);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 去除监控页面底部的广告
|
|
||||||
*/
|
|
||||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnProperty(name = "spring.datasource.druid.statViewServlet.enabled", havingValue = "true")
|
|
||||||
public FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties)
|
|
||||||
{
|
|
||||||
// 获取web监控页面的参数
|
|
||||||
DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
|
|
||||||
// 提取common.js的配置路径
|
|
||||||
String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
|
|
||||||
String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");
|
|
||||||
final String filePath = "support/http/resources/js/common.js";
|
|
||||||
// 创建filter进行过滤
|
|
||||||
Filter filter = new Filter()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void init(javax.servlet.FilterConfig filterConfig) throws ServletException
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
|
||||||
throws IOException, ServletException
|
|
||||||
{
|
|
||||||
chain.doFilter(request, response);
|
|
||||||
// 重置缓冲区,响应头不会被重置
|
|
||||||
response.resetBuffer();
|
|
||||||
// 获取common.js
|
|
||||||
String text = Utils.readFromResource(filePath);
|
|
||||||
// 正则替换banner, 除去底部的广告信息
|
|
||||||
text = text.replaceAll("<a.*?banner\"></a><br/>", "");
|
|
||||||
text = text.replaceAll("powered.*?shrek.wang</a>", "");
|
|
||||||
response.getWriter().write(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void destroy()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
};
|
|
||||||
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
|
|
||||||
registrationBean.setFilter(filter);
|
|
||||||
registrationBean.addUrlPatterns(commonJsPattern);
|
|
||||||
return registrationBean;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
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.autoconfigure.condition.ConditionalOnProperty;
|
|
||||||
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
|
|
||||||
@ConditionalOnProperty(value = "xss.enabled", havingValue = "true")
|
|
||||||
public class FilterConfig
|
|
||||||
{
|
|
||||||
@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(FilterRegistrationBean.HIGHEST_PRECEDENCE);
|
|
||||||
Map<String, String> initParameters = new HashMap<String, String>();
|
|
||||||
initParameters.put("excludes", excludes);
|
|
||||||
registration.setInitParameters(initParameters);
|
|
||||||
return registration;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,76 +0,0 @@
|
||||||
package com.ruoyi.framework.config;
|
|
||||||
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
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 SecureRandom();
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,132 +0,0 @@
|
||||||
package com.ruoyi.framework.config;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import javax.sql.DataSource;
|
|
||||||
import org.apache.ibatis.io.VFS;
|
|
||||||
import org.apache.ibatis.session.SqlSessionFactory;
|
|
||||||
import org.mybatis.spring.SqlSessionFactoryBean;
|
|
||||||
import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.core.env.Environment;
|
|
||||||
import org.springframework.core.io.DefaultResourceLoader;
|
|
||||||
import org.springframework.core.io.Resource;
|
|
||||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
|
||||||
import org.springframework.core.io.support.ResourcePatternResolver;
|
|
||||||
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
|
|
||||||
import org.springframework.core.type.classreading.MetadataReader;
|
|
||||||
import org.springframework.core.type.classreading.MetadataReaderFactory;
|
|
||||||
import org.springframework.util.ClassUtils;
|
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mybatis支持*匹配扫描包
|
|
||||||
*
|
|
||||||
* @author ruoyi
|
|
||||||
*/
|
|
||||||
@Configuration
|
|
||||||
public class MyBatisConfig
|
|
||||||
{
|
|
||||||
@Autowired
|
|
||||||
private Environment env;
|
|
||||||
|
|
||||||
static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
|
|
||||||
|
|
||||||
public static String setTypeAliasesPackage(String typeAliasesPackage)
|
|
||||||
{
|
|
||||||
ResourcePatternResolver resolver = (ResourcePatternResolver) new PathMatchingResourcePatternResolver();
|
|
||||||
MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver);
|
|
||||||
List<String> allResult = new ArrayList<String>();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
for (String aliasesPackage : typeAliasesPackage.split(","))
|
|
||||||
{
|
|
||||||
List<String> result = new ArrayList<String>();
|
|
||||||
aliasesPackage = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
|
|
||||||
+ ClassUtils.convertClassNameToResourcePath(aliasesPackage.trim()) + "/" + DEFAULT_RESOURCE_PATTERN;
|
|
||||||
Resource[] resources = resolver.getResources(aliasesPackage);
|
|
||||||
if (resources != null && resources.length > 0)
|
|
||||||
{
|
|
||||||
MetadataReader metadataReader = null;
|
|
||||||
for (Resource resource : resources)
|
|
||||||
{
|
|
||||||
if (resource.isReadable())
|
|
||||||
{
|
|
||||||
metadataReader = metadataReaderFactory.getMetadataReader(resource);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
result.add(Class.forName(metadataReader.getClassMetadata().getClassName()).getPackage().getName());
|
|
||||||
}
|
|
||||||
catch (ClassNotFoundException e)
|
|
||||||
{
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (result.size() > 0)
|
|
||||||
{
|
|
||||||
HashSet<String> hashResult = new HashSet<String>(result);
|
|
||||||
allResult.addAll(hashResult);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (allResult.size() > 0)
|
|
||||||
{
|
|
||||||
typeAliasesPackage = String.join(",", (String[]) allResult.toArray(new String[0]));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new RuntimeException("mybatis typeAliasesPackage 路径扫描错误,参数typeAliasesPackage:" + typeAliasesPackage + "未找到任何包");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (IOException e)
|
|
||||||
{
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return typeAliasesPackage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Resource[] resolveMapperLocations(String[] mapperLocations)
|
|
||||||
{
|
|
||||||
ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
|
|
||||||
List<Resource> resources = new ArrayList<Resource>();
|
|
||||||
if (mapperLocations != null)
|
|
||||||
{
|
|
||||||
for (String mapperLocation : mapperLocations)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Resource[] mappers = resourceResolver.getResources(mapperLocation);
|
|
||||||
resources.addAll(Arrays.asList(mappers));
|
|
||||||
}
|
|
||||||
catch (IOException e)
|
|
||||||
{
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return resources.toArray(new Resource[resources.size()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception
|
|
||||||
{
|
|
||||||
String typeAliasesPackage = env.getProperty("mybatis.typeAliasesPackage");
|
|
||||||
String mapperLocations = env.getProperty("mybatis.mapperLocations");
|
|
||||||
String configLocation = env.getProperty("mybatis.configLocation");
|
|
||||||
typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage);
|
|
||||||
VFS.addImplClass(SpringBootVFS.class);
|
|
||||||
|
|
||||||
final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
|
|
||||||
sessionFactory.setDataSource(dataSource);
|
|
||||||
sessionFactory.setTypeAliasesPackage(typeAliasesPackage);
|
|
||||||
sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ",")));
|
|
||||||
sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
|
|
||||||
return sessionFactory.getObject();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
package com.ruoyi.framework.config;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
|
||||||
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.RuoYiConfig;
|
|
||||||
import com.ruoyi.common.constant.Constants;
|
|
||||||
import com.ruoyi.framework.interceptor.RepeatSubmitInterceptor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 通用配置
|
|
||||||
*
|
|
||||||
* @author ruoyi
|
|
||||||
*/
|
|
||||||
@Configuration
|
|
||||||
public class ResourcesConfig implements WebMvcConfigurer
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* 首页地址
|
|
||||||
*/
|
|
||||||
@Value("${shiro.user.indexUrl}")
|
|
||||||
private String indexUrl;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private RepeatSubmitInterceptor repeatSubmitInterceptor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 默认首页的设置,当输入域名是可以自动跳转到默认指定的网页
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void addViewControllers(ViewControllerRegistry registry)
|
|
||||||
{
|
|
||||||
registry.addViewController("/").setViewName("forward:" + indexUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addResourceHandlers(ResourceHandlerRegistry registry)
|
|
||||||
{
|
|
||||||
/** 本地文件上传路径 */
|
|
||||||
registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**").addResourceLocations("file:" + RuoYiConfig.getProfile() + "/");
|
|
||||||
|
|
||||||
/** swagger配置 */
|
|
||||||
registry.addResourceHandler("/swagger-ui/**").addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 自定义拦截规则
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void addInterceptors(InterceptorRegistry registry)
|
|
||||||
{
|
|
||||||
registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,416 +0,0 @@
|
||||||
package com.ruoyi.framework.config;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import javax.servlet.Filter;
|
|
||||||
import org.apache.commons.io.IOUtils;
|
|
||||||
import org.apache.shiro.cache.ehcache.EhCacheManager;
|
|
||||||
import org.apache.shiro.codec.Base64;
|
|
||||||
import org.apache.shiro.config.ConfigurationException;
|
|
||||||
import org.apache.shiro.io.ResourceUtils;
|
|
||||||
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 com.ruoyi.common.constant.Constants;
|
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
|
||||||
import com.ruoyi.common.utils.security.CipherUtils;
|
|
||||||
import com.ruoyi.common.utils.spring.SpringUtils;
|
|
||||||
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.CustomShiroFilterFactoryBean;
|
|
||||||
import com.ruoyi.framework.shiro.web.filter.LogoutFilter;
|
|
||||||
import com.ruoyi.framework.shiro.web.filter.captcha.CaptchaValidateFilter;
|
|
||||||
import com.ruoyi.framework.shiro.web.filter.kickout.KickoutSessionFilter;
|
|
||||||
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 at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 权限配置加载
|
|
||||||
*
|
|
||||||
* @author ruoyi
|
|
||||||
*/
|
|
||||||
@Configuration
|
|
||||||
public class ShiroConfig
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Session超时时间,单位为毫秒(默认30分钟)
|
|
||||||
*/
|
|
||||||
@Value("${shiro.session.expireTime}")
|
|
||||||
private int expireTime;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 相隔多久检查一次session的有效性,单位毫秒,默认就是10分钟
|
|
||||||
*/
|
|
||||||
@Value("${shiro.session.validationInterval}")
|
|
||||||
private int validationInterval;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 同一个用户最大会话数
|
|
||||||
*/
|
|
||||||
@Value("${shiro.session.maxSession}")
|
|
||||||
private int maxSession;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 踢出之前登录的/之后登录的用户,默认踢出之前登录的用户
|
|
||||||
*/
|
|
||||||
@Value("${shiro.session.kickoutAfter}")
|
|
||||||
private boolean kickoutAfter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证码开关
|
|
||||||
*/
|
|
||||||
@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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置cipherKey密钥
|
|
||||||
*/
|
|
||||||
@Value("${shiro.cookie.cipherKey}")
|
|
||||||
private String cipherKey;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 登录地址
|
|
||||||
*/
|
|
||||||
@Value("${shiro.user.loginUrl}")
|
|
||||||
private String loginUrl;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 权限认证失败地址
|
|
||||||
*/
|
|
||||||
@Value("${shiro.user.unauthorizedUrl}")
|
|
||||||
private String unauthorizedUrl;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否开启记住我功能
|
|
||||||
*/
|
|
||||||
@Value("${shiro.rememberMe.enabled: false}")
|
|
||||||
private boolean rememberMe;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 缓存管理器 使用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.setCacheManager(new net.sf.ehcache.CacheManager(getCacheManagerConfigFileInputStream()));
|
|
||||||
return em;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
em.setCacheManager(cacheManager);
|
|
||||||
return em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 返回配置文件流 避免ehcache配置文件一直被占用,无法完全销毁项目重新部署
|
|
||||||
*/
|
|
||||||
protected InputStream getCacheManagerConfigFileInputStream()
|
|
||||||
{
|
|
||||||
String configFile = "classpath:ehcache/ehcache-shiro.xml";
|
|
||||||
InputStream inputStream = null;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
inputStream = ResourceUtils.getInputStreamForPath(configFile);
|
|
||||||
byte[] b = IOUtils.toByteArray(inputStream);
|
|
||||||
InputStream in = new ByteArrayInputStream(b);
|
|
||||||
return in;
|
|
||||||
}
|
|
||||||
catch (IOException e)
|
|
||||||
{
|
|
||||||
throw new ConfigurationException(
|
|
||||||
"Unable to obtain input stream for cacheManagerConfigFile [" + configFile + "]", e);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
IOUtils.closeQuietly(inputStream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 自定义Realm
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
public UserRealm userRealm(EhCacheManager cacheManager)
|
|
||||||
{
|
|
||||||
UserRealm userRealm = new UserRealm();
|
|
||||||
userRealm.setAuthorizationCacheName(Constants.SYS_AUTH_CACHE);
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 会话管理器
|
|
||||||
*/
|
|
||||||
@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(SpringUtils.getBean(SpringSessionValidationScheduler.class));
|
|
||||||
// 是否定时检查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(rememberMe ? rememberMeManager() : null);
|
|
||||||
// 注入缓存管理器;
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
CustomShiroFilterFactoryBean shiroFilterFactoryBean = new CustomShiroFilterFactoryBean();
|
|
||||||
// Shiro的核心安全接口,这个属性是必须的
|
|
||||||
shiroFilterFactoryBean.setSecurityManager(securityManager);
|
|
||||||
// 身份认证失败,则跳转到登录页面的配置
|
|
||||||
shiroFilterFactoryBean.setLoginUrl(loginUrl);
|
|
||||||
// 权限认证失败,则跳转到指定页面
|
|
||||||
shiroFilterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);
|
|
||||||
// Shiro连接约束配置,即过滤链的定义
|
|
||||||
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
|
|
||||||
// 对静态资源设置匿名访问
|
|
||||||
filterChainDefinitionMap.put("/favicon.ico**", "anon");
|
|
||||||
filterChainDefinitionMap.put("/ruoyi.png**", "anon");
|
|
||||||
filterChainDefinitionMap.put("/html/**", "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("/captcha/captchaImage**", "anon");
|
|
||||||
// 退出 logout地址,shiro去清除session
|
|
||||||
filterChainDefinitionMap.put("/logout", "logout");
|
|
||||||
// 不需要拦截的访问
|
|
||||||
filterChainDefinitionMap.put("/login", "anon,captchaValidate");
|
|
||||||
// 注册相关
|
|
||||||
filterChainDefinitionMap.put("/register", "anon,captchaValidate");
|
|
||||||
// 系统权限列表
|
|
||||||
// filterChainDefinitionMap.putAll(SpringUtils.getBean(IMenuService.class).selectPermsAll());
|
|
||||||
|
|
||||||
Map<String, Filter> filters = new LinkedHashMap<String, Filter>();
|
|
||||||
filters.put("onlineSession", onlineSessionFilter());
|
|
||||||
filters.put("syncOnlineSession", syncOnlineSessionFilter());
|
|
||||||
filters.put("captchaValidate", captchaValidateFilter());
|
|
||||||
filters.put("kickout", kickoutSessionFilter());
|
|
||||||
// 注销成功,则跳转到指定页面
|
|
||||||
filters.put("logout", logoutFilter());
|
|
||||||
shiroFilterFactoryBean.setFilters(filters);
|
|
||||||
|
|
||||||
// 所有请求需要认证
|
|
||||||
filterChainDefinitionMap.put("/**", "user,kickout,onlineSession,syncOnlineSession");
|
|
||||||
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
|
|
||||||
|
|
||||||
return shiroFilterFactoryBean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 自定义在线用户处理过滤器
|
|
||||||
*/
|
|
||||||
public OnlineSessionFilter onlineSessionFilter()
|
|
||||||
{
|
|
||||||
OnlineSessionFilter onlineSessionFilter = new OnlineSessionFilter();
|
|
||||||
onlineSessionFilter.setLoginUrl(loginUrl);
|
|
||||||
onlineSessionFilter.setOnlineSessionDAO(sessionDAO());
|
|
||||||
return onlineSessionFilter;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 自定义在线用户同步过滤器
|
|
||||||
*/
|
|
||||||
public SyncOnlineSessionFilter syncOnlineSessionFilter()
|
|
||||||
{
|
|
||||||
SyncOnlineSessionFilter syncOnlineSessionFilter = new SyncOnlineSessionFilter();
|
|
||||||
syncOnlineSessionFilter.setOnlineSessionDAO(sessionDAO());
|
|
||||||
return syncOnlineSessionFilter;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 自定义验证码过滤器
|
|
||||||
*/
|
|
||||||
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());
|
|
||||||
if (StringUtils.isNotEmpty(cipherKey))
|
|
||||||
{
|
|
||||||
cookieRememberMeManager.setCipherKey(Base64.decode(cipherKey));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cookieRememberMeManager.setCipherKey(CipherUtils.generateNewKey(128, "AES").getEncoded());
|
|
||||||
}
|
|
||||||
return cookieRememberMeManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 同一个用户多设备登录限制
|
|
||||||
*/
|
|
||||||
public KickoutSessionFilter kickoutSessionFilter()
|
|
||||||
{
|
|
||||||
KickoutSessionFilter kickoutSessionFilter = new KickoutSessionFilter();
|
|
||||||
kickoutSessionFilter.setCacheManager(getEhCacheManager());
|
|
||||||
kickoutSessionFilter.setSessionManager(sessionManager());
|
|
||||||
// 同一个用户最大的会话数,默认-1无限制;比如2的意思是同一个用户允许最多同时两个人登录
|
|
||||||
kickoutSessionFilter.setMaxSession(maxSession);
|
|
||||||
// 是否踢出后来登录的,默认是false;即后者登录的用户踢出前者登录的用户;踢出顺序
|
|
||||||
kickoutSessionFilter.setKickoutAfter(kickoutAfter);
|
|
||||||
// 被踢出后重定向到的地址;
|
|
||||||
kickoutSessionFilter.setKickoutUrl("/login?kickout=1");
|
|
||||||
return kickoutSessionFilter;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,89 +0,0 @@
|
||||||
package com.ruoyi.framework.config.properties;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import com.alibaba.druid.pool.DruidDataSource;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* druid 配置属性
|
|
||||||
*
|
|
||||||
* @author ruoyi
|
|
||||||
*/
|
|
||||||
@Configuration
|
|
||||||
public class DruidProperties
|
|
||||||
{
|
|
||||||
@Value("${spring.datasource.druid.initialSize}")
|
|
||||||
private int initialSize;
|
|
||||||
|
|
||||||
@Value("${spring.datasource.druid.minIdle}")
|
|
||||||
private int minIdle;
|
|
||||||
|
|
||||||
@Value("${spring.datasource.druid.maxActive}")
|
|
||||||
private int maxActive;
|
|
||||||
|
|
||||||
@Value("${spring.datasource.druid.maxWait}")
|
|
||||||
private int maxWait;
|
|
||||||
|
|
||||||
@Value("${spring.datasource.druid.connectTimeout}")
|
|
||||||
private int connectTimeout;
|
|
||||||
|
|
||||||
@Value("${spring.datasource.druid.socketTimeout}")
|
|
||||||
private int socketTimeout;
|
|
||||||
|
|
||||||
@Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}")
|
|
||||||
private int timeBetweenEvictionRunsMillis;
|
|
||||||
|
|
||||||
@Value("${spring.datasource.druid.minEvictableIdleTimeMillis}")
|
|
||||||
private int minEvictableIdleTimeMillis;
|
|
||||||
|
|
||||||
@Value("${spring.datasource.druid.maxEvictableIdleTimeMillis}")
|
|
||||||
private int maxEvictableIdleTimeMillis;
|
|
||||||
|
|
||||||
@Value("${spring.datasource.druid.validationQuery}")
|
|
||||||
private String validationQuery;
|
|
||||||
|
|
||||||
@Value("${spring.datasource.druid.testWhileIdle}")
|
|
||||||
private boolean testWhileIdle;
|
|
||||||
|
|
||||||
@Value("${spring.datasource.druid.testOnBorrow}")
|
|
||||||
private boolean testOnBorrow;
|
|
||||||
|
|
||||||
@Value("${spring.datasource.druid.testOnReturn}")
|
|
||||||
private boolean testOnReturn;
|
|
||||||
|
|
||||||
public DruidDataSource dataSource(DruidDataSource datasource)
|
|
||||||
{
|
|
||||||
/** 配置初始化大小、最小、最大 */
|
|
||||||
datasource.setInitialSize(initialSize);
|
|
||||||
datasource.setMaxActive(maxActive);
|
|
||||||
datasource.setMinIdle(minIdle);
|
|
||||||
|
|
||||||
/** 配置获取连接等待超时的时间 */
|
|
||||||
datasource.setMaxWait(maxWait);
|
|
||||||
|
|
||||||
/** 配置驱动连接超时时间,检测数据库建立连接的超时时间,单位是毫秒 */
|
|
||||||
datasource.setConnectTimeout(connectTimeout);
|
|
||||||
|
|
||||||
/** 配置网络超时时间,等待数据库操作完成的网络超时时间,单位是毫秒 */
|
|
||||||
datasource.setSocketTimeout(socketTimeout);
|
|
||||||
|
|
||||||
/** 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */
|
|
||||||
datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
|
|
||||||
|
|
||||||
/** 配置一个连接在池中最小、最大生存的时间,单位是毫秒 */
|
|
||||||
datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
|
|
||||||
datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
|
|
||||||
*/
|
|
||||||
datasource.setValidationQuery(validationQuery);
|
|
||||||
/** 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 */
|
|
||||||
datasource.setTestWhileIdle(testWhileIdle);
|
|
||||||
/** 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
|
|
||||||
datasource.setTestOnBorrow(testOnBorrow);
|
|
||||||
/** 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
|
|
||||||
datasource.setTestOnReturn(testOnReturn);
|
|
||||||
return datasource;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
package com.ruoyi.framework.datasource;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import javax.sql.DataSource;
|
|
||||||
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
|
|
||||||
import com.ruoyi.common.config.datasource.DynamicDataSourceContextHolder;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 动态数据源
|
|
||||||
*
|
|
||||||
* @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.getDataSourceType();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
package com.ruoyi.framework.interceptor;
|
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.web.method.HandlerMethod;
|
|
||||||
import org.springframework.web.servlet.HandlerInterceptor;
|
|
||||||
import com.ruoyi.common.json.JSON;
|
|
||||||
import com.ruoyi.common.annotation.RepeatSubmit;
|
|
||||||
import com.ruoyi.common.core.domain.AjaxResult;
|
|
||||||
import com.ruoyi.common.utils.ServletUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 防止重复提交拦截器
|
|
||||||
*
|
|
||||||
* @author ruoyi
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public abstract class RepeatSubmitInterceptor implements HandlerInterceptor
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
|
|
||||||
{
|
|
||||||
if (handler instanceof HandlerMethod)
|
|
||||||
{
|
|
||||||
HandlerMethod handlerMethod = (HandlerMethod) handler;
|
|
||||||
Method method = handlerMethod.getMethod();
|
|
||||||
RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
|
|
||||||
if (annotation != null)
|
|
||||||
{
|
|
||||||
if (this.isRepeatSubmit(request, annotation))
|
|
||||||
{
|
|
||||||
AjaxResult ajaxResult = AjaxResult.error(annotation.message());
|
|
||||||
ServletUtils.renderString(response, JSON.marshal(ajaxResult));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证是否重复提交由子类实现具体的防重复提交的规则
|
|
||||||
*
|
|
||||||
* @param request 请求对象
|
|
||||||
* @param annotation 防复注解
|
|
||||||
* @return 结果
|
|
||||||
*/
|
|
||||||
public abstract boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation) throws Exception;
|
|
||||||
}
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
package com.ruoyi.framework.interceptor.impl;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpSession;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import com.ruoyi.common.annotation.RepeatSubmit;
|
|
||||||
import com.ruoyi.common.json.JSON;
|
|
||||||
import com.ruoyi.framework.interceptor.RepeatSubmitInterceptor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断请求url和数据是否和上一次相同,
|
|
||||||
* 如果和上次相同,则是重复提交表单。 有效时间为10秒内。
|
|
||||||
*
|
|
||||||
* @author ruoyi
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class SameUrlDataInterceptor extends RepeatSubmitInterceptor
|
|
||||||
{
|
|
||||||
public final String REPEAT_PARAMS = "repeatParams";
|
|
||||||
|
|
||||||
public final String REPEAT_TIME = "repeatTime";
|
|
||||||
|
|
||||||
public final String SESSION_REPEAT_KEY = "repeatData";
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
|
||||||
public boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation) throws Exception
|
|
||||||
{
|
|
||||||
// 本次参数及系统时间
|
|
||||||
String nowParams = JSON.marshal(request.getParameterMap());
|
|
||||||
Map<String, Object> nowDataMap = new HashMap<String, Object>();
|
|
||||||
nowDataMap.put(REPEAT_PARAMS, nowParams);
|
|
||||||
nowDataMap.put(REPEAT_TIME, System.currentTimeMillis());
|
|
||||||
|
|
||||||
// 请求地址(作为存放session的key值)
|
|
||||||
String url = request.getRequestURI();
|
|
||||||
|
|
||||||
HttpSession session = request.getSession();
|
|
||||||
Object sessionObj = session.getAttribute(SESSION_REPEAT_KEY);
|
|
||||||
if (sessionObj != null)
|
|
||||||
{
|
|
||||||
Map<String, Object> sessionMap = (Map<String, Object>) sessionObj;
|
|
||||||
if (sessionMap.containsKey(url))
|
|
||||||
{
|
|
||||||
Map<String, Object> preDataMap = (Map<String, Object>) sessionMap.get(url);
|
|
||||||
if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap, annotation.interval()))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Map<String, Object> sessionMap = new HashMap<String, Object>();
|
|
||||||
sessionMap.put(url, nowDataMap);
|
|
||||||
session.setAttribute(SESSION_REPEAT_KEY, sessionMap);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断参数是否相同
|
|
||||||
*/
|
|
||||||
private boolean compareParams(Map<String, Object> nowMap, Map<String, Object> preMap)
|
|
||||||
{
|
|
||||||
String nowParams = (String) nowMap.get(REPEAT_PARAMS);
|
|
||||||
String preParams = (String) preMap.get(REPEAT_PARAMS);
|
|
||||||
return nowParams.equals(preParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断两次间隔时间
|
|
||||||
*/
|
|
||||||
private boolean compareTime(Map<String, Object> nowMap, Map<String, Object> preMap, int interval)
|
|
||||||
{
|
|
||||||
long time1 = (Long) nowMap.get(REPEAT_TIME);
|
|
||||||
long time2 = (Long) preMap.get(REPEAT_TIME);
|
|
||||||
if ((time1 - time2) < interval)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
package com.ruoyi.framework.manager;
|
|
||||||
|
|
||||||
import java.util.TimerTask;
|
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import com.ruoyi.common.utils.Threads;
|
|
||||||
import com.ruoyi.common.utils.spring.SpringUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 异步任务管理器
|
|
||||||
*
|
|
||||||
* @author liuhulu
|
|
||||||
*/
|
|
||||||
public class AsyncManager
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* 操作延迟10毫秒
|
|
||||||
*/
|
|
||||||
private final int OPERATE_DELAY_TIME = 10;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 异步操作任务调度线程池
|
|
||||||
*/
|
|
||||||
private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 单例模式
|
|
||||||
*/
|
|
||||||
private AsyncManager(){}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 停止任务线程池
|
|
||||||
*/
|
|
||||||
public void shutdown()
|
|
||||||
{
|
|
||||||
Threads.shutdownAndAwaitTermination(executor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,87 +0,0 @@
|
||||||
package com.ruoyi.framework.manager;
|
|
||||||
|
|
||||||
import com.ruoyi.framework.shiro.web.session.SpringSessionValidationScheduler;
|
|
||||||
import net.sf.ehcache.CacheManager;
|
|
||||||
import org.apache.shiro.cache.ehcache.EhCacheManager;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import javax.annotation.PreDestroy;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 确保应用退出时能关闭后台线程
|
|
||||||
*
|
|
||||||
* @author cj
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class ShutdownManager
|
|
||||||
{
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger("sys-user");
|
|
||||||
|
|
||||||
@Autowired(required = false)
|
|
||||||
private SpringSessionValidationScheduler springSessionValidationScheduler;
|
|
||||||
|
|
||||||
@Autowired(required = false)
|
|
||||||
private EhCacheManager ehCacheManager;
|
|
||||||
|
|
||||||
@PreDestroy
|
|
||||||
public void destroy()
|
|
||||||
{
|
|
||||||
shutdownSpringSessionValidationScheduler();
|
|
||||||
shutdownAsyncManager();
|
|
||||||
shutdownEhCacheManager();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 停止Seesion会话检查
|
|
||||||
*/
|
|
||||||
private void shutdownSpringSessionValidationScheduler()
|
|
||||||
{
|
|
||||||
if (springSessionValidationScheduler != null && springSessionValidationScheduler.isEnabled())
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
logger.info("====关闭会话验证任务====");
|
|
||||||
springSessionValidationScheduler.disableSessionValidation();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
logger.error(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 停止异步执行任务
|
|
||||||
*/
|
|
||||||
private void shutdownAsyncManager()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
logger.info("====关闭后台任务任务线程池====");
|
|
||||||
AsyncManager.me().shutdown();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
logger.error(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void shutdownEhCacheManager()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
logger.info("====关闭缓存====");
|
|
||||||
if (ehCacheManager != null)
|
|
||||||
{
|
|
||||||
CacheManager cacheManager = ehCacheManager.getCacheManager();
|
|
||||||
cacheManager.shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
logger.error(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,136 +0,0 @@
|
||||||
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.common.utils.LogUtils;
|
|
||||||
import com.ruoyi.common.utils.ServletUtils;
|
|
||||||
import com.ruoyi.common.utils.ShiroUtils;
|
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
|
||||||
import com.ruoyi.common.utils.spring.SpringUtils;
|
|
||||||
import com.ruoyi.framework.shiro.session.OnlineSession;
|
|
||||||
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.ISysUserOnlineService;
|
|
||||||
import com.ruoyi.system.service.impl.SysLogininforServiceImpl;
|
|
||||||
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(ISysUserOnlineService.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()
|
|
||||||
{
|
|
||||||
String address = AddressUtils.getRealAddressByIP(ip);
|
|
||||||
StringBuilder s = new StringBuilder();
|
|
||||||
s.append(LogUtils.getBlock(ip));
|
|
||||||
s.append(address);
|
|
||||||
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(address);
|
|
||||||
logininfor.setBrowser(browser);
|
|
||||||
logininfor.setOs(os);
|
|
||||||
logininfor.setMsg(message);
|
|
||||||
// 日志状态
|
|
||||||
if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER))
|
|
||||||
{
|
|
||||||
logininfor.setStatus(Constants.SUCCESS);
|
|
||||||
}
|
|
||||||
else if (Constants.LOGIN_FAIL.equals(status))
|
|
||||||
{
|
|
||||||
logininfor.setStatus(Constants.FAIL);
|
|
||||||
}
|
|
||||||
// 插入数据
|
|
||||||
SpringUtils.getBean(SysLogininforServiceImpl.class).insertLogininfor(logininfor);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,158 +0,0 @@
|
||||||
package com.ruoyi.framework.shiro.realm;
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
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.cache.Cache;
|
|
||||||
import org.apache.shiro.realm.AuthorizingRealm;
|
|
||||||
import org.apache.shiro.subject.PrincipalCollection;
|
|
||||||
import org.apache.shiro.subject.SimplePrincipalCollection;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import com.ruoyi.common.core.domain.entity.SysUser;
|
|
||||||
import com.ruoyi.common.exception.user.CaptchaException;
|
|
||||||
import com.ruoyi.common.exception.user.RoleBlockedException;
|
|
||||||
import com.ruoyi.common.exception.user.UserBlockedException;
|
|
||||||
import com.ruoyi.common.exception.user.UserNotExistsException;
|
|
||||||
import com.ruoyi.common.exception.user.UserPasswordNotMatchException;
|
|
||||||
import com.ruoyi.common.exception.user.UserPasswordRetryLimitExceedException;
|
|
||||||
import com.ruoyi.common.utils.ShiroUtils;
|
|
||||||
import com.ruoyi.framework.shiro.service.SysLoginService;
|
|
||||||
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 SysLoginService loginService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 授权
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0)
|
|
||||||
{
|
|
||||||
SysUser user = ShiroUtils.getSysUser();
|
|
||||||
// 角色列表
|
|
||||||
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(Object principal)
|
|
||||||
{
|
|
||||||
SimplePrincipalCollection principals = new SimplePrincipalCollection(principal, getName());
|
|
||||||
this.clearCachedAuthorizationInfo(principals);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清理所有用户授权信息缓存
|
|
||||||
*/
|
|
||||||
public void clearAllCachedAuthorizationInfo()
|
|
||||||
{
|
|
||||||
Cache<Object, AuthorizationInfo> cache = getAuthorizationCache();
|
|
||||||
if (cache != null)
|
|
||||||
{
|
|
||||||
for (Object key : cache.keys())
|
|
||||||
{
|
|
||||||
cache.remove(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,171 +0,0 @@
|
||||||
package com.ruoyi.framework.shiro.service;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import com.ruoyi.common.constant.Constants;
|
|
||||||
import com.ruoyi.common.constant.ShiroConstants;
|
|
||||||
import com.ruoyi.common.constant.UserConstants;
|
|
||||||
import com.ruoyi.common.core.domain.entity.SysRole;
|
|
||||||
import com.ruoyi.common.core.domain.entity.SysUser;
|
|
||||||
import com.ruoyi.common.enums.UserStatus;
|
|
||||||
import com.ruoyi.common.exception.user.CaptchaException;
|
|
||||||
import com.ruoyi.common.exception.user.UserBlockedException;
|
|
||||||
import com.ruoyi.common.exception.user.UserDeleteException;
|
|
||||||
import com.ruoyi.common.exception.user.UserNotExistsException;
|
|
||||||
import com.ruoyi.common.exception.user.UserPasswordNotMatchException;
|
|
||||||
import com.ruoyi.common.utils.DateUtils;
|
|
||||||
import com.ruoyi.common.utils.MessageUtils;
|
|
||||||
import com.ruoyi.common.utils.ServletUtils;
|
|
||||||
import com.ruoyi.common.utils.ShiroUtils;
|
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
|
||||||
import com.ruoyi.framework.manager.AsyncManager;
|
|
||||||
import com.ruoyi.framework.manager.factory.AsyncFactory;
|
|
||||||
import com.ruoyi.system.service.ISysMenuService;
|
|
||||||
import com.ruoyi.system.service.ISysUserService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 登录校验方法
|
|
||||||
*
|
|
||||||
* @author ruoyi
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class SysLoginService
|
|
||||||
{
|
|
||||||
@Autowired
|
|
||||||
private SysPasswordService passwordService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private ISysUserService userService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private ISysMenuService menuService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 登录
|
|
||||||
*/
|
|
||||||
public SysUser login(String username, String password)
|
|
||||||
{
|
|
||||||
// 验证码校验
|
|
||||||
if (ShiroConstants.CAPTCHA_ERROR.equals(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();
|
|
||||||
}
|
|
||||||
|
|
||||||
passwordService.validate(user, password);
|
|
||||||
|
|
||||||
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
|
|
||||||
setRolePermission(user);
|
|
||||||
recordLoginInfo(user.getUserId());
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置角色权限
|
|
||||||
*
|
|
||||||
* @param user 用户信息
|
|
||||||
*/
|
|
||||||
public void setRolePermission(SysUser user)
|
|
||||||
{
|
|
||||||
List<SysRole> roles = user.getRoles();
|
|
||||||
if (!roles.isEmpty() && roles.size() > 1)
|
|
||||||
{
|
|
||||||
// 多角色设置permissions属性,以便数据权限匹配权限
|
|
||||||
for (SysRole role : roles)
|
|
||||||
{
|
|
||||||
Set<String> rolePerms = menuService.selectPermsByRoleId(role.getRoleId());
|
|
||||||
role.setPermissions(rolePerms);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 记录登录信息
|
|
||||||
*
|
|
||||||
* @param userId 用户ID
|
|
||||||
*/
|
|
||||||
public void recordLoginInfo(Long userId)
|
|
||||||
{
|
|
||||||
SysUser user = new SysUser();
|
|
||||||
user.setUserId(userId);
|
|
||||||
user.setLoginIp(ShiroUtils.getIp());
|
|
||||||
user.setLoginDate(DateUtils.getNowDate());
|
|
||||||
userService.updateUserInfo(user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
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.common.constant.ShiroConstants;
|
|
||||||
import com.ruoyi.common.core.domain.entity.SysUser;
|
|
||||||
import com.ruoyi.common.exception.user.UserPasswordNotMatchException;
|
|
||||||
import com.ruoyi.common.exception.user.UserPasswordRetryLimitExceedException;
|
|
||||||
import com.ruoyi.common.utils.MessageUtils;
|
|
||||||
import com.ruoyi.framework.manager.AsyncManager;
|
|
||||||
import com.ruoyi.framework.manager.factory.AsyncFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 登录密码方法
|
|
||||||
*
|
|
||||||
* @author ruoyi
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class SysPasswordService
|
|
||||||
{
|
|
||||||
@Autowired
|
|
||||||
private CacheManager cacheManager;
|
|
||||||
|
|
||||||
private Cache<String, AtomicInteger> loginRecordCache;
|
|
||||||
|
|
||||||
@Value(value = "${user.password.maxRetryCount}")
|
|
||||||
private String maxRetryCount;
|
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
public void init()
|
|
||||||
{
|
|
||||||
loginRecordCache = cacheManager.getCache(ShiroConstants.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 loginName)
|
|
||||||
{
|
|
||||||
loginRecordCache.remove(loginName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String encryptPassword(String loginName, String password, String salt)
|
|
||||||
{
|
|
||||||
return new Md5Hash(loginName + password + salt).toHex();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
package com.ruoyi.framework.shiro.service;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import com.ruoyi.common.constant.Constants;
|
|
||||||
import com.ruoyi.common.constant.ShiroConstants;
|
|
||||||
import com.ruoyi.common.constant.UserConstants;
|
|
||||||
import com.ruoyi.common.core.domain.entity.SysUser;
|
|
||||||
import com.ruoyi.common.utils.DateUtils;
|
|
||||||
import com.ruoyi.common.utils.MessageUtils;
|
|
||||||
import com.ruoyi.common.utils.ServletUtils;
|
|
||||||
import com.ruoyi.common.utils.ShiroUtils;
|
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
|
||||||
import com.ruoyi.framework.manager.AsyncManager;
|
|
||||||
import com.ruoyi.framework.manager.factory.AsyncFactory;
|
|
||||||
import com.ruoyi.system.service.ISysUserService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 注册校验方法
|
|
||||||
*
|
|
||||||
* @author ruoyi
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class SysRegisterService
|
|
||||||
{
|
|
||||||
@Autowired
|
|
||||||
private ISysUserService userService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private SysPasswordService passwordService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 注册
|
|
||||||
*/
|
|
||||||
public String register(SysUser user)
|
|
||||||
{
|
|
||||||
String msg = "", loginName = user.getLoginName(), password = user.getPassword();
|
|
||||||
|
|
||||||
if (ShiroConstants.CAPTCHA_ERROR.equals(ServletUtils.getRequest().getAttribute(ShiroConstants.CURRENT_CAPTCHA)))
|
|
||||||
{
|
|
||||||
msg = "验证码错误";
|
|
||||||
}
|
|
||||||
else if (StringUtils.isEmpty(loginName))
|
|
||||||
{
|
|
||||||
msg = "用户名不能为空";
|
|
||||||
}
|
|
||||||
else if (StringUtils.isEmpty(password))
|
|
||||||
{
|
|
||||||
msg = "用户密码不能为空";
|
|
||||||
}
|
|
||||||
else if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
|
|
||||||
|| password.length() > UserConstants.PASSWORD_MAX_LENGTH)
|
|
||||||
{
|
|
||||||
msg = "密码长度必须在5到20个字符之间";
|
|
||||||
}
|
|
||||||
else if (loginName.length() < UserConstants.USERNAME_MIN_LENGTH
|
|
||||||
|| loginName.length() > UserConstants.USERNAME_MAX_LENGTH)
|
|
||||||
{
|
|
||||||
msg = "账户长度必须在2到20个字符之间";
|
|
||||||
}
|
|
||||||
else if (UserConstants.USER_NAME_NOT_UNIQUE.equals(userService.checkLoginNameUnique(user)))
|
|
||||||
{
|
|
||||||
msg = "保存用户'" + loginName + "'失败,注册账号已存在";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
user.setPwdUpdateDate(DateUtils.getNowDate());
|
|
||||||
user.setUserName(loginName);
|
|
||||||
user.setSalt(ShiroUtils.randomSalt());
|
|
||||||
user.setPassword(passwordService.encryptPassword(loginName, password, user.getSalt()));
|
|
||||||
boolean regFlag = userService.registerUser(user);
|
|
||||||
if (!regFlag)
|
|
||||||
{
|
|
||||||
msg = "注册失败,请联系系统管理人员";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
AsyncManager.me().execute(AsyncFactory.recordLogininfor(loginName, Constants.REGISTER, MessageUtils.message("user.register.success")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
package com.ruoyi.framework.shiro.service;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import org.apache.shiro.session.Session;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
|
||||||
import com.ruoyi.framework.shiro.session.OnlineSession;
|
|
||||||
import com.ruoyi.system.domain.SysUserOnline;
|
|
||||||
import com.ruoyi.system.service.ISysUserOnlineService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 会话db操作处理
|
|
||||||
*
|
|
||||||
* @author ruoyi
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class SysShiroService
|
|
||||||
{
|
|
||||||
@Autowired
|
|
||||||
private ISysUserOnlineService onlineService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除会话
|
|
||||||
*
|
|
||||||
* @param onlineSession 会话信息
|
|
||||||
*/
|
|
||||||
public void deleteSession(OnlineSession onlineSession)
|
|
||||||
{
|
|
||||||
onlineService.deleteOnlineById(String.valueOf(onlineSession.getId()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取会话信息
|
|
||||||
*
|
|
||||||
* @param sessionId
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public Session getSession(Serializable sessionId)
|
|
||||||
{
|
|
||||||
SysUserOnline userOnline = onlineService.selectOnlineById(String.valueOf(sessionId));
|
|
||||||
return StringUtils.isNull(userOnline) ? null : createSession(userOnline);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Session createSession(SysUserOnline userOnline)
|
|
||||||
{
|
|
||||||
OnlineSession onlineSession = new OnlineSession();
|
|
||||||
if (StringUtils.isNotNull(userOnline))
|
|
||||||
{
|
|
||||||
onlineSession.setId(userOnline.getSessionId());
|
|
||||||
onlineSession.setHost(userOnline.getIpaddr());
|
|
||||||
onlineSession.setBrowser(userOnline.getBrowser());
|
|
||||||
onlineSession.setOs(userOnline.getOs());
|
|
||||||
onlineSession.setDeptName(userOnline.getDeptName());
|
|
||||||
onlineSession.setLoginName(userOnline.getLoginName());
|
|
||||||
onlineSession.setStartTimestamp(userOnline.getStartTimestamp());
|
|
||||||
onlineSession.setLastAccessTime(userOnline.getLastAccessTime());
|
|
||||||
onlineSession.setTimeout(userOnline.getExpireTime());
|
|
||||||
}
|
|
||||||
return onlineSession;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,165 +0,0 @@
|
||||||
package com.ruoyi.framework.shiro.session;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
|
||||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
|
||||||
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;
|
|
||||||
|
|
||||||
/** 用户头像 */
|
|
||||||
private String avatar;
|
|
||||||
|
|
||||||
/** 登录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;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAvatar() {
|
|
||||||
return avatar;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAvatar(String avatar) {
|
|
||||||
this.avatar = avatar;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setAttribute(Object key, Object value)
|
|
||||||
{
|
|
||||||
super.setAttribute(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object removeAttribute(Object key)
|
|
||||||
{
|
|
||||||
return super.removeAttribute(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
|
|
||||||
.append("userId", getUserId())
|
|
||||||
.append("loginName", getLoginName())
|
|
||||||
.append("deptName", getDeptName())
|
|
||||||
.append("avatar", getAvatar())
|
|
||||||
.append("host", getHost())
|
|
||||||
.append("browser", getBrowser())
|
|
||||||
.append("os", getOs())
|
|
||||||
.append("status", getStatus())
|
|
||||||
.append("attributeChanged", isAttributeChanged())
|
|
||||||
.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,117 +0,0 @@
|
||||||
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.UnknownSessionException;
|
|
||||||
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.framework.shiro.service.SysShiroService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 针对自定义的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 SysShiroService sysShiroService;
|
|
||||||
|
|
||||||
public OnlineSessionDAO()
|
|
||||||
{
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
public OnlineSessionDAO(long expireTime)
|
|
||||||
{
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据会话ID获取会话
|
|
||||||
*
|
|
||||||
* @param sessionId 会话ID
|
|
||||||
* @return ShiroSession
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected Session doReadSession(Serializable sessionId)
|
|
||||||
{
|
|
||||||
return sysShiroService.getSession(sessionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void update(Session session) throws UnknownSessionException
|
|
||||||
{
|
|
||||||
super.update(session);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新会话;如更新会话最后访问时间/停止会话/设置超时时间/设置移除属性等会调用
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
// isGuest = true 访客
|
|
||||||
boolean isGuest = onlineSession.getUserId() == null || onlineSession.getUserId() == 0L;
|
|
||||||
|
|
||||||
// session 数据变更了 同步
|
|
||||||
if (!isGuest && onlineSession.isAttributeChanged())
|
|
||||||
{
|
|
||||||
needSync = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!needSync)
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
sysShiroService.deleteSession(onlineSession);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
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 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(request.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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
package com.ruoyi.framework.shiro.util;
|
|
||||||
|
|
||||||
import org.apache.shiro.SecurityUtils;
|
|
||||||
import org.apache.shiro.mgt.RealmSecurityManager;
|
|
||||||
import com.ruoyi.framework.shiro.realm.UserRealm;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户授权信息
|
|
||||||
*
|
|
||||||
* @author ruoyi
|
|
||||||
*/
|
|
||||||
public class AuthorizationUtils
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* 清理所有用户授权信息缓存
|
|
||||||
*/
|
|
||||||
public static void clearAllCachedAuthorizationInfo()
|
|
||||||
{
|
|
||||||
getUserRealm().clearAllCachedAuthorizationInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取自定义Realm
|
|
||||||
*/
|
|
||||||
public static UserRealm getUserRealm()
|
|
||||||
{
|
|
||||||
RealmSecurityManager rsm = (RealmSecurityManager) SecurityUtils.getSecurityManager();
|
|
||||||
return (UserRealm) rsm.getRealms().iterator().next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
package com.ruoyi.framework.shiro.web;
|
|
||||||
|
|
||||||
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
|
|
||||||
import org.apache.shiro.web.filter.InvalidRequestFilter;
|
|
||||||
import org.apache.shiro.web.filter.mgt.DefaultFilter;
|
|
||||||
import org.apache.shiro.web.filter.mgt.FilterChainManager;
|
|
||||||
import org.apache.shiro.web.filter.mgt.FilterChainResolver;
|
|
||||||
import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
|
|
||||||
import org.apache.shiro.web.mgt.WebSecurityManager;
|
|
||||||
import org.apache.shiro.web.servlet.AbstractShiroFilter;
|
|
||||||
import org.apache.shiro.mgt.SecurityManager;
|
|
||||||
import org.springframework.beans.factory.BeanInitializationException;
|
|
||||||
import javax.servlet.Filter;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 自定义ShiroFilterFactoryBean解决资源中文路径问题
|
|
||||||
*
|
|
||||||
* @author ruoyi
|
|
||||||
*/
|
|
||||||
public class CustomShiroFilterFactoryBean extends ShiroFilterFactoryBean
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public Class<MySpringShiroFilter> getObjectType()
|
|
||||||
{
|
|
||||||
return MySpringShiroFilter.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected AbstractShiroFilter createInstance() throws Exception
|
|
||||||
{
|
|
||||||
|
|
||||||
SecurityManager securityManager = getSecurityManager();
|
|
||||||
if (securityManager == null)
|
|
||||||
{
|
|
||||||
String msg = "SecurityManager property must be set.";
|
|
||||||
throw new BeanInitializationException(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(securityManager instanceof WebSecurityManager))
|
|
||||||
{
|
|
||||||
String msg = "The security manager does not implement the WebSecurityManager interface.";
|
|
||||||
throw new BeanInitializationException(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
FilterChainManager manager = createFilterChainManager();
|
|
||||||
// Expose the constructed FilterChainManager by first wrapping it in a
|
|
||||||
// FilterChainResolver implementation. The AbstractShiroFilter implementations
|
|
||||||
// do not know about FilterChainManagers - only resolvers:
|
|
||||||
PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
|
|
||||||
chainResolver.setFilterChainManager(manager);
|
|
||||||
|
|
||||||
Map<String, Filter> filterMap = manager.getFilters();
|
|
||||||
Filter invalidRequestFilter = filterMap.get(DefaultFilter.invalidRequest.name());
|
|
||||||
if (invalidRequestFilter instanceof InvalidRequestFilter)
|
|
||||||
{
|
|
||||||
// 此处是关键,设置false跳过URL携带中文400,servletPath中文校验bug
|
|
||||||
((InvalidRequestFilter) invalidRequestFilter).setBlockNonAscii(false);
|
|
||||||
}
|
|
||||||
// Now create a concrete ShiroFilter instance and apply the acquired SecurityManager and built
|
|
||||||
// FilterChainResolver. It doesn't matter that the instance is an anonymous inner class
|
|
||||||
// here - we're just using it because it is a concrete AbstractShiroFilter instance that accepts
|
|
||||||
// injection of the SecurityManager and FilterChainResolver:
|
|
||||||
return new MySpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class MySpringShiroFilter extends AbstractShiroFilter
|
|
||||||
{
|
|
||||||
protected MySpringShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver resolver)
|
|
||||||
{
|
|
||||||
if (webSecurityManager == null)
|
|
||||||
{
|
|
||||||
throw new IllegalArgumentException("WebSecurityManager property cannot be null.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.setSecurityManager(webSecurityManager);
|
|
||||||
if (resolver != null)
|
|
||||||
{
|
|
||||||
this.setFilterChainResolver(resolver);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,90 +0,0 @@
|
||||||
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.core.domain.entity.SysUser;
|
|
||||||
import com.ruoyi.common.utils.MessageUtils;
|
|
||||||
import com.ruoyi.common.utils.ShiroUtils;
|
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
|
||||||
import com.ruoyi.common.utils.spring.SpringUtils;
|
|
||||||
import com.ruoyi.framework.manager.AsyncManager;
|
|
||||||
import com.ruoyi.framework.manager.factory.AsyncFactory;
|
|
||||||
import com.ruoyi.system.service.ISysUserOnlineService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 退出过滤器
|
|
||||||
*
|
|
||||||
* @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.getSysUser();
|
|
||||||
if (StringUtils.isNotNull(user))
|
|
||||||
{
|
|
||||||
String loginName = user.getLoginName();
|
|
||||||
// 记录用户退出日志
|
|
||||||
AsyncManager.me().execute(AsyncFactory.recordLogininfor(loginName, Constants.LOGOUT, MessageUtils.message("user.logout.success")));
|
|
||||||
// 清理缓存
|
|
||||||
SpringUtils.getBean(ISysUserOnlineService.class).removeUserCache(loginName, ShiroUtils.getSessionId());
|
|
||||||
}
|
|
||||||
// 退出登录
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
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.ShiroUtils;
|
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证码过滤器
|
|
||||||
*
|
|
||||||
* @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 : "");
|
|
||||||
// 验证码清除,防止多次使用。
|
|
||||||
request.getSession().removeAttribute(Constants.KAPTCHA_SESSION_KEY);
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,176 +0,0 @@
|
||||||
package com.ruoyi.framework.shiro.web.filter.kickout;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.ArrayDeque;
|
|
||||||
import java.util.Deque;
|
|
||||||
import javax.servlet.ServletRequest;
|
|
||||||
import javax.servlet.ServletResponse;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import org.apache.shiro.cache.Cache;
|
|
||||||
import org.apache.shiro.cache.CacheManager;
|
|
||||||
import org.apache.shiro.session.Session;
|
|
||||||
import org.apache.shiro.session.mgt.DefaultSessionKey;
|
|
||||||
import org.apache.shiro.session.mgt.SessionManager;
|
|
||||||
import org.apache.shiro.subject.Subject;
|
|
||||||
import org.apache.shiro.web.filter.AccessControlFilter;
|
|
||||||
import org.apache.shiro.web.util.WebUtils;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import com.ruoyi.common.constant.ShiroConstants;
|
|
||||||
import com.ruoyi.common.core.domain.AjaxResult;
|
|
||||||
import com.ruoyi.common.core.domain.entity.SysUser;
|
|
||||||
import com.ruoyi.common.utils.ServletUtils;
|
|
||||||
import com.ruoyi.common.utils.ShiroUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 登录帐号控制过滤器
|
|
||||||
*
|
|
||||||
* @author ruoyi
|
|
||||||
*/
|
|
||||||
public class KickoutSessionFilter extends AccessControlFilter
|
|
||||||
{
|
|
||||||
private final static ObjectMapper objectMapper = new ObjectMapper();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 同一个用户最大会话数
|
|
||||||
**/
|
|
||||||
private int maxSession = -1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 踢出之前登录的/之后登录的用户 默认false踢出之前登录的用户
|
|
||||||
**/
|
|
||||||
private Boolean kickoutAfter = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 踢出后到的地址
|
|
||||||
**/
|
|
||||||
private String kickoutUrl;
|
|
||||||
|
|
||||||
private SessionManager sessionManager;
|
|
||||||
private Cache<String, Deque<Serializable>> cache;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o)
|
|
||||||
throws Exception
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception
|
|
||||||
{
|
|
||||||
Subject subject = getSubject(request, response);
|
|
||||||
if (!subject.isAuthenticated() && !subject.isRemembered() || maxSession == -1)
|
|
||||||
{
|
|
||||||
// 如果没有登录或用户最大会话数为-1,直接进行之后的流程
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Session session = subject.getSession();
|
|
||||||
// 当前登录用户
|
|
||||||
SysUser user = ShiroUtils.getSysUser();
|
|
||||||
String loginName = user.getLoginName();
|
|
||||||
Serializable sessionId = session.getId();
|
|
||||||
|
|
||||||
// 读取缓存用户 没有就存入
|
|
||||||
Deque<Serializable> deque = cache.get(loginName);
|
|
||||||
if (deque == null)
|
|
||||||
{
|
|
||||||
// 初始化队列
|
|
||||||
deque = new ArrayDeque<Serializable>();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果队列里没有此sessionId,且用户没有被踢出;放入队列
|
|
||||||
if (!deque.contains(sessionId) && session.getAttribute("kickout") == null)
|
|
||||||
{
|
|
||||||
// 将sessionId存入队列
|
|
||||||
deque.push(sessionId);
|
|
||||||
// 将用户的sessionId队列缓存
|
|
||||||
cache.put(loginName, deque);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果队列里的sessionId数超出最大会话数,开始踢人
|
|
||||||
while (deque.size() > maxSession)
|
|
||||||
{
|
|
||||||
// 是否踢出后来登录的,默认是false;即后者登录的用户踢出前者登录的用户;
|
|
||||||
Serializable kickoutSessionId = kickoutAfter ? deque.removeFirst() : deque.removeLast();
|
|
||||||
// 踢出后再更新下缓存队列
|
|
||||||
cache.put(loginName, deque);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 获取被踢出的sessionId的session对象
|
|
||||||
Session kickoutSession = sessionManager.getSession(new DefaultSessionKey(kickoutSessionId));
|
|
||||||
if (null != kickoutSession)
|
|
||||||
{
|
|
||||||
// 设置会话的kickout属性表示踢出了
|
|
||||||
kickoutSession.setAttribute("kickout", true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
// 面对异常,我们选择忽略
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果被踢出了,(前者或后者)直接退出,重定向到踢出后的地址
|
|
||||||
if (session.getAttribute("kickout") != null && (Boolean) session.getAttribute("kickout") == true)
|
|
||||||
{
|
|
||||||
// 退出登录
|
|
||||||
subject.logout();
|
|
||||||
saveRequest(request);
|
|
||||||
return isAjaxResponse(request, response);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
return isAjaxResponse(request, response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isAjaxResponse(ServletRequest request, ServletResponse response) throws IOException
|
|
||||||
{
|
|
||||||
HttpServletRequest req = (HttpServletRequest) request;
|
|
||||||
HttpServletResponse res = (HttpServletResponse) response;
|
|
||||||
if (ServletUtils.isAjaxRequest(req))
|
|
||||||
{
|
|
||||||
AjaxResult ajaxResult = AjaxResult.error("您已在别处登录,请您修改密码或重新登录");
|
|
||||||
ServletUtils.renderString(res, objectMapper.writeValueAsString(ajaxResult));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
WebUtils.issueRedirect(request, response, kickoutUrl);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMaxSession(int maxSession)
|
|
||||||
{
|
|
||||||
this.maxSession = maxSession;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setKickoutAfter(boolean kickoutAfter)
|
|
||||||
{
|
|
||||||
this.kickoutAfter = kickoutAfter;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setKickoutUrl(String kickoutUrl)
|
|
||||||
{
|
|
||||||
this.kickoutUrl = kickoutUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSessionManager(SessionManager sessionManager)
|
|
||||||
{
|
|
||||||
this.sessionManager = sessionManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置Cache的key的前缀
|
|
||||||
public void setCacheManager(CacheManager cacheManager)
|
|
||||||
{
|
|
||||||
// 必须和ehcache缓存配置中的缓存name一致
|
|
||||||
this.cache = cacheManager.getCache(ShiroConstants.SYS_USERCACHE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,99 +0,0 @@
|
||||||
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.Value;
|
|
||||||
import com.ruoyi.common.constant.ShiroConstants;
|
|
||||||
import com.ruoyi.common.core.domain.entity.SysUser;
|
|
||||||
import com.ruoyi.common.enums.OnlineStatus;
|
|
||||||
import com.ruoyi.common.utils.ShiroUtils;
|
|
||||||
import com.ruoyi.framework.shiro.session.OnlineSession;
|
|
||||||
import com.ruoyi.framework.shiro.session.OnlineSessionDAO;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 自定义访问控制
|
|
||||||
*
|
|
||||||
* @author ruoyi
|
|
||||||
*/
|
|
||||||
public class OnlineSessionFilter extends AccessControlFilter
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* 强制退出后重定向的地址
|
|
||||||
*/
|
|
||||||
@Value("${shiro.user.loginUrl}")
|
|
||||||
private String loginUrl;
|
|
||||||
|
|
||||||
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.getSysUser();
|
|
||||||
if (user != null)
|
|
||||||
{
|
|
||||||
onlineSession.setUserId(user.getUserId());
|
|
||||||
onlineSession.setLoginName(user.getLoginName());
|
|
||||||
onlineSession.setAvatar(user.getAvatar());
|
|
||||||
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 false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 跳转到登录页
|
|
||||||
@Override
|
|
||||||
protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException
|
|
||||||
{
|
|
||||||
WebUtils.issueRedirect(request, response, loginUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOnlineSessionDAO(OnlineSessionDAO onlineSessionDAO)
|
|
||||||
{
|
|
||||||
this.onlineSessionDAO = onlineSessionDAO;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
package com.ruoyi.framework.shiro.web.filter.sync;
|
|
||||||
|
|
||||||
import javax.servlet.ServletRequest;
|
|
||||||
import javax.servlet.ServletResponse;
|
|
||||||
import org.apache.shiro.web.filter.PathMatchingFilter;
|
|
||||||
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
|
|
||||||
{
|
|
||||||
private OnlineSessionDAO onlineSessionDAO;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 同步会话数据到DB 一次请求最多同步一次 防止过多处理 需要放到Shiro过滤器之前
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOnlineSessionDAO(OnlineSessionDAO onlineSessionDAO)
|
|
||||||
{
|
|
||||||
this.onlineSessionDAO = onlineSessionDAO;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,175 +0,0 @@
|
||||||
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.common.utils.StringUtils;
|
|
||||||
import com.ruoyi.common.utils.bean.BeanUtils;
|
|
||||||
import com.ruoyi.common.utils.spring.SpringUtils;
|
|
||||||
import com.ruoyi.framework.shiro.session.OnlineSession;
|
|
||||||
import com.ruoyi.system.domain.SysUserOnline;
|
|
||||||
import com.ruoyi.system.service.ISysUserOnlineService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 主要是在此如果会话的属性修改了 就标识下其修改了 然后方便 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 session = getOnlineSession(sessionKey);
|
|
||||||
session.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 = getOnlineSession(sessionKey);
|
|
||||||
s.markAttributeChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
return removed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OnlineSession getOnlineSession(SessionKey sessionKey)
|
|
||||||
{
|
|
||||||
OnlineSession session = null;
|
|
||||||
Object obj = doGetSession(sessionKey);
|
|
||||||
if (StringUtils.isNotNull(obj))
|
|
||||||
{
|
|
||||||
session = new OnlineSession();
|
|
||||||
BeanUtils.copyBeanProp(session, obj);
|
|
||||||
}
|
|
||||||
return session;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证session是否有效 用于删除过期session
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void validateSessions()
|
|
||||||
{
|
|
||||||
if (log.isInfoEnabled())
|
|
||||||
{
|
|
||||||
log.info("invalidation sessions...");
|
|
||||||
}
|
|
||||||
|
|
||||||
int invalidCount = 0;
|
|
||||||
|
|
||||||
int timeout = (int) this.getGlobalSessionTimeout();
|
|
||||||
if (timeout < 0)
|
|
||||||
{
|
|
||||||
// 永不过期不进行处理
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Date expiredDate = DateUtils.addMilliseconds(new Date(), 0 - timeout);
|
|
||||||
ISysUserOnlineService userOnlineService = SpringUtils.getBean(ISysUserOnlineService.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());
|
|
||||||
userOnlineService.removeUserCache(userOnline.getLoginName(), 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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,131 +0,0 @@
|
||||||
package com.ruoyi.framework.shiro.web.session;
|
|
||||||
|
|
||||||
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;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.context.annotation.Lazy;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import com.ruoyi.common.utils.Threads;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 自定义任务调度器完成
|
|
||||||
*
|
|
||||||
* @author ruoyi
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 定时器,用于处理超时的挂起请求,也用于连接断开时的重连。
|
|
||||||
*/
|
|
||||||
@Autowired
|
|
||||||
@Qualifier("scheduledExecutorService")
|
|
||||||
private ScheduledExecutorService executorService;
|
|
||||||
|
|
||||||
private volatile boolean enabled = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 会话验证管理器
|
|
||||||
*/
|
|
||||||
@Autowired
|
|
||||||
@Qualifier("sessionManager")
|
|
||||||
@Lazy
|
|
||||||
private ValidatingSessionManager sessionManager;
|
|
||||||
|
|
||||||
// 相隔多久检查一次session的有效性,单位毫秒,默认就是10分钟
|
|
||||||
@Value("${shiro.session.validationInterval}")
|
|
||||||
private long sessionValidationInterval;
|
|
||||||
|
|
||||||
@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 * 60 * 1000, 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...");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.enabled)
|
|
||||||
{
|
|
||||||
Threads.shutdownAndAwaitTermination(executorService);
|
|
||||||
}
|
|
||||||
this.enabled = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,241 +0,0 @@
|
||||||
package com.ruoyi.framework.web.domain;
|
|
||||||
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Properties;
|
|
||||||
import com.ruoyi.common.utils.Arith;
|
|
||||||
import com.ruoyi.common.utils.IpUtils;
|
|
||||||
import com.ruoyi.framework.web.domain.server.Cpu;
|
|
||||||
import com.ruoyi.framework.web.domain.server.Jvm;
|
|
||||||
import com.ruoyi.framework.web.domain.server.Mem;
|
|
||||||
import com.ruoyi.framework.web.domain.server.Sys;
|
|
||||||
import com.ruoyi.framework.web.domain.server.SysFile;
|
|
||||||
import oshi.SystemInfo;
|
|
||||||
import oshi.hardware.CentralProcessor;
|
|
||||||
import oshi.hardware.CentralProcessor.TickType;
|
|
||||||
import oshi.hardware.GlobalMemory;
|
|
||||||
import oshi.hardware.HardwareAbstractionLayer;
|
|
||||||
import oshi.software.os.FileSystem;
|
|
||||||
import oshi.software.os.OSFileStore;
|
|
||||||
import oshi.software.os.OperatingSystem;
|
|
||||||
import oshi.util.Util;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 服务器相关信息
|
|
||||||
*
|
|
||||||
* @author ruoyi
|
|
||||||
*/
|
|
||||||
public class Server
|
|
||||||
{
|
|
||||||
|
|
||||||
private static final int OSHI_WAIT_SECOND = 1000;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CPU相关信息
|
|
||||||
*/
|
|
||||||
private Cpu cpu = new Cpu();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 內存相关信息
|
|
||||||
*/
|
|
||||||
private Mem mem = new Mem();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JVM相关信息
|
|
||||||
*/
|
|
||||||
private Jvm jvm = new Jvm();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 服务器相关信息
|
|
||||||
*/
|
|
||||||
private Sys sys = new Sys();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 磁盘相关信息
|
|
||||||
*/
|
|
||||||
private List<SysFile> sysFiles = new LinkedList<SysFile>();
|
|
||||||
|
|
||||||
public Cpu getCpu()
|
|
||||||
{
|
|
||||||
return cpu;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCpu(Cpu cpu)
|
|
||||||
{
|
|
||||||
this.cpu = cpu;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Mem getMem()
|
|
||||||
{
|
|
||||||
return mem;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMem(Mem mem)
|
|
||||||
{
|
|
||||||
this.mem = mem;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Jvm getJvm()
|
|
||||||
{
|
|
||||||
return jvm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setJvm(Jvm jvm)
|
|
||||||
{
|
|
||||||
this.jvm = jvm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Sys getSys()
|
|
||||||
{
|
|
||||||
return sys;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSys(Sys sys)
|
|
||||||
{
|
|
||||||
this.sys = sys;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<SysFile> getSysFiles()
|
|
||||||
{
|
|
||||||
return sysFiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSysFiles(List<SysFile> sysFiles)
|
|
||||||
{
|
|
||||||
this.sysFiles = sysFiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void copyTo() throws Exception
|
|
||||||
{
|
|
||||||
SystemInfo si = new SystemInfo();
|
|
||||||
HardwareAbstractionLayer hal = si.getHardware();
|
|
||||||
|
|
||||||
setCpuInfo(hal.getProcessor());
|
|
||||||
|
|
||||||
setMemInfo(hal.getMemory());
|
|
||||||
|
|
||||||
setSysInfo();
|
|
||||||
|
|
||||||
setJvmInfo();
|
|
||||||
|
|
||||||
setSysFiles(si.getOperatingSystem());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置CPU信息
|
|
||||||
*/
|
|
||||||
private void setCpuInfo(CentralProcessor processor)
|
|
||||||
{
|
|
||||||
// CPU信息
|
|
||||||
long[] prevTicks = processor.getSystemCpuLoadTicks();
|
|
||||||
Util.sleep(OSHI_WAIT_SECOND);
|
|
||||||
long[] ticks = processor.getSystemCpuLoadTicks();
|
|
||||||
long nice = ticks[TickType.NICE.getIndex()] - prevTicks[TickType.NICE.getIndex()];
|
|
||||||
long irq = ticks[TickType.IRQ.getIndex()] - prevTicks[TickType.IRQ.getIndex()];
|
|
||||||
long softirq = ticks[TickType.SOFTIRQ.getIndex()] - prevTicks[TickType.SOFTIRQ.getIndex()];
|
|
||||||
long steal = ticks[TickType.STEAL.getIndex()] - prevTicks[TickType.STEAL.getIndex()];
|
|
||||||
long cSys = ticks[TickType.SYSTEM.getIndex()] - prevTicks[TickType.SYSTEM.getIndex()];
|
|
||||||
long user = ticks[TickType.USER.getIndex()] - prevTicks[TickType.USER.getIndex()];
|
|
||||||
long iowait = ticks[TickType.IOWAIT.getIndex()] - prevTicks[TickType.IOWAIT.getIndex()];
|
|
||||||
long idle = ticks[TickType.IDLE.getIndex()] - prevTicks[TickType.IDLE.getIndex()];
|
|
||||||
long totalCpu = user + nice + cSys + idle + iowait + irq + softirq + steal;
|
|
||||||
cpu.setCpuNum(processor.getLogicalProcessorCount());
|
|
||||||
cpu.setTotal(totalCpu);
|
|
||||||
cpu.setSys(cSys);
|
|
||||||
cpu.setUsed(user);
|
|
||||||
cpu.setWait(iowait);
|
|
||||||
cpu.setFree(idle);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置内存信息
|
|
||||||
*/
|
|
||||||
private void setMemInfo(GlobalMemory memory)
|
|
||||||
{
|
|
||||||
mem.setTotal(memory.getTotal());
|
|
||||||
mem.setUsed(memory.getTotal() - memory.getAvailable());
|
|
||||||
mem.setFree(memory.getAvailable());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置服务器信息
|
|
||||||
*/
|
|
||||||
private void setSysInfo()
|
|
||||||
{
|
|
||||||
Properties props = System.getProperties();
|
|
||||||
sys.setComputerName(IpUtils.getHostName());
|
|
||||||
sys.setComputerIp(IpUtils.getHostIp());
|
|
||||||
sys.setOsName(props.getProperty("os.name"));
|
|
||||||
sys.setOsArch(props.getProperty("os.arch"));
|
|
||||||
sys.setUserDir(props.getProperty("user.dir"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置Java虚拟机
|
|
||||||
*/
|
|
||||||
private void setJvmInfo() throws UnknownHostException
|
|
||||||
{
|
|
||||||
Properties props = System.getProperties();
|
|
||||||
jvm.setTotal(Runtime.getRuntime().totalMemory());
|
|
||||||
jvm.setMax(Runtime.getRuntime().maxMemory());
|
|
||||||
jvm.setFree(Runtime.getRuntime().freeMemory());
|
|
||||||
jvm.setVersion(props.getProperty("java.version"));
|
|
||||||
jvm.setHome(props.getProperty("java.home"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置磁盘信息
|
|
||||||
*/
|
|
||||||
private void setSysFiles(OperatingSystem os)
|
|
||||||
{
|
|
||||||
FileSystem fileSystem = os.getFileSystem();
|
|
||||||
List<OSFileStore> fsArray = fileSystem.getFileStores();
|
|
||||||
for (OSFileStore fs : fsArray)
|
|
||||||
{
|
|
||||||
long free = fs.getUsableSpace();
|
|
||||||
long total = fs.getTotalSpace();
|
|
||||||
long used = total - free;
|
|
||||||
SysFile sysFile = new SysFile();
|
|
||||||
sysFile.setDirName(fs.getMount());
|
|
||||||
sysFile.setSysTypeName(fs.getType());
|
|
||||||
sysFile.setTypeName(fs.getName());
|
|
||||||
sysFile.setTotal(convertFileSize(total));
|
|
||||||
sysFile.setFree(convertFileSize(free));
|
|
||||||
sysFile.setUsed(convertFileSize(used));
|
|
||||||
sysFile.setUsage(Arith.mul(Arith.div(used, total, 4), 100));
|
|
||||||
sysFiles.add(sysFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 字节转换
|
|
||||||
*
|
|
||||||
* @param size 字节大小
|
|
||||||
* @return 转换后值
|
|
||||||
*/
|
|
||||||
public String convertFileSize(long size)
|
|
||||||
{
|
|
||||||
long kb = 1024;
|
|
||||||
long mb = kb * 1024;
|
|
||||||
long gb = mb * 1024;
|
|
||||||
if (size >= gb)
|
|
||||||
{
|
|
||||||
return String.format("%.1f GB", (float) size / gb);
|
|
||||||
}
|
|
||||||
else if (size >= mb)
|
|
||||||
{
|
|
||||||
float f = (float) size / mb;
|
|
||||||
return String.format(f > 100 ? "%.0f MB" : "%.1f MB", f);
|
|
||||||
}
|
|
||||||
else if (size >= kb)
|
|
||||||
{
|
|
||||||
float f = (float) size / kb;
|
|
||||||
return String.format(f > 100 ? "%.0f KB" : "%.1f KB", f);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return String.format("%d B", size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,101 +0,0 @@
|
||||||
package com.ruoyi.framework.web.domain.server;
|
|
||||||
|
|
||||||
import com.ruoyi.common.utils.Arith;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CPU相关信息
|
|
||||||
*
|
|
||||||
* @author ruoyi
|
|
||||||
*/
|
|
||||||
public class Cpu
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* 核心数
|
|
||||||
*/
|
|
||||||
private int cpuNum;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CPU总的使用率
|
|
||||||
*/
|
|
||||||
private double total;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CPU系统使用率
|
|
||||||
*/
|
|
||||||
private double sys;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CPU用户使用率
|
|
||||||
*/
|
|
||||||
private double used;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CPU当前等待率
|
|
||||||
*/
|
|
||||||
private double wait;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CPU当前空闲率
|
|
||||||
*/
|
|
||||||
private double free;
|
|
||||||
|
|
||||||
public int getCpuNum()
|
|
||||||
{
|
|
||||||
return cpuNum;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCpuNum(int cpuNum)
|
|
||||||
{
|
|
||||||
this.cpuNum = cpuNum;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getTotal()
|
|
||||||
{
|
|
||||||
return Arith.round(Arith.mul(total, 100), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTotal(double total)
|
|
||||||
{
|
|
||||||
this.total = total;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getSys()
|
|
||||||
{
|
|
||||||
return Arith.round(Arith.mul(sys / total, 100), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSys(double sys)
|
|
||||||
{
|
|
||||||
this.sys = sys;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getUsed()
|
|
||||||
{
|
|
||||||
return Arith.round(Arith.mul(used / total, 100), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUsed(double used)
|
|
||||||
{
|
|
||||||
this.used = used;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getWait()
|
|
||||||
{
|
|
||||||
return Arith.round(Arith.mul(wait / total, 100), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setWait(double wait)
|
|
||||||
{
|
|
||||||
this.wait = wait;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getFree()
|
|
||||||
{
|
|
||||||
return Arith.round(Arith.mul(free / total, 100), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFree(double free)
|
|
||||||
{
|
|
||||||
this.free = free;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,130 +0,0 @@
|
||||||
package com.ruoyi.framework.web.domain.server;
|
|
||||||
|
|
||||||
import java.lang.management.ManagementFactory;
|
|
||||||
import com.ruoyi.common.utils.Arith;
|
|
||||||
import com.ruoyi.common.utils.DateUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JVM相关信息
|
|
||||||
*
|
|
||||||
* @author ruoyi
|
|
||||||
*/
|
|
||||||
public class Jvm
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* 当前JVM占用的内存总数(M)
|
|
||||||
*/
|
|
||||||
private double total;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JVM最大可用内存总数(M)
|
|
||||||
*/
|
|
||||||
private double max;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JVM空闲内存(M)
|
|
||||||
*/
|
|
||||||
private double free;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JDK版本
|
|
||||||
*/
|
|
||||||
private String version;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JDK路径
|
|
||||||
*/
|
|
||||||
private String home;
|
|
||||||
|
|
||||||
public double getTotal()
|
|
||||||
{
|
|
||||||
return Arith.div(total, (1024 * 1024), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTotal(double total)
|
|
||||||
{
|
|
||||||
this.total = total;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getMax()
|
|
||||||
{
|
|
||||||
return Arith.div(max, (1024 * 1024), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMax(double max)
|
|
||||||
{
|
|
||||||
this.max = max;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getFree()
|
|
||||||
{
|
|
||||||
return Arith.div(free, (1024 * 1024), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFree(double free)
|
|
||||||
{
|
|
||||||
this.free = free;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getUsed()
|
|
||||||
{
|
|
||||||
return Arith.div(total - free, (1024 * 1024), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getUsage()
|
|
||||||
{
|
|
||||||
return Arith.mul(Arith.div(total - free, total, 4), 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取JDK名称
|
|
||||||
*/
|
|
||||||
public String getName()
|
|
||||||
{
|
|
||||||
return ManagementFactory.getRuntimeMXBean().getVmName();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getVersion()
|
|
||||||
{
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setVersion(String version)
|
|
||||||
{
|
|
||||||
this.version = version;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getHome()
|
|
||||||
{
|
|
||||||
return home;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setHome(String home)
|
|
||||||
{
|
|
||||||
this.home = home;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JDK启动时间
|
|
||||||
*/
|
|
||||||
public String getStartTime()
|
|
||||||
{
|
|
||||||
return DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, DateUtils.getServerStartDate());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JDK运行时间
|
|
||||||
*/
|
|
||||||
public String getRunTime()
|
|
||||||
{
|
|
||||||
return DateUtils.getDatePoor(DateUtils.getNowDate(), DateUtils.getServerStartDate());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 运行参数
|
|
||||||
*/
|
|
||||||
public String getInputArgs()
|
|
||||||
{
|
|
||||||
return ManagementFactory.getRuntimeMXBean().getInputArguments().toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
package com.ruoyi.framework.web.domain.server;
|
|
||||||
|
|
||||||
import com.ruoyi.common.utils.Arith;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 內存相关信息
|
|
||||||
*
|
|
||||||
* @author ruoyi
|
|
||||||
*/
|
|
||||||
public class Mem
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* 内存总量
|
|
||||||
*/
|
|
||||||
private double total;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 已用内存
|
|
||||||
*/
|
|
||||||
private double used;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 剩余内存
|
|
||||||
*/
|
|
||||||
private double free;
|
|
||||||
|
|
||||||
public double getTotal()
|
|
||||||
{
|
|
||||||
return Arith.div(total, (1024 * 1024 * 1024), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTotal(long total)
|
|
||||||
{
|
|
||||||
this.total = total;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getUsed()
|
|
||||||
{
|
|
||||||
return Arith.div(used, (1024 * 1024 * 1024), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUsed(long used)
|
|
||||||
{
|
|
||||||
this.used = used;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getFree()
|
|
||||||
{
|
|
||||||
return Arith.div(free, (1024 * 1024 * 1024), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFree(long free)
|
|
||||||
{
|
|
||||||
this.free = free;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getUsage()
|
|
||||||
{
|
|
||||||
return Arith.mul(Arith.div(used, total, 4), 100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
package com.ruoyi.framework.web.domain.server;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 系统相关信息
|
|
||||||
*
|
|
||||||
* @author ruoyi
|
|
||||||
*/
|
|
||||||
public class Sys
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* 服务器名称
|
|
||||||
*/
|
|
||||||
private String computerName;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 服务器Ip
|
|
||||||
*/
|
|
||||||
private String computerIp;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 项目路径
|
|
||||||
*/
|
|
||||||
private String userDir;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 操作系统
|
|
||||||
*/
|
|
||||||
private String osName;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 系统架构
|
|
||||||
*/
|
|
||||||
private String osArch;
|
|
||||||
|
|
||||||
public String getComputerName()
|
|
||||||
{
|
|
||||||
return computerName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setComputerName(String computerName)
|
|
||||||
{
|
|
||||||
this.computerName = computerName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getComputerIp()
|
|
||||||
{
|
|
||||||
return computerIp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setComputerIp(String computerIp)
|
|
||||||
{
|
|
||||||
this.computerIp = computerIp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUserDir()
|
|
||||||
{
|
|
||||||
return userDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUserDir(String userDir)
|
|
||||||
{
|
|
||||||
this.userDir = userDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getOsName()
|
|
||||||
{
|
|
||||||
return osName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOsName(String osName)
|
|
||||||
{
|
|
||||||
this.osName = osName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getOsArch()
|
|
||||||
{
|
|
||||||
return osArch;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOsArch(String osArch)
|
|
||||||
{
|
|
||||||
this.osArch = osArch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,114 +0,0 @@
|
||||||
package com.ruoyi.framework.web.domain.server;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 系统文件相关信息
|
|
||||||
*
|
|
||||||
* @author ruoyi
|
|
||||||
*/
|
|
||||||
public class SysFile
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* 盘符路径
|
|
||||||
*/
|
|
||||||
private String dirName;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 盘符类型
|
|
||||||
*/
|
|
||||||
private String sysTypeName;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 文件类型
|
|
||||||
*/
|
|
||||||
private String typeName;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 总大小
|
|
||||||
*/
|
|
||||||
private String total;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 剩余大小
|
|
||||||
*/
|
|
||||||
private String free;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 已经使用量
|
|
||||||
*/
|
|
||||||
private String used;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 资源的使用率
|
|
||||||
*/
|
|
||||||
private double usage;
|
|
||||||
|
|
||||||
public String getDirName()
|
|
||||||
{
|
|
||||||
return dirName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDirName(String dirName)
|
|
||||||
{
|
|
||||||
this.dirName = dirName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSysTypeName()
|
|
||||||
{
|
|
||||||
return sysTypeName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSysTypeName(String sysTypeName)
|
|
||||||
{
|
|
||||||
this.sysTypeName = sysTypeName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTypeName()
|
|
||||||
{
|
|
||||||
return typeName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTypeName(String typeName)
|
|
||||||
{
|
|
||||||
this.typeName = typeName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTotal()
|
|
||||||
{
|
|
||||||
return total;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTotal(String total)
|
|
||||||
{
|
|
||||||
this.total = total;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getFree()
|
|
||||||
{
|
|
||||||
return free;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFree(String free)
|
|
||||||
{
|
|
||||||
this.free = free;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUsed()
|
|
||||||
{
|
|
||||||
return used;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUsed(String used)
|
|
||||||
{
|
|
||||||
this.used = used;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getUsage()
|
|
||||||
{
|
|
||||||
return usage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUsage(double usage)
|
|
||||||
{
|
|
||||||
this.usage = usage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,116 +0,0 @@
|
||||||
package com.ruoyi.framework.web.exception;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import org.apache.shiro.authz.AuthorizationException;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.validation.BindException;
|
|
||||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
|
||||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
|
||||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
|
||||||
import org.springframework.web.servlet.ModelAndView;
|
|
||||||
import com.ruoyi.common.core.domain.AjaxResult;
|
|
||||||
import com.ruoyi.common.exception.DemoModeException;
|
|
||||||
import com.ruoyi.common.exception.ServiceException;
|
|
||||||
import com.ruoyi.common.utils.ServletUtils;
|
|
||||||
import com.ruoyi.common.utils.security.PermissionUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 全局异常处理器
|
|
||||||
*
|
|
||||||
* @author ruoyi
|
|
||||||
*/
|
|
||||||
@RestControllerAdvice
|
|
||||||
public class GlobalExceptionHandler
|
|
||||||
{
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 权限校验异常(ajax请求返回json,redirect请求跳转页面)
|
|
||||||
*/
|
|
||||||
@ExceptionHandler(AuthorizationException.class)
|
|
||||||
public Object handleAuthorizationException(AuthorizationException e, HttpServletRequest request)
|
|
||||||
{
|
|
||||||
String requestURI = request.getRequestURI();
|
|
||||||
log.error("请求地址'{}',权限校验失败'{}'", requestURI, e.getMessage());
|
|
||||||
if (ServletUtils.isAjaxRequest(request))
|
|
||||||
{
|
|
||||||
return AjaxResult.error(PermissionUtils.getMsg(e.getMessage()));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return new ModelAndView("error/unauth");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 请求方式不支持
|
|
||||||
*/
|
|
||||||
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
|
|
||||||
public AjaxResult handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,
|
|
||||||
HttpServletRequest request)
|
|
||||||
{
|
|
||||||
String requestURI = request.getRequestURI();
|
|
||||||
log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod());
|
|
||||||
return AjaxResult.error(e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 拦截未知的运行时异常
|
|
||||||
*/
|
|
||||||
@ExceptionHandler(RuntimeException.class)
|
|
||||||
public AjaxResult handleRuntimeException(RuntimeException e, HttpServletRequest request)
|
|
||||||
{
|
|
||||||
String requestURI = request.getRequestURI();
|
|
||||||
log.error("请求地址'{}',发生未知异常.", requestURI, e);
|
|
||||||
return AjaxResult.error(e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 系统异常
|
|
||||||
*/
|
|
||||||
@ExceptionHandler(Exception.class)
|
|
||||||
public AjaxResult handleException(Exception e, HttpServletRequest request)
|
|
||||||
{
|
|
||||||
String requestURI = request.getRequestURI();
|
|
||||||
log.error("请求地址'{}',发生系统异常.", requestURI, e);
|
|
||||||
return AjaxResult.error(e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 业务异常
|
|
||||||
*/
|
|
||||||
@ExceptionHandler(ServiceException.class)
|
|
||||||
public Object handleServiceException(ServiceException e, HttpServletRequest request)
|
|
||||||
{
|
|
||||||
log.error(e.getMessage(), e);
|
|
||||||
if (ServletUtils.isAjaxRequest(request))
|
|
||||||
{
|
|
||||||
return AjaxResult.error(e.getMessage());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return new ModelAndView("error/service", "errorMessage", e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 自定义验证异常
|
|
||||||
*/
|
|
||||||
@ExceptionHandler(BindException.class)
|
|
||||||
public AjaxResult handleBindException(BindException e)
|
|
||||||
{
|
|
||||||
log.error(e.getMessage(), e);
|
|
||||||
String message = e.getAllErrors().get(0).getDefaultMessage();
|
|
||||||
return AjaxResult.error(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 演示模式异常
|
|
||||||
*/
|
|
||||||
@ExceptionHandler(DemoModeException.class)
|
|
||||||
public AjaxResult handleDemoModeException(DemoModeException e)
|
|
||||||
{
|
|
||||||
return AjaxResult.error("演示模式,不允许操作");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
package com.ruoyi.framework.web.service;
|
|
||||||
|
|
||||||
import java.util.Set;
|
|
||||||
import org.apache.commons.lang3.ArrayUtils;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import com.ruoyi.common.constant.Constants;
|
|
||||||
import com.ruoyi.common.utils.CacheUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 缓存操作处理
|
|
||||||
*
|
|
||||||
* @author ruoyi
|
|
||||||
*/
|
|
||||||
@Service
|
|
||||||
public class CacheService
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* 获取所有缓存名称
|
|
||||||
*
|
|
||||||
* @return 缓存列表
|
|
||||||
*/
|
|
||||||
public String[] getCacheNames()
|
|
||||||
{
|
|
||||||
String[] cacheNames = CacheUtils.getCacheNames();
|
|
||||||
return ArrayUtils.removeElement(cacheNames, Constants.SYS_AUTH_CACHE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据缓存名称获取所有键名
|
|
||||||
*
|
|
||||||
* @param cacheName 缓存名称
|
|
||||||
* @return 键名列表
|
|
||||||
*/
|
|
||||||
public Set<String> getCacheKeys(String cacheName)
|
|
||||||
{
|
|
||||||
return CacheUtils.getCache(cacheName).keys();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据缓存名称和键名获取内容值
|
|
||||||
*
|
|
||||||
* @param cacheName 缓存名称
|
|
||||||
* @param cacheKey 键名
|
|
||||||
* @return 键值
|
|
||||||
*/
|
|
||||||
public Object getCacheValue(String cacheName, String cacheKey)
|
|
||||||
{
|
|
||||||
return CacheUtils.get(cacheName, cacheKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据名称删除缓存信息
|
|
||||||
*
|
|
||||||
* @param cacheName 缓存名称
|
|
||||||
*/
|
|
||||||
public void clearCacheName(String cacheName)
|
|
||||||
{
|
|
||||||
CacheUtils.removeAll(cacheName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据名称和键名删除缓存信息
|
|
||||||
*
|
|
||||||
* @param cacheName 缓存名称
|
|
||||||
* @param cacheKey 键名
|
|
||||||
*/
|
|
||||||
public void clearCacheKey(String cacheName, String cacheKey)
|
|
||||||
{
|
|
||||||
CacheUtils.remove(cacheName, cacheKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清理所有缓存
|
|
||||||
*/
|
|
||||||
public void clearAll()
|
|
||||||
{
|
|
||||||
String[] cacheNames = getCacheNames();
|
|
||||||
for (String cacheName : cacheNames)
|
|
||||||
{
|
|
||||||
CacheUtils.removeAll(cacheName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
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 configKey 参数键名
|
|
||||||
* @return 参数键值
|
|
||||||
*/
|
|
||||||
public String getKey(String configKey)
|
|
||||||
{
|
|
||||||
return configService.selectConfigByKey(configKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
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.common.core.domain.entity.SysDictData;
|
|
||||||
import com.ruoyi.system.service.ISysDictDataService;
|
|
||||||
import com.ruoyi.system.service.ISysDictTypeService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RuoYi首创 html调用 thymeleaf 实现字典读取
|
|
||||||
*
|
|
||||||
* @author ruoyi
|
|
||||||
*/
|
|
||||||
@Service("dict")
|
|
||||||
public class DictService
|
|
||||||
{
|
|
||||||
@Autowired
|
|
||||||
private ISysDictTypeService dictTypeService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private ISysDictDataService dictDataService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据字典类型查询字典数据信息
|
|
||||||
*
|
|
||||||
* @param dictType 字典类型
|
|
||||||
* @return 参数键值
|
|
||||||
*/
|
|
||||||
public List<SysDictData> getType(String dictType)
|
|
||||||
{
|
|
||||||
return dictTypeService.selectDictDataByType(dictType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据字典类型和字典键值查询字典数据信息
|
|
||||||
*
|
|
||||||
* @param dictType 字典类型
|
|
||||||
* @param dictValue 字典键值
|
|
||||||
* @return 字典标签
|
|
||||||
*/
|
|
||||||
public String getLabel(String dictType, String dictValue)
|
|
||||||
{
|
|
||||||
return dictDataService.selectDictLabel(dictType, dictValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,262 +0,0 @@
|
||||||
package com.ruoyi.framework.web.service;
|
|
||||||
|
|
||||||
import java.beans.BeanInfo;
|
|
||||||
import java.beans.Introspector;
|
|
||||||
import java.beans.PropertyDescriptor;
|
|
||||||
import org.apache.shiro.SecurityUtils;
|
|
||||||
import org.apache.shiro.subject.Subject;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RuoYi首创 js调用 thymeleaf 实现按钮权限可见性
|
|
||||||
*
|
|
||||||
* @author ruoyi
|
|
||||||
*/
|
|
||||||
@Service("permission")
|
|
||||||
public class PermissionService
|
|
||||||
{
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(PermissionService.class);
|
|
||||||
|
|
||||||
/** 没有权限,hidden用于前端隐藏按钮 */
|
|
||||||
public static final String NOACCESS = "hidden";
|
|
||||||
|
|
||||||
private static final String ROLE_DELIMETER = ",";
|
|
||||||
|
|
||||||
private static final String PERMISSION_DELIMETER = ",";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证用户是否具备某权限,无权限返回hidden用于前端隐藏(如需返回Boolean使用isPermitted)
|
|
||||||
*
|
|
||||||
* @param permission 权限字符串
|
|
||||||
* @return 用户是否具备某权限
|
|
||||||
*/
|
|
||||||
public String hasPermi(String permission)
|
|
||||||
{
|
|
||||||
return isPermitted(permission) ? StringUtils.EMPTY : NOACCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证用户是否不具备某权限,与 hasPermi逻辑相反。无权限返回hidden用于前端隐藏(如需返回Boolean使用isLacksPermitted)
|
|
||||||
*
|
|
||||||
* @param permission 权限字符串
|
|
||||||
* @return 用户是否不具备某权限
|
|
||||||
*/
|
|
||||||
public String lacksPermi(String permission)
|
|
||||||
{
|
|
||||||
return isLacksPermitted(permission) ? StringUtils.EMPTY : NOACCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证用户是否具有以下任意一个权限,无权限返回hidden用于隐藏(如需返回Boolean使用hasAnyPermissions)
|
|
||||||
*
|
|
||||||
* @param permissions 以 PERMISSION_NAMES_DELIMETER 为分隔符的权限列表
|
|
||||||
* @return 用户是否具有以下任意一个权限
|
|
||||||
*/
|
|
||||||
public String hasAnyPermi(String permissions)
|
|
||||||
{
|
|
||||||
return hasAnyPermissions(permissions, PERMISSION_DELIMETER) ? StringUtils.EMPTY : NOACCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证用户是否具备某角色,无权限返回hidden用于隐藏(如需返回Boolean使用isRole)
|
|
||||||
*
|
|
||||||
* @param role 角色字符串
|
|
||||||
* @return 用户是否具备某角色
|
|
||||||
*/
|
|
||||||
public String hasRole(String role)
|
|
||||||
{
|
|
||||||
return isRole(role) ? StringUtils.EMPTY : NOACCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证用户是否不具备某角色,与hasRole逻辑相反。无权限返回hidden用于隐藏(如需返回Boolean使用isLacksRole)
|
|
||||||
*
|
|
||||||
* @param role 角色字符串
|
|
||||||
* @return 用户是否不具备某角色
|
|
||||||
*/
|
|
||||||
public String lacksRole(String role)
|
|
||||||
{
|
|
||||||
return isLacksRole(role) ? StringUtils.EMPTY : NOACCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证用户是否具有以下任意一个角色,无权限返回hidden用于隐藏(如需返回Boolean使用isAnyRoles)
|
|
||||||
*
|
|
||||||
* @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表
|
|
||||||
* @return 用户是否具有以下任意一个角色
|
|
||||||
*/
|
|
||||||
public String hasAnyRoles(String roles)
|
|
||||||
{
|
|
||||||
return isAnyRoles(roles, ROLE_DELIMETER) ? StringUtils.EMPTY : NOACCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证用户是否认证通过或已记住的用户。
|
|
||||||
*
|
|
||||||
* @return 用户是否认证通过或已记住的用户
|
|
||||||
*/
|
|
||||||
public boolean isUser()
|
|
||||||
{
|
|
||||||
Subject subject = SecurityUtils.getSubject();
|
|
||||||
return subject != null && subject.getPrincipal() != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断用户是否拥有某个权限
|
|
||||||
*
|
|
||||||
* @param permission 权限字符串
|
|
||||||
* @return 用户是否具备某权限
|
|
||||||
*/
|
|
||||||
public boolean isPermitted(String permission)
|
|
||||||
{
|
|
||||||
return SecurityUtils.getSubject().isPermitted(permission);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断用户是否不具备某权限,与 isPermitted逻辑相反。
|
|
||||||
*
|
|
||||||
* @param permission 权限名称
|
|
||||||
* @return 用户是否不具备某权限
|
|
||||||
*/
|
|
||||||
public boolean isLacksPermitted(String permission)
|
|
||||||
{
|
|
||||||
return isPermitted(permission) != true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证用户是否具有以下任意一个权限。
|
|
||||||
*
|
|
||||||
* @param permissions 以 PERMISSION_NAMES_DELIMETER 为分隔符的权限列表
|
|
||||||
* @return 用户是否具有以下任意一个权限
|
|
||||||
*/
|
|
||||||
public boolean hasAnyPermissions(String permissions)
|
|
||||||
{
|
|
||||||
return hasAnyPermissions(permissions, PERMISSION_DELIMETER);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证用户是否具有以下任意一个权限。
|
|
||||||
*
|
|
||||||
* @param permissions 以 delimeter 为分隔符的权限列表
|
|
||||||
* @param delimeter 权限列表分隔符
|
|
||||||
* @return 用户是否具有以下任意一个权限
|
|
||||||
*/
|
|
||||||
public boolean hasAnyPermissions(String permissions, String delimeter)
|
|
||||||
{
|
|
||||||
Subject subject = SecurityUtils.getSubject();
|
|
||||||
|
|
||||||
if (subject != null)
|
|
||||||
{
|
|
||||||
if (delimeter == null || delimeter.length() == 0)
|
|
||||||
{
|
|
||||||
delimeter = PERMISSION_DELIMETER;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (String permission : permissions.split(delimeter))
|
|
||||||
{
|
|
||||||
if (permission != null && subject.isPermitted(permission.trim()) == true)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断用户是否拥有某个角色
|
|
||||||
*
|
|
||||||
* @param role 角色字符串
|
|
||||||
* @return 用户是否具备某角色
|
|
||||||
*/
|
|
||||||
public boolean isRole(String role)
|
|
||||||
{
|
|
||||||
return SecurityUtils.getSubject().hasRole(role);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证用户是否不具备某角色,与 isRole逻辑相反。
|
|
||||||
*
|
|
||||||
* @param role 角色名称
|
|
||||||
* @return 用户是否不具备某角色
|
|
||||||
*/
|
|
||||||
public boolean isLacksRole(String role)
|
|
||||||
{
|
|
||||||
return isRole(role) != true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证用户是否具有以下任意一个角色。
|
|
||||||
*
|
|
||||||
* @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表
|
|
||||||
* @return 用户是否具有以下任意一个角色
|
|
||||||
*/
|
|
||||||
public boolean isAnyRoles(String roles)
|
|
||||||
{
|
|
||||||
return isAnyRoles(roles, ROLE_DELIMETER);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证用户是否具有以下任意一个角色。
|
|
||||||
*
|
|
||||||
* @param roles 以 delimeter 为分隔符的角色列表
|
|
||||||
* @param delimeter 角色列表分隔符
|
|
||||||
* @return 用户是否具有以下任意一个角色
|
|
||||||
*/
|
|
||||||
public boolean isAnyRoles(String roles, String delimeter)
|
|
||||||
{
|
|
||||||
Subject subject = SecurityUtils.getSubject();
|
|
||||||
if (subject != null)
|
|
||||||
{
|
|
||||||
if (delimeter == null || delimeter.length() == 0)
|
|
||||||
{
|
|
||||||
delimeter = ROLE_DELIMETER;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (String role : roles.split(delimeter))
|
|
||||||
{
|
|
||||||
if (subject.hasRole(role.trim()) == true)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 返回用户属性值
|
|
||||||
*
|
|
||||||
* @param property 属性名称
|
|
||||||
* @return 用户属性值
|
|
||||||
*/
|
|
||||||
public Object getPrincipalProperty(String property)
|
|
||||||
{
|
|
||||||
Subject subject = SecurityUtils.getSubject();
|
|
||||||
if (subject != null)
|
|
||||||
{
|
|
||||||
Object principal = subject.getPrincipal();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
BeanInfo bi = Introspector.getBeanInfo(principal.getClass());
|
|
||||||
for (PropertyDescriptor pd : bi.getPropertyDescriptors())
|
|
||||||
{
|
|
||||||
if (pd.getName().equals(property) == true)
|
|
||||||
{
|
|
||||||
return pd.getReadMethod().invoke(principal, (Object[]) null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
log.error("Error reading property [{}] from principal of type [{}]", property, principal.getClass().getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue