[SpringBoot] Evita que se modifique la interfaz de la API (acceso frecuente) y ejecución de código SQL

Prefacio

Recientemente, vi el video de combate real de vue en la estación b, en el que es necesario construir la interfaz api del servidor para solicitar el uso de vue. El código del proyecto api se proporciona en los materiales de aprendizaje. Es un poco problemático ejecutarlo localmente e iniciarlo todo el tiempo. Utilizando la API creada por el profesor, los datos de la base de datos a menudo son eliminados o modificados por otros estudiantes porque son públicos.

De una vez por todas, y para que tus amigos estudien juntos felices. Escribí un pequeño programa con SpringBoot y lo puse en el servidor para que se ejecutara. Ejecute el script sql regularmente a las 2 am todos los días para reimportar los datos de la base de datos. Al mismo tiempo, para situaciones de emergencia, se proporciona una interfaz API de acceso público y los datos de la base de datos se pueden restablecer en cualquier momento accediendo a la API. De esta forma, si utiliza el mismo servidor en conjunto, ya no tendrá miedo de que otros socios modifiquen los datos. Puede restablecerlo usted mismo en cualquier momento.

Sin embargo, la operación de restablecer la base de datos es ejecutar todo el script sql por completo, lo que demora aproximadamente 1 minuto en completar la ejecución, lo que ocupa extremadamente los recursos de la base de datos y del servidor. Si la interfaz para restablecer la base de datos está expuesta directamente, es posible que otras personas accedan a ella de forma maliciosa (cepillado loco). Entonces mi servidor se bloqueará, por lo que es necesario limitar la interfaz para restablecer la base de datos y evitar el cepillado. De hecho, es relativamente simple, por no decir más, basta con mirar el código.

Restablecer los datos de la base de datos (ejecutar el script sql)

DataController

package net.ysq.vueshopdata.controller;

import net.ysq.vueshopdata.component.Delay;
import net.ysq.vueshopdata.service.DataService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.sql.SQLException;

/**
 * @author passerbyYSQ
 * @create 2020-08-27 23:22
 */
@Controller
@ResponseBody
@EnableScheduling   // 2.开启定时任务
@RequestMapping("/vueshop")
public class DataController {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private DataService dataService;

    @RequestMapping("log")
    public void testLog() {
        logger.info("测试log");
    }

    @Scheduled(cron = "0 0 2 * * ?") // 定时任务,每天凌晨2点,执行该方法
    @Delay(time = 1000 * 60) // 使用自定义注解。接口被成功访问后的1分钟之内,其他请求均拦截并驳回
    @RequestMapping("/reset")
    public String resetDataBase() throws SQLException {
        logger.info("【开始】重置数据库vue_shop的数据");

        long start = System.currentTimeMillis();
        dataService.resetDataBase("mydb.sql");
        long end = System.currentTimeMillis();

        String cost = "用时:" + (end - start) / 1000.0 + "秒";
        logger.info("【结束】重置成功:" + cost);

        return cost;
    }

}

Servicio de datos

package net.ysq.vueshopdata.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.jdbc.datasource.init.ScriptUtils;
import org.springframework.stereotype.Service;

import javax.sql.DataSource;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.SQLException;

/**
 * @author passerbyYSQ
 * @create 2020-08-28 0:16
 */
@Service // 不要忘了加入IOC容器,否则无法使用@Autowired注入
public class DataService {

    @Autowired
    private DataSource dataSource;

