【SpringBoot】防止API接口被刷(频繁访问),以及代码执行sql脚本
前言最近在b站看vue的实战视频,其中需要搭建服务端的api接口,以给vue请求使用。学习资料里面提供了api的项目代码。在本地运行,每次都要启动,有点麻烦。使用老师搭建的api,由于是公用的,数据库数据经常被其他学习的同学删除或修改。为了一劳永逸,也为了各位小伙伴一起愉快学习。我就用SpringBoot写了一个小程序,放到服务器运行。每天凌晨2点定时执行sql脚本,将数据库数据重新导入。同时为了
前言
最近在b站看vue的实战视频,其中需要搭建服务端的api接口,以给vue请求使用。学习资料里面提供了api的项目代码。在本地运行,每次都要启动,有点麻烦。使用老师搭建的api,由于是公用的,数据库数据经常被其他学习的同学删除或修改。
为了一劳永逸,也为了各位小伙伴一起愉快学习。我就用SpringBoot写了一个小程序,放到服务器运行。每天凌晨2点定时执行sql脚本,将数据库数据重新导入。同时为了紧急情况,提供了一个公开访问的api接口,随时可以通过访问该api,将数据库的数据重置。这样,如果一起使用相同的服务端,再也不怕数据的被其他小伙伴改掉了。随时就可以自己重置。
但是重置数据库的操作,是完整地执行整个sql脚本,完整执行需要差不多1分钟的时间,极其占用数据库和服务器资源。如果重置数据库的接口直接暴露,可能会被其他人恶意访问(狂刷)。那么我的服务器会崩的,所以必需给重置数据库的接口限制防刷。其实也比较简单,不多说了,直接看代码。
重置数据库数据(执行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;
}
}
DataService
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;
}
}
防止API被频繁访问
自定义注解:Delay
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;
}
自定义拦截器: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;
}
}
WebMvc的配置类:WebMvcConfig,注册上面的拦截器
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);
}
}
更多推荐
所有评论(0)