Browse Source

登录接口限流

click33 2 years ago
parent
commit
fda32bdc55

+ 12 - 0
sp-com/pom.xml

@@ -128,6 +128,18 @@
             <version>1.2.73</version>
         </dependency>
 
+        <!-- Sentinel 限流 -->
+        <dependency>
+		    <groupId>com.alibaba.csp</groupId>
+		    <artifactId>sentinel-core</artifactId>
+		    <version>1.8.3</version>
+		</dependency>
+		<dependency>
+			<groupId>com.alibaba.csp</groupId>
+			<artifactId>sentinel-annotation-aspectj</artifactId>
+		    <version>1.8.3</version>
+		</dependency>
+
 		<!-- ConfigurationProperties -->
         <dependency>
         	<groupId>org.springframework.boot</groupId>

+ 7 - 0
sp-com/sp-admin/src/main/java/com/pj/project4sp/admin4acc/SpAccAdminController.java

@@ -6,6 +6,8 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
+import com.alibaba.csp.sentinel.annotation.SentinelResource;
+import com.alibaba.csp.sentinel.slots.block.BlockException;
 import com.pj.project4sp.admin.SpAdmin;
 import com.pj.project4sp.admin.SpAdminUtil;
 import com.pj.project4sp.role4permission.SpRolePermissionService;