    /**
     * 重置数据库数据
     * @param sqlScriptName 需要放在resources文件夹下面
     */
    public boolean resetDataBase(String sqlScriptName) {
        Connection conn = null;
        try {
            // 从Druid数据源(数据库连接池)中获取一个数据库连接
            conn = dataSource.getConnection();
            ClassPathResource rc = new ClassPathResource(sqlScriptName);
            EncodedResource er = new EncodedResource(rc, StandardCharsets.UTF_8);
            // ScriptUtils:是SpringBoot源码中使用的工具类,能够执行Sql脚本
            // sql脚本执行中途,遇到错误,默认会抛出异常,停止执行
            // 建议传入参数true,忽略中途的错误,但是后面4个参数又是必需的,只需要填入源码中的默认值即可
            ScriptUtils.executeSqlScript(conn, er, true, true,
                    "--", ";", "/*", "*/");
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 不要忘了释放连接
            try {
                if (conn != null) {
                    conn.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return false;
    }
}

Evite que se acceda con frecuencia a la API

Anotación personalizada: retraso

package net.ysq.vueshopdata.component;

import org.springframework.stereotype.Component;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 该注解放在controller层的方法上(api),
 * 用于防止别人恶意访问(刷)一些耗时占资源的接口
 * @author passerbyYSQ
 * @create 2020-08-28 18:25
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface Delay {
    // 默认两秒,表示:当api被成功访问后的2秒内,其他访问请求均拦截并驳回
    int time() default 2000;

}

Interceptor personalizado: RequestFrequencyInterceptor

package net.ysq.vueshopdata.component;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.util.HashMap;
import java.util.Map;

/**
 * @author passerbyYSQ
 * @create 2020-08-28 18:26
 */
@Component // 加入IOC容器中,以用于在配置类中注册拦截器
public class RequestFrequencyInterceptor implements HandlerInterceptor {

    private Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * 由于该注解可能加到多个方法(接口)上,每个接口上一次的访问时间都不一样。
     * key值:必须能够唯一标识方法(接口),由于不同的类中可能会出现同名的方法,所以
     * 并不建议直接使用方法名作为key值,
     * value值:接口上一次被成功访问(驳回的访问不算)的时间
     */
    private Map<String, Long> lastTimeMap = new HashMap<>();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        if(handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            // 作为标识该接口的key值
            String methodKey = handlerMethod.getBean().getClass().getName();
            // 如果该方法(接口)上有Delay注解,则获取
            Delay delay = handlerMethod.getMethodAnnotation(Delay.class);

            if (delay != null) {

                Long currentTime = System.currentTimeMillis();

                if (!lastTimeMap.containsKey(methodKey)) {
                    // 接口被第一次访问,没有lastTime
                    lastTimeMap.put(methodKey, currentTime);
                    // 放行请求
                    return true;
                } else {
                    // 方法正在被频繁访问,访问间隔小于delay.time(),请稍后重试
                    if (currentTime - lastTimeMap.get(methodKey) < delay.time()) {

                        logger.info("【频繁访问】,已被拦截");

                        String responseMsg = String.format("接口正在被频繁访问,访问间隔小于%d秒。您已被拦截,请稍后重试", delay.time() / 1000);
                        response.setContentType("application/json;charset=utf-8"); // 不设置中文会出现乱码
                        response.getWriter().write(responseMsg);
                        // 拦截
                        return false;
                    } else {
                        // 大于间隔,更新接口上一次被成功访问的时间,并放行请求
                        lastTimeMap.put(methodKey, currentTime);
                        return true;
                    }
                }
            }
        }

        // 该拦截器只处理访问被Delay注解标识的接口的请求,访问其他接口的请求不作拦截,一律放行
        return true;
    }
}

Clase de configuración de WebMvc: WebMvcConfig, registre el interceptor arriba

package net.ysq.vueshopdata.config;

import net.ysq.vueshopdata.component.RequestFrequencyInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author passerbyYSQ
 * @create 2020-08-28 18:45
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    private RequestFrequencyInterceptor frequencyInterceptor;

    /**
     * 注册拦截器RequestFrequencyInterceptor
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(frequencyInterceptor)
                .addPathPatterns( "/**" ).excludePathPatterns("/error");
    }

    /**
     * 支持跨域
     * @param registry
     */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowedMethods("GET", "POST", "PUT", "OPTIONS", "DELETE", "PATCH")
                .allowCredentials(true).maxAge(3600);
    }
}

 

Supongo que te gusta

Origin blog.csdn.net/qq_43290318/article/details/108289754
Recomendado
Clasificación