第一次提交
This commit is contained in:
109
fastbee-framework/pom.xml
Normal file
109
fastbee-framework/pom.xml
Normal file
@ -0,0 +1,109 @@
|
||||
<?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>fastbee</artifactId>
|
||||
<groupId>com.fastbee</groupId>
|
||||
<version>3.8.5</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>fastbee-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.fastbee</groupId>
|
||||
<artifactId>fastbee-system-service</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>
|
||||
|
||||
<!-- 获取系统信息 -->
|
||||
<dependency>
|
||||
<groupId>com.github.oshi</groupId>
|
||||
<artifactId>oshi-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!--redisson-->
|
||||
<dependency>
|
||||
<groupId>org.redisson</groupId>
|
||||
<artifactId>redisson-spring-boot-starter</artifactId>
|
||||
<version>${redisson.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.redisson</groupId>
|
||||
<artifactId>redisson-spring-data-31</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.redisson</groupId>
|
||||
<artifactId>redisson-spring-data-25</artifactId>
|
||||
<version>${redisson.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>lock4j-redisson-spring-boot-starter</artifactId>
|
||||
<version>${lock4j.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>redisson-spring-boot-starter</artifactId>
|
||||
<groupId>org.redisson</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>caffeine</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.session</groupId>
|
||||
<artifactId>spring-session-data-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -0,0 +1,167 @@
|
||||
package com.fastbee.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.fastbee.common.annotation.DataScope;
|
||||
import com.fastbee.common.core.domain.BaseEntity;
|
||||
import com.fastbee.common.core.domain.entity.SysRole;
|
||||
import com.fastbee.common.core.domain.entity.SysUser;
|
||||
import com.fastbee.common.core.domain.model.LoginUser;
|
||||
import com.fastbee.common.core.text.Convert;
|
||||
import com.fastbee.common.utils.SecurityUtils;
|
||||
import com.fastbee.common.utils.StringUtils;
|
||||
import com.fastbee.framework.security.context.PermissionContextHolder;
|
||||
|
||||
/**
|
||||
* 数据过滤处理
|
||||
*
|
||||
* @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)
|
||||
{
|
||||
// 获取当前的用户
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
if (StringUtils.isNotNull(loginUser))
|
||||
{
|
||||
SysUser currentUser = loginUser.getUser();
|
||||
// 如果是超级管理员,则不过滤数据
|
||||
if (StringUtils.isNotNull(currentUser) && !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, "");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
package com.fastbee.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.fastbee.common.annotation.DataSource;
|
||||
import com.fastbee.common.utils.StringUtils;
|
||||
import com.fastbee.framework.datasource.DynamicDataSourceContextHolder;
|
||||
|
||||
/**
|
||||
* 多数据源处理
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Aspect
|
||||
@Order(1)
|
||||
@Component
|
||||
public class DataSourceAspect
|
||||
{
|
||||
protected Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
@Pointcut("@annotation(com.fastbee.common.annotation.DataSource)"
|
||||
+ "|| @within(com.fastbee.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);
|
||||
}
|
||||
}
|
@ -0,0 +1,227 @@
|
||||
package com.fastbee.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.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.fastjson2.JSON;
|
||||
import com.fastbee.common.annotation.Log;
|
||||
import com.fastbee.common.core.domain.model.LoginUser;
|
||||
import com.fastbee.common.enums.BusinessStatus;
|
||||
import com.fastbee.common.enums.HttpMethod;
|
||||
import com.fastbee.common.filter.PropertyPreExcludeFilter;
|
||||
import com.fastbee.common.utils.SecurityUtils;
|
||||
import com.fastbee.common.utils.ServletUtils;
|
||||
import com.fastbee.common.utils.StringUtils;
|
||||
import com.fastbee.common.utils.ip.IpUtils;
|
||||
import com.fastbee.framework.manager.AsyncManager;
|
||||
import com.fastbee.framework.manager.factory.AsyncFactory;
|
||||
import com.fastbee.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" };
|
||||
|
||||
/**
|
||||
* 处理完请求后执行
|
||||
*
|
||||
* @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
|
||||
{
|
||||
// 获取当前的用户
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
|
||||
// *========数据库日志=========*//
|
||||
SysOperLog operLog = new SysOperLog();
|
||||
operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
|
||||
// 请求的地址
|
||||
String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
|
||||
operLog.setOperIp(ip);
|
||||
operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));
|
||||
if (loginUser != null)
|
||||
{
|
||||
operLog.setOperName(loginUser.getUsername());
|
||||
}
|
||||
|
||||
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("异常信息:{}", 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(JSON.toJSONString(jsonResult), 0, 2000));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求的参数,放到log中
|
||||
*
|
||||
* @param operLog 操作日志
|
||||
* @throws Exception 异常
|
||||
*/
|
||||
private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog) throws Exception
|
||||
{
|
||||
String requestMethod = operLog.getRequestMethod();
|
||||
if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod))
|
||||
{
|
||||
String params = argsArrayToString(joinPoint.getArgs());
|
||||
operLog.setOperParam(StringUtils.substring(params, 0, 2000));
|
||||
}
|
||||
else
|
||||
{
|
||||
Map<?, ?> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest());
|
||||
operLog.setOperParam(StringUtils.substring(JSON.toJSONString(paramsMap, excludePropertyPreFilter()), 0, 2000));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数拼装
|
||||
*/
|
||||
private String argsArrayToString(Object[] paramsArray)
|
||||
{
|
||||
String params = "";
|
||||
if (paramsArray != null && paramsArray.length > 0)
|
||||
{
|
||||
for (Object o : paramsArray)
|
||||
{
|
||||
if (StringUtils.isNotNull(o) && !isFilterObject(o))
|
||||
{
|
||||
try
|
||||
{
|
||||
String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter());
|
||||
params += jsonObj.toString() + " ";
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return params.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 忽略敏感属性
|
||||
*/
|
||||
public PropertyPreExcludeFilter excludePropertyPreFilter()
|
||||
{
|
||||
return new PropertyPreExcludeFilter().addExcludes(EXCLUDE_PROPERTIES);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否需要过滤的对象。
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
package com.fastbee.framework.aspectj;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.script.RedisScript;
|
||||
import org.springframework.stereotype.Component;
|
||||
import com.fastbee.common.annotation.RateLimiter;
|
||||
import com.fastbee.common.enums.LimitType;
|
||||
import com.fastbee.common.exception.ServiceException;
|
||||
import com.fastbee.common.utils.ServletUtils;
|
||||
import com.fastbee.common.utils.StringUtils;
|
||||
import com.fastbee.common.utils.ip.IpUtils;
|
||||
|
||||
/**
|
||||
* 限流处理
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Aspect
|
||||
@Component
|
||||
public class RateLimiterAspect
|
||||
{
|
||||
private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class);
|
||||
|
||||
private RedisTemplate<Object, Object> redisTemplate;
|
||||
|
||||
private RedisScript<Long> limitScript;
|
||||
|
||||
@Autowired
|
||||
public void setRedisTemplate1(RedisTemplate<Object, Object> redisTemplate)
|
||||
{
|
||||
this.redisTemplate = redisTemplate;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setLimitScript(RedisScript<Long> limitScript)
|
||||
{
|
||||
this.limitScript = limitScript;
|
||||
}
|
||||
|
||||
@Before("@annotation(rateLimiter)")
|
||||
public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable
|
||||
{
|
||||
int time = rateLimiter.time();
|
||||
int count = rateLimiter.count();
|
||||
|
||||
String combineKey = getCombineKey(rateLimiter, point);
|
||||
List<Object> keys = Collections.singletonList(combineKey);
|
||||
try
|
||||
{
|
||||
Long number = redisTemplate.execute(limitScript, keys, count, time);
|
||||
if (StringUtils.isNull(number) || number.intValue() > count)
|
||||
{
|
||||
throw new ServiceException("访问过于频繁,请稍候再试");
|
||||
}
|
||||
log.info("限制请求'{}',当前请求'{}',缓存key'{}'", count, number.intValue(), combineKey);
|
||||
}
|
||||
catch (ServiceException e)
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException("服务器限流异常,请稍候再试");
|
||||
}
|
||||
}
|
||||
|
||||
public String getCombineKey(RateLimiter rateLimiter, JoinPoint point)
|
||||
{
|
||||
StringBuffer stringBuffer = new StringBuffer(rateLimiter.key());
|
||||
if (rateLimiter.limitType() == LimitType.IP)
|
||||
{
|
||||
stringBuffer.append(IpUtils.getIpAddr(ServletUtils.getRequest())).append("-");
|
||||
}
|
||||
MethodSignature signature = (MethodSignature) point.getSignature();
|
||||
Method method = signature.getMethod();
|
||||
Class<?> targetClass = method.getDeclaringClass();
|
||||
stringBuffer.append(targetClass.getName()).append("-").append(method.getName());
|
||||
return stringBuffer.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package com.fastbee.framework.config;
|
||||
|
||||
import java.util.TimeZone;
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||
|
||||
/**
|
||||
* 程序注解配置
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Configuration
|
||||
// 表示通过aop框架暴露该代理对象,AopContext能够访问
|
||||
@EnableAspectJAutoProxy(exposeProxy = true)
|
||||
// 指定要扫描的Mapper类的包的路径
|
||||
@MapperScan("com.fastbee.**.mapper")
|
||||
public class ApplicationConfig
|
||||
{
|
||||
/**
|
||||
* 时区配置
|
||||
*/
|
||||
@Bean
|
||||
public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization()
|
||||
{
|
||||
return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault());
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package com.fastbee.framework.config;
|
||||
|
||||
import com.fastbee.framework.manager.SpringCacheManager;
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 缓存配置
|
||||
*/
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
@ConditionalOnProperty(name = "spring.cache.enable", havingValue = "true")
|
||||
public class CacheConfig {
|
||||
@Value("${spring.cache.ttl}")
|
||||
private Long ttl;
|
||||
|
||||
@Bean
|
||||
public Cache<Object, Object> caffeine() {
|
||||
return Caffeine.newBuilder()
|
||||
// 设置最后一次写入或访问后经过固定时间过期
|
||||
.expireAfterWrite(60, TimeUnit.SECONDS)
|
||||
// 初始的缓存空间大小
|
||||
.initialCapacity(100)
|
||||
// 缓存的最大条数
|
||||
.maximumSize(10000)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义缓存管理器 整合spring-cache
|
||||
*/
|
||||
@Bean
|
||||
public CacheManager cacheManager() {
|
||||
return new SpringCacheManager(ttl);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
package com.fastbee.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.fastbee.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;
|
||||
}
|
||||
}
|
@ -0,0 +1,126 @@
|
||||
package com.fastbee.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.fastbee.common.enums.DataSourceType;
|
||||
import com.fastbee.common.utils.spring.SpringUtils;
|
||||
import com.fastbee.framework.config.properties.DruidProperties;
|
||||
import com.fastbee.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;
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package com.fastbee.framework.config;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import org.springframework.data.redis.serializer.SerializationException;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONReader;
|
||||
import com.alibaba.fastjson2.JSONWriter;
|
||||
|
||||
/**
|
||||
* Redis使用FastJson序列化
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
|
||||
{
|
||||
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
|
||||
|
||||
private Class<T> clazz;
|
||||
|
||||
public FastJson2JsonRedisSerializer(Class<T> clazz)
|
||||
{
|
||||
super();
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] serialize(T t) throws SerializationException
|
||||
{
|
||||
if (t == null)
|
||||
{
|
||||
return new byte[0];
|
||||
}
|
||||
return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T deserialize(byte[] bytes) throws SerializationException
|
||||
{
|
||||
if (bytes == null || bytes.length <= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
String str = new String(bytes, DEFAULT_CHARSET);
|
||||
|
||||
return JSON.parseObject(str, clazz, JSONReader.Feature.SupportAutoType);
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
package com.fastbee.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.fastbee.common.filter.RepeatableFilter;
|
||||
import com.fastbee.common.filter.XssFilter;
|
||||
import com.fastbee.common.utils.StringUtils;
|
||||
|
||||
/**
|
||||
* Filter配置
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Configuration
|
||||
public class FilterConfig
|
||||
{
|
||||
@Value("${xss.excludes}")
|
||||
private String excludes;
|
||||
|
||||
@Value("${xss.urlPatterns}")
|
||||
private String urlPatterns;
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "xss.enabled", havingValue = "true")
|
||||
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;
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
@Bean
|
||||
public FilterRegistrationBean someFilterRegistration()
|
||||
{
|
||||
FilterRegistrationBean registration = new FilterRegistrationBean();
|
||||
registration.setFilter(new RepeatableFilter());
|
||||
registration.addUrlPatterns("/*");
|
||||
registration.setName("repeatableFilter");
|
||||
registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);
|
||||
return registration;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
package com.fastbee.framework.config;
|
||||
|
||||
import java.util.Random;
|
||||
import com.google.code.kaptcha.text.impl.DefaultTextCreator;
|
||||
|
||||
/**
|
||||
* 验证码文本生成器
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public class KaptchaTextCreator extends DefaultTextCreator
|
||||
{
|
||||
private static final String[] CNUMBERS = "0,1,2,3,4,5,6,7,8,9,10".split(",");
|
||||
|
||||
@Override
|
||||
public String getText()
|
||||
{
|
||||
Integer result = 0;
|
||||
Random random = new Random();
|
||||
int x = random.nextInt(10);
|
||||
int y = random.nextInt(10);
|
||||
StringBuilder suChinese = new StringBuilder();
|
||||
int randomoperands = random.nextInt(3);
|
||||
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 (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]);
|
||||
}
|
||||
}
|
||||
suChinese.append("=?@" + result);
|
||||
return suChinese.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,177 @@
|
||||
package com.fastbee.framework.config;
|
||||
|
||||
import com.baomidou.mybatisplus.autoconfigure.SpringBootVFS;
|
||||
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
|
||||
import com.fastbee.common.utils.StringUtils;
|
||||
import org.apache.ibatis.io.VFS;
|
||||
import org.apache.ibatis.session.SqlSessionFactory;
|
||||
import org.mybatis.spring.SqlSessionTemplate;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
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.jdbc.datasource.DataSourceTransactionManager;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 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()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* mybatis 配置
|
||||
*/
|
||||
// @Bean(name = "mysqlSessionFactory")
|
||||
// @Primary
|
||||
// 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();
|
||||
// }
|
||||
|
||||
/**
|
||||
* mybatis-plus 配置:把 SqlSessionFactoryBean 换成 MybatisSqlSessionFactoryBean就行
|
||||
* @param dataSource 数据源
|
||||
* @return
|
||||
*/
|
||||
@Bean(name = "mysqlSessionFactory")
|
||||
@Primary
|
||||
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception
|
||||
{
|
||||
String typeAliasesPackage = env.getProperty("mybatis-plus.typeAliasesPackage");
|
||||
String mapperLocations = env.getProperty("mybatis-plus.mapperLocations");
|
||||
String configLocation = env.getProperty("mybatis-plus.configLocation");
|
||||
typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage);
|
||||
VFS.addImplClass(SpringBootVFS.class);
|
||||
|
||||
final MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();
|
||||
sessionFactory.setDataSource(dataSource);
|
||||
sessionFactory.setTypeAliasesPackage(typeAliasesPackage);
|
||||
sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ",")));
|
||||
sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
|
||||
return sessionFactory.getObject();
|
||||
}
|
||||
|
||||
@Bean(name = "mysqlTransactionManager")
|
||||
@Primary
|
||||
public DataSourceTransactionManager mysqlTransactionManager(@Qualifier("dynamicDataSource") DataSource dataSource) {
|
||||
return new DataSourceTransactionManager(dataSource);
|
||||
}
|
||||
|
||||
@Bean(name = "mysqlSqlSessionTemplate")
|
||||
@Primary
|
||||
public SqlSessionTemplate mysqlSqlSessionTemplate(@Qualifier("mysqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
|
||||
return new SqlSessionTemplate(sqlSessionFactory);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package com.fastbee.framework.config;
|
||||
|
||||
import org.springframework.cache.annotation.CachingConfigurerSupport;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
/**
|
||||
* redis配置
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Configuration
|
||||
public class RedisConfig extends CachingConfigurerSupport
|
||||
{
|
||||
@Bean
|
||||
@SuppressWarnings(value = { "unchecked", "rawtypes" })
|
||||
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
|
||||
{
|
||||
RedisTemplate<Object, Object> template = new RedisTemplate<>();
|
||||
template.setConnectionFactory(connectionFactory);
|
||||
|
||||
FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
|
||||
|
||||
// 使用StringRedisSerializer来序列化和反序列化redis的key值
|
||||
template.setKeySerializer(new StringRedisSerializer());
|
||||
template.setValueSerializer(serializer);
|
||||
|
||||
// Hash的key也采用StringRedisSerializer的序列化方式
|
||||
template.setHashKeySerializer(new StringRedisSerializer());
|
||||
template.setHashValueSerializer(serializer);
|
||||
|
||||
template.afterPropertiesSet();
|
||||
return template;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DefaultRedisScript<Long> limitScript()
|
||||
{
|
||||
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
|
||||
redisScript.setScriptText(limitScriptText());
|
||||
redisScript.setResultType(Long.class);
|
||||
return redisScript;
|
||||
}
|
||||
|
||||
/**
|
||||
* 限流脚本
|
||||
*/
|
||||
private String limitScriptText()
|
||||
{
|
||||
return "local key = KEYS[1]\n" +
|
||||
"local count = tonumber(ARGV[1])\n" +
|
||||
"local time = tonumber(ARGV[2])\n" +
|
||||
"local current = redis.call('get', key);\n" +
|
||||
"if current and tonumber(current) > count then\n" +
|
||||
" return tonumber(current);\n" +
|
||||
"end\n" +
|
||||
"current = redis.call('incr', key)\n" +
|
||||
"if tonumber(current) == 1 then\n" +
|
||||
" redis.call('expire', key, time)\n" +
|
||||
"end\n" +
|
||||
"return tonumber(current);";
|
||||
}
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
package com.fastbee.framework.config;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.fastbee.framework.config.properties.RedissonProperties;
|
||||
import org.redisson.client.codec.StringCodec;
|
||||
import org.redisson.codec.CompositeCodec;
|
||||
import org.redisson.codec.TypedJsonJacksonCodec;
|
||||
import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import com.fastbee.framework.handler.KeyPrefixHandler;
|
||||
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* redis配置
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(RedissonProperties.class)
|
||||
public class RedissonConfig {
|
||||
|
||||
@Autowired
|
||||
private RedissonProperties redissonProperties;
|
||||
|
||||
@Bean
|
||||
public RedissonAutoConfigurationCustomizer redissonCustomizer() {
|
||||
return config -> {
|
||||
JavaTimeModule javaTimeModule = new JavaTimeModule();
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
|
||||
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
|
||||
ObjectMapper om = new ObjectMapper();
|
||||
om.registerModule(javaTimeModule);
|
||||
om.setTimeZone(TimeZone.getDefault());
|
||||
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
|
||||
// 指定序列化输入的类型,类必须是非final修饰的。序列化时将对象全类名一起保存下来
|
||||
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
|
||||
TypedJsonJacksonCodec jsonCodec = new TypedJsonJacksonCodec(Object.class, om);
|
||||
// 组合序列化 key 使用 String 内容使用通用 json 格式
|
||||
CompositeCodec codec = new CompositeCodec(StringCodec.INSTANCE, jsonCodec, jsonCodec);
|
||||
config.setThreads(redissonProperties.getThreads())
|
||||
.setNettyThreads(redissonProperties.getNettyThreads())
|
||||
// 缓存 Lua 脚本 减少网络传输(redisson 大部分的功能都是基于 Lua 脚本实现)
|
||||
.setUseScriptCache(true)
|
||||
.setCodec(codec);
|
||||
RedissonProperties.SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig();
|
||||
if (ObjectUtil.isNotNull(singleServerConfig)) {
|
||||
// 使用单机模式
|
||||
config.useSingleServer()
|
||||
//设置redis key前缀
|
||||
.setNameMapper(new KeyPrefixHandler(redissonProperties.getKeyPrefix()))
|
||||
.setTimeout(singleServerConfig.getTimeout())
|
||||
.setClientName(singleServerConfig.getClientName())
|
||||
.setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout())
|
||||
.setSubscriptionConnectionPoolSize(singleServerConfig.getSubscriptionConnectionPoolSize())
|
||||
.setConnectionMinimumIdleSize(singleServerConfig.getConnectionMinimumIdleSize())
|
||||
.setConnectionPoolSize(singleServerConfig.getConnectionPoolSize());
|
||||
}
|
||||
// 集群配置方式 参考下方注释
|
||||
RedissonProperties.ClusterServersConfig clusterServersConfig = redissonProperties.getClusterServersConfig();
|
||||
if (ObjectUtil.isNotNull(clusterServersConfig)) {
|
||||
config.useClusterServers()
|
||||
//设置redis key前缀
|
||||
.setNameMapper(new KeyPrefixHandler(redissonProperties.getKeyPrefix()))
|
||||
.setTimeout(clusterServersConfig.getTimeout())
|
||||
.setClientName(clusterServersConfig.getClientName())
|
||||
.setIdleConnectionTimeout(clusterServersConfig.getIdleConnectionTimeout())
|
||||
.setSubscriptionConnectionPoolSize(clusterServersConfig.getSubscriptionConnectionPoolSize())
|
||||
.setMasterConnectionMinimumIdleSize(clusterServersConfig.getMasterConnectionMinimumIdleSize())
|
||||
.setMasterConnectionPoolSize(clusterServersConfig.getMasterConnectionPoolSize())
|
||||
.setSlaveConnectionMinimumIdleSize(clusterServersConfig.getSlaveConnectionMinimumIdleSize())
|
||||
.setSlaveConnectionPoolSize(clusterServersConfig.getSlaveConnectionPoolSize())
|
||||
.setReadMode(clusterServersConfig.getReadMode())
|
||||
.setSubscriptionMode(clusterServersConfig.getSubscriptionMode());
|
||||
}
|
||||
log.info("初始化 redis 配置");
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
package com.fastbee.framework.config;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.fastbee.framework.interceptor.LanguageInterceptor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.CacheControl;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import com.fastbee.common.config.RuoYiConfig;
|
||||
import com.fastbee.common.constant.Constants;
|
||||
import com.fastbee.framework.interceptor.RepeatSubmitInterceptor;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 通用配置
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Configuration
|
||||
public class ResourcesConfig implements WebMvcConfigurer
|
||||
{
|
||||
@Autowired
|
||||
private RepeatSubmitInterceptor repeatSubmitInterceptor;
|
||||
@Resource
|
||||
private LanguageInterceptor languageInterceptor;
|
||||
|
||||
@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/")
|
||||
.setCacheControl(CacheControl.maxAge(5, TimeUnit.HOURS).cachePublic());;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义拦截规则
|
||||
*/
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry)
|
||||
{
|
||||
registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
|
||||
//这里配置国际化拦截器的白名单
|
||||
registry.addInterceptor(languageInterceptor).addPathPatterns("/**").excludePathPatterns("/v2/api-docs",
|
||||
"/tool/gen/**");
|
||||
}
|
||||
|
||||
/**
|
||||
* 跨域配置
|
||||
*/
|
||||
@Bean
|
||||
public CorsFilter corsFilter()
|
||||
{
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
config.setAllowCredentials(true);
|
||||
// 设置访问源地址
|
||||
config.addAllowedOriginPattern("*");
|
||||
// 设置访问源请求头
|
||||
config.addAllowedHeader("*");
|
||||
// 设置访问源请求方法
|
||||
config.addAllowedMethod("*");
|
||||
// 有效期 1800秒
|
||||
config.setMaxAge(1800L);
|
||||
// 添加映射路径,拦截一切请求
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", config);
|
||||
// 返回新的CorsFilter
|
||||
return new CorsFilter(source);
|
||||
}
|
||||
}
|
@ -0,0 +1,174 @@
|
||||
package com.fastbee.framework.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.security.web.authentication.logout.LogoutFilter;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
import com.fastbee.framework.config.properties.PermitAllUrlProperties;
|
||||
import com.fastbee.framework.security.filter.JwtAuthenticationTokenFilter;
|
||||
import com.fastbee.framework.security.handle.AuthenticationEntryPointImpl;
|
||||
import com.fastbee.framework.security.handle.LogoutSuccessHandlerImpl;
|
||||
|
||||
/**
|
||||
* spring security配置
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
//@EnableWebSecurity
|
||||
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
|
||||
public class SecurityConfig extends WebSecurityConfigurerAdapter
|
||||
{
|
||||
/**
|
||||
* 自定义用户认证逻辑
|
||||
*/
|
||||
@Autowired
|
||||
private UserDetailsService userDetailsService;
|
||||
|
||||
/**
|
||||
* 认证失败处理类
|
||||
*/
|
||||
@Autowired
|
||||
private AuthenticationEntryPointImpl unauthorizedHandler;
|
||||
|
||||
/**
|
||||
* 退出处理类
|
||||
*/
|
||||
@Autowired
|
||||
private LogoutSuccessHandlerImpl logoutSuccessHandler;
|
||||
|
||||
/**
|
||||
* token认证过滤器
|
||||
*/
|
||||
@Autowired
|
||||
private JwtAuthenticationTokenFilter authenticationTokenFilter;
|
||||
|
||||
/**
|
||||
* 跨域过滤器
|
||||
*/
|
||||
@Autowired
|
||||
private CorsFilter corsFilter;
|
||||
|
||||
/**
|
||||
* 允许匿名访问的地址
|
||||
*/
|
||||
@Autowired
|
||||
private PermitAllUrlProperties permitAllUrl;
|
||||
|
||||
/**
|
||||
* 解决 无法直接注入 AuthenticationManager
|
||||
*
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
@Bean
|
||||
@Override
|
||||
public AuthenticationManager authenticationManagerBean() throws Exception
|
||||
{
|
||||
return super.authenticationManagerBean();
|
||||
}
|
||||
|
||||
/**
|
||||
* anyRequest | 匹配所有请求路径
|
||||
* access | SpringEl表达式结果为true时可以访问
|
||||
* anonymous | 匿名可以访问
|
||||
* denyAll | 用户不能访问
|
||||
* fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录)
|
||||
* hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问
|
||||
* hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问
|
||||
* hasAuthority | 如果有参数,参数表示权限,则其权限可以访问
|
||||
* hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
|
||||
* hasRole | 如果有参数,参数表示角色,则其角色可以访问
|
||||
* permitAll | 用户可以任意访问
|
||||
* rememberMe | 允许通过remember-me登录的用户访问
|
||||
* authenticated | 用户登录后可访问
|
||||
*/
|
||||
@Override
|
||||
protected void configure(HttpSecurity httpSecurity) throws Exception
|
||||
{
|
||||
// 注解标记允许匿名访问的url
|
||||
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests();
|
||||
permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll());
|
||||
|
||||
httpSecurity
|
||||
// CSRF禁用,因为不使用session
|
||||
.csrf().disable()
|
||||
// 禁用HTTP响应标头
|
||||
.headers().cacheControl().disable().and()
|
||||
// 认证失败处理类
|
||||
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
|
||||
// 基于token,所以不需要session
|
||||
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
|
||||
// 过滤请求
|
||||
.authorizeRequests()
|
||||
// 对于登录login 注册register 验证码captchaImage 允许匿名访问
|
||||
.antMatchers("/login", "/register", "/captchaImage","/iot/tool/register","/iot/tool/ntp","/iot/tool/download",
|
||||
"/iot/tool/mqtt/auth","/iot/tool/mqtt/authv5","/iot/tool/mqtt/webhook","/iot/tool/mqtt/webhookv5","/auth/**/**",
|
||||
"/wechat/mobileLogin", "/wechat/miniLogin", "/wechat/wxBind/callback").permitAll()
|
||||
.antMatchers("/zlmhook/**").permitAll()
|
||||
.antMatchers("/ruleengine/rulemanager/**").permitAll()
|
||||
.antMatchers("/goview/sys/login","/goview/project/getData").permitAll()
|
||||
.antMatchers("/notify/smsLoginCaptcha","/auth/sms/login", "/notify/weComVerifyUrl"
|
||||
,"/wechat/publicAccount/callback","/notify/smsRegisterCaptcha").permitAll()
|
||||
.antMatchers("/app/language/list").permitAll()
|
||||
// 静态资源,可匿名访问
|
||||
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
|
||||
.antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
|
||||
|
||||
.antMatchers("/oauth2/**").permitAll()
|
||||
// // oauth
|
||||
// .antMatchers("/oauth/css/**","/oauth/fonts/**","/oauth/js/**").permitAll()
|
||||
// dueros
|
||||
.antMatchers("/dueros").permitAll()
|
||||
|
||||
// 除上面外的所有请求全部需要鉴权认证
|
||||
.anyRequest().authenticated()
|
||||
|
||||
// // oauth
|
||||
// .and()
|
||||
// .formLogin()
|
||||
// .loginPage("/oauth/login")
|
||||
// .permitAll()
|
||||
// .and()
|
||||
// .logout().logoutUrl("/oauth/logout")
|
||||
// .permitAll()
|
||||
|
||||
.and()
|
||||
.headers().frameOptions().disable();
|
||||
// 添加Logout filter
|
||||
httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
|
||||
// 添加JWT filter
|
||||
httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
// 添加CORS filter
|
||||
httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
|
||||
httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 强散列哈希加密实现
|
||||
*/
|
||||
@Bean
|
||||
public BCryptPasswordEncoder bCryptPasswordEncoder()
|
||||
{
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
/**
|
||||
* 身份认证接口
|
||||
*/
|
||||
@Override
|
||||
protected void configure(AuthenticationManagerBuilder auth) throws Exception
|
||||
{
|
||||
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package com.fastbee.framework.config;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import org.springframework.stereotype.Component;
|
||||
import com.fastbee.common.utils.ServletUtils;
|
||||
|
||||
/**
|
||||
* 服务相关配置
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Component
|
||||
public class ServerConfig
|
||||
{
|
||||
/**
|
||||
* 获取完整的请求路径,包括:域名,端口,上下文访问路径
|
||||
*
|
||||
* @return 服务地址
|
||||
*/
|
||||
public String getUrl()
|
||||
{
|
||||
HttpServletRequest request = ServletUtils.getRequest();
|
||||
return getDomain(request);
|
||||
}
|
||||
|
||||
public static String getDomain(HttpServletRequest request)
|
||||
{
|
||||
StringBuffer url = request.getRequestURL();
|
||||
String contextPath = request.getServletContext().getContextPath();
|
||||
return url.delete(url.length() - request.getRequestURI().length(), url.length()).append(contextPath).toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package com.fastbee.framework.config;
|
||||
|
||||
import com.fastbee.common.utils.Threads;
|
||||
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
/**
|
||||
* 线程池配置
|
||||
*
|
||||
* @author ruoyi
|
||||
**/
|
||||
@Configuration
|
||||
public class ThreadPoolConfig
|
||||
{
|
||||
// 核心线程池大小
|
||||
private int corePoolSize = 50;
|
||||
|
||||
// 最大可创建的线程数
|
||||
private int maxPoolSize = 200;
|
||||
|
||||
// 队列最大长度
|
||||
private int queueCapacity = 1000;
|
||||
|
||||
// 线程池维护线程所允许的空闲时间
|
||||
private int keepAliveSeconds = 300;
|
||||
|
||||
@Bean(name = "threadPoolTaskExecutor")
|
||||
public ThreadPoolTaskExecutor threadPoolTaskExecutor()
|
||||
{
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setMaxPoolSize(maxPoolSize);
|
||||
executor.setCorePoolSize(corePoolSize);
|
||||
executor.setQueueCapacity(queueCapacity);
|
||||
executor.setKeepAliveSeconds(keepAliveSeconds);
|
||||
// 线程池对拒绝任务(无线程可用)的处理策略
|
||||
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
return executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行周期性或定时任务
|
||||
*/
|
||||
@Bean(name = "scheduledExecutorService")
|
||||
protected ScheduledExecutorService scheduledExecutorService()
|
||||
{
|
||||
return new ScheduledThreadPoolExecutor(corePoolSize,
|
||||
new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(),
|
||||
new ThreadPoolExecutor.CallerRunsPolicy())
|
||||
{
|
||||
@Override
|
||||
protected void afterExecute(Runnable r, Throwable t)
|
||||
{
|
||||
super.afterExecute(r, t);
|
||||
Threads.printException(r, t);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
package com.fastbee.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.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.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;
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
package com.fastbee.framework.config.properties;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Pattern;
|
||||
import org.apache.commons.lang3.RegExUtils;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
import com.fastbee.common.annotation.Anonymous;
|
||||
|
||||
/**
|
||||
* 设置Anonymous注解允许匿名访问的url
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Configuration
|
||||
public class PermitAllUrlProperties implements InitializingBean, ApplicationContextAware
|
||||
{
|
||||
private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}");
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
private List<String> urls = new ArrayList<>();
|
||||
|
||||
public String ASTERISK = "*";
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet()
|
||||
{
|
||||
RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
|
||||
Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
|
||||
|
||||
map.keySet().forEach(info -> {
|
||||
HandlerMethod handlerMethod = map.get(info);
|
||||
|
||||
// 获取方法上边的注解 替代path variable 为 *
|
||||
Anonymous method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Anonymous.class);
|
||||
Optional.ofNullable(method).ifPresent(anonymous -> info.getPatternsCondition().getPatterns()
|
||||
.forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));
|
||||
|
||||
// 获取类上边的注解, 替代path variable 为 *
|
||||
Anonymous controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Anonymous.class);
|
||||
Optional.ofNullable(controller).ifPresent(anonymous -> info.getPatternsCondition().getPatterns()
|
||||
.forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext context) throws BeansException
|
||||
{
|
||||
this.applicationContext = context;
|
||||
}
|
||||
|
||||
public List<String> getUrls()
|
||||
{
|
||||
return urls;
|
||||
}
|
||||
|
||||
public void setUrls(List<String> urls)
|
||||
{
|
||||
this.urls = urls;
|
||||
}
|
||||
}
|
@ -0,0 +1,134 @@
|
||||
package com.fastbee.framework.config.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.redisson.config.ReadMode;
|
||||
import org.redisson.config.SubscriptionMode;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Redisson 配置属性
|
||||
*
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = "redisson")
|
||||
public class RedissonProperties {
|
||||
|
||||
/**
|
||||
* redis缓存key前缀
|
||||
*/
|
||||
private String keyPrefix;
|
||||
|
||||
/**
|
||||
* 线程池数量,默认值 = 当前处理核数量 * 2
|
||||
*/
|
||||
private int threads;
|
||||
|
||||
/**
|
||||
* Netty线程池数量,默认值 = 当前处理核数量 * 2
|
||||
*/
|
||||
private int nettyThreads;
|
||||
|
||||
/**
|
||||
* 单机服务配置
|
||||
*/
|
||||
private SingleServerConfig singleServerConfig;
|
||||
|
||||
/**
|
||||
* 集群服务配置
|
||||
*/
|
||||
private ClusterServersConfig clusterServersConfig;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public static class SingleServerConfig {
|
||||
|
||||
/**
|
||||
* 客户端名称
|
||||
*/
|
||||
private String clientName;
|
||||
|
||||
/**
|
||||
* 最小空闲连接数
|
||||
*/
|
||||
private int connectionMinimumIdleSize;
|
||||
|
||||
/**
|
||||
* 连接池大小
|
||||
*/
|
||||
private int connectionPoolSize;
|
||||
|
||||
/**
|
||||
* 连接空闲超时,单位:毫秒
|
||||
*/
|
||||
private int idleConnectionTimeout;
|
||||
|
||||
/**
|
||||
* 命令等待超时,单位:毫秒
|
||||
*/
|
||||
private int timeout;
|
||||
|
||||
/**
|
||||
* 发布和订阅连接池大小
|
||||
*/
|
||||
private int subscriptionConnectionPoolSize;
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public static class ClusterServersConfig {
|
||||
|
||||
/**
|
||||
* 客户端名称
|
||||
*/
|
||||
private String clientName;
|
||||
|
||||
/**
|
||||
* master最小空闲连接数
|
||||
*/
|
||||
private int masterConnectionMinimumIdleSize;
|
||||
|
||||
/**
|
||||
* master连接池大小
|
||||
*/
|
||||
private int masterConnectionPoolSize;
|
||||
|
||||
/**
|
||||
* slave最小空闲连接数
|
||||
*/
|
||||
private int slaveConnectionMinimumIdleSize;
|
||||
|
||||
/**
|
||||
* slave连接池大小
|
||||
*/
|
||||
private int slaveConnectionPoolSize;
|
||||
|
||||
/**
|
||||
* 连接空闲超时,单位:毫秒
|
||||
*/
|
||||
private int idleConnectionTimeout;
|
||||
|
||||
/**
|
||||
* 命令等待超时,单位:毫秒
|
||||
*/
|
||||
private int timeout;
|
||||
|
||||
/**
|
||||
* 发布和订阅连接池大小
|
||||
*/
|
||||
private int subscriptionConnectionPoolSize;
|
||||
|
||||
/**
|
||||
* 读取模式
|
||||
*/
|
||||
private ReadMode readMode;
|
||||
|
||||
/**
|
||||
* 订阅模式
|
||||
*/
|
||||
private SubscriptionMode subscriptionMode;
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package com.fastbee.framework.datasource;
|
||||
|
||||
import java.util.Map;
|
||||
import javax.sql.DataSource;
|
||||
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
|
||||
|
||||
/**
|
||||
* 动态数据源
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public class DynamicDataSource extends AbstractRoutingDataSource
|
||||
{
|
||||
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources)
|
||||
{
|
||||
super.setDefaultTargetDataSource(defaultTargetDataSource);
|
||||
super.setTargetDataSources(targetDataSources);
|
||||
super.afterPropertiesSet();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object determineCurrentLookupKey()
|
||||
{
|
||||
return DynamicDataSourceContextHolder.getDataSourceType();
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package com.fastbee.framework.datasource;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* 数据源切换处理
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public class DynamicDataSourceContextHolder
|
||||
{
|
||||
public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
|
||||
|
||||
/**
|
||||
* 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
|
||||
* 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
|
||||
*/
|
||||
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
|
||||
|
||||
/**
|
||||
* 设置数据源的变量
|
||||
*/
|
||||
public static void setDataSourceType(String dsType)
|
||||
{
|
||||
log.info("切换到{}数据源", dsType);
|
||||
CONTEXT_HOLDER.set(dsType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得数据源的变量
|
||||
*/
|
||||
public static String getDataSourceType()
|
||||
{
|
||||
return CONTEXT_HOLDER.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空数据源变量
|
||||
*/
|
||||
public static void clearDataSourceType()
|
||||
{
|
||||
CONTEXT_HOLDER.remove();
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package com.fastbee.framework.handler;
|
||||
|
||||
import com.fastbee.common.utils.StringUtils;
|
||||
import org.redisson.api.NameMapper;
|
||||
|
||||
/**
|
||||
* redis缓存key前缀处理
|
||||
*
|
||||
* @author ye
|
||||
* @date 2022/7/14 17:44
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public class KeyPrefixHandler implements NameMapper {
|
||||
|
||||
private final String keyPrefix;
|
||||
|
||||
public KeyPrefixHandler(String keyPrefix) {
|
||||
//前缀为空 则返回空前缀
|
||||
this.keyPrefix = StringUtils.isBlank(keyPrefix) ? "" : keyPrefix + ":";
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加前缀
|
||||
*/
|
||||
@Override
|
||||
public String map(String name) {
|
||||
if (StringUtils.isBlank(name)) {
|
||||
return null;
|
||||
}
|
||||
if (StringUtils.isNotBlank(keyPrefix) && !name.startsWith(keyPrefix)) {
|
||||
return keyPrefix + name;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 去除前缀
|
||||
*/
|
||||
@Override
|
||||
public String unmap(String name) {
|
||||
if (StringUtils.isBlank(name)) {
|
||||
return null;
|
||||
}
|
||||
if (StringUtils.isNotBlank(keyPrefix) && name.startsWith(keyPrefix)) {
|
||||
return name.substring(keyPrefix.length());
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
package com.fastbee.framework.interceptor;
|
||||
|
||||
import com.fastbee.common.utils.SecurityUtils;
|
||||
import com.fastbee.common.utils.StringUtils;
|
||||
import com.fastbee.system.enums.Language;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.Locale;
|
||||
|
||||
import static com.fastbee.common.constant.Constants.LANGUAGE;
|
||||
|
||||
|
||||
/**
|
||||
* 国际化语言拦截器
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class LanguageInterceptor implements HandlerInterceptor {
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
String language = request.getHeader(LANGUAGE);
|
||||
if (StringUtils.isEmpty(language) || language.equals("undefined")
|
||||
|| language.equals("")|| language.equals("null")){
|
||||
//针对有些接口没有增加语言字段,先去token获取
|
||||
language = SecurityUtils.getLanguage();
|
||||
}else if (!language.contains("-")){
|
||||
language = Language.matches(language);
|
||||
}
|
||||
// 前端传递的language必须是zh-CN格式的,中间的-必须要完整,不能只传递zh或en
|
||||
Locale locale = new Locale(language.split("-")[0],language.split("-")[1]);
|
||||
// 这样赋值以后,MessageUtils.message方法就不用修改了
|
||||
LocaleContextHolder.setLocale(locale);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后)
|
||||
*/
|
||||
@Override
|
||||
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 在整个请求结束之后被调用,也就是在DispatcherServlet 渲染了对应的视图之后执行(主要是用于进行资源清理工作)
|
||||
*/
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package com.fastbee.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.alibaba.fastjson2.JSON;
|
||||
import com.fastbee.common.annotation.RepeatSubmit;
|
||||
import com.fastbee.common.core.domain.AjaxResult;
|
||||
import com.fastbee.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.toJSONString(ajaxResult));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证是否重复提交由子类实现具体的防重复提交的规则
|
||||
*
|
||||
* @param request
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public abstract boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation);
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
package com.fastbee.framework.interceptor.impl;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.fastbee.common.annotation.RepeatSubmit;
|
||||
import com.fastbee.common.constant.CacheConstants;
|
||||
import com.fastbee.common.core.redis.RedisCache;
|
||||
import com.fastbee.common.filter.RepeatedlyRequestWrapper;
|
||||
import com.fastbee.common.utils.StringUtils;
|
||||
import com.fastbee.common.utils.http.HttpHelper;
|
||||
import com.fastbee.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";
|
||||
|
||||
// 令牌自定义标识
|
||||
@Value("${token.header}")
|
||||
private String header;
|
||||
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation)
|
||||
{
|
||||
String nowParams = "";
|
||||
if (request instanceof RepeatedlyRequestWrapper)
|
||||
{
|
||||
RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request;
|
||||
nowParams = HttpHelper.getBodyString(repeatedlyRequest);
|
||||
}
|
||||
|
||||
// body参数为空,获取Parameter的数据
|
||||
if (StringUtils.isEmpty(nowParams))
|
||||
{
|
||||
nowParams = JSON.toJSONString(request.getParameterMap());
|
||||
}
|
||||
Map<String, Object> nowDataMap = new HashMap<String, Object>();
|
||||
nowDataMap.put(REPEAT_PARAMS, nowParams);
|
||||
nowDataMap.put(REPEAT_TIME, System.currentTimeMillis());
|
||||
|
||||
// 请求地址(作为存放cache的key值)
|
||||
String url = request.getRequestURI();
|
||||
|
||||
// 唯一值(没有消息头则使用请求地址)
|
||||
String submitKey = StringUtils.trimToEmpty(request.getHeader(header));
|
||||
|
||||
// 唯一标识(指定key + url + 消息头)
|
||||
String cacheRepeatKey = CacheConstants.REPEAT_SUBMIT_KEY + url + submitKey;
|
||||
|
||||
Object sessionObj = redisCache.getCacheObject(cacheRepeatKey);
|
||||
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> cacheMap = new HashMap<String, Object>();
|
||||
cacheMap.put(url, nowDataMap);
|
||||
redisCache.setCacheObject(cacheRepeatKey, cacheMap, annotation.interval(), TimeUnit.MILLISECONDS);
|
||||
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;
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package com.fastbee.framework.manager;
|
||||
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import com.fastbee.common.utils.Threads;
|
||||
import com.fastbee.common.utils.spring.SpringUtils;
|
||||
|
||||
/**
|
||||
* 异步任务管理器
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
package com.fastbee.framework.manager;
|
||||
|
||||
import org.springframework.cache.Cache;
|
||||
import com.fastbee.common.utils.spring.SpringUtils;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
/**
|
||||
* Cache 装饰器模式(用于扩展 Caffeine 一级缓存)
|
||||
*
|
||||
*/
|
||||
public class CaffeineCacheDecorator implements Cache {
|
||||
|
||||
private static final com.github.benmanes.caffeine.cache.Cache<Object, Object>
|
||||
CAFFEINE = SpringUtils.getBean("caffeine");
|
||||
|
||||
private final Cache cache;
|
||||
|
||||
public CaffeineCacheDecorator(Cache cache) {
|
||||
this.cache = cache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return cache.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getNativeCache() {
|
||||
return cache.getNativeCache();
|
||||
}
|
||||
|
||||
public String getUniqueKey(Object key) {
|
||||
return cache.getName() + ":" + key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueWrapper get(Object key) {
|
||||
Object o = CAFFEINE.get(getUniqueKey(key), k -> cache.get(key));
|
||||
return (ValueWrapper) o;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T get(Object key, Class<T> type) {
|
||||
Object o = CAFFEINE.get(getUniqueKey(key), k -> cache.get(key, type));
|
||||
return (T) o;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(Object key, Object value) {
|
||||
CAFFEINE.invalidate(getUniqueKey(key));
|
||||
cache.put(key, value);
|
||||
}
|
||||
|
||||
public ValueWrapper putIfAbsent(Object key, Object value) {
|
||||
CAFFEINE.invalidate(getUniqueKey(key));
|
||||
return cache.putIfAbsent(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evict(Object key) {
|
||||
evictIfPresent(key);
|
||||
}
|
||||
|
||||
public boolean evictIfPresent(Object key) {
|
||||
boolean b = cache.evictIfPresent(key);
|
||||
if (b) {
|
||||
CAFFEINE.invalidate(getUniqueKey(key));
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
public boolean invalidate() {
|
||||
return cache.invalidate();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> T get(Object key, Callable<T> valueLoader) {
|
||||
Object o = CAFFEINE.get(getUniqueKey(key), k -> cache.get(key, valueLoader));
|
||||
return (T) o;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package com.fastbee.framework.manager;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
import javax.annotation.PreDestroy;
|
||||
|
||||
/**
|
||||
* 确保应用退出时能关闭后台线程
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Component
|
||||
public class ShutdownManager
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger("sys-user");
|
||||
|
||||
@PreDestroy
|
||||
public void destroy()
|
||||
{
|
||||
shutdownAsyncManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止异步执行任务
|
||||
*/
|
||||
private void shutdownAsyncManager()
|
||||
{
|
||||
try
|
||||
{
|
||||
logger.info("====关闭后台任务任务线程池====");
|
||||
AsyncManager.me().shutdown();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,185 @@
|
||||
package com.fastbee.framework.manager;
|
||||
import com.fastbee.framework.utils.RedisUtils;
|
||||
import org.redisson.api.RMap;
|
||||
import org.redisson.api.RMapCache;
|
||||
import org.redisson.spring.cache.CacheConfig;
|
||||
import org.redisson.spring.cache.RedissonCache;
|
||||
import org.springframework.boot.convert.DurationStyle;
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.transaction.TransactionAwareCacheDecorator;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
/**
|
||||
* A {@link CacheManager} implementation
|
||||
* backed by Redisson instance.
|
||||
* <p>
|
||||
* 修改 RedissonSpringCacheManager 源码
|
||||
* 重写 cacheName 处理方法 支持多参数
|
||||
*
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public class SpringCacheManager implements CacheManager {
|
||||
|
||||
private boolean dynamic = true;
|
||||
|
||||
private boolean allowNullValues = true;
|
||||
|
||||
private boolean transactionAware = true;
|
||||
|
||||
private Long ttl;
|
||||
|
||||
Map<String, CacheConfig> configMap = new ConcurrentHashMap<>();
|
||||
ConcurrentMap<String, Cache> instanceMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Creates CacheManager supplied by Redisson instance
|
||||
*/
|
||||
public SpringCacheManager() {
|
||||
}
|
||||
|
||||
public SpringCacheManager(Long ttl) {
|
||||
this.ttl = ttl;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Defines possibility of storing {@code null} values.
|
||||
* <p>
|
||||
* Default is <code>true</code>
|
||||
*
|
||||
* @param allowNullValues stores if <code>true</code>
|
||||
*/
|
||||
public void setAllowNullValues(boolean allowNullValues) {
|
||||
this.allowNullValues = allowNullValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines if cache aware of Spring-managed transactions.
|
||||
* If {@code true} put/evict operations are executed only for successful transaction in after-commit phase.
|
||||
* <p>
|
||||
* Default is <code>false</code>
|
||||
*
|
||||
* @param transactionAware cache is transaction aware if <code>true</code>
|
||||
*/
|
||||
public void setTransactionAware(boolean transactionAware) {
|
||||
this.transactionAware = transactionAware;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines 'fixed' cache names.
|
||||
* A new cache instance will not be created in dynamic for non-defined names.
|
||||
* <p>
|
||||
* `null` parameter setups dynamic mode
|
||||
*
|
||||
* @param names of caches
|
||||
*/
|
||||
public void setCacheNames(Collection<String> names) {
|
||||
if (names != null) {
|
||||
for (String name : names) {
|
||||
getCache(name);
|
||||
}
|
||||
dynamic = false;
|
||||
} else {
|
||||
dynamic = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set cache config mapped by cache name
|
||||
*
|
||||
* @param config object
|
||||
*/
|
||||
public void setConfig(Map<String, ? extends CacheConfig> config) {
|
||||
this.configMap = (Map<String, CacheConfig>) config;
|
||||
}
|
||||
|
||||
protected CacheConfig createDefaultConfig() {
|
||||
return new CacheConfig();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cache getCache(String name) {
|
||||
// 重写 cacheName 支持多参数
|
||||
String[] array = StringUtils.delimitedListToStringArray(name, "#");
|
||||
name = array[0];
|
||||
|
||||
Cache cache = instanceMap.get(name);
|
||||
if (cache != null) {
|
||||
return cache;
|
||||
}
|
||||
if (!dynamic) {
|
||||
return cache;
|
||||
}
|
||||
|
||||
CacheConfig config = configMap.get(name);
|
||||
if (config == null) {
|
||||
config = createDefaultConfig();
|
||||
configMap.put(name, config);
|
||||
}
|
||||
|
||||
if (ttl > 0) {
|
||||
config.setTTL(ttl * 1000);
|
||||
}
|
||||
|
||||
if (array.length > 1) {
|
||||
config.setTTL(DurationStyle.detectAndParse(array[1]).toMillis());
|
||||
}
|
||||
if (array.length > 2) {
|
||||
config.setMaxIdleTime(DurationStyle.detectAndParse(array[2]).toMillis());
|
||||
}
|
||||
if (array.length > 3) {
|
||||
config.setMaxSize(Integer.parseInt(array[3]));
|
||||
}
|
||||
|
||||
if (config.getMaxIdleTime() == 0 && config.getTTL() == 0 && config.getMaxSize() == 0) {
|
||||
return createMap(name, config);
|
||||
}
|
||||
|
||||
return createMapCache(name, config);
|
||||
}
|
||||
|
||||
private Cache createMap(String name, CacheConfig config) {
|
||||
RMap<Object, Object> map = RedisUtils.getClient().getMap(name);
|
||||
|
||||
Cache cache = new CaffeineCacheDecorator(new RedissonCache(map, allowNullValues));
|
||||
if (transactionAware) {
|
||||
cache = new TransactionAwareCacheDecorator(cache);
|
||||
}
|
||||
Cache oldCache = instanceMap.putIfAbsent(name, cache);
|
||||
if (oldCache != null) {
|
||||
cache = oldCache;
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
private Cache createMapCache(String name, CacheConfig config) {
|
||||
RMapCache<Object, Object> map = RedisUtils.getClient().getMapCache(name);
|
||||
|
||||
Cache cache = new CaffeineCacheDecorator(new RedissonCache(map, config, allowNullValues));
|
||||
if (transactionAware) {
|
||||
cache = new TransactionAwareCacheDecorator(cache);
|
||||
}
|
||||
Cache oldCache = instanceMap.putIfAbsent(name, cache);
|
||||
if (oldCache != null) {
|
||||
cache = oldCache;
|
||||
} else {
|
||||
map.setMaxSize(config.getMaxSize());
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getCacheNames() {
|
||||
return Collections.unmodifiableSet(configMap.keySet());
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
package com.fastbee.framework.manager.factory;
|
||||
|
||||
import java.util.TimerTask;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import com.fastbee.common.constant.Constants;
|
||||
import com.fastbee.common.utils.LogUtils;
|
||||
import com.fastbee.common.utils.ServletUtils;
|
||||
import com.fastbee.common.utils.StringUtils;
|
||||
import com.fastbee.common.utils.ip.AddressUtils;
|
||||
import com.fastbee.common.utils.ip.IpUtils;
|
||||
import com.fastbee.common.utils.spring.SpringUtils;
|
||||
import com.fastbee.system.domain.SysLogininfor;
|
||||
import com.fastbee.system.domain.SysOperLog;
|
||||
import com.fastbee.system.service.ISysLogininforService;
|
||||
import com.fastbee.system.service.ISysOperLogService;
|
||||
import eu.bitwalker.useragentutils.UserAgent;
|
||||
|
||||
/**
|
||||
* 异步工厂(产生任务用)
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public class AsyncFactory
|
||||
{
|
||||
private static final Logger sys_user_logger = LoggerFactory.getLogger("sys-user");
|
||||
|
||||
/**
|
||||
* 记录登录信息
|
||||
*
|
||||
* @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 = IpUtils.getIpAddr(ServletUtils.getRequest());
|
||||
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.setUserName(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(ISysLogininforService.class).insertLogininfor(logininfor);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作日志记录
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,136 @@
|
||||
package com.fastbee.framework.mybatis;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
||||
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* 拓展 MyBatis Plus QueryWrapper 类,主要增加如下功能:
|
||||
* <p>
|
||||
* 1. 拼接条件的方法,增加 xxxIfPresent 方法,用于判断值不存在的时候,不要拼接到条件中。
|
||||
*
|
||||
* @param <T> 数据类型
|
||||
*/
|
||||
public class LambdaQueryWrapperX<T> extends LambdaQueryWrapper<T> {
|
||||
|
||||
public LambdaQueryWrapperX<T> likeIfPresent(SFunction<T, ?> column, String val) {
|
||||
if (StringUtils.hasText(val)) {
|
||||
return (LambdaQueryWrapperX<T>) super.like(column, val);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public LambdaQueryWrapperX<T> inIfPresent(SFunction<T, ?> column, Collection<?> values) {
|
||||
if (!CollectionUtils.isEmpty(values)) {
|
||||
return (LambdaQueryWrapperX<T>) super.in(column, values);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public LambdaQueryWrapperX<T> inIfPresent(SFunction<T, ?> column, Object... values) {
|
||||
if (!ArrayUtil.isEmpty(values)) {
|
||||
return (LambdaQueryWrapperX<T>) super.in(column, values);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public LambdaQueryWrapperX<T> eqIfPresent(SFunction<T, ?> column, Object val) {
|
||||
if (ObjectUtil.isNotEmpty(val)) {
|
||||
return (LambdaQueryWrapperX<T>) super.eq(column, val);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public LambdaQueryWrapperX<T> neIfPresent(SFunction<T, ?> column, Object val) {
|
||||
if (ObjectUtil.isNotEmpty(val)) {
|
||||
return (LambdaQueryWrapperX<T>) super.ne(column, val);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public LambdaQueryWrapperX<T> gtIfPresent(SFunction<T, ?> column, Object val) {
|
||||
if (val != null) {
|
||||
return (LambdaQueryWrapperX<T>) super.gt(column, val);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public LambdaQueryWrapperX<T> geIfPresent(SFunction<T, ?> column, Object val) {
|
||||
if (val != null) {
|
||||
return (LambdaQueryWrapperX<T>) super.ge(column, val);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public LambdaQueryWrapperX<T> ltIfPresent(SFunction<T, ?> column, Object val) {
|
||||
if (val != null) {
|
||||
return (LambdaQueryWrapperX<T>) super.lt(column, val);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public LambdaQueryWrapperX<T> leIfPresent(SFunction<T, ?> column, Object val) {
|
||||
if (val != null) {
|
||||
return (LambdaQueryWrapperX<T>) super.le(column, val);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public LambdaQueryWrapperX<T> betweenIfPresent(SFunction<T, ?> column, Object val1, Object val2) {
|
||||
if (val1 != null && val2 != null) {
|
||||
return (LambdaQueryWrapperX<T>) super.between(column, val1, val2);
|
||||
}
|
||||
if (val1 != null) {
|
||||
return (LambdaQueryWrapperX<T>) ge(column, val1);
|
||||
}
|
||||
if (val2 != null) {
|
||||
return (LambdaQueryWrapperX<T>) le(column, val2);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public LambdaQueryWrapperX<T> betweenIfPresent(SFunction<T, ?> column, Object[] values) {
|
||||
Object val1 = ArrayUtils.get(values, 0);
|
||||
Object val2 = ArrayUtils.get(values, 1);
|
||||
return betweenIfPresent(column, val1, val2);
|
||||
}
|
||||
|
||||
// ========== 重写父类方法,方便链式调用 ==========
|
||||
|
||||
@Override
|
||||
public LambdaQueryWrapperX<T> eq(boolean condition, SFunction<T, ?> column, Object val) {
|
||||
super.eq(condition, column, val);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LambdaQueryWrapperX<T> eq(SFunction<T, ?> column, Object val) {
|
||||
super.eq(column, val);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LambdaQueryWrapperX<T> orderByDesc(SFunction<T, ?> column) {
|
||||
super.orderByDesc(true, column);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LambdaQueryWrapperX<T> last(String lastSql) {
|
||||
super.last(lastSql);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LambdaQueryWrapperX<T> in(SFunction<T, ?> column, Collection<?> coll) {
|
||||
super.in(column, coll);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,140 @@
|
||||
package com.fastbee.framework.mybatis;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.ArrayUtils;
|
||||
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* 拓展 MyBatis Plus QueryWrapper 类,主要增加如下功能:
|
||||
*
|
||||
* 1. 拼接条件的方法,增加 xxxIfPresent 方法,用于判断值不存在的时候,不要拼接到条件中。
|
||||
*
|
||||
* @param <T> 数据类型
|
||||
*/
|
||||
public class QueryWrapperX<T> extends QueryWrapper<T> {
|
||||
|
||||
public QueryWrapperX<T> likeIfPresent(String column, String val) {
|
||||
if (StringUtils.hasText(val)) {
|
||||
return (QueryWrapperX<T>) super.like(column, val);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryWrapperX<T> inIfPresent(String column, Collection<?> values) {
|
||||
if (!CollectionUtils.isEmpty(values)) {
|
||||
return (QueryWrapperX<T>) super.in(column, values);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryWrapperX<T> inIfPresent(String column, Object... values) {
|
||||
if (!ArrayUtils.isEmpty(values)) {
|
||||
return (QueryWrapperX<T>) super.in(column, values);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryWrapperX<T> eqIfPresent(String column, Object val) {
|
||||
if (val != null) {
|
||||
return (QueryWrapperX<T>) super.eq(column, val);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryWrapperX<T> neIfPresent(String column, Object val) {
|
||||
if (val != null) {
|
||||
return (QueryWrapperX<T>) super.ne(column, val);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryWrapperX<T> gtIfPresent(String column, Object val) {
|
||||
if (val != null) {
|
||||
return (QueryWrapperX<T>) super.gt(column, val);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryWrapperX<T> geIfPresent(String column, Object val) {
|
||||
if (val != null) {
|
||||
return (QueryWrapperX<T>) super.ge(column, val);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryWrapperX<T> ltIfPresent(String column, Object val) {
|
||||
if (val != null) {
|
||||
return (QueryWrapperX<T>) super.lt(column, val);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryWrapperX<T> leIfPresent(String column, Object val) {
|
||||
if (val != null) {
|
||||
return (QueryWrapperX<T>) super.le(column, val);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryWrapperX<T> betweenIfPresent(String column, Object val1, Object val2) {
|
||||
if (val1 != null && val2 != null) {
|
||||
return (QueryWrapperX<T>) super.between(column, val1, val2);
|
||||
}
|
||||
if (val1 != null) {
|
||||
return (QueryWrapperX<T>) ge(column, val1);
|
||||
}
|
||||
if (val2 != null) {
|
||||
return (QueryWrapperX<T>) le(column, val2);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryWrapperX<T> betweenIfPresent(String column, Object[] values) {
|
||||
if (values!= null && values.length != 0 && values[0] != null && values[1] != null) {
|
||||
return (QueryWrapperX<T>) super.between(column, values[0], values[1]);
|
||||
}
|
||||
if (values!= null && values.length != 0 && values[0] != null) {
|
||||
return (QueryWrapperX<T>) ge(column, values[0]);
|
||||
}
|
||||
if (values!= null && values.length != 0 && values[1] != null) {
|
||||
return (QueryWrapperX<T>) le(column, values[1]);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
// ========== 重写父类方法,方便链式调用 ==========
|
||||
|
||||
@Override
|
||||
public QueryWrapperX<T> eq(boolean condition, String column, Object val) {
|
||||
super.eq(condition, column, val);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryWrapperX<T> eq(String column, Object val) {
|
||||
super.eq(column, val);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryWrapperX<T> orderByDesc(String column) {
|
||||
super.orderByDesc(true, column);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryWrapperX<T> last(String lastSql) {
|
||||
super.last(lastSql);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryWrapperX<T> in(String column, Collection<?> coll) {
|
||||
super.in(column, coll);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
package com.fastbee.framework.mybatis.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
|
||||
import com.baomidou.mybatisplus.extension.toolkit.Db;
|
||||
import com.fastbee.common.core.domain.PageParam;
|
||||
import com.fastbee.common.core.domain.PageResult;
|
||||
import com.fastbee.framework.mybatis.utils.MyBatisUtils;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 在 MyBatis Plus 的 BaseMapper 的基础上拓展,提供更多的能力
|
||||
* <p>
|
||||
* 为什么继承 MPJBaseMapper 接口?支持 MyBatis Plus 多表 Join 的能力。
|
||||
*/
|
||||
public interface BaseMapperX<T> extends BaseMapper<T> {
|
||||
|
||||
default PageResult<T> selectPage(PageParam pageParam, @Param("ew") Wrapper<T> queryWrapper) {
|
||||
// MyBatis Plus 查询
|
||||
IPage<T> mpPage = MyBatisUtils.buildPage(pageParam);
|
||||
selectPage(mpPage, queryWrapper);
|
||||
// 转换返回
|
||||
return new PageResult<>(mpPage.getRecords(), mpPage.getTotal());
|
||||
}
|
||||
|
||||
default T selectOne(String field, Object value) {
|
||||
return selectOne(new QueryWrapper<T>().eq(field, value));
|
||||
}
|
||||
|
||||
default T selectOne(SFunction<T, ?> field, Object value) {
|
||||
return selectOne(new LambdaQueryWrapper<T>().eq(field, value));
|
||||
}
|
||||
|
||||
default T selectOne(String field1, Object value1, String field2, Object value2) {
|
||||
return selectOne(new QueryWrapper<T>().eq(field1, value1).eq(field2, value2));
|
||||
}
|
||||
|
||||
default T selectOne(SFunction<T, ?> field1, Object value1, SFunction<T, ?> field2, Object value2) {
|
||||
return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2));
|
||||
}
|
||||
|
||||
default Long selectCount() {
|
||||
return selectCount(new QueryWrapper<T>());
|
||||
}
|
||||
|
||||
default Long selectCount(String field, Object value) {
|
||||
return selectCount(new QueryWrapper<T>().eq(field, value));
|
||||
}
|
||||
|
||||
default Long selectCount(SFunction<T, ?> field, Object value) {
|
||||
return selectCount(new LambdaQueryWrapper<T>().eq(field, value));
|
||||
}
|
||||
|
||||
default List<T> selectList() {
|
||||
return selectList(new QueryWrapper<>());
|
||||
}
|
||||
|
||||
default List<T> selectList(String field, Object value) {
|
||||
return selectList(new QueryWrapper<T>().eq(field, value));
|
||||
}
|
||||
|
||||
default List<T> selectList(SFunction<T, ?> field, Object value) {
|
||||
return selectList(new LambdaQueryWrapper<T>().eq(field, value));
|
||||
}
|
||||
|
||||
default List<T> selectList(String field, Collection<?> values) {
|
||||
return selectList(new QueryWrapper<T>().in(field, values));
|
||||
}
|
||||
|
||||
default List<T> selectList(SFunction<T, ?> field, Collection<?> values) {
|
||||
return selectList(new LambdaQueryWrapper<T>().in(field, values));
|
||||
}
|
||||
|
||||
default List<T> selectList(SFunction<T, ?> leField, SFunction<T, ?> geField, Object value) {
|
||||
return selectList(new LambdaQueryWrapper<T>().le(leField, value).ge(geField, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量插入,适合大量数据插入
|
||||
*
|
||||
* @param entities 实体们
|
||||
*/
|
||||
default void insertBatch(Collection<T> entities) {
|
||||
Db.saveBatch(entities);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量插入,适合大量数据插入
|
||||
*
|
||||
* @param entities 实体们
|
||||
* @param size 插入数量 Db.saveBatch 默认为 1000
|
||||
*/
|
||||
default void insertBatch(Collection<T> entities, int size) {
|
||||
Db.saveBatch(entities, size);
|
||||
}
|
||||
|
||||
default void updateBatch(T update) {
|
||||
update(update, new QueryWrapper<>());
|
||||
}
|
||||
|
||||
default void updateBatch(Collection<T> entities, int size) {
|
||||
Db.updateBatchById(entities, size);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
package com.fastbee.framework.mybatis.utils;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import com.baomidou.mybatisplus.core.metadata.OrderItem;
|
||||
import com.baomidou.mybatisplus.core.toolkit.StringPool;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.fastbee.common.core.domain.PageParam;
|
||||
import com.fastbee.common.core.domain.SortingField;
|
||||
import net.sf.jsqlparser.expression.Alias;
|
||||
import net.sf.jsqlparser.schema.Column;
|
||||
import net.sf.jsqlparser.schema.Table;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* MyBatis 工具类
|
||||
*/
|
||||
public class MyBatisUtils {
|
||||
|
||||
private static final String MYSQL_ESCAPE_CHARACTER = "`";
|
||||
|
||||
public static <T> Page<T> buildPage(PageParam pageParam) {
|
||||
return buildPage(pageParam, null);
|
||||
}
|
||||
|
||||
public static <T> Page<T> buildPage(PageParam pageParam, Collection<SortingField> sortingFields) {
|
||||
// 页码 + 数量
|
||||
Page<T> page = new Page<>(pageParam.getPageNo(), pageParam.getPageSize());
|
||||
// 排序字段
|
||||
if (!CollectionUtil.isEmpty(sortingFields)) {
|
||||
page.addOrder(sortingFields.stream().map(sortingField -> SortingField.ORDER_ASC.equals(sortingField.getOrder()) ?
|
||||
OrderItem.asc(sortingField.getField()) : OrderItem.desc(sortingField.getField()))
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
return page;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将拦截器添加到链中
|
||||
* 由于 MybatisPlusInterceptor 不支持添加拦截器,所以只能全量设置
|
||||
*
|
||||
* @param interceptor 链
|
||||
* @param inner 拦截器
|
||||
* @param index 位置
|
||||
*/
|
||||
public static void addInterceptor(MybatisPlusInterceptor interceptor, InnerInterceptor inner, int index) {
|
||||
List<InnerInterceptor> inners = new ArrayList<>(interceptor.getInterceptors());
|
||||
inners.add(index, inner);
|
||||
interceptor.setInterceptors(inners);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得 Table 对应的表名
|
||||
*
|
||||
* 兼容 MySQL 转义表名 `t_xxx`
|
||||
*
|
||||
* @param table 表
|
||||
* @return 去除转移字符后的表名
|
||||
*/
|
||||
public static String getTableName(Table table) {
|
||||
String tableName = table.getName();
|
||||
if (tableName.startsWith(MYSQL_ESCAPE_CHARACTER) && tableName.endsWith(MYSQL_ESCAPE_CHARACTER)) {
|
||||
tableName = tableName.substring(1, tableName.length() - 1);
|
||||
}
|
||||
return tableName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 Column 对象
|
||||
*
|
||||
* @param tableName 表名
|
||||
* @param tableAlias 别名
|
||||
* @param column 字段名
|
||||
* @return Column 对象
|
||||
*/
|
||||
public static Column buildColumn(String tableName, Alias tableAlias, String column) {
|
||||
if (tableAlias != null) {
|
||||
tableName = tableAlias.getName();
|
||||
}
|
||||
return new Column(tableName + StringPool.DOT + column);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package com.fastbee.framework.security.context;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
|
||||
/**
|
||||
* 身份验证信息
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public class AuthenticationContextHolder
|
||||
{
|
||||
private static final ThreadLocal<Authentication> contextHolder = new ThreadLocal<>();
|
||||
|
||||
public static Authentication getContext()
|
||||
{
|
||||
return contextHolder.get();
|
||||
}
|
||||
|
||||
public static void setContext(Authentication context)
|
||||
{
|
||||
contextHolder.set(context);
|
||||
}
|
||||
|
||||
public static void clearContext()
|
||||
{
|
||||
contextHolder.remove();
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package com.fastbee.framework.security.context;
|
||||
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import com.fastbee.common.core.text.Convert;
|
||||
|
||||
/**
|
||||
* 权限信息
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public class PermissionContextHolder
|
||||
{
|
||||
private static final String PERMISSION_CONTEXT_ATTRIBUTES = "PERMISSION_CONTEXT";
|
||||
|
||||
public static void setContext(String permission)
|
||||
{
|
||||
RequestContextHolder.currentRequestAttributes().setAttribute(PERMISSION_CONTEXT_ATTRIBUTES, permission,
|
||||
RequestAttributes.SCOPE_REQUEST);
|
||||
}
|
||||
|
||||
public static String getContext()
|
||||
{
|
||||
return Convert.toStr(RequestContextHolder.currentRequestAttributes().getAttribute(PERMISSION_CONTEXT_ATTRIBUTES,
|
||||
RequestAttributes.SCOPE_REQUEST));
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package com.fastbee.framework.security.filter;
|
||||
|
||||
import java.io.IOException;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
import com.fastbee.common.core.domain.model.LoginUser;
|
||||
import com.fastbee.common.utils.SecurityUtils;
|
||||
import com.fastbee.common.utils.StringUtils;
|
||||
import com.fastbee.framework.web.service.TokenService;
|
||||
|
||||
/**
|
||||
* token过滤器 验证token有效性
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Component
|
||||
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
|
||||
{
|
||||
@Autowired
|
||||
private TokenService tokenService;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
|
||||
throws ServletException, IOException
|
||||
{
|
||||
LoginUser loginUser = tokenService.getLoginUser(request);
|
||||
if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication()))
|
||||
{
|
||||
tokenService.verifyToken(loginUser);
|
||||
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
|
||||
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
|
||||
}
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package com.fastbee.framework.security.handle;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.stereotype.Component;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.fastbee.common.constant.HttpStatus;
|
||||
import com.fastbee.common.core.domain.AjaxResult;
|
||||
import com.fastbee.common.utils.ServletUtils;
|
||||
import com.fastbee.common.utils.StringUtils;
|
||||
|
||||
/**
|
||||
* 认证失败处理类 返回未授权
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Component
|
||||
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable
|
||||
{
|
||||
private static final long serialVersionUID = -8970718410437077606L;
|
||||
|
||||
@Override
|
||||
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)
|
||||
throws IOException
|
||||
{
|
||||
// if (isAjaxRequest(request)){
|
||||
int code = HttpStatus.UNAUTHORIZED;
|
||||
String msg = StringUtils.format("请求访问:{},认证失败,无法访问系统资源", request.getRequestURI());
|
||||
ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(code, msg)));
|
||||
// }else {
|
||||
// response.sendRedirect("/oauth/login");
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
public static boolean isAjaxRequest(HttpServletRequest request) {
|
||||
String ajaxFlag = request.getHeader("X-Requested-With");
|
||||
return ajaxFlag != null && "XMLHttpRequest".equals(ajaxFlag);
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package com.fastbee.framework.security.handle;
|
||||
|
||||
import java.io.IOException;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.fastbee.common.constant.Constants;
|
||||
import com.fastbee.common.core.domain.AjaxResult;
|
||||
import com.fastbee.common.core.domain.model.LoginUser;
|
||||
import com.fastbee.common.utils.ServletUtils;
|
||||
import com.fastbee.common.utils.StringUtils;
|
||||
import com.fastbee.framework.manager.AsyncManager;
|
||||
import com.fastbee.framework.manager.factory.AsyncFactory;
|
||||
import com.fastbee.framework.web.service.TokenService;
|
||||
|
||||
/**
|
||||
* 自定义退出处理类 返回成功
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Configuration
|
||||
public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler
|
||||
{
|
||||
@Autowired
|
||||
private TokenService tokenService;
|
||||
|
||||
/**
|
||||
* 退出处理
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
||||
throws IOException, ServletException
|
||||
{
|
||||
LoginUser loginUser = tokenService.getLoginUser(request);
|
||||
if (StringUtils.isNotNull(loginUser))
|
||||
{
|
||||
String userName = loginUser.getUsername();
|
||||
// 删除用户缓存记录
|
||||
tokenService.delLoginUser(loginUser.getToken());
|
||||
// 记录用户退出日志
|
||||
AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGOUT, "退出成功"));
|
||||
}
|
||||
ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.success("退出成功")));
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
package com.fastbee.framework.utils;
|
||||
|
||||
import com.fastbee.common.utils.spring.SpringUtils;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.redisson.api.RMap;
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.CacheManager;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 缓存操作工具类 {@link }
|
||||
*
|
||||
* @author Michelle.Chung
|
||||
* @date 2022/8/13
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
@SuppressWarnings(value = {"unchecked"})
|
||||
public class CacheUtils {
|
||||
|
||||
private static final CacheManager CACHE_MANAGER = SpringUtils.getBean(CacheManager.class);
|
||||
|
||||
/**
|
||||
* 获取缓存组内所有的KEY
|
||||
*
|
||||
* @param cacheNames 缓存组名称
|
||||
*/
|
||||
public static Set<Object> keys(String cacheNames) {
|
||||
RMap<Object, Object> rmap = (RMap<Object, Object>) CACHE_MANAGER.getCache(cacheNames).getNativeCache();
|
||||
return rmap.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存值
|
||||
*
|
||||
* @param cacheNames 缓存组名称
|
||||
* @param key 缓存key
|
||||
*/
|
||||
public static <T> T get(String cacheNames, Object key) {
|
||||
Cache.ValueWrapper wrapper = CACHE_MANAGER.getCache(cacheNames).get(key);
|
||||
return wrapper != null ? (T) wrapper.get() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存缓存值
|
||||
*
|
||||
* @param cacheNames 缓存组名称
|
||||
* @param key 缓存key
|
||||
* @param value 缓存值
|
||||
*/
|
||||
public static void put(String cacheNames, Object key, Object value) {
|
||||
CACHE_MANAGER.getCache(cacheNames).put(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除缓存值
|
||||
*
|
||||
* @param cacheNames 缓存组名称
|
||||
* @param key 缓存key
|
||||
*/
|
||||
public static void evict(String cacheNames, Object key) {
|
||||
CACHE_MANAGER.getCache(cacheNames).evict(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空缓存值
|
||||
*
|
||||
* @param cacheNames 缓存组名称
|
||||
*/
|
||||
public static void clear(String cacheNames) {
|
||||
CACHE_MANAGER.getCache(cacheNames).clear();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,236 @@
|
||||
package com.fastbee.framework.utils;
|
||||
|
||||
import com.fastbee.common.utils.spring.SpringUtils;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.redisson.api.*;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* 分布式队列工具
|
||||
* 轻量级队列 重量级数据量 请使用 MQ
|
||||
* 要求 redis 5.X 以上
|
||||
*
|
||||
* @author Lion Li
|
||||
* @version 3.6.0 新增
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class QueueUtils {
|
||||
|
||||
private static final RedissonClient CLIENT = SpringUtils.getBean(RedissonClient.class);
|
||||
|
||||
|
||||
/**
|
||||
* 获取客户端实例
|
||||
*/
|
||||
public static RedissonClient getClient() {
|
||||
return CLIENT;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加普通队列数据
|
||||
*
|
||||
* @param queueName 队列名
|
||||
* @param data 数据
|
||||
*/
|
||||
public static <T> boolean addQueueObject(String queueName, T data) {
|
||||
RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
|
||||
return queue.offer(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用获取一个队列数据 没有数据返回 null(不支持延迟队列)
|
||||
*
|
||||
* @param queueName 队列名
|
||||
*/
|
||||
public static <T> T getQueueObject(String queueName) {
|
||||
RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
|
||||
return queue.poll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用删除队列数据(不支持延迟队列)
|
||||
*/
|
||||
public static <T> boolean removeQueueObject(String queueName, T data) {
|
||||
RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
|
||||
return queue.remove(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用销毁队列 所有阻塞监听 报错(不支持延迟队列)
|
||||
*/
|
||||
public static <T> boolean destroyQueue(String queueName) {
|
||||
RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
|
||||
return queue.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加延迟队列数据 默认毫秒
|
||||
*
|
||||
* @param queueName 队列名
|
||||
* @param data 数据
|
||||
* @param time 延迟时间
|
||||
*/
|
||||
public static <T> void addDelayedQueueObject(String queueName, T data, long time) {
|
||||
addDelayedQueueObject(queueName, data, time, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加延迟队列数据
|
||||
*
|
||||
* @param queueName 队列名
|
||||
* @param data 数据
|
||||
* @param time 延迟时间
|
||||
* @param timeUnit 单位
|
||||
*/
|
||||
public static <T> void addDelayedQueueObject(String queueName, T data, long time, TimeUnit timeUnit) {
|
||||
RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
|
||||
RDelayedQueue<T> delayedQueue = CLIENT.getDelayedQueue(queue);
|
||||
delayedQueue.offer(data, time, timeUnit);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个延迟队列数据 没有数据返回 null
|
||||
*
|
||||
* @param queueName 队列名
|
||||
*/
|
||||
public static <T> T getDelayedQueueObject(String queueName) {
|
||||
RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
|
||||
RDelayedQueue<T> delayedQueue = CLIENT.getDelayedQueue(queue);
|
||||
return delayedQueue.poll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除延迟队列数据
|
||||
*/
|
||||
public static <T> boolean removeDelayedQueueObject(String queueName, T data) {
|
||||
RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
|
||||
RDelayedQueue<T> delayedQueue = CLIENT.getDelayedQueue(queue);
|
||||
return delayedQueue.remove(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁延迟队列 所有阻塞监听 报错
|
||||
*/
|
||||
public static <T> void destroyDelayedQueue(String queueName) {
|
||||
RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
|
||||
RDelayedQueue<T> delayedQueue = CLIENT.getDelayedQueue(queue);
|
||||
delayedQueue.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加优先队列数据
|
||||
*
|
||||
* @param queueName 队列名
|
||||
* @param data 数据
|
||||
*/
|
||||
public static <T> boolean addPriorityQueueObject(String queueName, T data) {
|
||||
RPriorityBlockingQueue<T> priorityBlockingQueue = CLIENT.getPriorityBlockingQueue(queueName);
|
||||
return priorityBlockingQueue.offer(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 优先队列获取一个队列数据 没有数据返回 null(不支持延迟队列)
|
||||
*
|
||||
* @param queueName 队列名
|
||||
*/
|
||||
public static <T> T getPriorityQueueObject(String queueName) {
|
||||
RPriorityBlockingQueue<T> queue = CLIENT.getPriorityBlockingQueue(queueName);
|
||||
return queue.poll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 优先队列删除队列数据(不支持延迟队列)
|
||||
*/
|
||||
public static <T> boolean removePriorityQueueObject(String queueName, T data) {
|
||||
RPriorityBlockingQueue<T> queue = CLIENT.getPriorityBlockingQueue(queueName);
|
||||
return queue.remove(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 优先队列销毁队列 所有阻塞监听 报错(不支持延迟队列)
|
||||
*/
|
||||
public static <T> boolean destroyPriorityQueue(String queueName) {
|
||||
RPriorityBlockingQueue<T> queue = CLIENT.getPriorityBlockingQueue(queueName);
|
||||
return queue.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试设置 有界队列 容量 用于限制数量
|
||||
*
|
||||
* @param queueName 队列名
|
||||
* @param capacity 容量
|
||||
*/
|
||||
public static <T> boolean trySetBoundedQueueCapacity(String queueName, int capacity) {
|
||||
RBoundedBlockingQueue<T> boundedBlockingQueue = CLIENT.getBoundedBlockingQueue(queueName);
|
||||
return boundedBlockingQueue.trySetCapacity(capacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试设置 有界队列 容量 用于限制数量
|
||||
*
|
||||
* @param queueName 队列名
|
||||
* @param capacity 容量
|
||||
* @param destroy 已存在是否销毁
|
||||
*/
|
||||
public static <T> boolean trySetBoundedQueueCapacity(String queueName, int capacity, boolean destroy) {
|
||||
RBoundedBlockingQueue<T> boundedBlockingQueue = CLIENT.getBoundedBlockingQueue(queueName);
|
||||
if (boundedBlockingQueue.isExists() && destroy) {
|
||||
destroyQueue(queueName);
|
||||
}
|
||||
return boundedBlockingQueue.trySetCapacity(capacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加有界队列数据
|
||||
*
|
||||
* @param queueName 队列名
|
||||
* @param data 数据
|
||||
* @return 添加成功 true 已达到界限 false
|
||||
*/
|
||||
public static <T> boolean addBoundedQueueObject(String queueName, T data) {
|
||||
RBoundedBlockingQueue<T> boundedBlockingQueue = CLIENT.getBoundedBlockingQueue(queueName);
|
||||
return boundedBlockingQueue.offer(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 有界队列获取一个队列数据 没有数据返回 null(不支持延迟队列)
|
||||
*
|
||||
* @param queueName 队列名
|
||||
*/
|
||||
public static <T> T getBoundedQueueObject(String queueName) {
|
||||
RBoundedBlockingQueue<T> queue = CLIENT.getBoundedBlockingQueue(queueName);
|
||||
return queue.poll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 有界队列删除队列数据(不支持延迟队列)
|
||||
*/
|
||||
public static <T> boolean removeBoundedQueueObject(String queueName, T data) {
|
||||
RBoundedBlockingQueue<T> queue = CLIENT.getBoundedBlockingQueue(queueName);
|
||||
return queue.remove(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 有界队列销毁队列 所有阻塞监听 报错(不支持延迟队列)
|
||||
*/
|
||||
public static <T> boolean destroyBoundedQueue(String queueName) {
|
||||
RBoundedBlockingQueue<T> queue = CLIENT.getBoundedBlockingQueue(queueName);
|
||||
return queue.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅阻塞队列(可订阅所有实现类 例如: 延迟 优先 有界 等)
|
||||
*/
|
||||
public static <T> void subscribeBlockingQueue(String queueName, Consumer<T> consumer, boolean isDelayed) {
|
||||
RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
|
||||
if (isDelayed) {
|
||||
// 订阅延迟队列
|
||||
CLIENT.getDelayedQueue(queue);
|
||||
}
|
||||
queue.subscribeOnElements(consumer);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,548 @@
|
||||
package com.fastbee.framework.utils;
|
||||
|
||||
import com.fastbee.common.utils.spring.SpringUtils;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.redisson.api.*;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* redis 工具类
|
||||
*
|
||||
* @author Lion Li
|
||||
* @version 3.1.0 新增
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
@SuppressWarnings(value = {"unchecked", "rawtypes"})
|
||||
public class RedisUtils {
|
||||
|
||||
private static final RedissonClient CLIENT = SpringUtils.getBean(RedissonClient.class);
|
||||
|
||||
/**
|
||||
* 限流
|
||||
*
|
||||
* @param key 限流key
|
||||
* @param rateType 限流类型
|
||||
* @param rate 速率
|
||||
* @param rateInterval 速率间隔
|
||||
* @return -1 表示失败
|
||||
*/
|
||||
public static long rateLimiter(String key, RateType rateType, int rate, int rateInterval) {
|
||||
RRateLimiter rateLimiter = CLIENT.getRateLimiter(key);
|
||||
rateLimiter.trySetRate(rateType, rate, rateInterval, RateIntervalUnit.SECONDS);
|
||||
if (rateLimiter.tryAcquire()) {
|
||||
return rateLimiter.availablePermits();
|
||||
} else {
|
||||
return -1L;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端实例
|
||||
*/
|
||||
public static RedissonClient getClient() {
|
||||
return CLIENT;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布通道消息
|
||||
*
|
||||
* @param channelKey 通道key
|
||||
* @param msg 发送数据
|
||||
* @param consumer 自定义处理
|
||||
*/
|
||||
public static <T> void publish(String channelKey, T msg, Consumer<T> consumer) {
|
||||
RTopic topic = CLIENT.getTopic(channelKey);
|
||||
topic.publish(msg);
|
||||
consumer.accept(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布消息到指定的频道
|
||||
*
|
||||
* @param channelKey 通道key
|
||||
* @param msg 发送数据
|
||||
*/
|
||||
public static <T> void publish(String channelKey, T msg) {
|
||||
RTopic topic = CLIENT.getTopic(channelKey);
|
||||
topic.publish(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅通道接收消息
|
||||
*
|
||||
* @param channelKey 通道key
|
||||
* @param clazz 消息类型
|
||||
* @param consumer 自定义处理
|
||||
*/
|
||||
public static <T> void subscribe(String channelKey, Class<T> clazz, Consumer<T> consumer) {
|
||||
RTopic topic = CLIENT.getTopic(channelKey);
|
||||
topic.addListener(clazz, (channel, msg) -> consumer.accept(msg));
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存基本的对象,Integer、String、实体类等
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
* @param value 缓存的值
|
||||
*/
|
||||
public static <T> void setCacheObject(final String key, final T value) {
|
||||
setCacheObject(key, value, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存基本的对象,保留当前对象 TTL 有效期
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
* @param value 缓存的值
|
||||
* @param isSaveTtl 是否保留TTL有效期(例如: set之前ttl剩余90 set之后还是为90)
|
||||
* @since Redis 6.X 以上使用 setAndKeepTTL 兼容 5.X 方案
|
||||
*/
|
||||
public static <T> void setCacheObject(final String key, final T value, final boolean isSaveTtl) {
|
||||
RBucket<T> bucket = CLIENT.getBucket(key);
|
||||
if (isSaveTtl) {
|
||||
try {
|
||||
bucket.setAndKeepTTL(value);
|
||||
} catch (Exception e) {
|
||||
long timeToLive = bucket.remainTimeToLive();
|
||||
if (timeToLive == -1) {
|
||||
setCacheObject(key, value);
|
||||
} else {
|
||||
setCacheObject(key, value, Duration.ofMillis(timeToLive));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
bucket.set(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存基本的对象,Integer、String、实体类等
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
* @param value 缓存的值
|
||||
* @param duration 时间
|
||||
*/
|
||||
public static <T> void setCacheObject(final String key, final T value, final Duration duration) {
|
||||
RBatch batch = CLIENT.createBatch();
|
||||
RBucketAsync<T> bucket = batch.getBucket(key);
|
||||
bucket.setAsync(value);
|
||||
bucket.expireAsync(duration);
|
||||
batch.execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果不存在则设置 并返回 true 如果存在则返回 false
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
* @param value 缓存的值
|
||||
* @return set成功或失败
|
||||
*/
|
||||
public static <T> boolean setObjectIfAbsent(final String key, final T value, final Duration duration) {
|
||||
RBucket<T> bucket = CLIENT.getBucket(key);
|
||||
return bucket.setIfAbsent(value, duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果存在则设置 并返回 true 如果存在则返回 false
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
* @param value 缓存的值
|
||||
* @return set成功或失败
|
||||
*/
|
||||
public static <T> boolean setObjectIfExists(final String key, final T value, final Duration duration) {
|
||||
RBucket<T> bucket = CLIENT.getBucket(key);
|
||||
return bucket.setIfExists(value, duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册对象监听器
|
||||
* <p>
|
||||
* key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
* @param listener 监听器配置
|
||||
*/
|
||||
public static <T> void addObjectListener(final String key, final ObjectListener listener) {
|
||||
RBucket<T> result = CLIENT.getBucket(key);
|
||||
result.addListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置有效时间
|
||||
*
|
||||
* @param key Redis键
|
||||
* @param timeout 超时时间
|
||||
* @return true=设置成功;false=设置失败
|
||||
*/
|
||||
public static boolean expire(final String key, final long timeout) {
|
||||
return expire(key, Duration.ofSeconds(timeout));
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置有效时间
|
||||
*
|
||||
* @param key Redis键
|
||||
* @param duration 超时时间
|
||||
* @return true=设置成功;false=设置失败
|
||||
*/
|
||||
public static boolean expire(final String key, final Duration duration) {
|
||||
RBucket rBucket = CLIENT.getBucket(key);
|
||||
return rBucket.expire(duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得缓存的基本对象。
|
||||
*
|
||||
* @param key 缓存键值
|
||||
* @return 缓存键值对应的数据
|
||||
*/
|
||||
public static <T> T getCacheObject(final String key) {
|
||||
RBucket<T> rBucket = CLIENT.getBucket(key);
|
||||
return rBucket.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得key剩余存活时间
|
||||
*
|
||||
* @param key 缓存键值
|
||||
* @return 剩余存活时间
|
||||
*/
|
||||
public static <T> long getTimeToLive(final String key) {
|
||||
RBucket<T> rBucket = CLIENT.getBucket(key);
|
||||
return rBucket.remainTimeToLive();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除单个对象
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
*/
|
||||
public static boolean deleteObject(final String key) {
|
||||
return CLIENT.getBucket(key).delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除集合对象
|
||||
*
|
||||
* @param collection 多个对象
|
||||
*/
|
||||
public static void deleteObject(final Collection collection) {
|
||||
RBatch batch = CLIENT.createBatch();
|
||||
collection.forEach(t -> {
|
||||
batch.getBucket(t.toString()).deleteAsync();
|
||||
});
|
||||
batch.execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查缓存对象是否存在
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
*/
|
||||
public static boolean isExistsObject(final String key) {
|
||||
return CLIENT.getBucket(key).isExists();
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存List数据
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
* @param dataList 待缓存的List数据
|
||||
* @return 缓存的对象
|
||||
*/
|
||||
public static <T> boolean setCacheList(final String key, final List<T> dataList) {
|
||||
RList<T> rList = CLIENT.getList(key);
|
||||
return rList.addAll(dataList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 追加缓存List数据
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
* @param data 待缓存的数据
|
||||
* @return 缓存的对象
|
||||
*/
|
||||
public static <T> boolean addCacheList(final String key, final T data) {
|
||||
RList<T> rList = CLIENT.getList(key);
|
||||
return rList.add(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册List监听器
|
||||
* <p>
|
||||
* key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
* @param listener 监听器配置
|
||||
*/
|
||||
public static <T> void addListListener(final String key, final ObjectListener listener) {
|
||||
RList<T> rList = CLIENT.getList(key);
|
||||
rList.addListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得缓存的list对象
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
* @return 缓存键值对应的数据
|
||||
*/
|
||||
public static <T> List<T> getCacheList(final String key) {
|
||||
RList<T> rList = CLIENT.getList(key);
|
||||
return rList.readAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得缓存的list对象(范围)
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
* @param form 起始下标
|
||||
* @param to 截止下标
|
||||
* @return 缓存键值对应的数据
|
||||
*/
|
||||
public static <T> List<T> getCacheListRange(final String key, int form, int to) {
|
||||
RList<T> rList = CLIENT.getList(key);
|
||||
return rList.range(form, to);
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存Set
|
||||
*
|
||||
* @param key 缓存键值
|
||||
* @param dataSet 缓存的数据
|
||||
* @return 缓存数据的对象
|
||||
*/
|
||||
public static <T> boolean setCacheSet(final String key, final Set<T> dataSet) {
|
||||
RSet<T> rSet = CLIENT.getSet(key);
|
||||
return rSet.addAll(dataSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* 追加缓存Set数据
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
* @param data 待缓存的数据
|
||||
* @return 缓存的对象
|
||||
*/
|
||||
public static <T> boolean addCacheSet(final String key, final T data) {
|
||||
RSet<T> rSet = CLIENT.getSet(key);
|
||||
return rSet.add(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册Set监听器
|
||||
* <p>
|
||||
* key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
* @param listener 监听器配置
|
||||
*/
|
||||
public static <T> void addSetListener(final String key, final ObjectListener listener) {
|
||||
RSet<T> rSet = CLIENT.getSet(key);
|
||||
rSet.addListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得缓存的set
|
||||
*
|
||||
* @param key 缓存的key
|
||||
* @return set对象
|
||||
*/
|
||||
public static <T> Set<T> getCacheSet(final String key) {
|
||||
RSet<T> rSet = CLIENT.getSet(key);
|
||||
return rSet.readAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存Map
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
* @param dataMap 缓存的数据
|
||||
*/
|
||||
public static <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
|
||||
if (dataMap != null) {
|
||||
RMap<String, T> rMap = CLIENT.getMap(key);
|
||||
rMap.putAll(dataMap);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册Map监听器
|
||||
* <p>
|
||||
* key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
* @param listener 监听器配置
|
||||
*/
|
||||
public static <T> void addMapListener(final String key, final ObjectListener listener) {
|
||||
RMap<String, T> rMap = CLIENT.getMap(key);
|
||||
rMap.addListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得缓存的Map
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
* @return map对象
|
||||
*/
|
||||
public static <T> Map<String, T> getCacheMap(final String key) {
|
||||
RMap<String, T> rMap = CLIENT.getMap(key);
|
||||
return rMap.getAll(rMap.keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得缓存Map的key列表
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
* @return key列表
|
||||
*/
|
||||
public static <T> Set<String> getCacheMapKeySet(final String key) {
|
||||
RMap<String, T> rMap = CLIENT.getMap(key);
|
||||
return rMap.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 往Hash中存入数据
|
||||
*
|
||||
* @param key Redis键
|
||||
* @param hKey Hash键
|
||||
* @param value 值
|
||||
*/
|
||||
public static <T> void setCacheMapValue(final String key, final String hKey, final T value) {
|
||||
RMap<String, T> rMap = CLIENT.getMap(key);
|
||||
rMap.put(hKey, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Hash中的数据
|
||||
*
|
||||
* @param key Redis键
|
||||
* @param hKey Hash键
|
||||
* @return Hash中的对象
|
||||
*/
|
||||
public static <T> T getCacheMapValue(final String key, final String hKey) {
|
||||
RMap<String, T> rMap = CLIENT.getMap(key);
|
||||
return rMap.get(hKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除Hash中的数据
|
||||
*
|
||||
* @param key Redis键
|
||||
* @param hKey Hash键
|
||||
* @return Hash中的对象
|
||||
*/
|
||||
public static <T> T delCacheMapValue(final String key, final String hKey) {
|
||||
RMap<String, T> rMap = CLIENT.getMap(key);
|
||||
return rMap.remove(hKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除Hash中的数据
|
||||
*
|
||||
* @param key Redis键
|
||||
* @param hKeys Hash键
|
||||
*/
|
||||
public static <T> void delMultiCacheMapValue(final String key, final Set<String> hKeys) {
|
||||
RBatch batch = CLIENT.createBatch();
|
||||
RMapAsync<String, T> rMap = batch.getMap(key);
|
||||
for (String hKey : hKeys) {
|
||||
rMap.removeAsync(hKey);
|
||||
}
|
||||
batch.execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取多个Hash中的数据
|
||||
*
|
||||
* @param key Redis键
|
||||
* @param hKeys Hash键集合
|
||||
* @return Hash对象集合
|
||||
*/
|
||||
public static <K, V> Map<K, V> getMultiCacheMapValue(final String key, final Set<K> hKeys) {
|
||||
RMap<K, V> rMap = CLIENT.getMap(key);
|
||||
return rMap.getAll(hKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置原子值
|
||||
*
|
||||
* @param key Redis键
|
||||
* @param value 值
|
||||
*/
|
||||
public static void setAtomicValue(String key, long value) {
|
||||
RAtomicLong atomic = CLIENT.getAtomicLong(key);
|
||||
atomic.set(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取原子值
|
||||
*
|
||||
* @param key Redis键
|
||||
* @return 当前值
|
||||
*/
|
||||
public static long getAtomicValue(String key) {
|
||||
RAtomicLong atomic = CLIENT.getAtomicLong(key);
|
||||
return atomic.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 递增原子值
|
||||
*
|
||||
* @param key Redis键
|
||||
* @return 当前值
|
||||
*/
|
||||
public static long incrAtomicValue(String key) {
|
||||
RAtomicLong atomic = CLIENT.getAtomicLong(key);
|
||||
return atomic.incrementAndGet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 递减原子值
|
||||
*
|
||||
* @param key Redis键
|
||||
* @return 当前值
|
||||
*/
|
||||
public static long decrAtomicValue(String key) {
|
||||
RAtomicLong atomic = CLIENT.getAtomicLong(key);
|
||||
return atomic.decrementAndGet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得缓存的基本对象列表
|
||||
*
|
||||
* @param pattern 字符串前缀
|
||||
* @return 对象列表
|
||||
*/
|
||||
public static Collection<String> keys(final String pattern) {
|
||||
Stream<String> stream = CLIENT.getKeys().getKeysStreamByPattern(pattern);
|
||||
return stream.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除缓存的基本对象列表
|
||||
*
|
||||
* @param pattern 字符串前缀
|
||||
*/
|
||||
public static void deleteKeys(final String pattern) {
|
||||
CLIENT.getKeys().deleteByPattern(pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查redis中是否存在key
|
||||
*
|
||||
* @param key 键
|
||||
*/
|
||||
public static Boolean hasKey(String key) {
|
||||
RKeys rKeys = CLIENT.getKeys();
|
||||
return rKeys.countExists(key) > 0;
|
||||
}
|
||||
}
|
@ -0,0 +1,240 @@
|
||||
package com.fastbee.framework.web.domain;
|
||||
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import com.fastbee.common.utils.Arith;
|
||||
import com.fastbee.common.utils.ip.IpUtils;
|
||||
import com.fastbee.framework.web.domain.server.Cpu;
|
||||
import com.fastbee.framework.web.domain.server.Jvm;
|
||||
import com.fastbee.framework.web.domain.server.Mem;
|
||||
import com.fastbee.framework.web.domain.server.Sys;
|
||||
import com.fastbee.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);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
package com.fastbee.framework.web.domain.server;
|
||||
|
||||
import com.fastbee.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;
|
||||
}
|
||||
}
|
@ -0,0 +1,130 @@
|
||||
package com.fastbee.framework.web.domain.server;
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
import com.fastbee.common.utils.Arith;
|
||||
import com.fastbee.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();
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
package com.fastbee.framework.web.domain.server;
|
||||
|
||||
import com.fastbee.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);
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
package com.fastbee.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;
|
||||
}
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
package com.fastbee.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;
|
||||
}
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
package com.fastbee.framework.web.exception;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import com.fastbee.common.constant.HttpStatus;
|
||||
import com.fastbee.common.core.domain.AjaxResult;
|
||||
import com.fastbee.common.exception.DemoModeException;
|
||||
import com.fastbee.common.exception.ServiceException;
|
||||
import com.fastbee.common.utils.StringUtils;
|
||||
|
||||
/**
|
||||
* 全局异常处理器
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler
|
||||
{
|
||||
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
||||
|
||||
/**
|
||||
* 权限校验异常
|
||||
*/
|
||||
@ExceptionHandler(AccessDeniedException.class)
|
||||
public AjaxResult handleAccessDeniedException(AccessDeniedException e, HttpServletRequest request)
|
||||
{
|
||||
String requestURI = request.getRequestURI();
|
||||
log.error("请求地址'{}',权限校验失败'{}'", requestURI, e.getMessage());
|
||||
return AjaxResult.error(HttpStatus.FORBIDDEN, "没有权限,请联系管理员授权");
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求方式不支持
|
||||
*/
|
||||
@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(ServiceException.class)
|
||||
public AjaxResult handleServiceException(ServiceException e, HttpServletRequest request)
|
||||
{
|
||||
log.error(e.getMessage(), e);
|
||||
Integer code = e.getCode();
|
||||
return StringUtils.isNotNull(code) ? AjaxResult.error(code, e.getMessage()) : 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(BindException.class)
|
||||
public AjaxResult handleBindException(BindException e)
|
||||
{
|
||||
log.error(e.getMessage(), e);
|
||||
String message = e.getAllErrors().get(0).getDefaultMessage();
|
||||
return AjaxResult.error(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义验证异常
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e)
|
||||
{
|
||||
log.error(e.getMessage(), e);
|
||||
String message = e.getBindingResult().getFieldError().getDefaultMessage();
|
||||
return AjaxResult.error(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 演示模式异常
|
||||
*/
|
||||
@ExceptionHandler(DemoModeException.class)
|
||||
public AjaxResult handleDemoModeException(DemoModeException e)
|
||||
{
|
||||
return AjaxResult.error("演示模式,不允许操作");
|
||||
}
|
||||
}
|
@ -0,0 +1,168 @@
|
||||
package com.fastbee.framework.web.service;
|
||||
|
||||
import java.util.Set;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import com.fastbee.common.core.domain.entity.SysRole;
|
||||
import com.fastbee.common.core.domain.model.LoginUser;
|
||||
import com.fastbee.common.utils.SecurityUtils;
|
||||
import com.fastbee.common.utils.StringUtils;
|
||||
import com.fastbee.framework.security.context.PermissionContextHolder;
|
||||
|
||||
/**
|
||||
* RuoYi首创 自定义权限实现,ss取自SpringSecurity首字母
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Service("ss")
|
||||
public class PermissionService
|
||||
{
|
||||
/** 所有权限标识 */
|
||||
private static final String ALL_PERMISSION = "*:*:*";
|
||||
|
||||
/** 管理员角色权限标识 */
|
||||
private static final String SUPER_ADMIN = "admin";
|
||||
|
||||
private static final String ROLE_DELIMETER = ",";
|
||||
|
||||
private static final String PERMISSION_DELIMETER = ",";
|
||||
|
||||
/**
|
||||
* 验证用户是否具备某权限
|
||||
*
|
||||
* @param permission 权限字符串
|
||||
* @return 用户是否具备某权限
|
||||
*/
|
||||
public boolean hasPermi(String permission)
|
||||
{
|
||||
if (StringUtils.isEmpty(permission))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
PermissionContextHolder.setContext(permission);
|
||||
return hasPermissions(loginUser.getPermissions(), permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证用户是否不具备某权限,与 hasPermi逻辑相反
|
||||
*
|
||||
* @param permission 权限字符串
|
||||
* @return 用户是否不具备某权限
|
||||
*/
|
||||
public boolean lacksPermi(String permission)
|
||||
{
|
||||
return hasPermi(permission) != true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证用户是否具有以下任意一个权限
|
||||
*
|
||||
* @param permissions 以 PERMISSION_NAMES_DELIMETER 为分隔符的权限列表
|
||||
* @return 用户是否具有以下任意一个权限
|
||||
*/
|
||||
public boolean hasAnyPermi(String permissions)
|
||||
{
|
||||
if (StringUtils.isEmpty(permissions))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
PermissionContextHolder.setContext(permissions);
|
||||
Set<String> authorities = loginUser.getPermissions();
|
||||
for (String permission : permissions.split(PERMISSION_DELIMETER))
|
||||
{
|
||||
if (permission != null && hasPermissions(authorities, permission))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断用户是否拥有某个角色
|
||||
*
|
||||
* @param role 角色字符串
|
||||
* @return 用户是否具备某角色
|
||||
*/
|
||||
public boolean hasRole(String role)
|
||||
{
|
||||
if (StringUtils.isEmpty(role))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (SysRole sysRole : loginUser.getUser().getRoles())
|
||||
{
|
||||
String roleKey = sysRole.getRoleKey();
|
||||
if (SUPER_ADMIN.equals(roleKey) || roleKey.equals(StringUtils.trim(role)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证用户是否不具备某角色,与 isRole逻辑相反。
|
||||
*
|
||||
* @param role 角色名称
|
||||
* @return 用户是否不具备某角色
|
||||
*/
|
||||
public boolean lacksRole(String role)
|
||||
{
|
||||
return hasRole(role) != true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证用户是否具有以下任意一个角色
|
||||
*
|
||||
* @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表
|
||||
* @return 用户是否具有以下任意一个角色
|
||||
*/
|
||||
public boolean hasAnyRoles(String roles)
|
||||
{
|
||||
if (StringUtils.isEmpty(roles))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (String role : roles.split(ROLE_DELIMETER))
|
||||
{
|
||||
if (hasRole(role))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否包含权限
|
||||
*
|
||||
* @param permissions 权限列表
|
||||
* @param permission 权限字符串
|
||||
* @return 用户是否具备某权限
|
||||
*/
|
||||
private boolean hasPermissions(Set<String> permissions, String permission)
|
||||
{
|
||||
return permissions.contains(ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission));
|
||||
}
|
||||
}
|
@ -0,0 +1,236 @@
|
||||
package com.fastbee.framework.web.service;
|
||||
|
||||
import com.fastbee.common.constant.CacheConstants;
|
||||
import com.fastbee.common.constant.Constants;
|
||||
import com.fastbee.common.core.domain.entity.SysDept;
|
||||
import com.fastbee.common.core.domain.entity.SysUser;
|
||||
import com.fastbee.common.core.domain.model.LoginUser;
|
||||
import com.fastbee.common.core.redis.RedisCache;
|
||||
import com.fastbee.common.enums.UserStatus;
|
||||
import com.fastbee.common.exception.ServiceException;
|
||||
import com.fastbee.common.exception.user.CaptchaException;
|
||||
import com.fastbee.common.exception.user.CaptchaExpireException;
|
||||
import com.fastbee.common.exception.user.UserPasswordNotMatchException;
|
||||
import com.fastbee.common.utils.DateUtils;
|
||||
import com.fastbee.common.utils.MessageUtils;
|
||||
import com.fastbee.common.utils.ServletUtils;
|
||||
import com.fastbee.common.utils.StringUtils;
|
||||
import com.fastbee.common.utils.ip.IpUtils;
|
||||
import com.fastbee.framework.manager.AsyncManager;
|
||||
import com.fastbee.framework.manager.factory.AsyncFactory;
|
||||
import com.fastbee.framework.security.context.AuthenticationContextHolder;
|
||||
import com.fastbee.system.service.ISysConfigService;
|
||||
import com.fastbee.system.service.ISysDeptService;
|
||||
import com.fastbee.system.service.ISysUserService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 登录校验方法
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Component
|
||||
public class SysLoginService {
|
||||
@Autowired
|
||||
private TokenService tokenService;
|
||||
|
||||
@Resource
|
||||
private AuthenticationManager authenticationManager;
|
||||
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
|
||||
@Autowired
|
||||
private ISysUserService userService;
|
||||
|
||||
@Autowired
|
||||
private ISysConfigService configService;
|
||||
|
||||
@Autowired
|
||||
private UserDetailsServiceImpl userDetailsServiceImpl;
|
||||
|
||||
@Resource
|
||||
private SysPasswordService passwordService;
|
||||
@Resource
|
||||
private ISysDeptService sysDeptService;
|
||||
|
||||
/**
|
||||
* 登录验证
|
||||
*
|
||||
* @param username 用户名
|
||||
* @param password 密码
|
||||
* @param code 验证码
|
||||
* @param uuid 唯一标识
|
||||
* @return 结果
|
||||
*/
|
||||
public String login(String username, String password, String code, String uuid, Integer sourceType, String language) {
|
||||
boolean captchaEnabled = configService.selectCaptchaEnabled();
|
||||
// 验证码开关
|
||||
if (captchaEnabled) {
|
||||
validateCaptcha(username, code, uuid);
|
||||
}
|
||||
// 用户验证
|
||||
Authentication authentication = null;
|
||||
try {
|
||||
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
|
||||
AuthenticationContextHolder.setContext(authenticationToken);
|
||||
// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
|
||||
authentication = authenticationManager.authenticate(authenticationToken);
|
||||
} catch (Exception e) {
|
||||
if (e instanceof BadCredentialsException) {
|
||||
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
|
||||
throw new UserPasswordNotMatchException();
|
||||
} else {
|
||||
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
|
||||
throw new ServiceException(e.getMessage());
|
||||
}
|
||||
} finally {
|
||||
AuthenticationContextHolder.clearContext();
|
||||
}
|
||||
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
|
||||
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
|
||||
// 移动端、小程序限制终端用户登录
|
||||
Long deptId = loginUser.getDeptId();
|
||||
if (null != sourceType && 1 == sourceType && null == deptId) {
|
||||
throw new ServiceException("web端只允许租户登录!");
|
||||
}
|
||||
// if (!"admin".equals(loginUser.getUsername()) && null != sourceType) {
|
||||
// Long deptId = loginUser.getDeptId();
|
||||
// if (1 == sourceType && null == deptId) {
|
||||
// throw new ServiceException("web端只允许租户登录!");
|
||||
// }
|
||||
// if (1 != sourceType && null != deptId) {
|
||||
// throw new ServiceException("只允许终端用户登录!");
|
||||
// }
|
||||
// }
|
||||
SysDept sysDept = sysDeptService.selectDeptById(deptId);
|
||||
recordLoginInfo(loginUser.getUserId());
|
||||
loginUser.setLanguage(language);
|
||||
loginUser.setDeptUserId(sysDept.getDeptUserId());
|
||||
// 生成token
|
||||
return tokenService.createToken(loginUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* 第三方验证后,调用登录方法
|
||||
*
|
||||
* @param username 用户名
|
||||
* @param password 密码
|
||||
* @return token
|
||||
*/
|
||||
public String socialLogin(String username, String password, String language) {
|
||||
// 用户验证
|
||||
Authentication authentication = null;
|
||||
try {
|
||||
// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
|
||||
authentication = authenticationManager
|
||||
.authenticate(new UsernamePasswordAuthenticationToken(username, password));
|
||||
} catch (Exception e) {
|
||||
if (e instanceof BadCredentialsException) {
|
||||
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
|
||||
throw new UserPasswordNotMatchException();
|
||||
} else {
|
||||
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
|
||||
throw new ServiceException(e.getMessage());
|
||||
}
|
||||
}
|
||||
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
|
||||
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
|
||||
recordLoginInfo(loginUser.getUserId());
|
||||
loginUser.setLanguage(language);
|
||||
// 生成token
|
||||
return tokenService.createToken(loginUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* 三方跳转登录认证方法
|
||||
*
|
||||
* @param username 系统用户名
|
||||
* @param encodePwd 系统用户密码
|
||||
* @return
|
||||
*/
|
||||
public String redirectLogin(String username, String encodePwd, String language) {
|
||||
// UserDetails userDetails=userDetailsServiceImpl.loadUserByUsername(username);
|
||||
SysUser user = userService.selectUserByUserName(username);
|
||||
if (StringUtils.isNull(user)) {
|
||||
throw new ServiceException("登录用户:" + username + " 不存在");
|
||||
} else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
|
||||
throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
|
||||
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
|
||||
throw new ServiceException("对不起,您的账号:" + username + " 已停用");
|
||||
}
|
||||
// 重写验证方法
|
||||
passwordService.socialValidate(user, encodePwd);
|
||||
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
|
||||
UserDetails userDetails = userDetailsServiceImpl.createLoginUser(user);
|
||||
LoginUser loginUser = (LoginUser) userDetails;
|
||||
recordLoginInfo(loginUser.getUserId());
|
||||
loginUser.setLanguage(language);
|
||||
// 生成token
|
||||
return tokenService.createToken(loginUser);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验验证码
|
||||
*
|
||||
* @param username 用户名
|
||||
* @param code 验证码
|
||||
* @param uuid 唯一标识
|
||||
* @return 结果
|
||||
*/
|
||||
public void validateCaptcha(String username, String code, String uuid) {
|
||||
String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, "");
|
||||
String captcha = redisCache.getCacheObject(verifyKey);
|
||||
redisCache.deleteObject(verifyKey);
|
||||
if (captcha == null) {
|
||||
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
|
||||
throw new CaptchaExpireException();
|
||||
}
|
||||
if (!code.equalsIgnoreCase(captcha)) {
|
||||
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
|
||||
throw new CaptchaException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录登录信息
|
||||
*
|
||||
* @param userId 用户ID
|
||||
*/
|
||||
public void recordLoginInfo(Long userId) {
|
||||
SysUser sysUser = new SysUser();
|
||||
sysUser.setUserId(userId);
|
||||
sysUser.setLoginIp(IpUtils.getIpAddr(ServletUtils.getRequest()));
|
||||
sysUser.setLoginDate(DateUtils.getNowDate());
|
||||
userService.updateUserProfile(sysUser);
|
||||
}
|
||||
|
||||
public String ssoLogin(String username, String password, String language) {
|
||||
SysUser user = userService.selectUserByUserName(username);
|
||||
if (StringUtils.isNull(user)) {
|
||||
throw new ServiceException("登录用户:" + username + " 不存在");
|
||||
} else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
|
||||
throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
|
||||
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
|
||||
throw new ServiceException("对不起,您的账号:" + username + " 已停用");
|
||||
}
|
||||
// 重写验证方法
|
||||
passwordService.ssoValidate(user, password);
|
||||
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
|
||||
UserDetails userDetails = userDetailsServiceImpl.createLoginUser(user);
|
||||
LoginUser loginUser = (LoginUser) userDetails;
|
||||
recordLoginInfo(loginUser.getUserId());
|
||||
loginUser.setLanguage(language);
|
||||
// 生成token
|
||||
return tokenService.createToken(loginUser);
|
||||
}
|
||||
}
|
@ -0,0 +1,158 @@
|
||||
package com.fastbee.framework.web.service;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.stereotype.Component;
|
||||
import com.fastbee.common.constant.CacheConstants;
|
||||
import com.fastbee.common.constant.Constants;
|
||||
import com.fastbee.common.core.domain.entity.SysUser;
|
||||
import com.fastbee.common.core.redis.RedisCache;
|
||||
import com.fastbee.common.exception.user.UserPasswordNotMatchException;
|
||||
import com.fastbee.common.exception.user.UserPasswordRetryLimitExceedException;
|
||||
import com.fastbee.common.utils.MessageUtils;
|
||||
import com.fastbee.common.utils.SecurityUtils;
|
||||
import com.fastbee.framework.manager.AsyncManager;
|
||||
import com.fastbee.framework.manager.factory.AsyncFactory;
|
||||
import com.fastbee.framework.security.context.AuthenticationContextHolder;
|
||||
|
||||
/**
|
||||
* 登录密码方法
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Component
|
||||
public class SysPasswordService
|
||||
{
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
|
||||
@Value(value = "${user.password.maxRetryCount}")
|
||||
private int maxRetryCount;
|
||||
|
||||
@Value(value = "${user.password.lockTime}")
|
||||
private int lockTime;
|
||||
|
||||
/**
|
||||
* 登录账户密码错误次数缓存键名
|
||||
*
|
||||
* @param username 用户名
|
||||
* @return 缓存键key
|
||||
*/
|
||||
private String getCacheKey(String username)
|
||||
{
|
||||
return CacheConstants.PWD_ERR_CNT_KEY + username;
|
||||
}
|
||||
|
||||
public void validate(SysUser user)
|
||||
{
|
||||
Authentication usernamePasswordAuthenticationToken = AuthenticationContextHolder.getContext();
|
||||
String username = usernamePasswordAuthenticationToken.getName();
|
||||
String password = usernamePasswordAuthenticationToken.getCredentials().toString();
|
||||
|
||||
Integer retryCount = redisCache.getCacheObject(getCacheKey(username));
|
||||
|
||||
if (retryCount == null)
|
||||
{
|
||||
retryCount = 0;
|
||||
}
|
||||
|
||||
if (retryCount >= Integer.valueOf(maxRetryCount).intValue())
|
||||
{
|
||||
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL,
|
||||
MessageUtils.message("user.password.retry.limit.exceed", maxRetryCount, lockTime)));
|
||||
throw new UserPasswordRetryLimitExceedException(maxRetryCount, lockTime);
|
||||
}
|
||||
|
||||
if (!matches(user, password))
|
||||
{
|
||||
retryCount = retryCount + 1;
|
||||
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL,
|
||||
MessageUtils.message("user.password.retry.limit.count", retryCount)));
|
||||
redisCache.setCacheObject(getCacheKey(username), retryCount, lockTime, TimeUnit.MINUTES);
|
||||
throw new UserPasswordNotMatchException();
|
||||
}
|
||||
else
|
||||
{
|
||||
clearLoginRecordCache(username);
|
||||
}
|
||||
}
|
||||
|
||||
public void socialValidate(SysUser user, String encodePwd)
|
||||
{
|
||||
String username = user.getUserName();
|
||||
String password = user.getPassword();
|
||||
|
||||
Integer retryCount = redisCache.getCacheObject(getCacheKey(username));
|
||||
|
||||
if (retryCount == null)
|
||||
{
|
||||
retryCount = 0;
|
||||
}
|
||||
|
||||
if (retryCount >= Integer.valueOf(maxRetryCount).intValue())
|
||||
{
|
||||
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL,
|
||||
MessageUtils.message("user.password.retry.limit.exceed", maxRetryCount, lockTime)));
|
||||
throw new UserPasswordRetryLimitExceedException(maxRetryCount, lockTime);
|
||||
}
|
||||
|
||||
if(!password.equals(encodePwd)){
|
||||
retryCount = retryCount + 1;
|
||||
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL,
|
||||
MessageUtils.message("user.password.retry.limit.count", retryCount)));
|
||||
redisCache.setCacheObject(getCacheKey(username), retryCount, lockTime, TimeUnit.MINUTES);
|
||||
throw new UserPasswordNotMatchException();
|
||||
}
|
||||
else
|
||||
{
|
||||
clearLoginRecordCache(username);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean matches(SysUser user, String rawPassword)
|
||||
{
|
||||
return SecurityUtils.matchesPassword(rawPassword, user.getPassword());
|
||||
}
|
||||
|
||||
public void clearLoginRecordCache(String loginName)
|
||||
{
|
||||
if (redisCache.hasKey(getCacheKey(loginName)))
|
||||
{
|
||||
redisCache.deleteObject(getCacheKey(loginName));
|
||||
}
|
||||
}
|
||||
|
||||
public void ssoValidate(SysUser user, String encodePwd) {
|
||||
String username = user.getUserName();
|
||||
String password = user.getPassword();
|
||||
|
||||
Integer retryCount = redisCache.getCacheObject(getCacheKey(username));
|
||||
|
||||
if (retryCount == null)
|
||||
{
|
||||
retryCount = 0;
|
||||
}
|
||||
|
||||
if (retryCount >= Integer.valueOf(maxRetryCount).intValue())
|
||||
{
|
||||
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL,
|
||||
MessageUtils.message("user.password.retry.limit.exceed", maxRetryCount, lockTime)));
|
||||
throw new UserPasswordRetryLimitExceedException(maxRetryCount, lockTime);
|
||||
}
|
||||
|
||||
if (!matches(user, encodePwd))
|
||||
{
|
||||
retryCount = retryCount + 1;
|
||||
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL,
|
||||
MessageUtils.message("user.password.retry.limit.count", retryCount)));
|
||||
redisCache.setCacheObject(getCacheKey(username), retryCount, lockTime, TimeUnit.MINUTES);
|
||||
throw new UserPasswordNotMatchException();
|
||||
}
|
||||
else
|
||||
{
|
||||
clearLoginRecordCache(username);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
package com.fastbee.framework.web.service;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import com.fastbee.common.core.domain.entity.SysRole;
|
||||
import com.fastbee.common.core.domain.entity.SysUser;
|
||||
import com.fastbee.system.service.ISysMenuService;
|
||||
import com.fastbee.system.service.ISysRoleService;
|
||||
|
||||
/**
|
||||
* 用户权限处理
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Component
|
||||
public class SysPermissionService
|
||||
{
|
||||
@Autowired
|
||||
private ISysRoleService roleService;
|
||||
|
||||
@Autowired
|
||||
private ISysMenuService menuService;
|
||||
|
||||
/**
|
||||
* 获取角色数据权限
|
||||
*
|
||||
* @param user 用户信息
|
||||
* @return 角色权限信息
|
||||
*/
|
||||
public Set<String> getRolePermission(SysUser user)
|
||||
{
|
||||
Set<String> roles = new HashSet<String>();
|
||||
// 管理员拥有所有权限
|
||||
if (user.isAdmin())
|
||||
{
|
||||
roles.add("admin");
|
||||
}
|
||||
else
|
||||
{
|
||||
roles.addAll(roleService.selectRolePermissionByUserId(user.getUserId()));
|
||||
}
|
||||
return roles;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取菜单数据权限
|
||||
*
|
||||
* @param user 用户信息
|
||||
* @return 菜单权限信息
|
||||
*/
|
||||
public Set<String> getMenuPermission(SysUser user)
|
||||
{
|
||||
Set<String> perms = new HashSet<String>();
|
||||
// 管理员拥有所有权限
|
||||
if (user.isAdmin()) {
|
||||
perms.add("*:*:*");
|
||||
} else {
|
||||
List<SysRole> roles = user.getRoles();
|
||||
if (!roles.isEmpty() && roles.size() > 1)
|
||||
{
|
||||
// 多角色设置permissions属性,以便数据权限匹配权限
|
||||
for (SysRole role : roles)
|
||||
{
|
||||
Set<String> rolePerms = menuService.selectMenuPermsByRoleId(role.getRoleId());
|
||||
role.setPermissions(rolePerms);
|
||||
perms.addAll(rolePerms);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
perms.addAll(menuService.selectMenuPermsByUserId(user.getUserId()));
|
||||
}
|
||||
}
|
||||
return perms;
|
||||
}
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
package com.fastbee.framework.web.service;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import com.fastbee.common.constant.CacheConstants;
|
||||
import com.fastbee.common.constant.Constants;
|
||||
import com.fastbee.common.constant.UserConstants;
|
||||
import com.fastbee.common.core.domain.entity.SysUser;
|
||||
import com.fastbee.common.core.domain.model.RegisterBody;
|
||||
import com.fastbee.common.core.redis.RedisCache;
|
||||
import com.fastbee.common.exception.user.CaptchaException;
|
||||
import com.fastbee.common.exception.user.CaptchaExpireException;
|
||||
import com.fastbee.common.utils.MessageUtils;
|
||||
import com.fastbee.common.utils.SecurityUtils;
|
||||
import com.fastbee.common.utils.StringUtils;
|
||||
import com.fastbee.framework.manager.AsyncManager;
|
||||
import com.fastbee.framework.manager.factory.AsyncFactory;
|
||||
import com.fastbee.system.service.ISysConfigService;
|
||||
import com.fastbee.system.service.ISysUserService;
|
||||
|
||||
/**
|
||||
* 注册校验方法
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Component
|
||||
public class SysRegisterService
|
||||
{
|
||||
@Autowired
|
||||
private ISysUserService userService;
|
||||
|
||||
@Autowired
|
||||
private ISysConfigService configService;
|
||||
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
|
||||
/**
|
||||
* 注册
|
||||
*/
|
||||
public String register(RegisterBody registerBody)
|
||||
{
|
||||
String msg = "", username = registerBody.getUsername(), password = registerBody.getPassword();
|
||||
SysUser sysUser = new SysUser();
|
||||
sysUser.setUserName(username);
|
||||
|
||||
// 验证码开关
|
||||
boolean captchaEnabled = configService.selectCaptchaEnabled();
|
||||
if (captchaEnabled)
|
||||
{
|
||||
validateCaptcha(username, registerBody.getCode(), registerBody.getUuid());
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(username))
|
||||
{
|
||||
msg = "用户名不能为空";
|
||||
}
|
||||
else if (StringUtils.isEmpty(password))
|
||||
{
|
||||
msg = "用户密码不能为空";
|
||||
}
|
||||
else if (username.length() < UserConstants.USERNAME_MIN_LENGTH
|
||||
|| username.length() > UserConstants.USERNAME_MAX_LENGTH)
|
||||
{
|
||||
msg = "账户长度必须在2到20个字符之间";
|
||||
}
|
||||
else if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
|
||||
|| password.length() > UserConstants.PASSWORD_MAX_LENGTH)
|
||||
{
|
||||
msg = "密码长度必须在5到20个字符之间";
|
||||
}
|
||||
else if (UserConstants.NOT_UNIQUE.equals(userService.checkUserNameUnique(sysUser)))
|
||||
{
|
||||
msg = "保存用户'" + username + "'失败,注册账号已存在";
|
||||
}
|
||||
else
|
||||
{
|
||||
sysUser.setNickName(username);
|
||||
sysUser.setPassword(SecurityUtils.encryptPassword(password));
|
||||
boolean regFlag = userService.registerUser(sysUser);
|
||||
if (!regFlag)
|
||||
{
|
||||
msg = "注册失败,请联系系统管理人员";
|
||||
}
|
||||
else
|
||||
{
|
||||
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.REGISTER, MessageUtils.message("user.register.success")));
|
||||
}
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验验证码
|
||||
*
|
||||
* @param username 用户名
|
||||
* @param code 验证码
|
||||
* @param uuid 唯一标识
|
||||
* @return 结果
|
||||
*/
|
||||
public void validateCaptcha(String username, String code, String uuid)
|
||||
{
|
||||
String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, "");
|
||||
String captcha = redisCache.getCacheObject(verifyKey);
|
||||
redisCache.deleteObject(verifyKey);
|
||||
if (captcha == null)
|
||||
{
|
||||
throw new CaptchaExpireException();
|
||||
}
|
||||
if (!code.equalsIgnoreCase(captcha))
|
||||
{
|
||||
throw new CaptchaException();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,327 @@
|
||||
package com.fastbee.framework.web.service;
|
||||
|
||||
import com.fastbee.common.constant.CacheConstants;
|
||||
import com.fastbee.common.constant.Constants;
|
||||
import com.fastbee.common.core.domain.entity.SysUser;
|
||||
import com.fastbee.common.core.domain.model.LoginUser;
|
||||
import com.fastbee.common.core.redis.RedisCache;
|
||||
import com.fastbee.common.utils.ServletUtils;
|
||||
import com.fastbee.common.utils.StringUtils;
|
||||
import com.fastbee.common.utils.ip.AddressUtils;
|
||||
import com.fastbee.common.utils.ip.IpUtils;
|
||||
import com.fastbee.common.utils.uuid.IdUtils;
|
||||
import com.fastbee.system.domain.SysClient;
|
||||
import com.fastbee.system.service.ISysClientService;
|
||||
import eu.bitwalker.useragentutils.UserAgent;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
||||
/**
|
||||
* token验证处理
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Component
|
||||
public class TokenService
|
||||
{
|
||||
// 令牌自定义标识
|
||||
@Value("${token.header}")
|
||||
private String header;
|
||||
|
||||
// 令牌秘钥
|
||||
@Value("${token.secret}")
|
||||
private String secret;
|
||||
|
||||
// 令牌有效期(默认30分钟)
|
||||
@Value("${token.expireTime}")
|
||||
private int expireTime;
|
||||
|
||||
protected static final long MILLIS_SECOND = 1000;
|
||||
|
||||
protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;
|
||||
|
||||
private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;
|
||||
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
|
||||
@Autowired
|
||||
private ISysClientService sysClientService;
|
||||
|
||||
@Autowired
|
||||
private UserDetailsServiceImpl userDetailsServiceImpl;
|
||||
|
||||
/**
|
||||
* 获取用户身份信息
|
||||
*
|
||||
* @return 用户信息
|
||||
*/
|
||||
public LoginUser getLoginUser(HttpServletRequest request)
|
||||
{
|
||||
// 获取请求携带的令牌
|
||||
String token = getToken(request);
|
||||
if (StringUtils.isNotEmpty(token))
|
||||
{
|
||||
try
|
||||
{
|
||||
Claims claims = parseToken(token);
|
||||
// 解析对应的权限以及用户信息
|
||||
String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
|
||||
String userKey = getTokenKey(uuid);
|
||||
LoginUser user = redisCache.getCacheObject(userKey);
|
||||
return user;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户身份信息
|
||||
*
|
||||
* @return 用户信息
|
||||
*/
|
||||
public LoginUser getLoginUserByToken(String token) {
|
||||
if (StringUtils.isNotEmpty(token)) {
|
||||
try {
|
||||
Claims claims = parseToken(token);
|
||||
// 解析对应的权限以及用户信息
|
||||
String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
|
||||
String userKey = getTokenKey(uuid);
|
||||
LoginUser user = redisCache.getCacheObject(userKey);
|
||||
return user;
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户id获取用户身份信息
|
||||
* 由于多端登录,根据token获取的用户信息不一样,所以增加一个根据用户id获取用户信息的缓存key,以后多端需要获取用户最新信息就用这个方法吧
|
||||
* @return 用户信息
|
||||
*/
|
||||
public LoginUser getLoginUserByUserId(Long userId) {
|
||||
if (userId != null) {
|
||||
try {
|
||||
String userKey = getUserIdKey(userId);
|
||||
return redisCache.getCacheObject(userKey);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置用户身份信息
|
||||
*/
|
||||
public void setLoginUser(LoginUser loginUser)
|
||||
{
|
||||
if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken()))
|
||||
{
|
||||
refreshToken(loginUser);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除用户身份信息
|
||||
*/
|
||||
public void delLoginUser(String token)
|
||||
{
|
||||
if (StringUtils.isNotEmpty(token))
|
||||
{
|
||||
String userKey = getTokenKey(token);
|
||||
redisCache.deleteObject(userKey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建令牌
|
||||
*
|
||||
* @param loginUser 用户信息
|
||||
* @return 令牌
|
||||
*/
|
||||
public String createToken(LoginUser loginUser)
|
||||
{
|
||||
String token = IdUtils.fastUUID();
|
||||
loginUser.setToken(token);
|
||||
setUserAgent(loginUser);
|
||||
refreshToken(loginUser);
|
||||
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put(Constants.LOGIN_USER_KEY, token);
|
||||
return createToken(claims);
|
||||
}
|
||||
|
||||
public int addToken(SysUser user, SysClient sysClient) {
|
||||
UserDetails userDetails = userDetailsServiceImpl.createLoginUser(user);
|
||||
LoginUser loginUser = (LoginUser) userDetails;
|
||||
sysClient.setToken(Constants.TOKEN_PREFIX + createToken(loginUser, sysClient.getClientSecret(), Math.toIntExact(sysClient.getTimeout())));
|
||||
return sysClientService.insertSysClient(sysClient);
|
||||
}
|
||||
|
||||
public int updateToken(SysUser user, SysClient sysClient){
|
||||
UserDetails userDetails = userDetailsServiceImpl.createLoginUser(user);
|
||||
LoginUser loginUser = (LoginUser) userDetails;
|
||||
sysClient.setToken(Constants.TOKEN_PREFIX + createToken(loginUser, sysClient.getClientSecret(), Math.toIntExact(sysClient.getTimeout())));
|
||||
return sysClientService.updateSysClient(sysClient);
|
||||
}
|
||||
|
||||
public String createToken(LoginUser loginUser, String secret, int expireTime)
|
||||
{
|
||||
String token = IdUtils.fastUUID();
|
||||
loginUser.setToken(token);
|
||||
setUserAgent(loginUser);
|
||||
refreshToken(loginUser, expireTime);
|
||||
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put(Constants.LOGIN_USER_KEY, token);
|
||||
return createToken(claims, secret);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证令牌有效期,相差不足20分钟,自动刷新缓存
|
||||
*
|
||||
* @param loginUser
|
||||
* @return 令牌
|
||||
*/
|
||||
public void verifyToken(LoginUser loginUser)
|
||||
{
|
||||
long expireTime = loginUser.getExpireTime();
|
||||
long currentTime = System.currentTimeMillis();
|
||||
if (expireTime - currentTime <= MILLIS_MINUTE_TEN)
|
||||
{
|
||||
refreshToken(loginUser);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新令牌有效期
|
||||
*
|
||||
* @param loginUser 登录信息
|
||||
*/
|
||||
public void refreshToken(LoginUser loginUser, int expireTime)
|
||||
{
|
||||
loginUser.setLoginTime(System.currentTimeMillis());
|
||||
loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
|
||||
// 根据uuid将loginUser缓存
|
||||
String userKey = getTokenKey(loginUser.getToken());
|
||||
redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
|
||||
// 使用token作为用户信息缓存key,多端不能同步最新信息,需要重新登录,因此添加一个使用用户id作为缓存key
|
||||
String userIdKey = getUserIdKey(loginUser.getUserId());
|
||||
redisCache.setCacheObject(userIdKey, loginUser, expireTime, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
public void refreshToken(LoginUser loginUser)
|
||||
{
|
||||
loginUser.setLoginTime(System.currentTimeMillis());
|
||||
loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
|
||||
// 根据uuid将loginUser缓存
|
||||
String userKey = getTokenKey(loginUser.getToken());
|
||||
redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
|
||||
// 使用token作为用户信息缓存key,多端不能同步最新信息,需要重新登录,因此添加一个使用用户id作为缓存key
|
||||
String userIdKey = getUserIdKey(loginUser.getUserId());
|
||||
redisCache.setCacheObject(userIdKey, loginUser, expireTime, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置用户代理信息
|
||||
*
|
||||
* @param loginUser 登录信息
|
||||
*/
|
||||
public void setUserAgent(LoginUser loginUser)
|
||||
{
|
||||
UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
|
||||
String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
|
||||
loginUser.setIpaddr(ip);
|
||||
loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
|
||||
loginUser.setBrowser(userAgent.getBrowser().getName());
|
||||
loginUser.setOs(userAgent.getOperatingSystem().getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 从数据声明生成令牌
|
||||
*
|
||||
* @param claims 数据声明
|
||||
* @return 令牌
|
||||
*/
|
||||
private String createToken(Map<String, Object> claims)
|
||||
{
|
||||
String token = Jwts.builder()
|
||||
.setClaims(claims)
|
||||
.signWith(SignatureAlgorithm.HS512, secret).compact();
|
||||
return token;
|
||||
}
|
||||
|
||||
private String createToken(Map<String, Object> claims, String secret)
|
||||
{
|
||||
String token = Jwts.builder()
|
||||
.setClaims(claims)
|
||||
.signWith(SignatureAlgorithm.HS512, secret).compact();
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从令牌中获取数据声明
|
||||
*
|
||||
* @param token 令牌
|
||||
* @return 数据声明
|
||||
*/
|
||||
private Claims parseToken(String token)
|
||||
{
|
||||
return Jwts.parser()
|
||||
.setSigningKey(secret)
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从令牌中获取用户名
|
||||
*
|
||||
* @param token 令牌
|
||||
* @return 用户名
|
||||
*/
|
||||
public String getUsernameFromToken(String token)
|
||||
{
|
||||
Claims claims = parseToken(token);
|
||||
return claims.getSubject();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求token
|
||||
*
|
||||
* @param request
|
||||
* @return token
|
||||
*/
|
||||
private String getToken(HttpServletRequest request)
|
||||
{
|
||||
String token = request.getHeader(header);
|
||||
if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX))
|
||||
{
|
||||
token = token.replace(Constants.TOKEN_PREFIX, "");
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
private String getTokenKey(String uuid)
|
||||
{
|
||||
return CacheConstants.LOGIN_TOKEN_KEY + uuid;
|
||||
}
|
||||
|
||||
private String getUserIdKey(Long userId) {
|
||||
return CacheConstants.LOGIN_USERID_KEY + userId;
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
package com.fastbee.framework.web.service;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.fastbee.common.core.domain.entity.SysUser;
|
||||
import com.fastbee.common.core.domain.model.LoginUser;
|
||||
import com.fastbee.common.enums.UserStatus;
|
||||
import com.fastbee.common.exception.ServiceException;
|
||||
import com.fastbee.common.utils.StringUtils;
|
||||
import com.fastbee.system.service.ISysUserService;
|
||||
|
||||
/**
|
||||
* 用户验证处理
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Service
|
||||
public class UserDetailsServiceImpl implements UserDetailsService
|
||||
{
|
||||
private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);
|
||||
|
||||
@Autowired
|
||||
private ISysUserService userService;
|
||||
|
||||
@Autowired
|
||||
private SysPasswordService passwordService;
|
||||
|
||||
@Autowired
|
||||
private SysPermissionService permissionService;
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
|
||||
{
|
||||
SysUser user = userService.selectUserByUserName(username);
|
||||
if (StringUtils.isNull(user))
|
||||
{
|
||||
log.info("登录用户:{} 不存在.", username);
|
||||
throw new ServiceException("登录用户:" + username + " 不存在");
|
||||
}
|
||||
else if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
|
||||
{
|
||||
log.info("登录用户:{} 已被删除.", username);
|
||||
throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
|
||||
}
|
||||
else if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
|
||||
{
|
||||
log.info("登录用户:{} 已被停用.", username);
|
||||
throw new ServiceException("对不起,您的账号:" + username + " 已停用");
|
||||
}
|
||||
|
||||
passwordService.validate(user);
|
||||
|
||||
return createLoginUser(user);
|
||||
}
|
||||
|
||||
public UserDetails createLoginUser(SysUser user)
|
||||
{
|
||||
return new LoginUser(user.getUserId(), user.getDeptId(), user.getLanguage(), user, permissionService.getMenuPermission(user));
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user