初始化
This commit is contained in:
		| @ -0,0 +1,175 @@ | ||||
| package com.ruoyi.framework.aspectj; | ||||
|  | ||||
| import cn.hutool.core.lang.Validator; | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import com.ruoyi.common.annotation.DataScope; | ||||
| import com.ruoyi.common.core.domain.BaseEntity; | ||||
| import com.ruoyi.common.core.domain.entity.SysRole; | ||||
| import com.ruoyi.common.core.domain.entity.SysUser; | ||||
| import com.ruoyi.common.core.domain.model.LoginUser; | ||||
| import com.ruoyi.common.utils.ServletUtils; | ||||
| import com.ruoyi.common.utils.reflect.ReflectUtils; | ||||
| import com.ruoyi.common.utils.spring.SpringUtils; | ||||
| import com.ruoyi.framework.web.service.TokenService; | ||||
| import org.aspectj.lang.JoinPoint; | ||||
| import org.aspectj.lang.Signature; | ||||
| import org.aspectj.lang.annotation.Aspect; | ||||
| import org.aspectj.lang.annotation.Before; | ||||
| import org.aspectj.lang.annotation.Pointcut; | ||||
| import org.aspectj.lang.reflect.MethodSignature; | ||||
| import org.springframework.stereotype.Component; | ||||
|  | ||||
| import java.lang.reflect.Method; | ||||
| import java.util.Map; | ||||
|  | ||||
| /** | ||||
|  * 数据过滤处理 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @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"; | ||||
|  | ||||
| 	// 配置织入点 | ||||
| 	@Pointcut("@annotation(com.ruoyi.common.annotation.DataScope)") | ||||
| 	public void dataScopePointCut() { | ||||
| 	} | ||||
|  | ||||
| 	@Before("dataScopePointCut()") | ||||
| 	public void doBefore(JoinPoint point) throws Throwable { | ||||
| 		clearDataScope(point); | ||||
| 		handleDataScope(point); | ||||
| 	} | ||||
|  | ||||
| 	protected void handleDataScope(final JoinPoint joinPoint) { | ||||
| 		// 获得注解 | ||||
| 		DataScope controllerDataScope = getAnnotationLog(joinPoint); | ||||
| 		if (controllerDataScope == null) { | ||||
| 			return; | ||||
| 		} | ||||
| 		// 获取当前的用户 | ||||
| 		LoginUser loginUser = SpringUtils.getBean(TokenService.class).getLoginUser(ServletUtils.getRequest()); | ||||
| 		if (Validator.isNotNull(loginUser)) { | ||||
| 			SysUser currentUser = loginUser.getUser(); | ||||
| 			// 如果是超级管理员,则不过滤数据 | ||||
| 			if (Validator.isNotNull(currentUser) && !currentUser.isAdmin()) { | ||||
| 				dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(), | ||||
| 					controllerDataScope.userAlias(), controllerDataScope.isUser()); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * 数据范围过滤 | ||||
| 	 * | ||||
| 	 * @param joinPoint 切点 | ||||
| 	 * @param user      用户 | ||||
| 	 * @param userAlias 别名 | ||||
| 	 */ | ||||
| 	public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias, boolean isUser) { | ||||
| 		StringBuilder sqlString = new StringBuilder(); | ||||
|  | ||||
| 		// 将 "." 提取出,不写别名为单表查询,写别名为多表查询 | ||||
| 		deptAlias = StrUtil.isNotBlank(deptAlias) ? deptAlias + "." : ""; | ||||
| 		userAlias = StrUtil.isNotBlank(userAlias) ? userAlias + "." : ""; | ||||
|  | ||||
| 		for (SysRole role : user.getRoles()) { | ||||
| 			String dataScope = role.getDataScope(); | ||||
| 			if (DATA_SCOPE_ALL.equals(dataScope)) { | ||||
| 				sqlString = new StringBuilder(); | ||||
| 				break; | ||||
| 			} else if (DATA_SCOPE_CUSTOM.equals(dataScope)) { | ||||
| 				sqlString.append(StrUtil.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(StrUtil.format(" OR {}dept_id = {} ", | ||||
| 					deptAlias, user.getDeptId())); | ||||
| 			} else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) { | ||||
| 				sqlString.append(StrUtil.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 (isUser) { | ||||
| 					sqlString.append(StrUtil.format(" OR {}user_id = {} ", | ||||
| 						userAlias, user.getUserId())); | ||||
| 				} else { | ||||
| 					// 数据权限为仅本人且没有userAlias别名不查询任何数据 | ||||
| 					sqlString.append(" OR 1=0 "); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if (StrUtil.isNotBlank(sqlString.toString())) { | ||||
| 			putDataScope(joinPoint, sqlString.substring(4)); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * 是否存在注解,如果存在就获取 | ||||
| 	 */ | ||||
| 	private DataScope getAnnotationLog(JoinPoint joinPoint) { | ||||
| 		Signature signature = joinPoint.getSignature(); | ||||
| 		MethodSignature methodSignature = (MethodSignature) signature; | ||||
| 		Method method = methodSignature.getMethod(); | ||||
|  | ||||
| 		if (method != null) { | ||||
| 			return method.getAnnotation(DataScope.class); | ||||
| 		} | ||||
| 		return null; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * 拼接权限sql前先清空params.dataScope参数防止注入 | ||||
| 	 */ | ||||
| 	private void clearDataScope(final JoinPoint joinPoint) { | ||||
| 		Object params = joinPoint.getArgs()[0]; | ||||
| 		if (Validator.isNotNull(params)) { | ||||
| 			putDataScope(joinPoint, ""); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	private static void putDataScope(JoinPoint joinPoint, String sql) { | ||||
| 		Object params = joinPoint.getArgs()[0]; | ||||
| 		if (Validator.isNotNull(params)) { | ||||
| 			if (params instanceof BaseEntity) { | ||||
| 				BaseEntity baseEntity = (BaseEntity) params; | ||||
| 				baseEntity.getParams().put(DATA_SCOPE, sql); | ||||
| 			} else { | ||||
| 				Map<String, Object> invoke = ReflectUtils.invokeGetter(params, "params"); | ||||
| 				invoke.put(DATA_SCOPE, sql); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @ -0,0 +1,62 @@ | ||||
| package com.ruoyi.framework.aspectj; | ||||
|  | ||||
| import cn.hutool.core.lang.Validator; | ||||
| import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder; | ||||
| import com.ruoyi.common.annotation.DataSource; | ||||
| 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.springframework.core.annotation.AnnotationUtils; | ||||
| import org.springframework.core.annotation.Order; | ||||
| import org.springframework.stereotype.Component; | ||||
|  | ||||
| import java.util.Objects; | ||||
|  | ||||
| /** | ||||
|  * 多数据源处理 | ||||
|  * | ||||
|  * @author ruoyi | ||||
|  */ | ||||
| @Aspect | ||||
| @Order(-500) | ||||
| @Component | ||||
| public class DataSourceAspect { | ||||
|  | ||||
| 	@Pointcut("@annotation(com.ruoyi.common.annotation.DataSource)" | ||||
| 		+ "|| @within(com.ruoyi.common.annotation.DataSource)") | ||||
| 	public void dsPointCut() { | ||||
| 	} | ||||
|  | ||||
| 	@Around("dsPointCut()") | ||||
| 	public Object around(ProceedingJoinPoint point) throws Throwable { | ||||
| 		DataSource dataSource = getDataSource(point); | ||||
|  | ||||
| 		if (Validator.isNotNull(dataSource)) { | ||||
| 			DynamicDataSourceContextHolder.poll(); | ||||
| 			String source = dataSource.value().getSource(); | ||||
| 			DynamicDataSourceContextHolder.push(source); | ||||
| 		} | ||||
|  | ||||
| 		try { | ||||
| 			return point.proceed(); | ||||
| 		} finally { | ||||
| 			// 销毁数据源 在执行方法之后 | ||||
| 			DynamicDataSourceContextHolder.clear(); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * 获取需要切换的数据源 | ||||
| 	 */ | ||||
| 	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,238 @@ | ||||
| package com.ruoyi.framework.aspectj; | ||||
|  | ||||
| import cn.hutool.core.lang.Validator; | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import com.ruoyi.common.annotation.Log; | ||||
| import com.ruoyi.common.core.domain.model.LoginUser; | ||||
| import com.ruoyi.common.enums.BusinessStatus; | ||||
| import com.ruoyi.common.enums.HttpMethod; | ||||
| import com.ruoyi.common.utils.JsonUtils; | ||||
| import com.ruoyi.common.utils.ServletUtils; | ||||
| import com.ruoyi.common.utils.spring.SpringUtils; | ||||
| import com.ruoyi.framework.web.service.AsyncService; | ||||
| import com.ruoyi.framework.web.service.TokenService; | ||||
| import com.ruoyi.system.domain.SysOperLog; | ||||
| import org.aspectj.lang.JoinPoint; | ||||
| import org.aspectj.lang.Signature; | ||||
| import org.aspectj.lang.annotation.AfterReturning; | ||||
| import org.aspectj.lang.annotation.AfterThrowing; | ||||
| import org.aspectj.lang.annotation.Aspect; | ||||
| import org.aspectj.lang.annotation.Pointcut; | ||||
| import org.aspectj.lang.reflect.MethodSignature; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.stereotype.Component; | ||||
| import org.springframework.validation.BindingResult; | ||||
| import org.springframework.web.multipart.MultipartFile; | ||||
| import org.springframework.web.servlet.HandlerMapping; | ||||
|  | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
| import java.lang.reflect.Method; | ||||
| import java.util.Collection; | ||||
| import java.util.Map; | ||||
|  | ||||
| /** | ||||
|  * 操作日志记录处理 | ||||
|  * | ||||
|  * @author ruoyi | ||||
|  */ | ||||
| @Aspect | ||||
| @Component | ||||
| public class LogAspect | ||||
| { | ||||
|     private static final Logger log = LoggerFactory.getLogger(LogAspect.class); | ||||
|  | ||||
|     // 配置织入点 | ||||
|     @Pointcut("@annotation(com.ruoyi.common.annotation.Log)") | ||||
|     public void logPointCut() | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 处理完请求后执行 | ||||
|      * | ||||
|      * @param joinPoint 切点 | ||||
|      */ | ||||
|     @AfterReturning(pointcut = "logPointCut()", returning = "jsonResult") | ||||
|     public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) | ||||
|     { | ||||
|         handleLog(joinPoint, null, jsonResult); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 拦截异常操作 | ||||
|      * | ||||
|      * @param joinPoint 切点 | ||||
|      * @param e 异常 | ||||
|      */ | ||||
|     @AfterThrowing(value = "logPointCut()", throwing = "e") | ||||
|     public void doAfterThrowing(JoinPoint joinPoint, Exception e) | ||||
|     { | ||||
|         handleLog(joinPoint, e, null); | ||||
|     } | ||||
|  | ||||
|     protected void handleLog(final JoinPoint joinPoint, final Exception e, Object jsonResult) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             // 获得注解 | ||||
|             Log controllerLog = getAnnotationLog(joinPoint); | ||||
|             if (controllerLog == null) | ||||
|             { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             // 获取当前的用户 | ||||
|             LoginUser loginUser = SpringUtils.getBean(TokenService.class).getLoginUser(ServletUtils.getRequest()); | ||||
|  | ||||
|             // *========数据库日志=========*// | ||||
|             SysOperLog operLog = new SysOperLog(); | ||||
|             operLog.setStatus(BusinessStatus.SUCCESS.ordinal()); | ||||
|             // 请求的地址 | ||||
|             String ip = ServletUtils.getClientIP(); | ||||
|             operLog.setOperIp(ip); | ||||
|             // 返回参数 | ||||
|             operLog.setJsonResult(JsonUtils.toJsonString(jsonResult)); | ||||
|  | ||||
|             operLog.setOperUrl(ServletUtils.getRequest().getRequestURI()); | ||||
|             if (loginUser != null) | ||||
|             { | ||||
|                 operLog.setOperName(loginUser.getUsername()); | ||||
|             } | ||||
|  | ||||
|             if (e != null) | ||||
|             { | ||||
|                 operLog.setStatus(BusinessStatus.FAIL.ordinal()); | ||||
|                 operLog.setErrorMsg(StrUtil.sub(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); | ||||
|             // 保存数据库 | ||||
| 			SpringUtils.getBean(AsyncService.class).recordOper(operLog); | ||||
|         } | ||||
|         catch (Exception exp) | ||||
|         { | ||||
|             // 记录本地异常日志 | ||||
|             log.error("==前置通知异常=="); | ||||
|             log.error("异常信息:{}", exp.getMessage()); | ||||
|             exp.printStackTrace(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取注解中对方法的描述信息 用于Controller层注解 | ||||
|      * | ||||
|      * @param log 日志 | ||||
|      * @param operLog 操作日志 | ||||
|      * @throws Exception | ||||
|      */ | ||||
|     public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog) throws Exception | ||||
|     { | ||||
|         // 设置action动作 | ||||
|         operLog.setBusinessType(log.businessType().ordinal()); | ||||
|         // 设置标题 | ||||
|         operLog.setTitle(log.title()); | ||||
|         // 设置操作人类别 | ||||
|         operLog.setOperatorType(log.operatorType().ordinal()); | ||||
|         // 是否需要保存request,参数和值 | ||||
|         if (log.isSaveRequestData()) | ||||
|         { | ||||
|             // 获取参数的信息,传入到数据库中。 | ||||
|             setRequestValue(joinPoint, operLog); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取请求的参数,放到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(StrUtil.sub(params, 0, 2000)); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             Map<?, ?> paramsMap = (Map<?, ?>) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); | ||||
|             operLog.setOperParam(StrUtil.sub(paramsMap.toString(), 0, 2000)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 是否存在注解,如果存在就获取 | ||||
|      */ | ||||
|     private Log getAnnotationLog(JoinPoint joinPoint) throws Exception | ||||
|     { | ||||
|         Signature signature = joinPoint.getSignature(); | ||||
|         MethodSignature methodSignature = (MethodSignature) signature; | ||||
|         Method method = methodSignature.getMethod(); | ||||
|  | ||||
|         if (method != null) | ||||
|         { | ||||
|             return method.getAnnotation(Log.class); | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 参数拼装 | ||||
|      */ | ||||
|     private String argsArrayToString(Object[] paramsArray) | ||||
|     { | ||||
|         StringBuilder params = new StringBuilder(); | ||||
|         if (paramsArray != null && paramsArray.length > 0) | ||||
|         { | ||||
| 			for (Object o : paramsArray) { | ||||
| 				if (Validator.isNotNull(o) && !isFilterObject(o)) { | ||||
| 					params.append(JsonUtils.toJsonString(o)).append(" "); | ||||
| 				} | ||||
| 			} | ||||
|         } | ||||
|         return params.toString().trim(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 判断是否需要过滤的对象。 | ||||
|      * | ||||
|      * @param o 对象信息。 | ||||
|      * @return 如果是需要过滤的对象,则返回true;否则返回false。 | ||||
|      */ | ||||
|     @SuppressWarnings("rawtypes") | ||||
|     public boolean isFilterObject(final Object o) | ||||
|     { | ||||
|         Class<?> clazz = o.getClass(); | ||||
|         if (clazz.isArray()) | ||||
|         { | ||||
|             return clazz.getComponentType().isAssignableFrom(MultipartFile.class); | ||||
|         } | ||||
|         else if (Collection.class.isAssignableFrom(clazz)) | ||||
|         { | ||||
|             Collection collection = (Collection) o; | ||||
| 			for (Object value : collection) { | ||||
| 				return value instanceof MultipartFile; | ||||
| 			} | ||||
|         } | ||||
|         else if (Map.class.isAssignableFrom(clazz)) | ||||
|         { | ||||
|             Map map = (Map) o; | ||||
| 			for (Object value : map.entrySet()) { | ||||
| 				Map.Entry entry = (Map.Entry) value; | ||||
| 				return entry.getValue() instanceof MultipartFile; | ||||
| 			} | ||||
|         } | ||||
|         return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse | ||||
|                 || o instanceof BindingResult; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,85 @@ | ||||
| package com.ruoyi.framework.captcha; | ||||
|  | ||||
| import cn.hutool.captcha.generator.CodeGenerator; | ||||
| import cn.hutool.core.math.Calculator; | ||||
| import cn.hutool.core.util.CharUtil; | ||||
| import cn.hutool.core.util.RandomUtil; | ||||
| import cn.hutool.core.util.StrUtil; | ||||
|  | ||||
| /** | ||||
|  * 无符号计算生成器 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| public class UnsignedMathGenerator implements CodeGenerator { | ||||
|  | ||||
| 	private static final long serialVersionUID = -5514819971774091076L; | ||||
|  | ||||
| 	private static final String operators = "+-*"; | ||||
|  | ||||
| 	/** | ||||
| 	 * 参与计算数字最大长度 | ||||
| 	 */ | ||||
| 	private final int numberLength; | ||||
|  | ||||
| 	/** | ||||
| 	 * 构造 | ||||
| 	 */ | ||||
| 	public UnsignedMathGenerator() { | ||||
| 		this(2); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * 构造 | ||||
| 	 * | ||||
| 	 * @param numberLength 参与计算最大数字位数 | ||||
| 	 */ | ||||
| 	public UnsignedMathGenerator(int numberLength) { | ||||
| 		this.numberLength = numberLength; | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public String generate() { | ||||
| 		final int limit = getLimit(); | ||||
| 		int min = RandomUtil.randomInt(limit); | ||||
| 		int max = RandomUtil.randomInt(min, limit); | ||||
| 		String number1 = Integer.toString(max); | ||||
| 		String number2 = Integer.toString(min); | ||||
| 		number1 = StrUtil.padAfter(number1, this.numberLength, CharUtil.SPACE); | ||||
| 		number2 = StrUtil.padAfter(number2, this.numberLength, CharUtil.SPACE); | ||||
|  | ||||
| 		return number1 + RandomUtil.randomChar(operators) + number2 + '='; | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public boolean verify(String code, String userInputCode) { | ||||
| 		int result; | ||||
| 		try { | ||||
| 			result = Integer.parseInt(userInputCode); | ||||
| 		} catch (NumberFormatException e) { | ||||
| 			// 用户输入非数字 | ||||
| 			return false; | ||||
| 		} | ||||
|  | ||||
| 		final int calculateResult = (int) Calculator.conversion(code); | ||||
| 		return result == calculateResult; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * 获取验证码长度 | ||||
| 	 * | ||||
| 	 * @return 验证码长度 | ||||
| 	 */ | ||||
| 	public int getLength() { | ||||
| 		return this.numberLength * 2 + 2; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * 根据长度获取参与计算数字最大值 | ||||
| 	 * | ||||
| 	 * @return 最大值 | ||||
| 	 */ | ||||
| 	private int getLimit() { | ||||
| 		return Integer.parseInt("1" + StrUtil.repeat('0', this.numberLength)); | ||||
| 	} | ||||
| } | ||||
| @ -0,0 +1,63 @@ | ||||
| package com.ruoyi.framework.config; | ||||
|  | ||||
| import de.codecentric.boot.admin.server.config.EnableAdminServer; | ||||
| import org.springframework.beans.factory.ObjectProvider; | ||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; | ||||
| import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration; | ||||
| import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafProperties; | ||||
| import org.springframework.boot.task.TaskExecutorBuilder; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| import org.springframework.context.annotation.Lazy; | ||||
| import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; | ||||
| import org.thymeleaf.dialect.IDialect; | ||||
| import org.thymeleaf.spring5.ISpringTemplateEngine; | ||||
| import org.thymeleaf.spring5.SpringTemplateEngine; | ||||
| import org.thymeleaf.templateresolver.ITemplateResolver; | ||||
|  | ||||
| import java.util.Comparator; | ||||
| import java.util.LinkedHashSet; | ||||
| import java.util.Set; | ||||
| import java.util.concurrent.Executor; | ||||
| import java.util.stream.Collectors; | ||||
|  | ||||
| /** | ||||
|  * springboot-admin server配置类 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Configuration | ||||
| @EnableAdminServer | ||||
| public class AdminServerConfig { | ||||
|  | ||||
|     @Lazy | ||||
|     @Bean(name = TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME) | ||||
|     @ConditionalOnMissingBean(Executor.class) | ||||
|     public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) { | ||||
|         return builder.build(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 解决 admin 与 项目 页面的交叉引用 将 admin 的路由放到最后 | ||||
|      * @param properties | ||||
|      * @param templateResolvers | ||||
|      * @param dialects | ||||
|      * @return | ||||
|      */ | ||||
|     @Bean | ||||
|     @ConditionalOnMissingBean(ISpringTemplateEngine.class) | ||||
|     SpringTemplateEngine templateEngine(ThymeleafProperties properties, | ||||
|                                         ObjectProvider<ITemplateResolver> templateResolvers, ObjectProvider<IDialect> dialects) { | ||||
|         SpringTemplateEngine engine = new SpringTemplateEngine(); | ||||
|         engine.setEnableSpringELCompiler(properties.isEnableSpringElCompiler()); | ||||
|         engine.setRenderHiddenMarkersBeforeCheckboxes(properties.isRenderHiddenMarkersBeforeCheckboxes()); | ||||
|         templateResolvers.orderedStream().forEach(engine::addTemplateResolver); | ||||
|         dialects.orderedStream().forEach(engine::addDialect); | ||||
|         Set<ITemplateResolver> templateResolvers1 = engine.getTemplateResolvers(); | ||||
|         templateResolvers1 = templateResolvers1.stream() | ||||
|                 .sorted(Comparator.comparing(ITemplateResolver::getOrder)) | ||||
|                 .collect(Collectors.toCollection(LinkedHashSet::new)); | ||||
|         engine.setTemplateResolvers(templateResolvers1); | ||||
|         return engine; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,26 @@ | ||||
| package com.ruoyi.framework.config; | ||||
|  | ||||
| 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; | ||||
|  | ||||
| import java.util.TimeZone; | ||||
|  | ||||
| /** | ||||
|  * 程序注解配置 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Configuration | ||||
| // 表示通过aop框架暴露该代理对象,AopContext能够访问 | ||||
| @EnableAspectJAutoProxy(exposeProxy = true) | ||||
| public class ApplicationConfig { | ||||
|     /** | ||||
|      * 时区配置 | ||||
|      */ | ||||
|     @Bean | ||||
|     public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization() { | ||||
|         return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault()); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,51 @@ | ||||
| package com.ruoyi.framework.config; | ||||
|  | ||||
| import com.ruoyi.common.exception.CustomException; | ||||
| import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.beans.factory.annotation.Qualifier; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| import org.springframework.scheduling.annotation.AsyncConfigurerSupport; | ||||
| import org.springframework.scheduling.annotation.EnableAsync; | ||||
| import org.springframework.security.concurrent.DelegatingSecurityContextExecutorService; | ||||
|  | ||||
| import java.util.Arrays; | ||||
| import java.util.concurrent.Executor; | ||||
| import java.util.concurrent.ScheduledExecutorService; | ||||
|  | ||||
| /** | ||||
|  * 异步配置 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @EnableAsync | ||||
| @Configuration | ||||
| public class AsyncConfig extends AsyncConfigurerSupport { | ||||
|  | ||||
| 	@Autowired | ||||
| 	@Qualifier("scheduledExecutorService") | ||||
| 	private ScheduledExecutorService scheduledExecutorService; | ||||
|  | ||||
|     /** | ||||
|      * 异步执行需要使用权限框架自带的包装线程池  保证权限信息的传递 | ||||
|      */ | ||||
|     @Override | ||||
|     public Executor getAsyncExecutor() { | ||||
|         return new DelegatingSecurityContextExecutorService(scheduledExecutorService); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 异步执行异常处理 | ||||
|      */ | ||||
|     @Override | ||||
|     public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { | ||||
|         return (throwable, method, objects) -> { | ||||
|             throwable.printStackTrace(); | ||||
|             throw new CustomException( | ||||
|                     "Exception message - " + throwable.getMessage() | ||||
|                     + ", Method name - " + method.getName() | ||||
|                     + ", Parameter value - " + Arrays.toString(objects)); | ||||
|         }; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,55 @@ | ||||
| package com.ruoyi.framework.config; | ||||
|  | ||||
| import java.awt.*; | ||||
|  | ||||
| import cn.hutool.captcha.*; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
|  | ||||
| /** | ||||
|  * 验证码配置 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Configuration | ||||
| public class CaptchaConfig { | ||||
|  | ||||
|     private final int width = 160; | ||||
|     private final int height = 60; | ||||
|     private final Color background = Color.PINK; | ||||
|     private final Font font = new Font("Arial", Font.BOLD, 48); | ||||
|  | ||||
|     /** | ||||
|      * 圆圈干扰验证码 | ||||
|      */ | ||||
|     @Bean(name = "CircleCaptcha") | ||||
|     public CircleCaptcha getCircleCaptcha() { | ||||
|         CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(width, height); | ||||
|         captcha.setBackground(background); | ||||
|         captcha.setFont(font); | ||||
|         return captcha; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 线段干扰的验证码 | ||||
|      */ | ||||
|     @Bean(name = "LineCaptcha") | ||||
|     public LineCaptcha getLineCaptcha() { | ||||
|         LineCaptcha captcha = CaptchaUtil.createLineCaptcha(width, height); | ||||
|         captcha.setBackground(background); | ||||
|         captcha.setFont(font); | ||||
|         return captcha; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 扭曲干扰验证码 | ||||
|      */ | ||||
|     @Bean(name = "ShearCaptcha") | ||||
|     public ShearCaptcha getShearCaptcha() { | ||||
|         ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(width, height); | ||||
|         captcha.setBackground(background); | ||||
|         captcha.setFont(font); | ||||
|         return captcha; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,66 @@ | ||||
| package com.ruoyi.framework.config; | ||||
|  | ||||
| import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties; | ||||
| import com.alibaba.druid.util.Utils; | ||||
| 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 javax.servlet.*; | ||||
| import java.io.IOException; | ||||
|  | ||||
| /** | ||||
|  * druid 配置多数据源 | ||||
|  * | ||||
|  * @author ruoyi | ||||
|  */ | ||||
| @Configuration | ||||
| public class DruidConfig { | ||||
|  | ||||
|     /** | ||||
|      * 去除监控页面底部的广告 | ||||
|      */ | ||||
|     @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,56 @@ | ||||
| package com.ruoyi.framework.config; | ||||
|  | ||||
| import feign.*; | ||||
| import okhttp3.ConnectionPool; | ||||
| import okhttp3.OkHttpClient; | ||||
| import org.springframework.boot.autoconfigure.AutoConfigureBefore; | ||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; | ||||
| import org.springframework.cloud.openfeign.EnableFeignClients; | ||||
| import org.springframework.cloud.openfeign.FeignAutoConfiguration; | ||||
| import org.springframework.cloud.openfeign.support.SpringMvcContract; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
|  | ||||
| import java.util.concurrent.TimeUnit; | ||||
|  | ||||
| /** | ||||
|  * openfeign配置类 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @EnableFeignClients("${feign.package}") | ||||
| @Configuration | ||||
| @ConditionalOnClass(Feign.class) | ||||
| @AutoConfigureBefore(FeignAutoConfiguration.class) | ||||
| public class FeignConfig { | ||||
|     @Bean | ||||
|     public OkHttpClient okHttpClient(){ | ||||
|         return new OkHttpClient.Builder() | ||||
|                 .readTimeout(60, TimeUnit.SECONDS) | ||||
|                 .connectTimeout(60, TimeUnit.SECONDS) | ||||
|                 .writeTimeout(120, TimeUnit.SECONDS) | ||||
|                 .connectionPool(new ConnectionPool()) | ||||
|                 .build(); | ||||
|     } | ||||
|  | ||||
|     @Bean | ||||
|     public Contract feignContract() { | ||||
|         return new SpringMvcContract(); | ||||
|     } | ||||
|  | ||||
|     @Bean | ||||
|     public Logger.Level feignLoggerLevel() { | ||||
|         return Logger.Level.BASIC; | ||||
|     } | ||||
|  | ||||
|     @Bean | ||||
|     public Request.Options feignRequestOptions() { | ||||
|         return new Request.Options(10, TimeUnit.SECONDS, 60,TimeUnit.SECONDS,true); | ||||
|     } | ||||
|  | ||||
|     @Bean | ||||
|     public Retryer feignRetry() { | ||||
|         return new Retryer.Default(); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,54 @@ | ||||
| package com.ruoyi.framework.config; | ||||
|  | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import com.ruoyi.common.filter.RepeatableFilter; | ||||
| import com.ruoyi.common.filter.XssFilter; | ||||
| import com.ruoyi.framework.config.properties.XssProperties; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.boot.web.servlet.FilterRegistrationBean; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
|  | ||||
| import javax.servlet.DispatcherType; | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
|  | ||||
| /** | ||||
|  * Filter配置 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Configuration | ||||
| public class FilterConfig { | ||||
|  | ||||
|     @Autowired | ||||
|     private XssProperties xssProperties; | ||||
|  | ||||
|     @SuppressWarnings({"rawtypes", "unchecked"}) | ||||
|     @Bean | ||||
|     public FilterRegistrationBean xssFilterRegistration() { | ||||
|         FilterRegistrationBean registration = new FilterRegistrationBean(); | ||||
|         registration.setDispatcherTypes(DispatcherType.REQUEST); | ||||
|         registration.setFilter(new XssFilter()); | ||||
|         registration.addUrlPatterns(StrUtil.splitToArray(xssProperties.getUrlPatterns(), ",")); | ||||
|         registration.setName("xssFilter"); | ||||
|         registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE); | ||||
|         Map<String, String> initParameters = new HashMap<String, String>(); | ||||
|         initParameters.put("excludes", xssProperties.getExcludes()); | ||||
|         initParameters.put("enabled", xssProperties.getEnabled()); | ||||
|         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,64 @@ | ||||
| package com.ruoyi.framework.config; | ||||
|  | ||||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||||
| import com.fasterxml.jackson.databind.module.SimpleModule; | ||||
| import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; | ||||
| import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; | ||||
| import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer; | ||||
| import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; | ||||
| import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; | ||||
| import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; | ||||
| import com.ruoyi.common.utils.JsonUtils; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.beans.BeansException; | ||||
| import org.springframework.beans.factory.config.BeanPostProcessor; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
|  | ||||
| import java.text.DateFormat; | ||||
| import java.time.LocalDate; | ||||
| import java.time.LocalDateTime; | ||||
| import java.time.LocalTime; | ||||
| import java.time.format.DateTimeFormatter; | ||||
| import java.util.TimeZone; | ||||
|  | ||||
| /** | ||||
|  * jackson 配置 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Slf4j | ||||
| @Configuration | ||||
| public class JacksonConfig { | ||||
|  | ||||
| 	@Bean | ||||
| 	public BeanPostProcessor objectMapperBeanPostProcessor() { | ||||
| 		return new BeanPostProcessor() { | ||||
| 			@Override | ||||
| 			public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { | ||||
| 				if (!(bean instanceof ObjectMapper)) { | ||||
| 					return bean; | ||||
| 				} | ||||
| 				ObjectMapper objectMapper = (ObjectMapper) bean; | ||||
| 				// 全局配置序列化返回 JSON 处理 | ||||
| 				init(objectMapper); | ||||
| 				JsonUtils.init(objectMapper); | ||||
| 				log.info("初始化 jackson 配置"); | ||||
| 				return bean; | ||||
| 			} | ||||
| 		}; | ||||
| 	} | ||||
|  | ||||
| 	static void init(ObjectMapper objectMapper) { | ||||
| 		SimpleModule simpleModule = new SimpleModule(); | ||||
| 		simpleModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); | ||||
| 		simpleModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); | ||||
| 		simpleModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ISO_DATE)); | ||||
| 		simpleModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ISO_DATE)); | ||||
| 		simpleModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ISO_TIME)); | ||||
| 		simpleModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ISO_TIME)); | ||||
| 		objectMapper.registerModule(simpleModule); | ||||
| 		objectMapper.setTimeZone(TimeZone.getDefault()); | ||||
| 	} | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,128 @@ | ||||
| package com.ruoyi.framework.config; | ||||
|  | ||||
| import com.baomidou.mybatisplus.annotation.DbType; | ||||
| import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; | ||||
| import com.baomidou.mybatisplus.core.injector.AbstractMethod; | ||||
| import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector; | ||||
| import com.baomidou.mybatisplus.core.injector.ISqlInjector; | ||||
| import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; | ||||
| import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor; | ||||
| import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; | ||||
| import com.ruoyi.common.core.mybatisplus.methods.InsertAll; | ||||
| import com.ruoyi.framework.mybatisplus.CreateAndUpdateMetaObjectHandler; | ||||
| import org.mybatis.spring.annotation.MapperScan; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| import org.springframework.transaction.annotation.EnableTransactionManagement; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
|  * mybatis-plus配置类 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @EnableTransactionManagement(proxyTargetClass = true) | ||||
| @Configuration | ||||
| // 指定要扫描的Mapper类的包的路径 | ||||
| @MapperScan("${mybatis-plus.mapperPackage}") | ||||
| public class MybatisPlusConfig { | ||||
|  | ||||
| 	@Bean | ||||
| 	public MybatisPlusInterceptor mybatisPlusInterceptor() { | ||||
| 		MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); | ||||
| 		// 分页插件 | ||||
| 		interceptor.addInnerInterceptor(paginationInnerInterceptor()); | ||||
| 		// 乐观锁插件 | ||||
| 		interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor()); | ||||
| 		// 阻断插件 | ||||
| //		interceptor.addInnerInterceptor(blockAttackInnerInterceptor()); | ||||
| 		return interceptor; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * 分页插件,自动识别数据库类型 | ||||
| 	 * https://baomidou.com/guide/interceptor-pagination.html | ||||
| 	 */ | ||||
| 	public PaginationInnerInterceptor paginationInnerInterceptor() { | ||||
| 		PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(); | ||||
| 		// 设置数据库类型为mysql | ||||
| 		paginationInnerInterceptor.setDbType(DbType.MYSQL); | ||||
| 		// 设置最大单页限制数量,默认 500 条,-1 不受限制 | ||||
| 		paginationInnerInterceptor.setMaxLimit(-1L); | ||||
| 		return paginationInnerInterceptor; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * 乐观锁插件 | ||||
| 	 * https://baomidou.com/guide/interceptor-optimistic-locker.html | ||||
| 	 */ | ||||
| 	public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() { | ||||
| 		return new OptimisticLockerInnerInterceptor(); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * 如果是对全表的删除或更新操作,就会终止该操作 | ||||
| 	 * https://baomidou.com/guide/interceptor-block-attack.html | ||||
| 	 */ | ||||
| //	public BlockAttackInnerInterceptor blockAttackInnerInterceptor() { | ||||
| //		return new BlockAttackInnerInterceptor(); | ||||
| //	} | ||||
|  | ||||
| 	/** | ||||
| 	 * sql性能规范插件(垃圾SQL拦截) | ||||
| 	 * 如有需要可以启用 | ||||
| 	 */ | ||||
| //	public IllegalSQLInnerInterceptor illegalSQLInnerInterceptor() { | ||||
| //		return new IllegalSQLInnerInterceptor(); | ||||
| //	} | ||||
|  | ||||
|  | ||||
| 	/** | ||||
| 	 * 自定义主键策略 | ||||
| 	 * https://baomidou.com/guide/id-generator.html | ||||
| 	 */ | ||||
| //	@Bean | ||||
| //	public IdentifierGenerator idGenerator() { | ||||
| //		return new CustomIdGenerator(); | ||||
| //	} | ||||
|  | ||||
| 	/** | ||||
| 	 * 元对象字段填充控制器 | ||||
| 	 * https://baomidou.com/guide/auto-fill-metainfo.html | ||||
| 	 */ | ||||
| 	@Bean | ||||
| 	public MetaObjectHandler metaObjectHandler() { | ||||
| 		return new CreateAndUpdateMetaObjectHandler(); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * sql注入器配置 | ||||
| 	 * https://baomidou.com/guide/sql-injector.html | ||||
| 	 */ | ||||
| 	@Bean | ||||
| 	public ISqlInjector sqlInjector() { | ||||
| 		return new DefaultSqlInjector() { | ||||
| 			@Override | ||||
| 			public List<AbstractMethod> getMethodList(Class<?> mapperClass) { | ||||
| 				List<AbstractMethod> methodList = super.getMethodList(mapperClass); | ||||
| 				methodList.add(new InsertAll()); | ||||
| 				return methodList; | ||||
| 			} | ||||
| 		}; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * TenantLineInnerInterceptor 多租户插件 | ||||
| 	 * https://baomidou.com/guide/interceptor-tenant-line.html | ||||
| 	 * DynamicTableNameInnerInterceptor 动态表名插件 | ||||
| 	 * https://baomidou.com/guide/interceptor-dynamic-table-name.html | ||||
| 	 */ | ||||
| //	@Bean | ||||
| //	public org.apache.ibatis.session.Configuration myBatisConfiguration() { | ||||
| //		org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration(); | ||||
| //		// 设置日志实现为 StdOutImpl,以便输出 SQL 日志 | ||||
| //		configuration.setLogImpl(org.apache.ibatis.logging.stdout.StdOutImpl.class); | ||||
| //		return configuration; | ||||
| //	} | ||||
| } | ||||
| @ -0,0 +1,110 @@ | ||||
| package com.ruoyi.framework.config; | ||||
|  | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import com.fasterxml.jackson.annotation.JsonAutoDetect; | ||||
| import com.fasterxml.jackson.annotation.PropertyAccessor; | ||||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||||
| import com.fasterxml.jackson.databind.SerializationFeature; | ||||
| import com.fasterxml.jackson.databind.module.SimpleModule; | ||||
| import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; | ||||
| import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; | ||||
| import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer; | ||||
| import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; | ||||
| import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; | ||||
| import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; | ||||
| import com.ruoyi.framework.config.properties.RedissonProperties; | ||||
| import org.redisson.Redisson; | ||||
| import org.redisson.api.RedissonClient; | ||||
| import org.redisson.codec.JsonJacksonCodec; | ||||
| import org.redisson.config.Config; | ||||
| import org.redisson.spring.cache.CacheConfig; | ||||
| import org.redisson.spring.cache.RedissonSpringCacheManager; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; | ||||
| import org.springframework.boot.autoconfigure.data.redis.RedisProperties; | ||||
| import org.springframework.cache.CacheManager; | ||||
| import org.springframework.cache.annotation.CachingConfigurerSupport; | ||||
| import org.springframework.cache.annotation.EnableCaching; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
|  | ||||
| import java.io.IOException; | ||||
| import java.time.LocalDate; | ||||
| import java.time.LocalDateTime; | ||||
| import java.time.LocalTime; | ||||
| import java.time.format.DateTimeFormatter; | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
| import java.util.TimeZone; | ||||
|  | ||||
| /** | ||||
|  * redis配置 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Configuration | ||||
| @EnableCaching | ||||
| public class RedisConfig extends CachingConfigurerSupport { | ||||
|  | ||||
| 	private static final String REDIS_PROTOCOL_PREFIX = "redis://"; | ||||
| 	private static final String REDISS_PROTOCOL_PREFIX = "rediss://"; | ||||
|  | ||||
| 	@Autowired | ||||
| 	private RedisProperties redisProperties; | ||||
|  | ||||
| 	@Autowired | ||||
| 	private RedissonProperties redissonProperties; | ||||
|  | ||||
| 	@Bean(destroyMethod = "shutdown") | ||||
| 	@ConditionalOnMissingBean(RedissonClient.class) | ||||
| 	public RedissonClient redisson() throws IOException { | ||||
| 		String prefix = REDIS_PROTOCOL_PREFIX; | ||||
| 		if (redisProperties.isSsl()) { | ||||
| 			prefix = REDISS_PROTOCOL_PREFIX; | ||||
| 		} | ||||
| 		Config config = new Config(); | ||||
| 		config.setThreads(redissonProperties.getThreads()) | ||||
| 			.setNettyThreads(redissonProperties.getNettyThreads()) | ||||
| 			.setCodec(JsonJacksonCodec.INSTANCE) | ||||
| 			.setTransportMode(redissonProperties.getTransportMode()); | ||||
|  | ||||
| 		RedissonProperties.SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig(); | ||||
| 		// 使用单机模式 | ||||
| 		config.useSingleServer() | ||||
| 			.setAddress(prefix + redisProperties.getHost() + ":" + redisProperties.getPort()) | ||||
| 			.setConnectTimeout(((Long) redisProperties.getTimeout().toMillis()).intValue()) | ||||
| 			.setDatabase(redisProperties.getDatabase()) | ||||
| 			.setPassword(StrUtil.isNotBlank(redisProperties.getPassword()) ? redisProperties.getPassword() : null) | ||||
| 			.setTimeout(singleServerConfig.getTimeout()) | ||||
| 			.setRetryAttempts(singleServerConfig.getRetryAttempts()) | ||||
| 			.setRetryInterval(singleServerConfig.getRetryInterval()) | ||||
| 			.setSubscriptionsPerConnection(singleServerConfig.getSubscriptionsPerConnection()) | ||||
| 			.setClientName(singleServerConfig.getClientName()) | ||||
| 			.setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout()) | ||||
| 			.setSubscriptionConnectionMinimumIdleSize(singleServerConfig.getSubscriptionConnectionMinimumIdleSize()) | ||||
| 			.setSubscriptionConnectionPoolSize(singleServerConfig.getSubscriptionConnectionPoolSize()) | ||||
| 			.setConnectionMinimumIdleSize(singleServerConfig.getConnectionMinimumIdleSize()) | ||||
| 			.setConnectionPoolSize(singleServerConfig.getConnectionPoolSize()) | ||||
| 			.setDnsMonitoringInterval(singleServerConfig.getDnsMonitoringInterval()); | ||||
|  | ||||
| 		ObjectMapper om = new ObjectMapper(); | ||||
| 		om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); | ||||
| 		// 解决jackson2无法反序列化LocalDateTime的问题 | ||||
| 		om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); | ||||
|         JacksonConfig.init(om); | ||||
|         config.setCodec(new JsonJacksonCodec(om)); | ||||
|  | ||||
| 		return Redisson.create(config); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * 整合spring-cache | ||||
| 	 */ | ||||
| 	@Bean | ||||
| 	public CacheManager cacheManager(RedissonClient redissonClient) { | ||||
| 		Map<String, CacheConfig> config = new HashMap<>(); | ||||
| 		config.put("redissonCacheMap", new CacheConfig(30*60*1000, 10*60*1000)); | ||||
| 		return new RedissonSpringCacheManager(redissonClient, config, JsonJacksonCodec.INSTANCE); | ||||
| 	} | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,62 @@ | ||||
| package com.ruoyi.framework.config; | ||||
|  | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| 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.ruoyi.common.config.RuoYiConfig; | ||||
| import com.ruoyi.common.constant.Constants; | ||||
| import com.ruoyi.framework.interceptor.RepeatSubmitInterceptor; | ||||
|  | ||||
| /** | ||||
|  * 通用配置 | ||||
|  * | ||||
|  * @author ruoyi | ||||
|  */ | ||||
| @Configuration | ||||
| public class ResourcesConfig implements WebMvcConfigurer | ||||
| { | ||||
|     @Autowired | ||||
|     private RepeatSubmitInterceptor repeatSubmitInterceptor; | ||||
|  | ||||
|     @Override | ||||
|     public void addResourceHandlers(ResourceHandlerRegistry registry) | ||||
|     { | ||||
|         /** 本地文件上传路径 */ | ||||
|         registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**").addResourceLocations("file:" + RuoYiConfig.getProfile() + "/"); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 自定义拦截规则 | ||||
|      */ | ||||
|     @Override | ||||
|     public void addInterceptors(InterceptorRegistry registry) | ||||
|     { | ||||
|         registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**"); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 跨域配置 | ||||
|      */ | ||||
|     @Bean | ||||
|     public CorsFilter corsFilter() | ||||
|     { | ||||
|         UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); | ||||
|         CorsConfiguration config = new CorsConfiguration(); | ||||
|         config.setAllowCredentials(true); | ||||
|         // 设置访问源地址 | ||||
| 		config.addAllowedOriginPattern("*"); | ||||
|         // 设置访问源请求头 | ||||
|         config.addAllowedHeader("*"); | ||||
|         // 设置访问源请求方法 | ||||
|         config.addAllowedMethod("*"); | ||||
|         // 对接口配置跨域设置 | ||||
|         source.registerCorsConfiguration("/**", config); | ||||
|         return new CorsFilter(source); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,156 @@ | ||||
| package com.ruoyi.framework.config; | ||||
|  | ||||
| import com.ruoyi.framework.security.filter.JwtAuthenticationTokenFilter; | ||||
| import com.ruoyi.framework.security.handle.AuthenticationEntryPointImpl; | ||||
| import com.ruoyi.framework.security.handle.LogoutSuccessHandlerImpl; | ||||
| import de.codecentric.boot.admin.server.config.AdminServerProperties; | ||||
| 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.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; | ||||
|  | ||||
| /** | ||||
|  * spring security配置 | ||||
|  * | ||||
|  * @author ruoyi | ||||
|  */ | ||||
| @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 AdminServerProperties adminServerProperties; | ||||
|  | ||||
|     /** | ||||
|      * 解决 无法直接注入 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 | ||||
|     { | ||||
|         httpSecurity | ||||
|                 // CSRF禁用,因为不使用session | ||||
|                 .csrf().disable() | ||||
|                 // 认证失败处理类 | ||||
|                 .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() | ||||
|                 // 基于token,所以不需要session | ||||
|                 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() | ||||
|                 // 过滤请求 | ||||
|                 .authorizeRequests() | ||||
|                 // 对于登录login 验证码captchaImage 允许匿名访问 | ||||
|                 .antMatchers("/login", "/captchaImage","/demo/tress/all").anonymous() | ||||
|                 .antMatchers( | ||||
|                         HttpMethod.GET, | ||||
|                         "/*.html", | ||||
|                         "/**/*.html", | ||||
|                         "/**/*.css", | ||||
|                         "/**/*.js" | ||||
|                 ).permitAll() | ||||
|                 .antMatchers("/profile/**").anonymous() | ||||
|                 .antMatchers("/common/download**").anonymous() | ||||
|                 .antMatchers("/common/download/resource**").anonymous() | ||||
|                 .antMatchers("/doc.html").anonymous() | ||||
|                 .antMatchers("/swagger-resources/**").anonymous() | ||||
|                 .antMatchers("/webjars/**").anonymous() | ||||
|                 .antMatchers("/*/api-docs").anonymous() | ||||
|                 .antMatchers("/druid/**").anonymous() | ||||
|                 // Spring Boot Admin Server 的安全配置 | ||||
|                 .antMatchers(adminServerProperties.getContextPath()).anonymous() | ||||
|                 .antMatchers(adminServerProperties.getContextPath() + "/**").anonymous() | ||||
|                 // Spring Boot Actuator 的安全配置 | ||||
|                 .antMatchers("/actuator").anonymous() | ||||
|                 .antMatchers("/actuator/**").anonymous() | ||||
|                 // 除上面外的所有请求全部需要鉴权认证 | ||||
|                 .anyRequest().authenticated() | ||||
|                 .and() | ||||
|                 .headers().frameOptions().disable(); | ||||
|         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.ruoyi.framework.config; | ||||
|  | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import org.springframework.stereotype.Component; | ||||
| import com.ruoyi.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,108 @@ | ||||
| package com.ruoyi.framework.config; | ||||
|  | ||||
| import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j; | ||||
| import com.ruoyi.framework.config.properties.SwaggerProperties; | ||||
| import io.swagger.annotations.ApiOperation; | ||||
| import io.swagger.models.auth.In; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| import springfox.documentation.builders.ApiInfoBuilder; | ||||
| import springfox.documentation.builders.PathSelectors; | ||||
| import springfox.documentation.builders.RequestHandlerSelectors; | ||||
| import springfox.documentation.service.*; | ||||
| import springfox.documentation.spi.DocumentationType; | ||||
| import springfox.documentation.spi.service.contexts.SecurityContext; | ||||
| import springfox.documentation.spring.web.plugins.Docket; | ||||
|  | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
|  * Swagger 文档配置 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Configuration | ||||
| @EnableKnife4j | ||||
| public class SwaggerConfig { | ||||
|  | ||||
| 	@Autowired | ||||
| 	private SwaggerProperties swaggerProperties; | ||||
|  | ||||
| 	/** | ||||
| 	 * 创建API | ||||
| 	 */ | ||||
| 	@Bean | ||||
| 	public Docket createRestApi() { | ||||
| 		return new Docket(DocumentationType.OAS_30) | ||||
| 			.enable(swaggerProperties.getEnabled()) | ||||
| 			// 用来创建该API的基本信息,展示在文档的页面中(自定义展示的信息) | ||||
| 			.apiInfo(apiInfo()) | ||||
| 			// 设置哪些接口暴露给Swagger展示 | ||||
| 			.select() | ||||
| 			// 扫描所有有注解的api,用这种方式更灵活 | ||||
| 			.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) | ||||
| 			// 扫描指定包中的swagger注解 | ||||
| 			// .apis(RequestHandlerSelectors.basePackage("com.ruoyi.project.tool.swagger")) | ||||
| 			// 扫描所有 .apis(RequestHandlerSelectors.any()) | ||||
| 			.paths(PathSelectors.any()) | ||||
| 			.build() | ||||
| 			/* 设置安全模式,swagger可以设置访问token */ | ||||
| 			.securitySchemes(securitySchemes()) | ||||
| 			.securityContexts(securityContexts()) | ||||
| 			.pathMapping(swaggerProperties.getPathMapping()); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * 安全模式,这里指定token通过Authorization头请求头传递 | ||||
| 	 */ | ||||
| 	private List<SecurityScheme> securitySchemes() { | ||||
| 		List<SecurityScheme> apiKeyList = new ArrayList<SecurityScheme>(); | ||||
| 		apiKeyList.add(new ApiKey("Authorization", "Authorization", In.HEADER.toValue())); | ||||
| 		return apiKeyList; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * 安全上下文 | ||||
| 	 */ | ||||
| 	private List<SecurityContext> securityContexts() { | ||||
| 		List<SecurityContext> securityContexts = new ArrayList<>(); | ||||
| 		securityContexts.add( | ||||
| 			SecurityContext.builder() | ||||
| 				.securityReferences(defaultAuth()) | ||||
| 				.operationSelector(o -> o.requestMappingPattern().matches("/.*")) | ||||
| 				.build()); | ||||
| 		return securityContexts; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * 默认的安全上引用 | ||||
| 	 */ | ||||
| 	private List<SecurityReference> defaultAuth() { | ||||
| 		AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything"); | ||||
| 		AuthorizationScope[] authorizationScopes = new AuthorizationScope[1]; | ||||
| 		authorizationScopes[0] = authorizationScope; | ||||
| 		List<SecurityReference> securityReferences = new ArrayList<>(); | ||||
| 		securityReferences.add(new SecurityReference("Authorization", authorizationScopes)); | ||||
| 		return securityReferences; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * 添加摘要信息 | ||||
| 	 */ | ||||
| 	private ApiInfo apiInfo() { | ||||
| 		// 用ApiInfoBuilder进行定制 | ||||
| 		SwaggerProperties.Contact contact = swaggerProperties.getContact(); | ||||
| 		return new ApiInfoBuilder() | ||||
| 			// 设置标题 | ||||
| 			.title(swaggerProperties.getTitle()) | ||||
| 			// 描述 | ||||
| 			.description(swaggerProperties.getDescription()) | ||||
| 			// 作者信息 | ||||
| 			.contact(new Contact(contact.getName(), contact.getUrl(), contact.getEmail())) | ||||
| 			// 版本 | ||||
| 			.version(swaggerProperties.getVersion()) | ||||
| 			.build(); | ||||
| 	} | ||||
| } | ||||
| @ -0,0 +1,69 @@ | ||||
| package com.ruoyi.framework.config; | ||||
|  | ||||
| import com.ruoyi.common.utils.Threads; | ||||
| import com.ruoyi.framework.config.properties.ThreadPoolProperties; | ||||
| import org.apache.commons.lang3.concurrent.BasicThreadFactory; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; | ||||
|  | ||||
| import java.util.concurrent.RejectedExecutionHandler; | ||||
| import java.util.concurrent.ScheduledExecutorService; | ||||
| import java.util.concurrent.ScheduledThreadPoolExecutor; | ||||
| import java.util.concurrent.ThreadPoolExecutor; | ||||
|  | ||||
| /** | ||||
|  * 线程池配置 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  **/ | ||||
| @Configuration | ||||
| public class ThreadPoolConfig { | ||||
|  | ||||
|     @Autowired | ||||
|     private ThreadPoolProperties threadPoolProperties; | ||||
|  | ||||
|     @Bean(name = "threadPoolTaskExecutor") | ||||
|     @ConditionalOnProperty(prefix = "threadPoolTaskExecutor", name = "enabled", havingValue = "true") | ||||
|     public ThreadPoolTaskExecutor threadPoolTaskExecutor() { | ||||
|         ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); | ||||
|         executor.setMaxPoolSize(threadPoolProperties.getMaxPoolSize()); | ||||
|         executor.setCorePoolSize(threadPoolProperties.getCorePoolSize()); | ||||
|         executor.setQueueCapacity(threadPoolProperties.getQueueCapacity()); | ||||
|         executor.setKeepAliveSeconds(threadPoolProperties.getKeepAliveSeconds()); | ||||
|         RejectedExecutionHandler handler; | ||||
|         switch (threadPoolProperties.getRejectedExecutionHandler()) { | ||||
|             case "CallerRunsPolicy": | ||||
|                 handler = new ThreadPoolExecutor.CallerRunsPolicy(); | ||||
|                 break; | ||||
|             case "DiscardOldestPolicy": | ||||
|                 handler = new ThreadPoolExecutor.DiscardOldestPolicy(); | ||||
|                 break; | ||||
|             case "DiscardPolicy": | ||||
|                 handler = new ThreadPoolExecutor.DiscardPolicy(); | ||||
|                 break; | ||||
|             default: | ||||
|                 handler = new ThreadPoolExecutor.AbortPolicy(); | ||||
|                 break; | ||||
|         } | ||||
|         executor.setRejectedExecutionHandler(handler); | ||||
|         return executor; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 执行周期性或定时任务 | ||||
|      */ | ||||
|     @Bean(name = "scheduledExecutorService") | ||||
|     protected ScheduledExecutorService scheduledExecutorService() { | ||||
|         return new ScheduledThreadPoolExecutor(threadPoolProperties.getCorePoolSize(), | ||||
|                 new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build()) { | ||||
|             @Override | ||||
|             protected void afterExecute(Runnable r, Throwable t) { | ||||
|                 super.afterExecute(r, t); | ||||
|                 Threads.printException(r, t); | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,31 @@ | ||||
| package com.ruoyi.framework.config; | ||||
|  | ||||
| import org.hibernate.validator.HibernateValidator; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
|  | ||||
| import javax.validation.Validation; | ||||
| import javax.validation.Validator; | ||||
| import javax.validation.ValidatorFactory; | ||||
|  | ||||
| /** | ||||
|  * 校验框架配置类 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Configuration | ||||
| public class ValidatorConfig { | ||||
|  | ||||
|     /** | ||||
|      * 配置校验框架 快速返回模式 | ||||
|      */ | ||||
|     @Bean | ||||
|     public Validator validator() { | ||||
|         ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class) | ||||
|                 .configure() | ||||
|                 .failFast(true) | ||||
|                 .buildValidatorFactory(); | ||||
|         return validatorFactory.getValidator(); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,41 @@ | ||||
| package com.ruoyi.framework.config.properties; | ||||
|  | ||||
| import lombok.Data; | ||||
| import org.springframework.boot.context.properties.ConfigurationProperties; | ||||
| import org.springframework.stereotype.Component; | ||||
|  | ||||
| /** | ||||
|  * 验证码 配置属性 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Data | ||||
| @Component | ||||
| @ConfigurationProperties(prefix = "captcha") | ||||
| public class CaptchaProperties { | ||||
|  | ||||
| 	/** | ||||
| 	 * 验证码开关 | ||||
| 	 */ | ||||
| 	private Boolean enabled; | ||||
|  | ||||
| 	/** | ||||
| 	 * 验证码类型 | ||||
|  	 */ | ||||
|     private String type; | ||||
|  | ||||
| 	/** | ||||
| 	 * 验证码类别 | ||||
| 	 */ | ||||
|     private String category; | ||||
|  | ||||
| 	/** | ||||
| 	 * 数字验证码位数 | ||||
| 	 */ | ||||
|     private Integer numberLength; | ||||
|  | ||||
| 	/** | ||||
| 	 * 字符验证码长度 | ||||
| 	 */ | ||||
|     private Integer charLength; | ||||
| } | ||||
| @ -0,0 +1,101 @@ | ||||
| package com.ruoyi.framework.config.properties; | ||||
|  | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
| import org.redisson.client.codec.Codec; | ||||
| import org.redisson.config.TransportMode; | ||||
| import org.springframework.boot.context.properties.ConfigurationProperties; | ||||
| import org.springframework.stereotype.Component; | ||||
|  | ||||
| /** | ||||
|  * Redisson 配置属性 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Data | ||||
| @Component | ||||
| @ConfigurationProperties(prefix = "redisson") | ||||
| public class RedissonProperties { | ||||
|  | ||||
| 	/** | ||||
| 	 * 线程池数量,默认值 = 当前处理核数量 * 2 | ||||
| 	 */ | ||||
| 	private int threads; | ||||
|  | ||||
| 	/** | ||||
| 	 * Netty线程池数量,默认值 = 当前处理核数量 * 2 | ||||
| 	 */ | ||||
| 	private int nettyThreads; | ||||
|  | ||||
| 	/** | ||||
| 	 * 传输模式 | ||||
| 	 */ | ||||
| 	private TransportMode transportMode; | ||||
|  | ||||
| 	/** | ||||
| 	 * 单机服务配置 | ||||
| 	 */ | ||||
| 	private SingleServerConfig singleServerConfig; | ||||
|  | ||||
| 	@Data | ||||
| 	@NoArgsConstructor | ||||
| 	public static class SingleServerConfig { | ||||
|  | ||||
| 		/** | ||||
| 		 * 客户端名称 | ||||
| 		 */ | ||||
| 		private String clientName; | ||||
|  | ||||
| 		/** | ||||
| 		 * 最小空闲连接数 | ||||
| 		 */ | ||||
| 		private int connectionMinimumIdleSize; | ||||
|  | ||||
| 		/** | ||||
| 		 * 连接池大小 | ||||
| 		 */ | ||||
| 		private int connectionPoolSize; | ||||
|  | ||||
| 		/** | ||||
| 		 * 连接空闲超时,单位:毫秒 | ||||
| 		 */ | ||||
| 		private int idleConnectionTimeout; | ||||
|  | ||||
| 		/** | ||||
| 		 * 命令等待超时,单位:毫秒 | ||||
| 		 */ | ||||
| 		private int timeout; | ||||
|  | ||||
| 		/** | ||||
| 		 * 如果尝试在此限制之内发送成功,则开始启用 timeout 计时。 | ||||
| 		 */ | ||||
| 		private int retryAttempts; | ||||
|  | ||||
| 		/** | ||||
| 		 * 命令重试发送时间间隔,单位:毫秒 | ||||
| 		 */ | ||||
| 		private int retryInterval; | ||||
|  | ||||
| 		/** | ||||
| 		 * 发布和订阅连接的最小空闲连接数 | ||||
| 		 */ | ||||
| 		private int subscriptionConnectionMinimumIdleSize; | ||||
|  | ||||
| 		/** | ||||
| 		 * 发布和订阅连接池大小 | ||||
| 		 */ | ||||
| 		private int subscriptionConnectionPoolSize; | ||||
|  | ||||
| 		/** | ||||
| 		 * 单个连接最大订阅数量 | ||||
| 		 */ | ||||
| 		private int subscriptionsPerConnection; | ||||
|  | ||||
| 		/** | ||||
| 		 * DNS监测时间间隔,单位:毫秒 | ||||
| 		 */ | ||||
| 		private int dnsMonitoringInterval; | ||||
|  | ||||
| 	} | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,63 @@ | ||||
| package com.ruoyi.framework.config.properties; | ||||
|  | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
| import org.springframework.boot.context.properties.ConfigurationProperties; | ||||
| import org.springframework.stereotype.Component; | ||||
|  | ||||
| /** | ||||
|  * swagger 配置属性 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Data | ||||
| @Component | ||||
| @ConfigurationProperties(prefix = "swagger") | ||||
| public class SwaggerProperties { | ||||
|  | ||||
|     /** | ||||
|      * 验证码类型 | ||||
|      */ | ||||
|     private Boolean enabled; | ||||
| 	/** | ||||
| 	 * 设置请求的统一前缀 | ||||
| 	 */ | ||||
| 	private String pathMapping; | ||||
|     /** | ||||
|      * 验证码类别 | ||||
|      */ | ||||
|     private String title; | ||||
|     /** | ||||
|      * 数字验证码位数 | ||||
|      */ | ||||
|     private String description; | ||||
|     /** | ||||
|      * 字符验证码长度 | ||||
|      */ | ||||
|     private String version; | ||||
|  | ||||
| 	/** | ||||
| 	 * 联系方式 | ||||
| 	 */ | ||||
|     private Contact contact; | ||||
|  | ||||
|     @Data | ||||
| 	@NoArgsConstructor | ||||
| 	public static class Contact{ | ||||
|  | ||||
| 		/** | ||||
| 		 * 联系人 | ||||
| 		 **/ | ||||
| 		private String name; | ||||
| 		/** | ||||
| 		 * 联系人url | ||||
| 		 **/ | ||||
| 		private String url; | ||||
| 		/** | ||||
| 		 * 联系人email | ||||
| 		 **/ | ||||
| 		private String email; | ||||
|  | ||||
| 	} | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,47 @@ | ||||
| package com.ruoyi.framework.config.properties; | ||||
|  | ||||
| import lombok.Data; | ||||
| import org.springframework.boot.context.properties.ConfigurationProperties; | ||||
| import org.springframework.stereotype.Component; | ||||
|  | ||||
| /** | ||||
|  * 线程池 配置属性 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Data | ||||
| @Component | ||||
| @ConfigurationProperties(prefix = "thread-pool") | ||||
| public class ThreadPoolProperties { | ||||
|  | ||||
|     /** | ||||
|      * 是否开启线程池 | ||||
|      */ | ||||
|     private boolean enabled; | ||||
|  | ||||
|     /** | ||||
|      * 核心线程池大小 | ||||
|      */ | ||||
|     private int corePoolSize; | ||||
|  | ||||
|     /** | ||||
|      * 最大可创建的线程数 | ||||
|      */ | ||||
|     private int maxPoolSize; | ||||
|  | ||||
|     /** | ||||
|      * 队列最大长度 | ||||
|      */ | ||||
|     private int queueCapacity; | ||||
|  | ||||
|     /** | ||||
|      * 线程池维护线程所允许的空闲时间 | ||||
|      */ | ||||
|     private int keepAliveSeconds; | ||||
|  | ||||
|     /** | ||||
|      * 线程池对拒绝任务(无线程可用)的处理策略 | ||||
|      */ | ||||
|     private String rejectedExecutionHandler; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,31 @@ | ||||
| package com.ruoyi.framework.config.properties; | ||||
|  | ||||
| import lombok.Data; | ||||
| import org.springframework.boot.context.properties.ConfigurationProperties; | ||||
| import org.springframework.stereotype.Component; | ||||
|  | ||||
| /** | ||||
|  * token 配置属性 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Data | ||||
| @Component | ||||
| @ConfigurationProperties(prefix = "token") | ||||
| public class TokenProperties { | ||||
|  | ||||
|     /** | ||||
|      * 令牌自定义标识 | ||||
|      */ | ||||
|     private String header; | ||||
|  | ||||
|     /** | ||||
|      * 令牌秘钥 | ||||
|      */ | ||||
|     private String secret; | ||||
|  | ||||
|     /** | ||||
|      * 令牌有效期(默认30分钟) | ||||
|      */ | ||||
|     private int expireTime; | ||||
| } | ||||
| @ -0,0 +1,32 @@ | ||||
| package com.ruoyi.framework.config.properties; | ||||
|  | ||||
| import lombok.Data; | ||||
| import org.springframework.boot.context.properties.ConfigurationProperties; | ||||
| import org.springframework.stereotype.Component; | ||||
|  | ||||
| /** | ||||
|  * xss过滤 配置属性 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Data | ||||
| @Component | ||||
| @ConfigurationProperties(prefix = "xss") | ||||
| public class XssProperties { | ||||
|  | ||||
|     /** | ||||
|      * 过滤开关 | ||||
|      */ | ||||
|     private String enabled; | ||||
|  | ||||
|     /** | ||||
|      * 排除链接(多个用逗号分隔) | ||||
|      */ | ||||
|     private String excludes; | ||||
|  | ||||
|     /** | ||||
|      * 匹配链接 | ||||
|      */ | ||||
|     private String urlPatterns; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,56 @@ | ||||
| package com.ruoyi.framework.interceptor; | ||||
|  | ||||
| import com.ruoyi.common.annotation.RepeatSubmit; | ||||
| import com.ruoyi.common.core.domain.AjaxResult; | ||||
| import com.ruoyi.common.utils.JsonUtils; | ||||
| import com.ruoyi.common.utils.ServletUtils; | ||||
| import org.springframework.stereotype.Component; | ||||
| import org.springframework.web.method.HandlerMethod; | ||||
| import org.springframework.web.servlet.HandlerInterceptor; | ||||
|  | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
| import java.lang.reflect.Method; | ||||
|  | ||||
| /** | ||||
|  * 防止重复提交拦截器 | ||||
|  * | ||||
|  * @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)) | ||||
|                 { | ||||
|                     AjaxResult ajaxResult = AjaxResult.error("不允许重复提交,请稍后再试"); | ||||
|                     ServletUtils.renderString(response, JsonUtils.toJsonString(ajaxResult)); | ||||
|                     return false; | ||||
|                 } | ||||
|             } | ||||
|             return true; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             return true; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 验证是否重复提交由子类实现具体的防重复提交的规则 | ||||
|      * | ||||
|      * @param request | ||||
|      * @return | ||||
|      * @throws Exception | ||||
|      */ | ||||
|     public abstract boolean isRepeatSubmit(HttpServletRequest request); | ||||
| } | ||||
| @ -0,0 +1,129 @@ | ||||
| package com.ruoyi.framework.interceptor.impl; | ||||
|  | ||||
| import cn.hutool.core.io.IoUtil; | ||||
| import cn.hutool.core.lang.Validator; | ||||
| import com.ruoyi.common.constant.Constants; | ||||
| import com.ruoyi.common.core.redis.RedisCache; | ||||
| import com.ruoyi.common.filter.RepeatedlyRequestWrapper; | ||||
| import com.ruoyi.common.utils.JsonUtils; | ||||
| import com.ruoyi.framework.interceptor.RepeatSubmitInterceptor; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.beans.factory.annotation.Value; | ||||
| import org.springframework.stereotype.Component; | ||||
|  | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import java.io.IOException; | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
| import java.util.concurrent.TimeUnit; | ||||
|  | ||||
| /** | ||||
|  * 判断请求url和数据是否和上一次相同, | ||||
|  * 如果和上次相同,则是重复提交表单。 有效时间为10秒内。 | ||||
|  * | ||||
|  * @author ruoyi | ||||
|  */ | ||||
| @Slf4j | ||||
| @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; | ||||
|  | ||||
|     /** | ||||
|      * 间隔时间,单位:秒 默认10秒 | ||||
|      * | ||||
|      * 两次相同参数的请求,如果间隔时间大于该参数,系统不会认定为重复提交的数据 | ||||
|      */ | ||||
|     private int intervalTime = 10; | ||||
|  | ||||
|     public void setIntervalTime(int intervalTime) | ||||
|     { | ||||
|         this.intervalTime = intervalTime; | ||||
|     } | ||||
|  | ||||
|     @SuppressWarnings("unchecked") | ||||
|     @Override | ||||
|     public boolean isRepeatSubmit(HttpServletRequest request) | ||||
|     { | ||||
|         String nowParams = ""; | ||||
|         if (request instanceof RepeatedlyRequestWrapper) | ||||
|         { | ||||
|             RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request; | ||||
| 			try { | ||||
| 				nowParams = IoUtil.readUtf8(repeatedlyRequest.getInputStream()); | ||||
| 			} catch (IOException e) { | ||||
| 				log.warn("读取流出现问题!"); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
|         // body参数为空,获取Parameter的数据 | ||||
|         if (Validator.isEmpty(nowParams)) | ||||
|         { | ||||
|             nowParams = JsonUtils.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 = request.getHeader(header); | ||||
|         if (Validator.isEmpty(submitKey)) | ||||
|         { | ||||
|             submitKey = url; | ||||
|         } | ||||
|  | ||||
|         // 唯一标识(指定key + 消息头) | ||||
|         String cacheRepeatKey = Constants.REPEAT_SUBMIT_KEY + 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)) | ||||
|                 { | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         Map<String, Object> cacheMap = new HashMap<String, Object>(); | ||||
|         cacheMap.put(url, nowDataMap); | ||||
|         redisCache.setCacheObject(cacheRepeatKey, cacheMap, intervalTime, TimeUnit.SECONDS); | ||||
|         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) | ||||
|     { | ||||
|         long time1 = (Long) nowMap.get(REPEAT_TIME); | ||||
|         long time2 = (Long) preMap.get(REPEAT_TIME); | ||||
| 		return (time1 - time2) < (this.intervalTime * 1000L); | ||||
| 	} | ||||
| } | ||||
| @ -0,0 +1,41 @@ | ||||
| package com.ruoyi.framework.manager; | ||||
|  | ||||
| import com.ruoyi.common.utils.Threads; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.beans.factory.annotation.Qualifier; | ||||
| import org.springframework.stereotype.Component; | ||||
|  | ||||
| import javax.annotation.PreDestroy; | ||||
| import java.util.concurrent.ScheduledExecutorService; | ||||
|  | ||||
| /** | ||||
|  * 确保应用退出时能关闭后台线程 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Slf4j(topic = "sys-user") | ||||
| @Component | ||||
| public class ShutdownManager { | ||||
|  | ||||
| 	@Autowired | ||||
| 	@Qualifier("scheduledExecutorService") | ||||
| 	private ScheduledExecutorService scheduledExecutorService; | ||||
|  | ||||
| 	@PreDestroy | ||||
| 	public void destroy() { | ||||
| 		shutdownAsyncManager(); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * 停止异步执行任务 | ||||
| 	 */ | ||||
| 	private void shutdownAsyncManager() { | ||||
| 		try { | ||||
| 			log.info("====关闭后台任务任务线程池===="); | ||||
| 			Threads.shutdownAndAwaitTermination(scheduledExecutorService); | ||||
| 		} catch (Exception e) { | ||||
| 			log.error(e.getMessage(), e); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @ -0,0 +1,56 @@ | ||||
| package com.ruoyi.framework.mybatisplus; | ||||
|  | ||||
| import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; | ||||
| import com.ruoyi.common.enums.UserStatus; | ||||
| import com.ruoyi.common.utils.SecurityUtils; | ||||
| import org.apache.ibatis.reflection.MetaObject; | ||||
|  | ||||
| import java.time.LocalDateTime; | ||||
| import java.util.Date; | ||||
|  | ||||
| /** | ||||
|  * MP注入处理器 | ||||
|  * @author Lion Li | ||||
|  * @date 2021/4/25 | ||||
|  */ | ||||
| public class CreateAndUpdateMetaObjectHandler implements MetaObjectHandler { | ||||
|  | ||||
| 	@Override | ||||
| 	public void insertFill(MetaObject metaObject) { | ||||
| 		//根据属性名字设置要填充的值 | ||||
| 		if (metaObject.hasGetter("createTime")) { | ||||
| 			Class<?> createTime = metaObject.getGetterType("createTime"); | ||||
| 			String typeName = createTime.getTypeName(); | ||||
| 			if (typeName.endsWith("LocalDateTime")) { | ||||
| 				this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐使用) | ||||
| 			} else if (typeName.endsWith("Date")) { | ||||
| 				this.setFieldValByName("createTime", new Date(), metaObject); | ||||
| 			} | ||||
| 		} | ||||
| 		if (metaObject.hasGetter("createBy")) { | ||||
| 			if (metaObject.getValue("createBy") == null) { | ||||
| 				this.setFieldValByName("createBy", SecurityUtils.getUsername(), metaObject); | ||||
| 			} | ||||
| 		} | ||||
| 		updateFill(metaObject); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void updateFill(MetaObject metaObject) { | ||||
| 		if (metaObject.hasGetter("updateBy")) { | ||||
| 			if (metaObject.getValue("updateBy") == null) { | ||||
| 				this.setFieldValByName("updateBy", SecurityUtils.getUsername(), metaObject); | ||||
| 			} | ||||
| 		} | ||||
| 		if (metaObject.hasGetter("updateTime")) { | ||||
| 			Class<?> updateTime = metaObject.getGetterType("updateTime"); | ||||
| 			String typeName = updateTime.getTypeName(); | ||||
| 			if (typeName.endsWith("LocalDateTime")) { | ||||
| 				this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject); | ||||
| 			} else if (typeName.endsWith("Date")) { | ||||
| 				this.setFieldValByName("updateTime", new Date(), metaObject); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,45 @@ | ||||
| package com.ruoyi.framework.security.filter; | ||||
|  | ||||
| import cn.hutool.core.lang.Validator; | ||||
| import com.ruoyi.common.core.domain.model.LoginUser; | ||||
| import com.ruoyi.common.utils.SecurityUtils; | ||||
| import com.ruoyi.framework.web.service.TokenService; | ||||
| 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 javax.servlet.FilterChain; | ||||
| import javax.servlet.ServletException; | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
| import java.io.IOException; | ||||
|  | ||||
| /** | ||||
|  * 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 (Validator.isNotNull(loginUser) && Validator.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,35 @@ | ||||
| package com.ruoyi.framework.security.handle; | ||||
|  | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import cn.hutool.http.HttpStatus; | ||||
| import com.ruoyi.common.core.domain.AjaxResult; | ||||
| import com.ruoyi.common.utils.JsonUtils; | ||||
| import com.ruoyi.common.utils.ServletUtils; | ||||
| import org.springframework.security.core.AuthenticationException; | ||||
| import org.springframework.security.web.AuthenticationEntryPoint; | ||||
| import org.springframework.stereotype.Component; | ||||
|  | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
| import java.io.IOException; | ||||
| import java.io.Serializable; | ||||
|  | ||||
| /** | ||||
|  * 认证失败处理类 返回未授权 | ||||
|  * | ||||
|  * @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 | ||||
|     { | ||||
|         int code = HttpStatus.HTTP_UNAUTHORIZED; | ||||
|         String msg = StrUtil.format("请求访问:{},认证失败,无法访问系统资源", request.getRequestURI()); | ||||
|         ServletUtils.renderString(response, JsonUtils.toJsonString(AjaxResult.error(code, msg))); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,53 @@ | ||||
| package com.ruoyi.framework.security.handle; | ||||
|  | ||||
| import cn.hutool.core.lang.Validator; | ||||
| import cn.hutool.http.HttpStatus; | ||||
| import com.ruoyi.common.constant.Constants; | ||||
| import com.ruoyi.common.core.domain.AjaxResult; | ||||
| import com.ruoyi.common.core.domain.model.LoginUser; | ||||
| import com.ruoyi.common.utils.JsonUtils; | ||||
| import com.ruoyi.common.utils.ServletUtils; | ||||
| import com.ruoyi.framework.web.service.AsyncService; | ||||
| import com.ruoyi.framework.web.service.TokenService; | ||||
| 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 javax.servlet.ServletException; | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
| import java.io.IOException; | ||||
|  | ||||
| /** | ||||
|  * 自定义退出处理类 返回成功 | ||||
|  * | ||||
|  * @author ruoyi | ||||
|  */ | ||||
| @Configuration | ||||
| public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler { | ||||
|  | ||||
| 	@Autowired | ||||
| 	private TokenService tokenService; | ||||
|  | ||||
| 	@Autowired | ||||
| 	private AsyncService asyncService; | ||||
|  | ||||
| 	/** | ||||
| 	 * 退出处理 | ||||
| 	 */ | ||||
| 	@Override | ||||
| 	public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) | ||||
| 		throws IOException, ServletException { | ||||
| 		LoginUser loginUser = tokenService.getLoginUser(request); | ||||
| 		if (Validator.isNotNull(loginUser)) { | ||||
| 			String userName = loginUser.getUsername(); | ||||
| 			// 删除用户缓存记录 | ||||
| 			tokenService.delLoginUser(loginUser.getToken()); | ||||
| 			// 记录用户退出日志 | ||||
| 			asyncService.recordLogininfor(userName, Constants.LOGOUT, "退出成功", request); | ||||
| 		} | ||||
| 		ServletUtils.renderString(response, JsonUtils.toJsonString(AjaxResult.error(HttpStatus.HTTP_OK, "退出成功"))); | ||||
| 	} | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,129 @@ | ||||
| package com.ruoyi.framework.web.exception; | ||||
|  | ||||
| import cn.hutool.core.lang.Validator; | ||||
| import cn.hutool.http.HttpStatus; | ||||
| import com.ruoyi.common.core.domain.AjaxResult; | ||||
| import com.ruoyi.common.exception.BaseException; | ||||
| import com.ruoyi.common.exception.CustomException; | ||||
| import com.ruoyi.common.exception.DemoModeException; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.security.access.AccessDeniedException; | ||||
| import org.springframework.security.authentication.AccountExpiredException; | ||||
| import org.springframework.security.core.userdetails.UsernameNotFoundException; | ||||
| import org.springframework.validation.BindException; | ||||
| import org.springframework.web.bind.MethodArgumentNotValidException; | ||||
| import org.springframework.web.bind.annotation.ExceptionHandler; | ||||
| import org.springframework.web.bind.annotation.RestControllerAdvice; | ||||
| import org.springframework.web.servlet.NoHandlerFoundException; | ||||
|  | ||||
| import javax.validation.ConstraintViolationException; | ||||
|  | ||||
| /** | ||||
|  * 全局异常处理器 | ||||
|  *  | ||||
|  * @author ruoyi | ||||
|  */ | ||||
| @RestControllerAdvice | ||||
| public class GlobalExceptionHandler | ||||
| { | ||||
|     private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); | ||||
|  | ||||
|     /** | ||||
|      * 基础异常 | ||||
|      */ | ||||
|     @ExceptionHandler(BaseException.class) | ||||
|     public AjaxResult baseException(BaseException e) | ||||
|     { | ||||
|         return AjaxResult.error(e.getMessage()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 业务异常 | ||||
|      */ | ||||
|     @ExceptionHandler(CustomException.class) | ||||
|     public AjaxResult businessException(CustomException e) | ||||
|     { | ||||
|         if (Validator.isNull(e.getCode())) | ||||
|         { | ||||
|             return AjaxResult.error(e.getMessage()); | ||||
|         } | ||||
|         return AjaxResult.error(e.getCode(), e.getMessage()); | ||||
|     } | ||||
|  | ||||
|     @ExceptionHandler(NoHandlerFoundException.class) | ||||
|     public AjaxResult handlerNoFoundException(Exception e) | ||||
|     { | ||||
|         log.error(e.getMessage(), e); | ||||
|         return AjaxResult.error(HttpStatus.HTTP_NOT_FOUND, "路径不存在,请检查路径是否正确"); | ||||
|     } | ||||
|  | ||||
|     @ExceptionHandler(AccessDeniedException.class) | ||||
|     public AjaxResult handleAuthorizationException(AccessDeniedException e) | ||||
|     { | ||||
|         log.error(e.getMessage()); | ||||
|         return AjaxResult.error(HttpStatus.HTTP_FORBIDDEN, "没有权限,请联系管理员授权"); | ||||
|     } | ||||
|  | ||||
|     @ExceptionHandler(AccountExpiredException.class) | ||||
|     public AjaxResult handleAccountExpiredException(AccountExpiredException e) | ||||
|     { | ||||
|         log.error(e.getMessage(), e); | ||||
|         return AjaxResult.error(e.getMessage()); | ||||
|     } | ||||
|  | ||||
|     @ExceptionHandler(UsernameNotFoundException.class) | ||||
|     public AjaxResult handleUsernameNotFoundException(UsernameNotFoundException e) | ||||
|     { | ||||
|         log.error(e.getMessage(), e); | ||||
|         return AjaxResult.error(e.getMessage()); | ||||
|     } | ||||
|  | ||||
|     @ExceptionHandler(Exception.class) | ||||
|     public AjaxResult handleException(Exception e) | ||||
|     { | ||||
|         log.error(e.getMessage(), e); | ||||
|         return AjaxResult.error(e.getMessage()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 自定义验证异常 | ||||
|      */ | ||||
|     @ExceptionHandler(BindException.class) | ||||
|     public AjaxResult validatedBindException(BindException e) | ||||
|     { | ||||
|         log.error(e.getMessage(), e); | ||||
|         String message = e.getAllErrors().get(0).getDefaultMessage(); | ||||
|         return AjaxResult.error(message); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 自定义验证异常 | ||||
|      */ | ||||
|     @ExceptionHandler(ConstraintViolationException.class) | ||||
|     public AjaxResult constraintViolationException(ConstraintViolationException e) { | ||||
|         log.error(e.getMessage(), e); | ||||
|         String message = e.getConstraintViolations().iterator().next().getMessage(); | ||||
|         return AjaxResult.error(message); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 自定义验证异常 | ||||
|      */ | ||||
|     @ExceptionHandler(MethodArgumentNotValidException.class) | ||||
|     public Object validExceptionHandler(MethodArgumentNotValidException e) | ||||
|     { | ||||
|         log.error(e.getMessage(), e); | ||||
|         String message = e.getBindingResult().getFieldError().getDefaultMessage(); | ||||
|         return AjaxResult.error(message); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 演示模式异常 | ||||
|      */ | ||||
|     @ExceptionHandler(DemoModeException.class) | ||||
|     public AjaxResult demoModeException(DemoModeException e) | ||||
|     { | ||||
|         return AjaxResult.error("演示模式,不允许操作"); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,96 @@ | ||||
| package com.ruoyi.framework.web.service; | ||||
|  | ||||
| import cn.hutool.http.useragent.UserAgent; | ||||
| import cn.hutool.http.useragent.UserAgentUtil; | ||||
| import com.ruoyi.common.constant.Constants; | ||||
| import com.ruoyi.common.utils.ServletUtils; | ||||
| import com.ruoyi.common.utils.ip.AddressUtils; | ||||
| import com.ruoyi.system.domain.SysLogininfor; | ||||
| import com.ruoyi.system.domain.SysOperLog; | ||||
| import com.ruoyi.system.service.ISysLogininforService; | ||||
| import com.ruoyi.system.service.ISysOperLogService; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.scheduling.annotation.Async; | ||||
| import org.springframework.stereotype.Component; | ||||
|  | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
|  | ||||
| /** | ||||
|  * 异步工厂(产生任务用) | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Slf4j(topic = "sys-user") | ||||
| @Async | ||||
| @Component | ||||
| public class AsyncService { | ||||
|  | ||||
| 	@Autowired | ||||
| 	private ISysLogininforService iSysLogininforService; | ||||
|  | ||||
| 	@Autowired | ||||
| 	private ISysOperLogService iSysOperLogService; | ||||
|  | ||||
| 	/** | ||||
| 	 * 记录登录信息 | ||||
| 	 * | ||||
| 	 * @param username 用户名 | ||||
| 	 * @param status   状态 | ||||
| 	 * @param message  消息 | ||||
| 	 * @param args     列表 | ||||
| 	 */ | ||||
| 	public void recordLogininfor(final String username, final String status, final String message, | ||||
| 								 HttpServletRequest request, final Object... args) { | ||||
| 		final UserAgent userAgent = UserAgentUtil.parse(request.getHeader("User-Agent")); | ||||
| 		final String ip = ServletUtils.getClientIP(request); | ||||
|  | ||||
| 		String address = AddressUtils.getRealAddressByIP(ip); | ||||
| 		StringBuilder s = new StringBuilder(); | ||||
| 		s.append(getBlock(ip)); | ||||
| 		s.append(address); | ||||
| 		s.append(getBlock(username)); | ||||
| 		s.append(getBlock(status)); | ||||
| 		s.append(getBlock(message)); | ||||
| 		// 打印信息到日志 | ||||
| 		log.info(s.toString(), args); | ||||
| 		// 获取客户端操作系统 | ||||
| 		String os = userAgent.getOs().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 (Constants.LOGIN_SUCCESS.equals(status) || Constants.LOGOUT.equals(status)) { | ||||
| 			logininfor.setStatus(Constants.SUCCESS); | ||||
| 		} else if (Constants.LOGIN_FAIL.equals(status)) { | ||||
| 			logininfor.setStatus(Constants.FAIL); | ||||
| 		} | ||||
| 		// 插入数据 | ||||
| 		iSysLogininforService.insertLogininfor(logininfor); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * 操作日志记录 | ||||
| 	 * | ||||
| 	 * @param operLog 操作日志信息 | ||||
| 	 */ | ||||
| 	public void recordOper(final SysOperLog operLog) { | ||||
| 		// 远程查询操作地点 | ||||
| 		operLog.setOperLocation(AddressUtils.getRealAddressByIP(operLog.getOperIp())); | ||||
| 		iSysOperLogService.insertOperlog(operLog); | ||||
| 	} | ||||
|  | ||||
| 	private String getBlock(Object msg) { | ||||
| 		if (msg == null) { | ||||
| 			msg = ""; | ||||
| 		} | ||||
| 		return "[" + msg.toString() + "]"; | ||||
| 	} | ||||
| } | ||||
| @ -0,0 +1,170 @@ | ||||
| package com.ruoyi.framework.web.service; | ||||
|  | ||||
| import cn.hutool.core.lang.Validator; | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import com.ruoyi.common.core.domain.entity.SysRole; | ||||
| import com.ruoyi.common.core.domain.model.LoginUser; | ||||
| import com.ruoyi.common.utils.ServletUtils; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.stereotype.Service; | ||||
|  | ||||
| import java.util.Set; | ||||
|  | ||||
| /** | ||||
|  * 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 = ","; | ||||
|  | ||||
|     @Autowired | ||||
|     private TokenService tokenService; | ||||
|  | ||||
|     /** | ||||
|      * 验证用户是否具备某权限 | ||||
|      *  | ||||
|      * @param permission 权限字符串 | ||||
|      * @return 用户是否具备某权限 | ||||
|      */ | ||||
|     public boolean hasPermi(String permission) | ||||
|     { | ||||
|         if (Validator.isEmpty(permission)) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
|         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest()); | ||||
|         if (Validator.isNull(loginUser) || Validator.isEmpty(loginUser.getPermissions())) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
|         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 (Validator.isEmpty(permissions)) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
|         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest()); | ||||
|         if (Validator.isNull(loginUser) || Validator.isEmpty(loginUser.getPermissions())) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
|         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 (Validator.isEmpty(role)) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
|         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest()); | ||||
|         if (Validator.isNull(loginUser) || Validator.isEmpty(loginUser.getUser().getRoles())) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
|         for (SysRole sysRole : loginUser.getUser().getRoles()) | ||||
|         { | ||||
|             String roleKey = sysRole.getRoleKey(); | ||||
|             if (SUPER_ADMIN.equals(roleKey) || roleKey.equals(StrUtil.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 (Validator.isEmpty(roles)) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
|         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest()); | ||||
|         if (Validator.isNull(loginUser) || Validator.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(StrUtil.trim(permission)); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,115 @@ | ||||
| package com.ruoyi.framework.web.service; | ||||
|  | ||||
| import com.ruoyi.common.constant.Constants; | ||||
| import com.ruoyi.common.core.domain.entity.SysUser; | ||||
| import com.ruoyi.common.core.domain.model.LoginUser; | ||||
| import com.ruoyi.common.core.redis.RedisCache; | ||||
| import com.ruoyi.common.exception.CustomException; | ||||
| import com.ruoyi.common.exception.user.CaptchaException; | ||||
| import com.ruoyi.common.exception.user.CaptchaExpireException; | ||||
| import com.ruoyi.common.exception.user.UserPasswordNotMatchException; | ||||
| import com.ruoyi.common.utils.DateUtils; | ||||
| import com.ruoyi.common.utils.MessageUtils; | ||||
| import com.ruoyi.common.utils.ServletUtils; | ||||
| import com.ruoyi.framework.config.properties.CaptchaProperties; | ||||
| import com.ruoyi.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.stereotype.Component; | ||||
|  | ||||
| import javax.annotation.Resource; | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
|  | ||||
| /** | ||||
|  * 登录校验方法 | ||||
|  * | ||||
|  * @author ruoyi | ||||
|  */ | ||||
| @Component | ||||
| public class SysLoginService | ||||
| { | ||||
|     @Autowired | ||||
|     private TokenService tokenService; | ||||
|  | ||||
|     @Resource | ||||
|     private AuthenticationManager authenticationManager; | ||||
|  | ||||
|     @Autowired | ||||
|     private RedisCache redisCache; | ||||
|  | ||||
| 	@Autowired | ||||
| 	private CaptchaProperties captchaProperties; | ||||
|  | ||||
| 	@Autowired | ||||
|     private ISysUserService userService; | ||||
|  | ||||
| 	@Autowired | ||||
| 	private AsyncService asyncService; | ||||
|  | ||||
|     /** | ||||
|      * 登录验证 | ||||
|      * | ||||
|      * @param username 用户名 | ||||
|      * @param password 密码 | ||||
|      * @param code 验证码 | ||||
|      * @param uuid 唯一标识 | ||||
|      * @return 结果 | ||||
|      */ | ||||
|     public String login(String username, String password, String code, String uuid) | ||||
|     { | ||||
| 		HttpServletRequest request = ServletUtils.getRequest(); | ||||
| 		if(captchaProperties.getEnabled()) { | ||||
| 			String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid; | ||||
| 			String captcha = redisCache.getCacheObject(verifyKey); | ||||
| 			redisCache.deleteObject(verifyKey); | ||||
| 			if (captcha == null) { | ||||
| 				asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"), request); | ||||
| 				throw new CaptchaExpireException(); | ||||
| 			} | ||||
| 			if (!code.equalsIgnoreCase(captcha)) { | ||||
| 				asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"), request); | ||||
| 				throw new CaptchaException(); | ||||
| 			} | ||||
| 		} | ||||
|         // 用户验证 | ||||
|         Authentication authentication = null; | ||||
|         try | ||||
|         { | ||||
|             // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername | ||||
|             authentication = authenticationManager | ||||
|                     .authenticate(new UsernamePasswordAuthenticationToken(username, password)); | ||||
|         } | ||||
|         catch (Exception e) | ||||
|         { | ||||
|             if (e instanceof BadCredentialsException) | ||||
|             { | ||||
| 				asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"), request); | ||||
|                 throw new UserPasswordNotMatchException(); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
| 				asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage(), request); | ||||
|                 throw new CustomException(e.getMessage()); | ||||
|             } | ||||
|         } | ||||
| 		asyncService.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"), request); | ||||
|         LoginUser loginUser = (LoginUser) authentication.getPrincipal(); | ||||
|         recordLoginInfo(loginUser.getUser()); | ||||
|         // 生成token | ||||
|         return tokenService.createToken(loginUser); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 记录登录信息 | ||||
|      */ | ||||
|     public void recordLoginInfo(SysUser user) | ||||
|     { | ||||
|         user.setLoginIp(ServletUtils.getClientIP()); | ||||
|         user.setLoginDate(DateUtils.getNowDate()); | ||||
| 		user.setUpdateBy(user.getUserName()); | ||||
|         userService.updateUserProfile(user); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,66 @@ | ||||
| package com.ruoyi.framework.web.service; | ||||
|  | ||||
| import java.util.HashSet; | ||||
| import java.util.Set; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.stereotype.Component; | ||||
| import com.ruoyi.common.core.domain.entity.SysUser; | ||||
| import com.ruoyi.system.service.ISysMenuService; | ||||
| import com.ruoyi.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 | ||||
|         { | ||||
|             perms.addAll(menuService.selectMenuPermsByUserId(user.getUserId())); | ||||
|         } | ||||
|         return perms; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,194 @@ | ||||
| package com.ruoyi.framework.web.service; | ||||
|  | ||||
| import cn.hutool.core.lang.Validator; | ||||
| import cn.hutool.core.util.IdUtil; | ||||
| import cn.hutool.http.useragent.UserAgent; | ||||
| import cn.hutool.http.useragent.UserAgentUtil; | ||||
| import com.ruoyi.common.constant.Constants; | ||||
| import com.ruoyi.common.core.domain.model.LoginUser; | ||||
| import com.ruoyi.common.core.redis.RedisCache; | ||||
| import com.ruoyi.common.utils.ServletUtils; | ||||
| import com.ruoyi.common.utils.ip.AddressUtils; | ||||
| import com.ruoyi.framework.config.properties.TokenProperties; | ||||
| import io.jsonwebtoken.Claims; | ||||
| import io.jsonwebtoken.Jwts; | ||||
| import io.jsonwebtoken.SignatureAlgorithm; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| 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 Lion Li | ||||
|  */ | ||||
| @Component | ||||
| public class TokenService { | ||||
|  | ||||
|     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 TokenProperties tokenProperties; | ||||
|  | ||||
|     /** | ||||
|      * 获取用户身份信息 | ||||
|      * | ||||
|      * @return 用户信息 | ||||
|      */ | ||||
|     public LoginUser getLoginUser(HttpServletRequest request) { | ||||
|         // 获取请求携带的令牌 | ||||
|         String token = getToken(request); | ||||
|         if (Validator.isNotEmpty(token)) { | ||||
|             Claims claims = parseToken(token); | ||||
|             // 解析对应的权限以及用户信息 | ||||
|             String uuid = (String) claims.get(Constants.LOGIN_USER_KEY); | ||||
|             String userKey = getTokenKey(uuid); | ||||
|             LoginUser user = redisCache.getCacheObject(userKey); | ||||
|             return user; | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 设置用户身份信息 | ||||
|      */ | ||||
|     public void setLoginUser(LoginUser loginUser) { | ||||
|         if (Validator.isNotNull(loginUser) && Validator.isNotEmpty(loginUser.getToken())) { | ||||
|             refreshToken(loginUser); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 删除用户身份信息 | ||||
|      */ | ||||
|     public void delLoginUser(String token) { | ||||
|         if (Validator.isNotEmpty(token)) { | ||||
|             String userKey = getTokenKey(token); | ||||
|             redisCache.deleteObject(userKey); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 创建令牌 | ||||
|      * | ||||
|      * @param loginUser 用户信息 | ||||
|      * @return 令牌 | ||||
|      */ | ||||
|     public String createToken(LoginUser loginUser) { | ||||
|         String token = IdUtil.fastUUID(); | ||||
|         loginUser.setToken(token); | ||||
|         setUserAgent(loginUser); | ||||
|         refreshToken(loginUser); | ||||
|  | ||||
|         Map<String, Object> claims = new HashMap<>(); | ||||
|         claims.put(Constants.LOGIN_USER_KEY, token); | ||||
|         return createToken(claims); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 验证令牌有效期,相差不足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) { | ||||
|         loginUser.setLoginTime(System.currentTimeMillis()); | ||||
|         loginUser.setExpireTime(loginUser.getLoginTime() + tokenProperties.getExpireTime() * MILLIS_MINUTE); | ||||
|         // 根据uuid将loginUser缓存 | ||||
|         String userKey = getTokenKey(loginUser.getToken()); | ||||
|         redisCache.setCacheObject(userKey, loginUser, tokenProperties.getExpireTime(), TimeUnit.MINUTES); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 设置用户代理信息 | ||||
|      * | ||||
|      * @param loginUser 登录信息 | ||||
|      */ | ||||
|     public void setUserAgent(LoginUser loginUser) { | ||||
|         UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent")); | ||||
|         String ip = ServletUtils.getClientIP(); | ||||
|         loginUser.setIpaddr(ip); | ||||
|         loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip)); | ||||
|         loginUser.setBrowser(userAgent.getBrowser().getName()); | ||||
|         loginUser.setOs(userAgent.getOs().getName()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 从数据声明生成令牌 | ||||
|      * | ||||
|      * @param claims 数据声明 | ||||
|      * @return 令牌 | ||||
|      */ | ||||
|     private String createToken(Map<String, Object> claims) { | ||||
|         String token = Jwts.builder() | ||||
|                 .setClaims(claims) | ||||
|                 .signWith(SignatureAlgorithm.HS512, tokenProperties.getSecret()).compact(); | ||||
|         return token; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 从令牌中获取数据声明 | ||||
|      * | ||||
|      * @param token 令牌 | ||||
|      * @return 数据声明 | ||||
|      */ | ||||
|     private Claims parseToken(String token) { | ||||
|         return Jwts.parser() | ||||
|                 .setSigningKey(tokenProperties.getSecret()) | ||||
|                 .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(tokenProperties.getHeader()); | ||||
|         if (Validator.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX)) { | ||||
|             token = token.replace(Constants.TOKEN_PREFIX, ""); | ||||
|         } | ||||
|         return token; | ||||
|     } | ||||
|  | ||||
|     private String getTokenKey(String uuid) { | ||||
|         return Constants.LOGIN_TOKEN_KEY + uuid; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,60 @@ | ||||
| package com.ruoyi.framework.web.service; | ||||
|  | ||||
| import cn.hutool.core.lang.Validator; | ||||
| import com.ruoyi.common.core.domain.entity.SysUser; | ||||
| import com.ruoyi.common.core.domain.model.LoginUser; | ||||
| import com.ruoyi.common.enums.UserStatus; | ||||
| import com.ruoyi.common.exception.BaseException; | ||||
| import com.ruoyi.system.service.ISysUserService; | ||||
| 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; | ||||
|  | ||||
| /** | ||||
|  * 用户验证处理 | ||||
|  * | ||||
|  * @author ruoyi | ||||
|  */ | ||||
| @Service | ||||
| public class UserDetailsServiceImpl implements UserDetailsService | ||||
| { | ||||
|     private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class); | ||||
|  | ||||
|     @Autowired | ||||
|     private ISysUserService userService; | ||||
|  | ||||
|     @Autowired | ||||
|     private SysPermissionService permissionService; | ||||
|  | ||||
|     @Override | ||||
|     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException | ||||
|     { | ||||
|         SysUser user = userService.selectUserByUserName(username); | ||||
|         if (Validator.isNull(user)) | ||||
|         { | ||||
|             log.info("登录用户:{} 不存在.", username); | ||||
|             throw new UsernameNotFoundException("登录用户:" + username + " 不存在"); | ||||
|         } | ||||
|         else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) | ||||
|         { | ||||
|             log.info("登录用户:{} 已被删除.", username); | ||||
|             throw new BaseException("对不起,您的账号:" + username + " 已被删除"); | ||||
|         } | ||||
|         else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) | ||||
|         { | ||||
|             log.info("登录用户:{} 已被停用.", username); | ||||
|             throw new BaseException("对不起,您的账号:" + username + " 已停用"); | ||||
|         } | ||||
|  | ||||
|         return createLoginUser(user); | ||||
|     } | ||||
|  | ||||
|     public UserDetails createLoginUser(SysUser user) | ||||
|     { | ||||
|         return new LoginUser(user, permissionService.getMenuPermission(user)); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										16
									
								
								ruoyi-framework/src/main/resources/rebel.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								ruoyi-framework/src/main/resources/rebel.xml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
|  | ||||
| <!-- | ||||
|   This is the JRebel configuration file. It maps the running application to your IDE workspace, enabling JRebel reloading for this project. | ||||
|   Refer to https://manuals.jrebel.com/jrebel/standalone/config.html for more information. | ||||
| --> | ||||
| <application generated-by="intellij" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.zeroturnaround.com" xsi:schemaLocation="http://www.zeroturnaround.com http://update.zeroturnaround.com/jrebel/rebel-2_3.xsd"> | ||||
|  | ||||
| 	<id>ruoyi-framework</id> | ||||
|  | ||||
| 	<classpath> | ||||
| 		<dir name="E:/intellijWork/old-tress-sys/ruoyi-framework/target/classes"> | ||||
| 		</dir> | ||||
| 	</classpath> | ||||
|  | ||||
| </application> | ||||
		Reference in New Issue
	
	Block a user
	 zt
					zt