@@ -34,6 +36,7 @@ public class SpAccAdminController {
 	
 	/** 账号、密码登录  */
 	@RequestMapping("doLogin")
+	@SentinelResource(value = "qps-max-1", blockHandler = "doLoginBlock")
 	AjaxJson doLogin(String key, String password) {
 		// 1、验证参数 
 		if(NbUtil.hasNull(key, password)) {
@@ -41,6 +44,10 @@ public class SpAccAdminController {
 		}
 		return spAccAdminService.doLogin(key, password);
 	}
+	// 限流之后触发的函数 
+	AjaxJson doLoginBlock(String key, String password, BlockException e) {
+		return AjaxJson.getError("访问过于频繁,请稍后再试");
+	}
 	
 	/** 退出登录  */
 	@RequestMapping("doExit")

+ 54 - 0
sp-com/sp-core/src/main/java/com/pj/current/sentinel/SentinelConfigure.java

@@ -0,0 +1,54 @@
+package com.pj.current.sentinel;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect;
+import com.alibaba.csp.sentinel.slots.block.RuleConstant;
+import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
+import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
+
+/**
+ * Sentinel 限流配置 
+ * 
+ * @author kong
+ *
+ */
+@Configuration
+public class SentinelConfigure {
+	
+	/**
+	 * 注册Sentinel切面对象 
+	 * @return 
+	 */
+	@Bean
+    public SentinelResourceAspect sentinelResourceAspect() {
+		initFlowRules();
+        return new SentinelResourceAspect();
+    }
+	
+	/**
+	 * 初始化降级规则 
+	 */
+	private static void initFlowRules() {
+		System.out.println("----------------------- 初始化限流规则 !!!");
+		
+        List<FlowRule> rules = new ArrayList<>();
+
+        // 规则  qps > 1 时,触发降级 
+        FlowRule rule = new FlowRule();
+        rule.setResource("qps-max-1");
+        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
+        rule.setCount(1);
+        rules.add(rule);
+
+        // 可创建多个规则
+        // ...
+        
+        FlowRuleManager.loadRules(rules);
+    }
+
+}

+ 2 - 1
sp-com/sp-core/src/main/java/com/pj/utils/sg/AjaxJson.java

@@ -7,6 +7,7 @@ import java.util.Map;
 import com.pj.utils.so.SoMap;
 
 
+
 /**
  * ajax请求返回Json格式数据的封装 <br>
  * 所有预留字段:<br>
@@ -69,7 +70,7 @@ public class AjaxJson extends LinkedHashMap<String, Object> implements Serializa
 	}
 	/** 获取data */
 	public Object getData() {
-		return (String)this.get("data");
+		return this.get("data");
 	}
 	/** 将data还原为指定类型并返回 */
 	@SuppressWarnings("unchecked")

+ 32 - 0
sp-devdoc/doc/more/update-log.md

@@ -1,5 +1,37 @@
 # 更新日志 
 
+
+--- 
+### 2021-02-26 @v1.27.0
+- 升级:Sa-Token 升级至 v1.29.0 版本 
+- 优化:优化全局 el-alert 样式
+- 优化:AjaxJson在未添加分页条件时,将不返回dataCount字段 
+- 修复:AjaxJson getData 的返回值改为Object 
+- 修复:Redis 序列化格式改为 json 序列化形式 
+- 修复:修复全局API日志的时间格式与真实请求不一致的问题 
+- 修复:启动时的 Network 打印加上try-catch,解决某些情况下的获取失败 
+- 修复:新增404接口处理,返回json格式化消息 
+- 优化:优化API全局日志算法,使404也能记录API全局日志 
+- 优化:将默认角色更名为开发者权限和系统管理员权限 
+- 新增:新增以演示模式启动的方式 
+- 新增:新增配置 `log-to-file` 和 `log-to-db` 决定是否输出API请求日志 
+- 修改:配置项 “是否抛出sql” 转移到 application.yml 中 
+- 优化:让API日志插入不打印sql,且改为异步插入提高请求响应速度
+- 修改:列表查询时排序条件改为具体的字段名 
+- 优化:设定项目缓存默认为json序列化方式,方便在控制台的二次修改  
+- 优化:接口鉴权方式改为注解鉴权 
+- 优化:修改部分权限码,更符合语义	
+- 新增:全局配置随机头像功能 
+- 新增:新增账号模拟登陆功能 
+- 新增:新增后台管理相关接口文档  
+- 新增:管理员的登陆日志   
+- 新增:后台登录新增 [记住我] 功能 
+- 修复:修复部分代码生成的错误之处 
+- 升级:升级 sp-com 多模块版相关功能 
+- 升级:升级 vue 单页版相关功能 
+- 升级:登录接口集成 Sentinel 限流 
+
+
 --- 
 ### 2021-10-24 @v1.26.0
 - 优化:优化前端样式,删除不必要的 `size="mini"` 配置

+ 2 - 0
sp-server/src/main/java/com/pj/project4sp/admin4acc/SpAccAdminController.java

@@ -9,6 +9,7 @@ import com.pj.project4sp.admin.SpAdminUtil;
 import com.pj.project4sp.role4permission.SpRolePermissionService;
 import com.pj.project4sp.spcfg.SpCfgUtil;
 import com.pj.utils.sg.AjaxJson;
+import com.pj.utils.sg.IpCheckUtil;
 import com.pj.utils.sg.NbUtil;
 import com.pj.utils.so.SoMap;
 
@@ -33,6 +34,7 @@ public class SpAccAdminController {
 	/** 账号、密码登录  */
 	@RequestMapping("doLogin")
 	AjaxJson doLogin(String key, String password) {
+		IpCheckUtil.checkResToNow("admin-login", 1);
 		// 1、验证参数 
 		if(NbUtil.hasNull(key, password)) {
 			return AjaxJson.getError("请提供key与password参数");

+ 5 - 0
sp-server/src/main/java/com/pj/utils/cache/RedisUtil.java

@@ -67,6 +67,11 @@ public class RedisUtil {
 		stringRedisTemplate.opsForValue().set(key, value, timeout, TimeUnit.MINUTES);
 	}
 
+	// 写入,并设置时长,单位 秒 SECONDS
+	public static void setBySECONDS(String key, String value, long timeout) {
+		stringRedisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
+	}
+
 	// 写入,永久有效 
 	public static void setByForever(String key, String value) {
 		stringRedisTemplate.opsForValue().set(key, value);

+ 1 - 1
sp-server/src/main/java/com/pj/utils/sg/AjaxJson.java

@@ -69,7 +69,7 @@ public class AjaxJson extends LinkedHashMap<String, Object> implements Serializa
 	}
 	/** 获取data */
 	public Object getData() {
-		return (String)this.get("data");
+		return this.get("data");
 	}
 	/** 将data还原为指定类型并返回 */
 	@SuppressWarnings("unchecked")

+ 23 - 23
sp-server/src/main/java/com/pj/utils/sg/IpCheckUtil.java

@@ -12,55 +12,55 @@ import cn.dev33.satoken.spring.SpringMVCUtil;
 public class IpCheckUtil {
 
 	/**
-	 *  持久化的key 
+	 *  持久化的key 前缀 
 	 */
 	static String key = "sys_ck_ip:";
 	
+	static long timeout = 86400;
 	
 	/**
-	 * 指定ip的访问点设置为
+	 * 对当前ip对某个资源的最后访问时间设为Now 
 	 * @param ip
 	 */
-	public static void setNow(String ip){
-		RedisUtil.set(key + ip, System.currentTimeMillis() + "");
-	}
-	public static void setNow(){
-		setNow(getMyIp());
+	public static void setNow(String res){
+		RedisUtil.setBySECONDS(key + res + ":" + getMyIp(), System.currentTimeMillis() + "", timeout);
 	}
 	
 	/**
-	 * 返回指定ip上一次接入是几秒前
+	 * 返回当前ip上一次访问某个资源是几秒前 
 	 * @param ip
 	 * @return
 	 */
-	public static long getTs(String ip){
-		String str = RedisUtil.get(key + ip);
+	public static long getTs(String res){
+		String str = RedisUtil.get(key + res + ":" + getMyIp());
 		if(str == null) {
 			str = "0";
 		}
 		return (System.currentTimeMillis() - Long.parseLong(str)) / 1000;
 	}
-	public static long getTs(){
-		return getTs(getMyIp());
-	}
 	
 	/**
-	 * 检查指定ip是否属于频繁访问 ,如果是,则抛出异常 , 参数:ip、频繁秒数 
-	 * @param ip
-	 * @param s
+	 * 检查当前ip访问某个资源 是否属于频繁访问 ,如果是,则抛出异常
+	 * @param res 指定资源
+	 * @param s 限定多少秒内连续访问才算频繁 
 	 */
-	public static void checkIp(String ip, int s) {
-		if(IpCheckUtil.getTs(ip) < s){
+	public static void checkRes(String res, int s) {
+		if(IpCheckUtil.getTs(res) < s){
 			throw AjaxError.get("操作频繁,请稍后尝试");
 		}
 	}
-	public static void checkIp(int s) {
-		checkIp(getMyIp(), s);
-	}
-	
 	
+	/**
+	 * 1、校验当前ip是否访问某个资源频繁,2、将最后访问时间点设为now() 
+	 * @param res 具体资源 
+	 * @param s 限定属于频繁的秒数  
+	 */
+	public static void checkResToNow(String res, int s) {
+		checkRes(res, s);
+		setNow(res);
+	}
 	
-	// 返回我的ip
+	// 返回当前访问者 IP 
 	private static String getMyIp() {
 		String ip = IpUtil.getIP(SpringMVCUtil.getRequest());
 		return ip;