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