feat: 添加LeetCode210、LeetCode215、LeetCode238、LeetCode189解题代码
feat: 添加限流功能 feat: 添加Redis配置,实现对指定方法的限流控制。同时添加限流相关的配置和测试接口。 Default Changelist application.yaml CacheConstants.java fx.java LeetCode146.java LeetCode167.java LeetCode189.java LeetCode210.java LeetCode215.java LeetCode238.java LimitType.java P1.java RateLimitAspect.java RateLimiter.java RedisConfig.java TestController.java ThreadLocalExample.java
This commit is contained in:
parent
3789fe03e3
commit
c73979681f
50
ForJdk17/src/main/java/cn/whaifree/interview/Szml/P1.java
Normal file
50
ForJdk17/src/main/java/cn/whaifree/interview/Szml/P1.java
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package cn.whaifree.interview.Szml;
|
||||||
|
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @version 1.0
|
||||||
|
* @Author whai文海
|
||||||
|
* @Date 2024/10/8 19:50
|
||||||
|
* @注释
|
||||||
|
*/
|
||||||
|
public class P1 {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
Scanner in = new Scanner(System.in);
|
||||||
|
// 注意 hasNext 和 hasNextLine 的区别
|
||||||
|
while (true) {
|
||||||
|
String next = in.next("");
|
||||||
|
System.out.print(next);
|
||||||
|
System.out.print(" -> ");
|
||||||
|
System.out.println(method(next));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String method(String s) {
|
||||||
|
char[] charArray = s.toCharArray();
|
||||||
|
reverse(charArray, 0, charArray.length - 1); // 整体翻转
|
||||||
|
for (int i = 0; i < s.length(); i++) {
|
||||||
|
// 遇到连续的三个lia,对这个lia再次翻转
|
||||||
|
if (i < s.length() - 2 && charArray[i] == 'i' && charArray[i + 1] == 'l' && charArray[i + 2] == 'a') {
|
||||||
|
reverse(charArray, i, i + 2);
|
||||||
|
i += 2; // 跳过这三个字符
|
||||||
|
}else {
|
||||||
|
charArray[i] = Character.toUpperCase(charArray[i]); // 变为大写
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new String(charArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void reverse(char[] chars,int left ,int right) {
|
||||||
|
while (left < right) {
|
||||||
|
char tmp = chars[left];
|
||||||
|
chars[left] = chars[right];
|
||||||
|
chars[right] = tmp;
|
||||||
|
left++;
|
||||||
|
right--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
package cn.whaifree.redo.redo_all_240924;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @version 1.0
|
||||||
|
* @Author whai文海
|
||||||
|
* @Date 2024/10/8 11:22
|
||||||
|
* @注释
|
||||||
|
*/
|
||||||
|
public class LeetCode146 {
|
||||||
|
|
||||||
|
class LRUCache {
|
||||||
|
|
||||||
|
class V{
|
||||||
|
int value;
|
||||||
|
V next;
|
||||||
|
|
||||||
|
public V(int value, V next) {
|
||||||
|
this.value = value;
|
||||||
|
this.next = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<Integer, V> map;
|
||||||
|
int maxSize;
|
||||||
|
Integer headIndex;
|
||||||
|
|
||||||
|
|
||||||
|
public LRUCache(int capacity) {
|
||||||
|
map = new HashMap<>();
|
||||||
|
maxSize = capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int get(int key) {
|
||||||
|
int value = map.get(key).value;
|
||||||
|
map.remove(key);
|
||||||
|
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void put(int key, int value) {
|
||||||
|
V beforeHea = map.get(headIndex);
|
||||||
|
V newV = new V(value, beforeHea);
|
||||||
|
headIndex = key;
|
||||||
|
map.put(key, newV);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Your LRUCache object will be instantiated and called as such:
|
||||||
|
* LRUCache obj = new LRUCache(capacity);
|
||||||
|
* int param_1 = obj.get(key);
|
||||||
|
* obj.put(key,value);
|
||||||
|
*/
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
package cn.whaifree.redo.redo_all_240924;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @version 1.0
|
||||||
|
* @Author whai文海
|
||||||
|
* @Date 2024/10/8 11:35
|
||||||
|
* @注释
|
||||||
|
*/
|
||||||
|
public class LeetCode167 {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
Solution solution = new Solution();
|
||||||
|
int[] ints = solution.twoSum(new int[]{2, 7, 11, 15}, 9);
|
||||||
|
System.out.println(Arrays.toString(ints));
|
||||||
|
}
|
||||||
|
|
||||||
|
class Solution {
|
||||||
|
public int[] twoSum(int[] numbers, int target) {
|
||||||
|
int left = 0;
|
||||||
|
int right = numbers.length - 1;
|
||||||
|
while (left < right) {
|
||||||
|
int v = numbers[left] + numbers[right];
|
||||||
|
if (v == target) {
|
||||||
|
return new int[]{left + 1, right + 1};
|
||||||
|
} else if (v < target) {
|
||||||
|
left++;
|
||||||
|
}else {
|
||||||
|
right--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new int[]{-1, -1};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package cn.whaifree.redo.redo_all_240924;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @version 1.0
|
||||||
|
* @Author whai文海
|
||||||
|
* @Date 2024/10/8 11:53
|
||||||
|
* @注释
|
||||||
|
*/
|
||||||
|
public class LeetCode189 {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
int[] nums = new int[]{-1};
|
||||||
|
int k = 2;
|
||||||
|
new Solution().rotate(nums, k);
|
||||||
|
for (int i : nums) {
|
||||||
|
System.out.println(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7 6 5 4 3 2 1
|
||||||
|
// 5 6 7 1 2 3 4
|
||||||
|
class Solution {
|
||||||
|
public void rotate(int[] nums, int k) {
|
||||||
|
if (k > nums.length) {
|
||||||
|
k = k % nums.length;
|
||||||
|
}
|
||||||
|
int start = nums.length - k;
|
||||||
|
int[] newN = new int[nums.length * 2];
|
||||||
|
System.arraycopy(nums, 0, newN, 0, nums.length);
|
||||||
|
System.arraycopy(nums, 0, newN, nums.length, nums.length);
|
||||||
|
System.arraycopy(newN, start, nums, 0, nums.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,71 @@
|
|||||||
|
package cn.whaifree.redo.redo_all_240924;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @version 1.0
|
||||||
|
* @Author whai文海
|
||||||
|
* @Date 2024/10/9 12:25
|
||||||
|
* @注释
|
||||||
|
*/
|
||||||
|
public class LeetCode210 {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
int[][] prerequisites = {{1,0},{2,0},{3,1},{3,2}};
|
||||||
|
int[] ints = new Solution().findOrder(4, prerequisites);
|
||||||
|
System.out.println(Arrays.toString(ints));
|
||||||
|
}
|
||||||
|
|
||||||
|
class Solution {
|
||||||
|
public int[] findOrder(int numCourses, int[][] prerequisites) {
|
||||||
|
// 统计所有入度
|
||||||
|
Map<Integer, List<Integer>> map = new HashMap<>();
|
||||||
|
for (int[] prerequisite : prerequisites) {
|
||||||
|
if (!map.containsKey(prerequisite[0])) {
|
||||||
|
map.put(prerequisite[0], new ArrayList<>());
|
||||||
|
}
|
||||||
|
map.get(prerequisite[0]).add(prerequisite[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Deque<Integer> deque = new LinkedList<>();
|
||||||
|
for (int i = 0; i < numCourses; i++) {
|
||||||
|
if (!map.containsKey(i)) {
|
||||||
|
deque.add(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Integer> list = new ArrayList<>();
|
||||||
|
int exec = 0;
|
||||||
|
while (!deque.isEmpty()) {
|
||||||
|
Integer pop = deque.pop();
|
||||||
|
list.add(pop);
|
||||||
|
exec++;
|
||||||
|
for (int[] prerequisite : prerequisites) {
|
||||||
|
int start = prerequisite[1]; // 出
|
||||||
|
int end = prerequisite[0]; // 入
|
||||||
|
if (start == pop) {
|
||||||
|
List<Integer> item = map.get(end);
|
||||||
|
item.remove(Integer.valueOf(start));
|
||||||
|
// 如果此时item还有元素,则不加入队列
|
||||||
|
if (item.isEmpty()) {
|
||||||
|
deque.add(end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exec != numCourses) {
|
||||||
|
return new int[]{};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (list.size() == numCourses) {
|
||||||
|
return list.stream().mapToInt(i -> i).toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new int[]{};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,77 @@
|
|||||||
|
package cn.whaifree.redo.redo_all_240924;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @version 1.0
|
||||||
|
* @Author whai文海
|
||||||
|
* @Date 2024/10/10 11:17
|
||||||
|
* @注释
|
||||||
|
*/
|
||||||
|
public class LeetCode215 {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
int[] nums = {3,2,1,5,6,4};
|
||||||
|
int k = 2;
|
||||||
|
Solution solution = new Solution();
|
||||||
|
int kthLargest = solution.findKthLargest(nums, k);
|
||||||
|
System.out.println(kthLargest);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Solution {
|
||||||
|
public int findKthLargest(int[] nums, int k) {
|
||||||
|
return find(nums, 0, nums.length - 1, nums.length - k);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int find(int[] nums, int start, int end, int k) {
|
||||||
|
if (start > end) {
|
||||||
|
return nums[end];
|
||||||
|
}
|
||||||
|
|
||||||
|
int q = new Random().nextInt(end - start + 1) + start;
|
||||||
|
|
||||||
|
swap(nums, q, end);
|
||||||
|
|
||||||
|
int base = nums[end];
|
||||||
|
int left = start;
|
||||||
|
int right = end;
|
||||||
|
while (left < right) {
|
||||||
|
|
||||||
|
// 从左往右遍历,当左指针指向的元素小于等于基数时,i++。左指针持续向右移动
|
||||||
|
while (nums[left] >= base && left < right) {
|
||||||
|
left++;
|
||||||
|
}
|
||||||
|
// 从右往左遍历,当右指针指向的元素大于等于基数时,j--。右指针持续向左移动
|
||||||
|
while (nums[right] <= base && left < right) {
|
||||||
|
right--;
|
||||||
|
}
|
||||||
|
if (left < right) {
|
||||||
|
// 当左右两个指针停下来时,交换两个元素
|
||||||
|
swap(nums, left, right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
swap(nums, left, end);
|
||||||
|
|
||||||
|
// 从大到小排序,如果左边k-1个,则left就是第k个,左边k-1个比他大
|
||||||
|
if (left == k - 1) {
|
||||||
|
return nums[left];
|
||||||
|
}
|
||||||
|
// 左边的数量太少了,往右边找
|
||||||
|
if (left < k - 1) {
|
||||||
|
return find(nums, left + 1, end, k);
|
||||||
|
}
|
||||||
|
return find(nums, start, left - 1, k);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void swap(int[] nums, int i, int j) {
|
||||||
|
int temp = nums[i];
|
||||||
|
nums[i] = nums[j];
|
||||||
|
nums[j] = temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
package cn.whaifree.redo.redo_all_240924;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @version 1.0
|
||||||
|
* @Author whai文海
|
||||||
|
* @Date 2024/10/10 11:36
|
||||||
|
* @注释
|
||||||
|
*/
|
||||||
|
public class LeetCode238 {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test()
|
||||||
|
{
|
||||||
|
int[] nums = new int[]{-1,1,0,-3,3};
|
||||||
|
Solution solution = new Solution();
|
||||||
|
int[] res = solution.productExceptSelf(nums);
|
||||||
|
for (int i : res) {
|
||||||
|
System.out.println(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Solution {
|
||||||
|
public int[] productExceptSelf(int[] nums) {
|
||||||
|
|
||||||
|
int countOfZero = 0;
|
||||||
|
int product = 1;
|
||||||
|
int zeroIndex = 0;
|
||||||
|
for (int i = 0; i < nums.length; i++) {
|
||||||
|
int num = nums[i];
|
||||||
|
if (num == 0) {
|
||||||
|
countOfZero++;
|
||||||
|
zeroIndex = i;
|
||||||
|
}else {
|
||||||
|
product *= num;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (countOfZero >= 2) {
|
||||||
|
return new int[nums.length];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (countOfZero == 1) {
|
||||||
|
int[] res = new int[nums.length];
|
||||||
|
res[zeroIndex] = product;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
int[] res = new int[nums.length];
|
||||||
|
for (int i = 0; i < nums.length; i++) {
|
||||||
|
res[i] = product / nums[i];
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
ForJdk17/src/main/java/cn/whaifree/tech/java/fx.java
Normal file
35
ForJdk17/src/main/java/cn/whaifree/tech/java/fx.java
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package cn.whaifree.tech.java;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @version 1.0
|
||||||
|
* @Author whai文海
|
||||||
|
* @Date 2024/10/9 22:24
|
||||||
|
* @注释
|
||||||
|
*/
|
||||||
|
public class fx {
|
||||||
|
|
||||||
|
class Base{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class User <T extends Base, M,N,O, P> { // ,是并列的关系
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
interface interfaceM{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class Book <T extends Base & interfaceM> { // 必须类在前,接口在后
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class BookBase extends Base implements interfaceM{
|
||||||
|
// 满足,同时是Base的子类和interfaceM的实现类
|
||||||
|
}
|
||||||
|
|
||||||
|
public void method() {
|
||||||
|
new Book<BookBase>();
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,8 @@ public class ThreadLocalExample {
|
|||||||
|
|
||||||
public static void main(String[] args) throws InterruptedException {
|
public static void main(String[] args) throws InterruptedException {
|
||||||
for(int i=0 ; i<3; i++){
|
for(int i=0 ; i<3; i++){
|
||||||
|
threadLocal.remove();
|
||||||
|
|
||||||
new Thread(new MyThread()).start();
|
new Thread(new MyThread()).start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,100 @@
|
|||||||
|
package cn.whaifree.springdemo.aspect;
|
||||||
|
|
||||||
|
import cn.whaifree.springdemo.aspect.annotation.RateLimiter;
|
||||||
|
import cn.whaifree.springdemo.constant.LimitType;
|
||||||
|
import org.aspectj.lang.JoinPoint;
|
||||||
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
|
import org.aspectj.lang.annotation.Before;
|
||||||
|
import org.aspectj.lang.reflect.MethodSignature;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.data.redis.core.script.RedisScript;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.context.request.RequestContextHolder;
|
||||||
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @version 1.0
|
||||||
|
* @Author whai文海
|
||||||
|
* @Date 2024/10/9 17:46
|
||||||
|
* @注释
|
||||||
|
*/
|
||||||
|
@Aspect
|
||||||
|
@Component
|
||||||
|
public class RateLimitAspect {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(RateLimitAspect.class);
|
||||||
|
|
||||||
|
private RedisTemplate<Object, Object> redisTemplate;
|
||||||
|
|
||||||
|
private RedisScript<Long> limitScript;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public void setRedisTemplate1(RedisTemplate<Object, Object> redisTemplate) {
|
||||||
|
this.redisTemplate = redisTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public void setLimitScript(RedisScript<Long> limitScript) {
|
||||||
|
this.limitScript = limitScript;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* key 对应方法,value 已经被访问的次数
|
||||||
|
*
|
||||||
|
* lua的逻辑:
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param point
|
||||||
|
* @param rateLimiter
|
||||||
|
* @throws Throwable
|
||||||
|
*/
|
||||||
|
@Before("@annotation(rateLimiter)")
|
||||||
|
public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable {
|
||||||
|
int time = rateLimiter.time(); // 多长时间
|
||||||
|
int count = rateLimiter.count(); // 允许次数
|
||||||
|
|
||||||
|
|
||||||
|
String combineKey = getCombineKey(rateLimiter, point); // 组合key, Class-Method
|
||||||
|
List<Object> keys = Collections.singletonList(combineKey);
|
||||||
|
try {
|
||||||
|
Long number = redisTemplate.execute(limitScript, keys, count, time);
|
||||||
|
if (Objects.isNull(number) || number.intValue() > count) { // 如果超过限额,报错
|
||||||
|
throw new Exception("访问过于频繁,请稍候再试");
|
||||||
|
}
|
||||||
|
log.info("限制请求'{}',当前请求'{}',缓存key'{}'", count, number.intValue(), combineKey);
|
||||||
|
} catch (Exception e) {
|
||||||
|
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) {
|
||||||
|
StringBuffer stringBuffer = new StringBuffer(rateLimiter.key()); // 获取key
|
||||||
|
// if (rateLimiter.limitType() == LimitType.IP) {
|
||||||
|
// stringBuffer.append(IpUtils.getIpAddr(ServletUtils.getRequest())).append("-");
|
||||||
|
// }
|
||||||
|
if (rateLimiter.limitType() == LimitType.USER) {
|
||||||
|
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||||
|
String username = attributes.getRequest().getParameter("userId");
|
||||||
|
if (username != null) {
|
||||||
|
stringBuffer.append(username).append("-");
|
||||||
|
}else {
|
||||||
|
throw new RuntimeException("用户id为空"); // 抛出异常,禁止继续执行,防止缓存穿透,缓存穿透指缓存中不存在该key,但是数据库中存在该ke
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MethodSignature signature = (MethodSignature) point.getSignature();
|
||||||
|
Method method = signature.getMethod();
|
||||||
|
Class<?> targetClass = method.getDeclaringClass();
|
||||||
|
stringBuffer.append(targetClass.getName()).append("-").append(method.getName());
|
||||||
|
return stringBuffer.toString();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package cn.whaifree.springdemo.aspect.annotation;
|
||||||
|
|
||||||
|
import cn.whaifree.springdemo.constant.CacheConstants;
|
||||||
|
import cn.whaifree.springdemo.constant.LimitType;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @version 1.0
|
||||||
|
* @Author whai文海
|
||||||
|
* @Date 2024/10/9 17:43
|
||||||
|
* @注释
|
||||||
|
*/
|
||||||
|
@Target(ElementType.METHOD)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Documented
|
||||||
|
public @interface RateLimiter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 限流key
|
||||||
|
*/
|
||||||
|
public String key() default CacheConstants.RATE_LIMIT_KEY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 限流时间,单位秒
|
||||||
|
*/
|
||||||
|
public int time() default 60;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 限流次数
|
||||||
|
*/
|
||||||
|
public int count() default 100;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 限流类型
|
||||||
|
*/
|
||||||
|
public LimitType limitType() default LimitType.DEFAULT;
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
package cn.whaifree.springdemo.config;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
||||||
|
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @version 1.0
|
||||||
|
* @Author whai文海
|
||||||
|
* @Date 2024/10/9 17:47
|
||||||
|
* @注释
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class RedisConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@SuppressWarnings(value = {"unchecked", "rawtypes"})
|
||||||
|
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
|
||||||
|
RedisTemplate<Object, Object> template = new RedisTemplate<>();
|
||||||
|
template.setConnectionFactory(connectionFactory);
|
||||||
|
|
||||||
|
FastJsonRedisSerializer jsonRedisSerializer = new FastJsonRedisSerializer(Object.class);
|
||||||
|
|
||||||
|
// 使用StringRedisSerializer来序列化和反序列化redis的key值
|
||||||
|
template.setKeySerializer(new StringRedisSerializer());
|
||||||
|
template.setValueSerializer(jsonRedisSerializer);
|
||||||
|
|
||||||
|
// Hash的key也采用StringRedisSerializer的序列化方式
|
||||||
|
template.setHashKeySerializer(new StringRedisSerializer());
|
||||||
|
template.setHashValueSerializer(jsonRedisSerializer);
|
||||||
|
|
||||||
|
template.afterPropertiesSet();
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RedisScript是Redis官方Java客户端Jedis中的一个类,用于执行Redis脚本(Lua脚本)。
|
||||||
|
*
|
||||||
|
* 在Redis中,Lua脚本是一种强大的工具,可以用于执行复杂的操作,如事务、发布/订阅、锁等。通过使用Lua脚本,你可以将多个操作封装在一个脚本中,然后一次性发送给Redis服务器执行,这样可以减少网络延迟和服务器负载。
|
||||||
|
*
|
||||||
|
* RedisScript类提供了一些方法,可以用于设置脚本的返回类型、执行脚本、获取脚本的返回值等。
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public DefaultRedisScript<Long> limitScript() {
|
||||||
|
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
|
||||||
|
redisScript.setScriptText(limitScriptText());
|
||||||
|
redisScript.setResultType(Long.class);
|
||||||
|
return redisScript;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 限流脚本
|
||||||
|
*/
|
||||||
|
private String limitScriptText() {
|
||||||
|
|
||||||
|
// 传入的参数有 time多长时间 count多少次
|
||||||
|
|
||||||
|
return "local key = KEYS[1]\n" + //test:cn.whaifree.springdemo.controller.TestController-test
|
||||||
|
"local count = tonumber(ARGV[1])\n" + // 传入的次数, 比如1次
|
||||||
|
"local time = tonumber(ARGV[2])\n" + // 传入的时间,比如2s
|
||||||
|
"local current = redis.call('get', key);\n" + // 获取当前的次数
|
||||||
|
"if current and tonumber(current) > count then\n" +
|
||||||
|
" return tonumber(current);\n" + // 当前的次数超过count 表示当前的次数超过限额,直接返回,表示拒绝
|
||||||
|
"end\n" +
|
||||||
|
"current = redis.call('incr', key)\n" + // 如果没有超过限额,对value,即current增加
|
||||||
|
"if tonumber(current) == 1 then\n" + // 如果是第一次,增加过期时间
|
||||||
|
" redis.call('expire', key, time)\n" +
|
||||||
|
"end\n" +
|
||||||
|
"return tonumber(current);";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package cn.whaifree.springdemo.constant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @version 1.0
|
||||||
|
* @Author whai文海
|
||||||
|
* @Date 2024/10/9 17:44
|
||||||
|
* @注释
|
||||||
|
*/
|
||||||
|
public class CacheConstants {
|
||||||
|
public static final String RATE_LIMIT_KEY = "rate_limit:";
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package cn.whaifree.springdemo.constant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @version 1.0
|
||||||
|
* @Author whai文海
|
||||||
|
* @Date 2024/10/9 17:45
|
||||||
|
* @注释
|
||||||
|
*/
|
||||||
|
public enum LimitType {
|
||||||
|
IP,
|
||||||
|
USER,
|
||||||
|
DEFAULT;
|
||||||
|
}
|
@ -0,0 +1,109 @@
|
|||||||
|
package cn.whaifree.springdemo.controller;
|
||||||
|
|
||||||
|
import cn.whaifree.springdemo.aspect.annotation.RateLimiter;
|
||||||
|
import cn.whaifree.springdemo.constant.LimitType;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @version 1.0
|
||||||
|
* @Author whai文海
|
||||||
|
* @Date 2024/10/9 17:40
|
||||||
|
* @注释
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
public class TestController {
|
||||||
|
@RequestMapping("/test")
|
||||||
|
@RateLimiter(key = "test:", time = 10, count = 1) // 10s只能1次
|
||||||
|
public String test() {
|
||||||
|
return "test";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* BG:重复导入多个容器,有一定几率触发15s响应超时
|
||||||
|
* 1. 限频率,如果重复导入直接返回已经在导入中
|
||||||
|
* 2. 控制导入的数量,对导入的数量分片+线程池处理
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@RequestMapping("addContainer")
|
||||||
|
@RateLimiter(key = "addContainer:", limitType = LimitType.USER, time = 5, count = 1) // 10s只能1次
|
||||||
|
public String addContainerInstanceToCluster(@RequestBody List<String> instances, int userId) {
|
||||||
|
// 导入容器节点到集群中
|
||||||
|
return addToCluster(instances, "clusterId", userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param instances 导入实例的ID
|
||||||
|
* @param userID
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String addToCluster(List<String> instances,String clusterId, int userID) {
|
||||||
|
return new MockK8sAPI().loadTo(instances, clusterId).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
|
||||||
|
|
||||||
|
|
||||||
|
class CallableRun implements Callable {
|
||||||
|
|
||||||
|
String id;
|
||||||
|
|
||||||
|
public CallableRun(String id, CountDownLatch countDownLatch) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object call() throws Exception {
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockK8sAPI{
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param instances
|
||||||
|
* @param clusterId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<String> loadTo(List<String> instances, String clusterId) {
|
||||||
|
|
||||||
|
CountDownLatch countDownLatch = new CountDownLatch(instances.size());
|
||||||
|
|
||||||
|
for (String instance : instances) {
|
||||||
|
executor.submit(new Callable<String>() {
|
||||||
|
@Override
|
||||||
|
public String call() throws Exception {
|
||||||
|
try {
|
||||||
|
System.out.println(instance);
|
||||||
|
}finally {
|
||||||
|
countDownLatch.countDown();
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
countDownLatch.await(10, TimeUnit.SECONDS);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
System.out.println("超时");
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return instances;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
13
springDemo/src/main/resources/application.yaml
Normal file
13
springDemo/src/main/resources/application.yaml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
url: jdbc:mysql://localhost:3306/springTest?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
|
||||||
|
username: root
|
||||||
|
password: 123456
|
||||||
|
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
|
|
||||||
|
data:
|
||||||
|
redis:
|
||||||
|
host: localhost
|
||||||
|
port: 6379
|
||||||
|
# 选择db1
|
||||||
|
database: 3
|
Loading…
Reference in New Issue
Block a user