mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-11-04 12:18:42 +08:00 
			
		
		
		
	完成主要在线 session 的功能
This commit is contained in:
		@@ -0,0 +1,22 @@
 | 
			
		||||
package com.ruoyi.web.controller.tool;
 | 
			
		||||
 | 
			
		||||
import org.springframework.security.access.prepost.PreAuthorize;
 | 
			
		||||
import org.springframework.stereotype.Controller;
 | 
			
		||||
import org.springframework.web.bind.annotation.GetMapping;
 | 
			
		||||
import org.springframework.web.bind.annotation.RequestMapping;
 | 
			
		||||
import com.ruoyi.common.core.controller.BaseController;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * swagger 接口
 | 
			
		||||
 *
 | 
			
		||||
 * @author ruoyi
 | 
			
		||||
 */
 | 
			
		||||
@Controller
 | 
			
		||||
@RequestMapping("/tool/swagger")
 | 
			
		||||
public class SwaggerController extends BaseController {
 | 
			
		||||
    @PreAuthorize("@ss.hasPermi('tool:swagger:view')")
 | 
			
		||||
    @GetMapping()
 | 
			
		||||
    public String index() {
 | 
			
		||||
        return redirect("/swagger-ui.html");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,145 @@
 | 
			
		||||
package com.ruoyi.framework.aspectj;
 | 
			
		||||
 | 
			
		||||
import java.lang.reflect.Method;
 | 
			
		||||
 | 
			
		||||
import org.aspectj.lang.JoinPoint;
 | 
			
		||||
import org.aspectj.lang.Signature;
 | 
			
		||||
import org.aspectj.lang.annotation.Aspect;
 | 
			
		||||
import org.aspectj.lang.annotation.Before;
 | 
			
		||||
import org.aspectj.lang.annotation.Pointcut;
 | 
			
		||||
import org.aspectj.lang.reflect.MethodSignature;
 | 
			
		||||
import org.springframework.stereotype.Component;
 | 
			
		||||
import com.ruoyi.common.annotation.DataScope;
 | 
			
		||||
import com.ruoyi.common.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.StringUtils;
 | 
			
		||||
import com.ruoyi.common.utils.spring.SpringUtils;
 | 
			
		||||
import com.ruoyi.framework.web.service.TokenService;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 数据过滤处理
 | 
			
		||||
 *
 | 
			
		||||
 * @author ruoyi
 | 
			
		||||
 */
 | 
			
		||||
@Aspect
 | 
			
		||||
@Component
 | 
			
		||||
public class DataScopeAspect {
 | 
			
		||||
    /**
 | 
			
		||||
     * 全部数据权限
 | 
			
		||||
     */
 | 
			
		||||
    public static final String DATA_SCOPE_ALL = "1";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 自定数据权限
 | 
			
		||||
     */
 | 
			
		||||
    public static final String DATA_SCOPE_CUSTOM = "2";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 部门数据权限
 | 
			
		||||
     */
 | 
			
		||||
    public static final String DATA_SCOPE_DEPT = "3";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 部门及以下数据权限
 | 
			
		||||
     */
 | 
			
		||||
    public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 仅本人数据权限
 | 
			
		||||
     */
 | 
			
		||||
    public static final String DATA_SCOPE_SELF = "5";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 数据权限过滤关键字
 | 
			
		||||
     */
 | 
			
		||||
    public static final String DATA_SCOPE = "dataScope";
 | 
			
		||||
 | 
			
		||||
    // 配置织入点
 | 
			
		||||
    @Pointcut("@annotation(com.ruoyi.common.annotation.DataScope)")
 | 
			
		||||
    public void dataScopePointCut() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Before("dataScopePointCut()")
 | 
			
		||||
    public void doBefore(JoinPoint point) throws Throwable {
 | 
			
		||||
        handleDataScope(point);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected void handleDataScope(final JoinPoint joinPoint) {
 | 
			
		||||
        // 获得注解
 | 
			
		||||
        DataScope controllerDataScope = getAnnotationLog(joinPoint);
 | 
			
		||||
        if (controllerDataScope == null) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        // 获取当前的用户
 | 
			
		||||
        LoginUser loginUser = SpringUtils.getBean(TokenService.class).getLoginUser(ServletUtils.getRequest());
 | 
			
		||||
        if (StringUtils.isNotNull(loginUser)) {
 | 
			
		||||
            SysUser currentUser = loginUser.getUser();
 | 
			
		||||
            // 如果是超级管理员,则不过滤数据
 | 
			
		||||
            if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin()) {
 | 
			
		||||
                dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),
 | 
			
		||||
                        controllerDataScope.userAlias());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 数据范围过滤
 | 
			
		||||
     *
 | 
			
		||||
     * @param joinPoint 切点
 | 
			
		||||
     * @param user      用户
 | 
			
		||||
     * @param userAlias 别名
 | 
			
		||||
     */
 | 
			
		||||
    public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias) {
 | 
			
		||||
        StringBuilder sqlString = new StringBuilder();
 | 
			
		||||
 | 
			
		||||
        for (SysRole role : user.getRoles()) {
 | 
			
		||||
            String dataScope = role.getDataScope();
 | 
			
		||||
            if (DATA_SCOPE_ALL.equals(dataScope)) {
 | 
			
		||||
                sqlString = new StringBuilder();
 | 
			
		||||
                break;
 | 
			
		||||
            } else if (DATA_SCOPE_CUSTOM.equals(dataScope)) {
 | 
			
		||||
                sqlString.append(StringUtils.format(
 | 
			
		||||
                        " OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias,
 | 
			
		||||
                        role.getRoleId()));
 | 
			
		||||
            } else if (DATA_SCOPE_DEPT.equals(dataScope)) {
 | 
			
		||||
                sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));
 | 
			
		||||
            } else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) {
 | 
			
		||||
                sqlString.append(StringUtils.format(
 | 
			
		||||
                        " OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )",
 | 
			
		||||
                        deptAlias, user.getDeptId(), user.getDeptId()));
 | 
			
		||||
            } else if (DATA_SCOPE_SELF.equals(dataScope)) {
 | 
			
		||||
                if (StringUtils.isNotBlank(userAlias)) {
 | 
			
		||||
                    sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));
 | 
			
		||||
                } else {
 | 
			
		||||
                    // 数据权限为仅本人且没有userAlias别名不查询任何数据
 | 
			
		||||
                    sqlString.append(" OR 1=0 ");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (StringUtils.isNotBlank(sqlString.toString())) {
 | 
			
		||||
            Object params = joinPoint.getArgs()[0];
 | 
			
		||||
            if (StringUtils.isNotNull(params) && params instanceof BaseEntity) {
 | 
			
		||||
                BaseEntity baseEntity = (BaseEntity) params;
 | 
			
		||||
                baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 是否存在注解,如果存在就获取
 | 
			
		||||
     */
 | 
			
		||||
    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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,64 @@
 | 
			
		||||
package com.ruoyi.framework.aspectj;
 | 
			
		||||
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
 | 
			
		||||
import org.aspectj.lang.ProceedingJoinPoint;
 | 
			
		||||
import org.aspectj.lang.annotation.Around;
 | 
			
		||||
import org.aspectj.lang.annotation.Aspect;
 | 
			
		||||
import org.aspectj.lang.annotation.Pointcut;
 | 
			
		||||
import org.aspectj.lang.reflect.MethodSignature;
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
import org.springframework.core.annotation.AnnotationUtils;
 | 
			
		||||
import org.springframework.core.annotation.Order;
 | 
			
		||||
import org.springframework.stereotype.Component;
 | 
			
		||||
import com.ruoyi.common.annotation.DataSource;
 | 
			
		||||
import com.ruoyi.common.utils.StringUtils;
 | 
			
		||||
import com.ruoyi.framework.datasource.DynamicDataSourceContextHolder;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 多数据源处理
 | 
			
		||||
 *
 | 
			
		||||
 * @author ruoyi
 | 
			
		||||
 */
 | 
			
		||||
@Aspect
 | 
			
		||||
@Order(1)
 | 
			
		||||
@Component
 | 
			
		||||
public class DataSourceAspect {
 | 
			
		||||
    protected Logger logger = LoggerFactory.getLogger(getClass());
 | 
			
		||||
 | 
			
		||||
    @Pointcut("@annotation(com.ruoyi.common.annotation.DataSource)"
 | 
			
		||||
            + "|| @within(com.ruoyi.common.annotation.DataSource)")
 | 
			
		||||
    public void dsPointCut() {
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Around("dsPointCut()")
 | 
			
		||||
    public Object around(ProceedingJoinPoint point) throws Throwable {
 | 
			
		||||
        DataSource dataSource = getDataSource(point);
 | 
			
		||||
 | 
			
		||||
        if (StringUtils.isNotNull(dataSource)) {
 | 
			
		||||
            DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            return point.proceed();
 | 
			
		||||
        } finally {
 | 
			
		||||
            // 销毁数据源 在执行方法之后
 | 
			
		||||
            DynamicDataSourceContextHolder.clearDataSourceType();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取需要切换的数据源
 | 
			
		||||
     */
 | 
			
		||||
    public DataSource getDataSource(ProceedingJoinPoint point) {
 | 
			
		||||
        MethodSignature signature = (MethodSignature) point.getSignature();
 | 
			
		||||
        DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
 | 
			
		||||
        if (Objects.nonNull(dataSource)) {
 | 
			
		||||
            return dataSource;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,19 +1,19 @@
 | 
			
		||||
package com.ruoyi.common.enums;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 数据源
 | 
			
		||||
 * 
 | 
			
		||||
 * @author ruoyi
 | 
			
		||||
 */
 | 
			
		||||
public enum DataSourceType
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * 主库
 | 
			
		||||
     */
 | 
			
		||||
    MASTER,
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 从库
 | 
			
		||||
     */
 | 
			
		||||
    SLAVE
 | 
			
		||||
}
 | 
			
		||||
package com.ruoyi.common.enums;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 数据源
 | 
			
		||||
 * 
 | 
			
		||||
 * @author ruoyi
 | 
			
		||||
 */
 | 
			
		||||
public enum DataSourceType
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * 主库
 | 
			
		||||
     */
 | 
			
		||||
    MASTER,
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 从库
 | 
			
		||||
     */
 | 
			
		||||
    SLAVE
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,24 @@
 | 
			
		||||
package com.ruoyi.framework.datasource;
 | 
			
		||||
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import javax.sql.DataSource;
 | 
			
		||||
 | 
			
		||||
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 动态数据源
 | 
			
		||||
 *
 | 
			
		||||
 * @author ruoyi
 | 
			
		||||
 */
 | 
			
		||||
public class DynamicDataSource extends AbstractRoutingDataSource {
 | 
			
		||||
    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
 | 
			
		||||
        super.setDefaultTargetDataSource(defaultTargetDataSource);
 | 
			
		||||
        super.setTargetDataSources(targetDataSources);
 | 
			
		||||
        super.afterPropertiesSet();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected Object determineCurrentLookupKey() {
 | 
			
		||||
        return DynamicDataSourceContextHolder.getDataSourceType();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,41 @@
 | 
			
		||||
package com.ruoyi.framework.datasource;
 | 
			
		||||
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 数据源切换处理
 | 
			
		||||
 *
 | 
			
		||||
 * @author ruoyi
 | 
			
		||||
 */
 | 
			
		||||
public class DynamicDataSourceContextHolder {
 | 
			
		||||
    public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
 | 
			
		||||
     * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
 | 
			
		||||
     */
 | 
			
		||||
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 设置数据源的变量
 | 
			
		||||
     */
 | 
			
		||||
    public static void setDataSourceType(String dsType) {
 | 
			
		||||
        log.info("切换到{}数据源", dsType);
 | 
			
		||||
        CONTEXT_HOLDER.set(dsType);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获得数据源的变量
 | 
			
		||||
     */
 | 
			
		||||
    public static String getDataSourceType() {
 | 
			
		||||
        return CONTEXT_HOLDER.get();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 清空数据源变量
 | 
			
		||||
     */
 | 
			
		||||
    public static void clearDataSourceType() {
 | 
			
		||||
        CONTEXT_HOLDER.remove();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,116 @@
 | 
			
		||||
package com.ruoyi.framework.config;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import javax.servlet.Filter;
 | 
			
		||||
import javax.servlet.FilterChain;
 | 
			
		||||
import javax.servlet.ServletException;
 | 
			
		||||
import javax.servlet.ServletRequest;
 | 
			
		||||
import javax.servlet.ServletResponse;
 | 
			
		||||
import javax.sql.DataSource;
 | 
			
		||||
 | 
			
		||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 | 
			
		||||
import org.springframework.boot.context.properties.ConfigurationProperties;
 | 
			
		||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
 | 
			
		||||
import org.springframework.context.annotation.Bean;
 | 
			
		||||
import org.springframework.context.annotation.Configuration;
 | 
			
		||||
import org.springframework.context.annotation.Primary;
 | 
			
		||||
import com.alibaba.druid.pool.DruidDataSource;
 | 
			
		||||
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
 | 
			
		||||
import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
 | 
			
		||||
import com.alibaba.druid.util.Utils;
 | 
			
		||||
import com.ruoyi.common.enums.DataSourceType;
 | 
			
		||||
import com.ruoyi.common.utils.spring.SpringUtils;
 | 
			
		||||
import com.ruoyi.framework.config.properties.DruidProperties;
 | 
			
		||||
import com.ruoyi.framework.datasource.DynamicDataSource;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * druid 配置多数据源
 | 
			
		||||
 *
 | 
			
		||||
 * @author ruoyi
 | 
			
		||||
 */
 | 
			
		||||
@Configuration
 | 
			
		||||
public class DruidConfig {
 | 
			
		||||
    @Bean
 | 
			
		||||
    @ConfigurationProperties("spring.datasource.druid.master")
 | 
			
		||||
    public DataSource masterDataSource(DruidProperties druidProperties) {
 | 
			
		||||
        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
 | 
			
		||||
        return druidProperties.dataSource(dataSource);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Bean
 | 
			
		||||
    @ConfigurationProperties("spring.datasource.druid.slave")
 | 
			
		||||
    @ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
 | 
			
		||||
    public DataSource slaveDataSource(DruidProperties druidProperties) {
 | 
			
		||||
        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
 | 
			
		||||
        return druidProperties.dataSource(dataSource);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Bean(name = "dynamicDataSource")
 | 
			
		||||
    @Primary
 | 
			
		||||
    public DynamicDataSource dataSource(DataSource masterDataSource) {
 | 
			
		||||
        Map<Object, Object> targetDataSources = new HashMap<>();
 | 
			
		||||
        targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
 | 
			
		||||
        setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource");
 | 
			
		||||
        return new DynamicDataSource(masterDataSource, targetDataSources);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 设置数据源
 | 
			
		||||
     *
 | 
			
		||||
     * @param targetDataSources 备选数据源集合
 | 
			
		||||
     * @param sourceName        数据源名称
 | 
			
		||||
     * @param beanName          bean名称
 | 
			
		||||
     */
 | 
			
		||||
    public void setDataSource(Map<Object, Object> targetDataSources, String sourceName, String beanName) {
 | 
			
		||||
        try {
 | 
			
		||||
            DataSource dataSource = SpringUtils.getBean(beanName);
 | 
			
		||||
            targetDataSources.put(sourceName, dataSource);
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 去除监控页面底部的广告
 | 
			
		||||
     */
 | 
			
		||||
    @SuppressWarnings({"rawtypes", "unchecked"})
 | 
			
		||||
    @Bean
 | 
			
		||||
    @ConditionalOnProperty(name = "spring.datasource.druid.statViewServlet.enabled", havingValue = "true")
 | 
			
		||||
    public FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties) {
 | 
			
		||||
        // 获取web监控页面的参数
 | 
			
		||||
        DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
 | 
			
		||||
        // 提取common.js的配置路径
 | 
			
		||||
        String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
 | 
			
		||||
        String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");
 | 
			
		||||
        final String filePath = "support/http/resources/js/common.js";
 | 
			
		||||
        // 创建filter进行过滤
 | 
			
		||||
        Filter filter = new Filter() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public void init(javax.servlet.FilterConfig filterConfig) throws ServletException {
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
 | 
			
		||||
                    throws IOException, ServletException {
 | 
			
		||||
                chain.doFilter(request, response);
 | 
			
		||||
                // 重置缓冲区,响应头不会被重置
 | 
			
		||||
                response.resetBuffer();
 | 
			
		||||
                // 获取common.js
 | 
			
		||||
                String text = Utils.readFromResource(filePath);
 | 
			
		||||
                // 正则替换banner, 除去底部的广告信息
 | 
			
		||||
                text = text.replaceAll("<a.*?banner\"></a><br/>", "");
 | 
			
		||||
                text = text.replaceAll("powered.*?shrek.wang</a>", "");
 | 
			
		||||
                response.getWriter().write(text);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public void destroy() {
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
 | 
			
		||||
        registrationBean.setFilter(filter);
 | 
			
		||||
        registrationBean.addUrlPatterns(commonJsPattern);
 | 
			
		||||
        return registrationBean;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,58 @@
 | 
			
		||||
package com.ruoyi.framework.config;
 | 
			
		||||
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import javax.servlet.DispatcherType;
 | 
			
		||||
 | 
			
		||||
import org.springframework.beans.factory.annotation.Value;
 | 
			
		||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
 | 
			
		||||
import org.springframework.context.annotation.Bean;
 | 
			
		||||
import org.springframework.context.annotation.Configuration;
 | 
			
		||||
import com.ruoyi.common.filter.RepeatableFilter;
 | 
			
		||||
import com.ruoyi.common.filter.XssFilter;
 | 
			
		||||
import com.ruoyi.common.utils.StringUtils;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Filter配置
 | 
			
		||||
 *
 | 
			
		||||
 * @author ruoyi
 | 
			
		||||
 */
 | 
			
		||||
@Configuration
 | 
			
		||||
public class FilterConfig {
 | 
			
		||||
    @Value("${xss.enabled}")
 | 
			
		||||
    private String enabled;
 | 
			
		||||
 | 
			
		||||
    @Value("${xss.excludes}")
 | 
			
		||||
    private String excludes;
 | 
			
		||||
 | 
			
		||||
    @Value("${xss.urlPatterns}")
 | 
			
		||||
    private String urlPatterns;
 | 
			
		||||
 | 
			
		||||
    @SuppressWarnings({"rawtypes", "unchecked"})
 | 
			
		||||
    @Bean
 | 
			
		||||
    public FilterRegistrationBean xssFilterRegistration() {
 | 
			
		||||
        FilterRegistrationBean registration = new FilterRegistrationBean();
 | 
			
		||||
        registration.setDispatcherTypes(DispatcherType.REQUEST);
 | 
			
		||||
        registration.setFilter(new XssFilter());
 | 
			
		||||
        registration.addUrlPatterns(StringUtils.split(urlPatterns, ","));
 | 
			
		||||
        registration.setName("xssFilter");
 | 
			
		||||
        registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
 | 
			
		||||
        Map<String, String> initParameters = new HashMap<String, String>();
 | 
			
		||||
        initParameters.put("excludes", excludes);
 | 
			
		||||
        initParameters.put("enabled", enabled);
 | 
			
		||||
        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,44 @@
 | 
			
		||||
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() + "/");
 | 
			
		||||
 | 
			
		||||
        /** swagger配置 */
 | 
			
		||||
        registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
 | 
			
		||||
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 自定义拦截规则
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void addInterceptors(InterceptorRegistry registry) {
 | 
			
		||||
        registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,30 @@
 | 
			
		||||
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,58 @@
 | 
			
		||||
package com.ruoyi.framework.config;
 | 
			
		||||
 | 
			
		||||
import java.util.concurrent.ScheduledExecutorService;
 | 
			
		||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
 | 
			
		||||
import java.util.concurrent.ThreadPoolExecutor;
 | 
			
		||||
 | 
			
		||||
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
 | 
			
		||||
import org.springframework.context.annotation.Bean;
 | 
			
		||||
import org.springframework.context.annotation.Configuration;
 | 
			
		||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 | 
			
		||||
import com.ruoyi.common.utils.Threads;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 线程池配置
 | 
			
		||||
 *
 | 
			
		||||
 * @author ruoyi
 | 
			
		||||
 **/
 | 
			
		||||
@Configuration
 | 
			
		||||
public class ThreadPoolConfig {
 | 
			
		||||
    // 核心线程池大小
 | 
			
		||||
    private int corePoolSize = 50;
 | 
			
		||||
 | 
			
		||||
    // 最大可创建的线程数
 | 
			
		||||
    private int maxPoolSize = 200;
 | 
			
		||||
 | 
			
		||||
    // 队列最大长度
 | 
			
		||||
    private int queueCapacity = 1000;
 | 
			
		||||
 | 
			
		||||
    // 线程池维护线程所允许的空闲时间
 | 
			
		||||
    private int keepAliveSeconds = 300;
 | 
			
		||||
 | 
			
		||||
    @Bean(name = "threadPoolTaskExecutor")
 | 
			
		||||
    public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
 | 
			
		||||
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
 | 
			
		||||
        executor.setMaxPoolSize(maxPoolSize);
 | 
			
		||||
        executor.setCorePoolSize(corePoolSize);
 | 
			
		||||
        executor.setQueueCapacity(queueCapacity);
 | 
			
		||||
        executor.setKeepAliveSeconds(keepAliveSeconds);
 | 
			
		||||
        // 线程池对拒绝任务(无线程可用)的处理策略
 | 
			
		||||
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
 | 
			
		||||
        return executor;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 执行周期性或定时任务
 | 
			
		||||
     */
 | 
			
		||||
    @Bean(name = "scheduledExecutorService")
 | 
			
		||||
    protected ScheduledExecutorService scheduledExecutorService() {
 | 
			
		||||
        return new ScheduledThreadPoolExecutor(corePoolSize,
 | 
			
		||||
                new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build()) {
 | 
			
		||||
            @Override
 | 
			
		||||
            protected void afterExecute(Runnable r, Throwable t) {
 | 
			
		||||
                super.afterExecute(r, t);
 | 
			
		||||
                Threads.printException(r, t);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,75 @@
 | 
			
		||||
package com.ruoyi.framework.config.properties;
 | 
			
		||||
 | 
			
		||||
import org.springframework.beans.factory.annotation.Value;
 | 
			
		||||
import org.springframework.context.annotation.Configuration;
 | 
			
		||||
import com.alibaba.druid.pool.DruidDataSource;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * druid 配置属性
 | 
			
		||||
 *
 | 
			
		||||
 * @author ruoyi
 | 
			
		||||
 */
 | 
			
		||||
@Configuration
 | 
			
		||||
public class DruidProperties {
 | 
			
		||||
    @Value("${spring.datasource.druid.initialSize}")
 | 
			
		||||
    private int initialSize;
 | 
			
		||||
 | 
			
		||||
    @Value("${spring.datasource.druid.minIdle}")
 | 
			
		||||
    private int minIdle;
 | 
			
		||||
 | 
			
		||||
    @Value("${spring.datasource.druid.maxActive}")
 | 
			
		||||
    private int maxActive;
 | 
			
		||||
 | 
			
		||||
    @Value("${spring.datasource.druid.maxWait}")
 | 
			
		||||
    private int maxWait;
 | 
			
		||||
 | 
			
		||||
    @Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}")
 | 
			
		||||
    private int timeBetweenEvictionRunsMillis;
 | 
			
		||||
 | 
			
		||||
    @Value("${spring.datasource.druid.minEvictableIdleTimeMillis}")
 | 
			
		||||
    private int minEvictableIdleTimeMillis;
 | 
			
		||||
 | 
			
		||||
    @Value("${spring.datasource.druid.maxEvictableIdleTimeMillis}")
 | 
			
		||||
    private int maxEvictableIdleTimeMillis;
 | 
			
		||||
 | 
			
		||||
    @Value("${spring.datasource.druid.validationQuery}")
 | 
			
		||||
    private String validationQuery;
 | 
			
		||||
 | 
			
		||||
    @Value("${spring.datasource.druid.testWhileIdle}")
 | 
			
		||||
    private boolean testWhileIdle;
 | 
			
		||||
 | 
			
		||||
    @Value("${spring.datasource.druid.testOnBorrow}")
 | 
			
		||||
    private boolean testOnBorrow;
 | 
			
		||||
 | 
			
		||||
    @Value("${spring.datasource.druid.testOnReturn}")
 | 
			
		||||
    private boolean testOnReturn;
 | 
			
		||||
 | 
			
		||||
    public DruidDataSource dataSource(DruidDataSource datasource) {
 | 
			
		||||
        /** 配置初始化大小、最小、最大 */
 | 
			
		||||
        datasource.setInitialSize(initialSize);
 | 
			
		||||
        datasource.setMaxActive(maxActive);
 | 
			
		||||
        datasource.setMinIdle(minIdle);
 | 
			
		||||
 | 
			
		||||
        /** 配置获取连接等待超时的时间 */
 | 
			
		||||
        datasource.setMaxWait(maxWait);
 | 
			
		||||
 | 
			
		||||
        /** 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */
 | 
			
		||||
        datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
 | 
			
		||||
 | 
			
		||||
        /** 配置一个连接在池中最小、最大生存的时间,单位是毫秒 */
 | 
			
		||||
        datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
 | 
			
		||||
        datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis);
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
 | 
			
		||||
         */
 | 
			
		||||
        datasource.setValidationQuery(validationQuery);
 | 
			
		||||
        /** 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 */
 | 
			
		||||
        datasource.setTestWhileIdle(testWhileIdle);
 | 
			
		||||
        /** 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
 | 
			
		||||
        datasource.setTestOnBorrow(testOnBorrow);
 | 
			
		||||
        /** 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
 | 
			
		||||
        datasource.setTestOnReturn(testOnReturn);
 | 
			
		||||
        return datasource;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,21 +1,21 @@
 | 
			
		||||
package com.ruoyi.common.annotation;
 | 
			
		||||
 | 
			
		||||
import java.lang.annotation.Documented;
 | 
			
		||||
import java.lang.annotation.ElementType;
 | 
			
		||||
import java.lang.annotation.Inherited;
 | 
			
		||||
import java.lang.annotation.Retention;
 | 
			
		||||
import java.lang.annotation.RetentionPolicy;
 | 
			
		||||
import java.lang.annotation.Target;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 自定义注解防止表单重复提交
 | 
			
		||||
 *
 | 
			
		||||
 * @author ruoyi
 | 
			
		||||
 */
 | 
			
		||||
@Inherited
 | 
			
		||||
@Target(ElementType.METHOD)
 | 
			
		||||
@Retention(RetentionPolicy.RUNTIME)
 | 
			
		||||
@Documented
 | 
			
		||||
public @interface RepeatSubmit {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
package com.ruoyi.common.annotation;
 | 
			
		||||
 | 
			
		||||
import java.lang.annotation.Documented;
 | 
			
		||||
import java.lang.annotation.ElementType;
 | 
			
		||||
import java.lang.annotation.Inherited;
 | 
			
		||||
import java.lang.annotation.Retention;
 | 
			
		||||
import java.lang.annotation.RetentionPolicy;
 | 
			
		||||
import java.lang.annotation.Target;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 自定义注解防止表单重复提交
 | 
			
		||||
 *
 | 
			
		||||
 * @author ruoyi
 | 
			
		||||
 */
 | 
			
		||||
@Inherited
 | 
			
		||||
@Target(ElementType.METHOD)
 | 
			
		||||
@Retention(RetentionPolicy.RUNTIME)
 | 
			
		||||
@Documented
 | 
			
		||||
public @interface RepeatSubmit {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,49 @@
 | 
			
		||||
package com.ruoyi.framework.interceptor;
 | 
			
		||||
 | 
			
		||||
import java.lang.reflect.Method;
 | 
			
		||||
import javax.servlet.http.HttpServletRequest;
 | 
			
		||||
import javax.servlet.http.HttpServletResponse;
 | 
			
		||||
 | 
			
		||||
import org.springframework.stereotype.Component;
 | 
			
		||||
import org.springframework.web.method.HandlerMethod;
 | 
			
		||||
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
 | 
			
		||||
import com.alibaba.fastjson.JSONObject;
 | 
			
		||||
import com.ruoyi.common.annotation.RepeatSubmit;
 | 
			
		||||
import com.ruoyi.common.core.domain.AjaxResult;
 | 
			
		||||
import com.ruoyi.common.utils.ServletUtils;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 防止重复提交拦截器
 | 
			
		||||
 *
 | 
			
		||||
 * @author ruoyi
 | 
			
		||||
 */
 | 
			
		||||
@Component
 | 
			
		||||
public abstract class RepeatSubmitInterceptor extends HandlerInterceptorAdapter {
 | 
			
		||||
    @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, JSONObject.toJSONString(ajaxResult));
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return true;
 | 
			
		||||
        } else {
 | 
			
		||||
            return super.preHandle(request, response, handler);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 验证是否重复提交由子类实现具体的防重复提交的规则
 | 
			
		||||
     *
 | 
			
		||||
     * @param request
 | 
			
		||||
     * @return
 | 
			
		||||
     * @throws Exception
 | 
			
		||||
     */
 | 
			
		||||
    public abstract boolean isRepeatSubmit(HttpServletRequest request);
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,114 @@
 | 
			
		||||
package com.ruoyi.framework.interceptor.impl;
 | 
			
		||||
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
import javax.servlet.http.HttpServletRequest;
 | 
			
		||||
 | 
			
		||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Value;
 | 
			
		||||
import org.springframework.stereotype.Component;
 | 
			
		||||
import com.alibaba.fastjson.JSONObject;
 | 
			
		||||
import com.ruoyi.common.constant.Constants;
 | 
			
		||||
import com.ruoyi.common.core.redis.RedisCache;
 | 
			
		||||
import com.ruoyi.common.filter.RepeatedlyRequestWrapper;
 | 
			
		||||
import com.ruoyi.common.utils.StringUtils;
 | 
			
		||||
import com.ruoyi.common.utils.http.HttpHelper;
 | 
			
		||||
import com.ruoyi.framework.interceptor.RepeatSubmitInterceptor;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 判断请求url和数据是否和上一次相同,
 | 
			
		||||
 * 如果和上次相同,则是重复提交表单。 有效时间为10秒内。
 | 
			
		||||
 *
 | 
			
		||||
 * @author ruoyi
 | 
			
		||||
 */
 | 
			
		||||
@Component
 | 
			
		||||
public class SameUrlDataInterceptor extends RepeatSubmitInterceptor {
 | 
			
		||||
    public final String REPEAT_PARAMS = "repeatParams";
 | 
			
		||||
 | 
			
		||||
    public final String REPEAT_TIME = "repeatTime";
 | 
			
		||||
 | 
			
		||||
    // 令牌自定义标识
 | 
			
		||||
    @Value("${token.header}")
 | 
			
		||||
    private String header;
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private RedisCache redisCache;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 间隔时间,单位:秒 默认10秒
 | 
			
		||||
     * <p>
 | 
			
		||||
     * 两次相同参数的请求,如果间隔时间大于该参数,系统不会认定为重复提交的数据
 | 
			
		||||
     */
 | 
			
		||||
    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;
 | 
			
		||||
            nowParams = HttpHelper.getBodyString(repeatedlyRequest);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // body参数为空,获取Parameter的数据
 | 
			
		||||
        if (StringUtils.isEmpty(nowParams)) {
 | 
			
		||||
            nowParams = JSONObject.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 (StringUtils.isEmpty(submitKey)) {
 | 
			
		||||
            submitKey = url;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 唯一标识(指定key + 消息头)
 | 
			
		||||
        String cache_repeat_key = Constants.REPEAT_SUBMIT_KEY + submitKey;
 | 
			
		||||
 | 
			
		||||
        Object sessionObj = redisCache.getCacheObject(cache_repeat_key);
 | 
			
		||||
        if (sessionObj != null) {
 | 
			
		||||
            Map<String, Object> sessionMap = (Map<String, Object>) sessionObj;
 | 
			
		||||
            if (sessionMap.containsKey(url)) {
 | 
			
		||||
                Map<String, Object> preDataMap = (Map<String, Object>) sessionMap.get(url);
 | 
			
		||||
                if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap)) {
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        Map<String, Object> cacheMap = new HashMap<String, Object>();
 | 
			
		||||
        cacheMap.put(url, nowDataMap);
 | 
			
		||||
        redisCache.setCacheObject(cache_repeat_key, 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);
 | 
			
		||||
        if ((time1 - time2) < (this.intervalTime * 1000)) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										57
									
								
								ruoyi-common/src/main/resources/application-druid.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								ruoyi-common/src/main/resources/application-druid.yml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,57 @@
 | 
			
		||||
# 数据源配置
 | 
			
		||||
spring:
 | 
			
		||||
    datasource:
 | 
			
		||||
        type: com.alibaba.druid.pool.DruidDataSource
 | 
			
		||||
        driverClassName: com.mysql.cj.jdbc.Driver
 | 
			
		||||
        druid:
 | 
			
		||||
            # 主库数据源
 | 
			
		||||
            master:
 | 
			
		||||
                url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
 | 
			
		||||
                username: root
 | 
			
		||||
                password: password
 | 
			
		||||
            # 从库数据源
 | 
			
		||||
            slave:
 | 
			
		||||
                # 从数据源开关/默认关闭
 | 
			
		||||
                enabled: false
 | 
			
		||||
                url: 
 | 
			
		||||
                username: 
 | 
			
		||||
                password: 
 | 
			
		||||
            # 初始连接数
 | 
			
		||||
            initialSize: 5
 | 
			
		||||
            # 最小连接池数量
 | 
			
		||||
            minIdle: 10
 | 
			
		||||
            # 最大连接池数量
 | 
			
		||||
            maxActive: 20
 | 
			
		||||
            # 配置获取连接等待超时的时间
 | 
			
		||||
            maxWait: 60000
 | 
			
		||||
            # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
 | 
			
		||||
            timeBetweenEvictionRunsMillis: 60000
 | 
			
		||||
            # 配置一个连接在池中最小生存的时间,单位是毫秒
 | 
			
		||||
            minEvictableIdleTimeMillis: 300000
 | 
			
		||||
            # 配置一个连接在池中最大生存的时间,单位是毫秒
 | 
			
		||||
            maxEvictableIdleTimeMillis: 900000
 | 
			
		||||
            # 配置检测连接是否有效
 | 
			
		||||
            validationQuery: SELECT 1 FROM DUAL
 | 
			
		||||
            testWhileIdle: true
 | 
			
		||||
            testOnBorrow: false
 | 
			
		||||
            testOnReturn: false
 | 
			
		||||
            webStatFilter: 
 | 
			
		||||
                enabled: true
 | 
			
		||||
            statViewServlet:
 | 
			
		||||
                enabled: true
 | 
			
		||||
                # 设置白名单,不填则允许所有访问
 | 
			
		||||
                allow:
 | 
			
		||||
                url-pattern: /druid/*
 | 
			
		||||
                # 控制台管理用户名和密码
 | 
			
		||||
                login-username: 
 | 
			
		||||
                login-password: 
 | 
			
		||||
            filter:
 | 
			
		||||
                stat:
 | 
			
		||||
                    enabled: true
 | 
			
		||||
                    # 慢SQL记录
 | 
			
		||||
                    log-slow-sql: true
 | 
			
		||||
                    slow-sql-millis: 1000
 | 
			
		||||
                    merge-sql: true
 | 
			
		||||
                wall:
 | 
			
		||||
                    config:
 | 
			
		||||
                        multi-statement-allow: true
 | 
			
		||||
							
								
								
									
										44
									
								
								ruoyi-common/src/main/resources/application.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								ruoyi-common/src/main/resources/application.yml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
# 项目相关配置
 | 
			
		||||
ruoyi:
 | 
			
		||||
  # 名称
 | 
			
		||||
  name: RuoYi
 | 
			
		||||
  # 版本
 | 
			
		||||
  version: 3.3.0
 | 
			
		||||
  # 版权年份
 | 
			
		||||
  copyrightYear: 2020
 | 
			
		||||
  # 实例演示开关
 | 
			
		||||
  demoEnabled: true
 | 
			
		||||
  # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
 | 
			
		||||
  profile: D:/ruoyi/uploadPath
 | 
			
		||||
  # 获取ip地址开关
 | 
			
		||||
  addressEnabled: false
 | 
			
		||||
 | 
			
		||||
# 开发环境配置
 | 
			
		||||
server:
 | 
			
		||||
  # 服务器的HTTP端口,默认为8080
 | 
			
		||||
  port: 8080
 | 
			
		||||
  servlet:
 | 
			
		||||
    # 应用的访问路径
 | 
			
		||||
    context-path: /
 | 
			
		||||
  tomcat:
 | 
			
		||||
    # tomcat的URI编码
 | 
			
		||||
    uri-encoding: UTF-8
 | 
			
		||||
    # tomcat最大线程数,默认为200
 | 
			
		||||
    max-threads: 800
 | 
			
		||||
    # Tomcat启动初始化的线程数,默认值25
 | 
			
		||||
    min-spare-threads: 30
 | 
			
		||||
 | 
			
		||||
# 日志配置
 | 
			
		||||
logging:
 | 
			
		||||
  level:
 | 
			
		||||
    com.ruoyi: debug
 | 
			
		||||
    org.springframework: warn
 | 
			
		||||
 | 
			
		||||
# 防止XSS攻击
 | 
			
		||||
xss:
 | 
			
		||||
  # 过滤开关
 | 
			
		||||
  enabled: true
 | 
			
		||||
  # 排除链接(多个用逗号分隔)
 | 
			
		||||
  excludes: /system/notice/*
 | 
			
		||||
  # 匹配链接
 | 
			
		||||
  urlPatterns: /system/*,/monitor/*,/tool/*
 | 
			
		||||
							
								
								
									
										36
									
								
								ruoyi-common/src/main/resources/i18n/messages.properties
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								ruoyi-common/src/main/resources/i18n/messages.properties
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
			
		||||
#错误消息
 | 
			
		||||
not.null=* 必须填写
 | 
			
		||||
user.jcaptcha.error=验证码错误
 | 
			
		||||
user.jcaptcha.expire=验证码已失效
 | 
			
		||||
user.not.exists=用户不存在/密码错误
 | 
			
		||||
user.password.not.match=用户不存在/密码错误
 | 
			
		||||
user.password.retry.limit.count=密码输入错误{0}次
 | 
			
		||||
user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定10分钟
 | 
			
		||||
user.password.delete=对不起,您的账号已被删除
 | 
			
		||||
user.blocked=用户已封禁,请联系管理员
 | 
			
		||||
role.blocked=角色已封禁,请联系管理员
 | 
			
		||||
user.logout.success=退出成功
 | 
			
		||||
 | 
			
		||||
length.not.valid=长度必须在{min}到{max}个字符之间
 | 
			
		||||
 | 
			
		||||
user.username.not.valid=* 2到20个汉字、字母、数字或下划线组成,且必须以非数字开头
 | 
			
		||||
user.password.not.valid=* 5-50个字符
 | 
			
		||||
 
 | 
			
		||||
user.email.not.valid=邮箱格式错误
 | 
			
		||||
user.mobile.phone.number.not.valid=手机号格式错误
 | 
			
		||||
user.login.success=登录成功
 | 
			
		||||
user.notfound=请重新登录
 | 
			
		||||
user.forcelogout=管理员强制退出,请重新登录
 | 
			
		||||
user.unknown.error=未知错误,请重新登录
 | 
			
		||||
 | 
			
		||||
##文件上传消息
 | 
			
		||||
upload.exceed.maxSize=上传的文件大小超出限制的文件大小!<br/>允许的文件最大大小是:{0}MB!
 | 
			
		||||
upload.filename.exceed.length=上传的文件名最长{0}个字符
 | 
			
		||||
 | 
			
		||||
##权限
 | 
			
		||||
no.permission=您没有数据的权限,请联系管理员添加权限 [{0}]
 | 
			
		||||
no.create.permission=您没有创建数据的权限,请联系管理员添加权限 [{0}]
 | 
			
		||||
no.update.permission=您没有修改数据的权限,请联系管理员添加权限 [{0}]
 | 
			
		||||
no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}]
 | 
			
		||||
no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}]
 | 
			
		||||
no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}]
 | 
			
		||||
							
								
								
									
										93
									
								
								ruoyi-common/src/main/resources/logback.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								ruoyi-common/src/main/resources/logback.xml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,93 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<configuration>
 | 
			
		||||
    <!-- 日志存放路径 -->
 | 
			
		||||
	<property name="log.path" value="/home/ruoyi/logs" />
 | 
			
		||||
    <!-- 日志输出格式 -->
 | 
			
		||||
	<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
 | 
			
		||||
 | 
			
		||||
	<!-- 控制台输出 -->
 | 
			
		||||
	<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
 | 
			
		||||
		<encoder>
 | 
			
		||||
			<pattern>${log.pattern}</pattern>
 | 
			
		||||
		</encoder>
 | 
			
		||||
	</appender>
 | 
			
		||||
	
 | 
			
		||||
	<!-- 系统日志输出 -->
 | 
			
		||||
	<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
 | 
			
		||||
	    <file>${log.path}/sys-info.log</file>
 | 
			
		||||
        <!-- 循环政策:基于时间创建日志文件 -->
 | 
			
		||||
		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
 | 
			
		||||
            <!-- 日志文件名格式 -->
 | 
			
		||||
			<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
 | 
			
		||||
			<!-- 日志最大的历史 60天 -->
 | 
			
		||||
			<maxHistory>60</maxHistory>
 | 
			
		||||
		</rollingPolicy>
 | 
			
		||||
		<encoder>
 | 
			
		||||
			<pattern>${log.pattern}</pattern>
 | 
			
		||||
		</encoder>
 | 
			
		||||
		<filter class="ch.qos.logback.classic.filter.LevelFilter">
 | 
			
		||||
            <!-- 过滤的级别 -->
 | 
			
		||||
            <level>INFO</level>
 | 
			
		||||
            <!-- 匹配时的操作:接收(记录) -->
 | 
			
		||||
            <onMatch>ACCEPT</onMatch>
 | 
			
		||||
            <!-- 不匹配时的操作:拒绝(不记录) -->
 | 
			
		||||
            <onMismatch>DENY</onMismatch>
 | 
			
		||||
        </filter>
 | 
			
		||||
	</appender>
 | 
			
		||||
	
 | 
			
		||||
	<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
 | 
			
		||||
	    <file>${log.path}/sys-error.log</file>
 | 
			
		||||
        <!-- 循环政策:基于时间创建日志文件 -->
 | 
			
		||||
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
 | 
			
		||||
            <!-- 日志文件名格式 -->
 | 
			
		||||
            <fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
 | 
			
		||||
			<!-- 日志最大的历史 60天 -->
 | 
			
		||||
			<maxHistory>60</maxHistory>
 | 
			
		||||
        </rollingPolicy>
 | 
			
		||||
        <encoder>
 | 
			
		||||
            <pattern>${log.pattern}</pattern>
 | 
			
		||||
        </encoder>
 | 
			
		||||
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
 | 
			
		||||
            <!-- 过滤的级别 -->
 | 
			
		||||
            <level>ERROR</level>
 | 
			
		||||
			<!-- 匹配时的操作:接收(记录) -->
 | 
			
		||||
            <onMatch>ACCEPT</onMatch>
 | 
			
		||||
			<!-- 不匹配时的操作:拒绝(不记录) -->
 | 
			
		||||
            <onMismatch>DENY</onMismatch>
 | 
			
		||||
        </filter>
 | 
			
		||||
    </appender>
 | 
			
		||||
	
 | 
			
		||||
	<!-- 用户访问日志输出  -->
 | 
			
		||||
    <appender name="sys-user" class="ch.qos.logback.core.rolling.RollingFileAppender">
 | 
			
		||||
		<file>${log.path}/sys-user.log</file>
 | 
			
		||||
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
 | 
			
		||||
            <!-- 按天回滚 daily -->
 | 
			
		||||
            <fileNamePattern>${log.path}/sys-user.%d{yyyy-MM-dd}.log</fileNamePattern>
 | 
			
		||||
            <!-- 日志最大的历史 60天 -->
 | 
			
		||||
            <maxHistory>60</maxHistory>
 | 
			
		||||
        </rollingPolicy>
 | 
			
		||||
        <encoder>
 | 
			
		||||
            <pattern>${log.pattern}</pattern>
 | 
			
		||||
        </encoder>
 | 
			
		||||
    </appender>
 | 
			
		||||
	
 | 
			
		||||
	<!-- 系统模块日志级别控制  -->
 | 
			
		||||
	<logger name="com.ruoyi" level="info" />
 | 
			
		||||
	<!-- Spring日志级别控制  -->
 | 
			
		||||
	<logger name="org.springframework" level="warn" />
 | 
			
		||||
 | 
			
		||||
	<root level="info">
 | 
			
		||||
		<appender-ref ref="console" />
 | 
			
		||||
	</root>
 | 
			
		||||
	
 | 
			
		||||
	<!--系统操作日志-->
 | 
			
		||||
    <root level="info">
 | 
			
		||||
        <appender-ref ref="file_info" />
 | 
			
		||||
        <appender-ref ref="file_error" />
 | 
			
		||||
    </root>
 | 
			
		||||
	
 | 
			
		||||
	<!--系统用户操作日志-->
 | 
			
		||||
    <logger name="sys-user" level="info">
 | 
			
		||||
        <appender-ref ref="sys-user"/>
 | 
			
		||||
    </logger>
 | 
			
		||||
</configuration> 
 | 
			
		||||
		Reference in New Issue
	
	Block a user