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:
whaifree 2024-10-10 22:49:32 +08:00
parent 3789fe03e3
commit c73979681f
16 changed files with 788 additions and 0 deletions

View 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--;
}
}
}

View File

@ -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);
*/
}

View File

@ -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};
}
}
}

View File

@ -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);
}
}
}

View File

@ -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[]{};
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View 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>();
}
}

View File

@ -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();
} }
} }

View File

@ -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();
}
}

View File

@ -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;
}

View File

@ -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);";
}
}

View File

@ -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:";
}

View File

@ -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;
}

View File

@ -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;
}
}
}

View 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