三次輸錯密碼后,系統是怎么做到不讓我繼續嘗試的?

1故事背景

忘記密碼這件事,相信絕大多數人都遇到過,輸一次錯一次,錯到幾次以上,就不允許你繼續嘗試了。

但當你嘗試重置密碼,又發現新密碼不能和原密碼重復:

圖片

圖片

相信此刻心情只能用一張圖形容:

圖片

圖片

雖然,但是,密碼還是很重要的,順便我有了一個問題:三次輸錯密碼后,系統是怎么做到不讓我繼續嘗試的?

2我想了想,有如下幾個問題需要搞定

  • 是只有輸錯密碼才鎖定,還是賬戶名和密碼任何一個輸錯就鎖定?

  • 輸錯之后也不是完全凍結,為啥隔了幾分鐘又可以重新輸了?

  • 技術棧到底麻不麻煩?

去網上搜了搜,也問了下ChatGPT,找到一套解決方案:SpringBoot+Redis+Lua腳本。

這套方案也不算新,很早就有人在用了,不過難得是自己想到的問題和解法,就記錄一下吧。

順便回答一下上面的三個問題:

  • 鎖定的是IP,不是輸入的賬戶名或者密碼,也就是說任一一個輸錯3次就會被鎖定

  • Redis的Lua腳本中實現了key過期策略,當key消失時鎖定自然也就消失了

  • 技術棧同SpringBoot+Redis+Lua腳本

3那么自己動手實現一下

前端部分

首先寫一個賬密輸入頁面,使用很簡單HTML加表單提交

<!DOCTYPE?html>
<html>
<head><title>登錄頁面</title><style>body?{background-color:?#F5F5F5;}form?{width:?300px;margin:?0?auto;margin-top:?100px;padding:?20px;background-color:?white;border-radius:?5px;box-shadow:?0?0?10px?rgba(0,0,0,0.2);}label?{display:?block;margin-bottom:?10px;}input[type="text"],?input[type="password"]?{border:?none;padding:?10px;margin-bottom:?20px;border-radius:?5px;box-shadow:?0?0?5px?rgba(0,0,0,0.1);width:?100%;box-sizing:?border-box;font-size:?16px;}input[type="submit"]?{background-color:?#30B0F0;color:?white;border:?none;padding:?10px;border-radius:?5px;box-shadow:?0?0?5px?rgba(0,0,0,0.1);width:?100%;font-size:?16px;cursor:?pointer;}input[type="submit"]:hover?{background-color:?#1C90D6;}</style>
</head>
<body><form?action="http://localhost:8080/login"?method="get"><label?for="username">用戶名</label><input?type="text"?id="username"?name="username"?placeholder="請輸入用戶名"?required><label?for="password">密碼</label><input?type="password"?id="password"?name="password"?placeholder="請輸入密碼"?required><input?type="submit"?value="登錄"></form>
</body>
</html>

效果如下:

圖片

圖片

后端部分

技術選型分析

首先我們畫一個流程圖來分析一下這個登錄限制流程

圖片

圖片

  • 從流程圖上看,首先訪問次數的統計與判斷不是在登錄邏輯執行后,而是執行前就加1了;

  • 其次登錄邏輯的成功與失敗并不會影響到次數的統計;

  • 最后還有一點流程圖上沒有體現出來,這個次數的統計是有過期時間的,當過期之后又可以重新登錄了。

那為什么是Redis+Lua腳本呢?

Redis的選擇不難看出,這個流程比較重要的是存在一個用來計數的變量,這個變量既要滿足分布式讀寫需求,還要滿足全局遞增或遞減的需求,那Redis的incr方法是最優選了。

那為什么需要Lua腳本呢?流程上在驗證用戶操作前有些操作,如圖:

圖片

圖片

這里至少有3步Redis的操作,get、incr、expire,如果全放到應用里面來操作,有點慢且浪費資源。

Lua腳本的優點如下:

  • 減少網絡開銷。?可以將多個請求通過腳本的形式一次發送,減少網絡時延。

  • 原子操作。?Redis會將整個腳本作為一個整體執行,中間不會被其他請求插入。因此在腳本運行過程中無需擔心會出現競態條件,無需使用事務。

  • 復用。?客戶端發送的腳本會永久存在redis中,這樣其他客戶端可以復用這一腳本,而不需要使用代碼完成相同的邏輯。

最后為了增加功能的復用性,我打算使用Java注解的方式實現這個功能。

代碼實現

項目結構如下

圖片

圖片

配置文件

pom.xml

<?xml?version="1.0"?encoding="UTF-8"?>
<project?xmlns="http://maven.apache.org/POM/4.0.0"?xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0?https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.11</version><relativePath/>?<!--?lookup?parent?from?repository?--></parent><groupId>com.example</groupId><artifactId>LoginLimit</artifactId><version>0.0.1-SNAPSHOT</version><name>LoginLimit</name><description>Demo?project?for?Spring?Boot</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!--?redis?--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!--?Jedis?--><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId></dependency><!--切面依賴?--><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId></dependency><!--?commons-lang3?--><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId></dependency><!--?guava?--><dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>23.0</version></dependency><!--?lombok?--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

application.properties

##?Redis配置
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=
spring.redis.timeout=1000
##?Jedis配置
spring.redis.jedis.pool.min-idle=0
spring.redis.jedis.pool.max-idle=500
spring.redis.jedis.pool.max-active=2000
spring.redis.jedis.pool.max-wait=10000

注解部分

LimitCount.java

package?com.example.loginlimit.annotation;import?java.lang.annotation.ElementType;
import?java.lang.annotation.Retention;
import?java.lang.annotation.RetentionPolicy;
import?java.lang.annotation.Target;/***?次數限制注解*?作用在接口方法上*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public?@interface?LimitCount?{/***?資源名稱,用于描述接口功能*/String?name()?default?"";/***?資源?key*/String?key()?default?"";/***?key?prefix**?@return*/String?prefix()?default?"";/***?時間的,單位秒*?默認60s過期*/int?period()?default?60;/***?限制訪問次數*?默認3次*/int?count()?default?3;
}

核心處理邏輯類:LimitCountAspect.java

package?com.example.loginlimit.aspect;import?java.io.Serializable;
import?java.lang.reflect.Method;
import?java.util.Objects;import?javax.servlet.http.HttpServletRequest;import?com.example.loginlimit.annotation.LimitCount;
import?com.example.loginlimit.util.IPUtil;
import?com.google.common.collect.ImmutableList;
import?lombok.extern.slf4j.Slf4j;
import?org.apache.commons.lang3.StringUtils;
import?org.aspectj.lang.ProceedingJoinPoint;
import?org.aspectj.lang.annotation.Around;
import?org.aspectj.lang.annotation.Aspect;
import?org.aspectj.lang.annotation.Pointcut;
import?org.aspectj.lang.reflect.MethodSignature;
import?org.springframework.beans.factory.annotation.Autowired;
import?org.springframework.data.redis.core.RedisTemplate;
import?org.springframework.data.redis.core.script.DefaultRedisScript;
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;@Slf4j
@Aspect
@Component
public?class?LimitCountAspect?{private?final?RedisTemplate<String,?Serializable>?limitRedisTemplate;@Autowiredpublic?LimitCountAspect(RedisTemplate<String,?Serializable>?limitRedisTemplate)?{this.limitRedisTemplate?=?limitRedisTemplate;}@Pointcut("@annotation(com.example.loginlimit.annotation.LimitCount)")public?void?pointcut()?{//?do?nothing}@Around("pointcut()")public?Object?around(ProceedingJoinPoint?point)?throws?Throwable?{HttpServletRequest?request?=?((ServletRequestAttributes)Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();MethodSignature?signature?=?(MethodSignature)point.getSignature();Method?method?=?signature.getMethod();LimitCount?annotation?=?method.getAnnotation(LimitCount.class);//注解名稱String?name?=?annotation.name();//注解keyString?key?=?annotation.key();//訪問IPString?ip?=?IPUtil.getIpAddr(request);//過期時間int?limitPeriod?=?annotation.period();//過期次數int?limitCount?=?annotation.count();ImmutableList<String>?keys?=?ImmutableList.of(StringUtils.join(annotation.prefix()?+?"_",?key,?ip));String?luaScript?=?buildLuaScript();RedisScript<Number>?redisScript?=?new?DefaultRedisScript<>(luaScript,?Number.class);Number?count?=?limitRedisTemplate.execute(redisScript,?keys,?limitCount,?limitPeriod);log.info("IP:{}?第?{}?次訪問key為?{},描述為?[{}]?的接口",?ip,?count,?keys,?name);if?(count?!=?null?&&?count.intValue()?<=?limitCount)?{return?point.proceed();}?else?{return?"接口訪問超出頻率限制";}}/***?限流腳本*?調用的時候不超過閾值,則直接返回并執行計算器自加。**?@return?lua腳本*/private?String?buildLuaScript()?{return?"local?c"?+"\nc?=?redis.call('get',KEYS[1])"?+"\nif?c?and?tonumber(c)?>?tonumber(ARGV[1])?then"?+"\nreturn?c;"?+"\nend"?+"\nc?=?redis.call('incr',KEYS[1])"?+"\nif?tonumber(c)?==?1?then"?+"\nredis.call('expire',KEYS[1],ARGV[2])"?+"\nend"?+"\nreturn?c;";}}

獲取IP地址的功能我寫了一個工具類IPUtil.java,代碼如下:

package?com.example.loginlimit.util;import?javax.servlet.http.HttpServletRequest;public?class?IPUtil?{private?static?final?String?UNKNOWN?=?"unknown";protected?IPUtil()?{}/***?獲取?IP地址*?使用?Nginx等反向代理軟件,?則不能通過?request.getRemoteAddr()獲取?IP地址*?如果使用了多級反向代理的話,X-Forwarded-For的值并不止一個,而是一串IP地址,*?X-Forwarded-For中第一個非?unknown的有效IP字符串,則為真實IP地址*/public?static?String?getIpAddr(HttpServletRequest?request)?{String?ip?=?request.getHeader("x-forwarded-for");if?(ip?==?null?||?ip.length()?==?0?||?UNKNOWN.equalsIgnoreCase(ip))?{ip?=?request.getHeader("Proxy-Client-IP");}if?(ip?==?null?||?ip.length()?==?0?||?UNKNOWN.equalsIgnoreCase(ip))?{ip?=?request.getHeader("WL-Proxy-Client-IP");}if?(ip?==?null?||?ip.length()?==?0?||?UNKNOWN.equalsIgnoreCase(ip))?{ip?=?request.getRemoteAddr();}return?"0:0:0:0:0:0:0:1".equals(ip)???"127.0.0.1"?:?ip;}}

另外就是Lua限流腳本的說明,腳本代碼如下:

??private?String?buildLuaScript()?{return?"local?c"?+"\nc?=?redis.call('get',KEYS[1])"?+"\nif?c?and?tonumber(c)?>?tonumber(ARGV[1])?then"?+"\nreturn?c;"?+"\nend"?+"\nc?=?redis.call('incr',KEYS[1])"?+"\nif?tonumber(c)?==?1?then"?+"\nredis.call('expire',KEYS[1],ARGV[2])"?+"\nend"?+"\nreturn?c;";}

這段腳本有一個判斷,?tonumber(c) > tonumber(ARGV[1])這行表示如果當前key 的值大于了limitCount,直接返回;否則調用incr方法進行累加1,且調用expire方法設置過期時間。

最后就是RedisConfig.java,代碼如下:

package?com.example.loginlimit.config;import?java.io.IOException;
import?java.io.Serializable;
import?java.time.Duration;
import?java.util.Arrays;import?com.fasterxml.jackson.core.JsonProcessingException;
import?com.fasterxml.jackson.databind.ObjectMapper;
import?org.apache.commons.lang3.StringUtils;
import?org.springframework.beans.factory.annotation.Value;
import?org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import?org.springframework.cache.CacheManager;
import?org.springframework.cache.annotation.CachingConfigurerSupport;
import?org.springframework.cache.interceptor.KeyGenerator;
import?org.springframework.context.annotation.Bean;
import?org.springframework.context.annotation.Configuration;
import?org.springframework.data.redis.cache.RedisCacheManager;
import?org.springframework.data.redis.connection.RedisConnectionFactory;
import?org.springframework.data.redis.connection.RedisPassword;
import?org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import?org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import?org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import?org.springframework.data.redis.core.RedisTemplate;
import?org.springframework.data.redis.core.StringRedisTemplate;
import?org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import?org.springframework.data.redis.serializer.RedisSerializer;
import?org.springframework.data.redis.serializer.SerializationException;
import?org.springframework.data.redis.serializer.StringRedisSerializer;
import?redis.clients.jedis.JedisPool;
import?redis.clients.jedis.JedisPoolConfig;@Configuration
public?class?RedisConfig?extends?CachingConfigurerSupport?{@Value("${spring.redis.host}")private?String?host;@Value("${spring.redis.port}")private?int?port;@Value("${spring.redis.password}")private?String?password;@Value("${spring.redis.timeout}")private?int?timeout;@Value("${spring.redis.jedis.pool.max-idle}")private?int?maxIdle;@Value("${spring.redis.jedis.pool.max-wait}")private?long?maxWaitMillis;@Value("${spring.redis.database:0}")private?int?database;@Beanpublic?JedisPool?redisPoolFactory()?{JedisPoolConfig?jedisPoolConfig?=?new?JedisPoolConfig();jedisPoolConfig.setMaxIdle(maxIdle);jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);if?(StringUtils.isNotBlank(password))?{return?new?JedisPool(jedisPoolConfig,?host,?port,?timeout,?password,?database);}?else?{return?new?JedisPool(jedisPoolConfig,?host,?port,?timeout,?null,?database);}}@BeanJedisConnectionFactory?jedisConnectionFactory()?{RedisStandaloneConfiguration?redisStandaloneConfiguration?=?new?RedisStandaloneConfiguration();redisStandaloneConfiguration.setHostName(host);redisStandaloneConfiguration.setPort(port);redisStandaloneConfiguration.setPassword(RedisPassword.of(password));redisStandaloneConfiguration.setDatabase(database);JedisClientConfiguration.JedisClientConfigurationBuilder?jedisClientConfiguration?=?JedisClientConfiguration.builder();jedisClientConfiguration.connectTimeout(Duration.ofMillis(timeout));jedisClientConfiguration.usePooling();return?new?JedisConnectionFactory(redisStandaloneConfiguration,?jedisClientConfiguration.build());}@Bean(name?=?"redisTemplate")@SuppressWarnings({"rawtypes"})@ConditionalOnMissingBean(name?=?"redisTemplate")public?RedisTemplate<Object,?Object>?redisTemplate(RedisConnectionFactory?redisConnectionFactory)?{RedisTemplate<Object,?Object>?template?=?new?RedisTemplate<>();//使用?fastjson?序列化JacksonRedisSerializer?jacksonRedisSerializer?=?new?JacksonRedisSerializer<>(Object.class);//?value?值的序列化采用?fastJsonRedisSerializertemplate.setValueSerializer(jacksonRedisSerializer);template.setHashValueSerializer(jacksonRedisSerializer);//?key?的序列化采用?StringRedisSerializertemplate.setKeySerializer(new?StringRedisSerializer());template.setHashKeySerializer(new?StringRedisSerializer());template.setConnectionFactory(redisConnectionFactory);return?template;}//緩存管理器@Beanpublic?CacheManager?cacheManager(RedisConnectionFactory?redisConnectionFactory)?{RedisCacheManager.RedisCacheManagerBuilder?builder?=?RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(redisConnectionFactory);return?builder.build();}@Bean@ConditionalOnMissingBean(StringRedisTemplate.class)public?StringRedisTemplate?stringRedisTemplate(RedisConnectionFactory?redisConnectionFactory)?{StringRedisTemplate?template?=?new?StringRedisTemplate();template.setConnectionFactory(redisConnectionFactory);return?template;}@Beanpublic?KeyGenerator?wiselyKeyGenerator()?{return?(target,?method,?params)?->?{StringBuilder?sb?=?new?StringBuilder();sb.append(target.getClass().getName());sb.append(method.getName());Arrays.stream(params).map(Object::toString).forEach(sb::append);return?sb.toString();};}@Beanpublic?RedisTemplate<String,?Serializable>?limitRedisTemplate(RedisConnectionFactory?redisConnectionFactory)?{RedisTemplate<String,?Serializable>?template?=?new?RedisTemplate<>();template.setKeySerializer(new?StringRedisSerializer());template.setValueSerializer(new?GenericJackson2JsonRedisSerializer());template.setConnectionFactory(redisConnectionFactory);return?template;}
}class?JacksonRedisSerializer<T>?implements?RedisSerializer<T>?{private?Class<T>?clazz;private?ObjectMapper?mapper;JacksonRedisSerializer(Class<T>?clazz)?{super();this.clazz?=?clazz;this.mapper?=?new?ObjectMapper();mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);}@Overridepublic?byte[]?serialize(T?t)?throws?SerializationException?{try?{return?mapper.writeValueAsBytes(t);}?catch?(JsonProcessingException?e)?{e.printStackTrace();return?null;}}@Overridepublic?T?deserialize(byte[]?bytes)?throws?SerializationException?{if?(bytes.length?<=?0)?{return?null;}try?{return?mapper.readValue(bytes,?clazz);}?catch?(IOException?e)?{e.printStackTrace();return?null;}}
}

LoginController.java

package?com.example.loginlimit.controller;import?javax.servlet.http.HttpServletRequest;import?com.example.loginlimit.annotation.LimitCount;
import?lombok.extern.slf4j.Slf4j;
import?org.apache.commons.lang3.StringUtils;
import?org.springframework.web.bind.annotation.GetMapping;
import?org.springframework.web.bind.annotation.RequestParam;
import?org.springframework.web.bind.annotation.RestController;@Slf4j
@RestController
public?class?LoginController?{@GetMapping("/login")@LimitCount(key?=?"login",?name?=?"登錄接口",?prefix?=?"limit")public?String?login(@RequestParam(required?=?true)?String?username,@RequestParam(required?=?true)?String?password,?HttpServletRequest?request)?throws?Exception?{if?(StringUtils.equals("張三",?username)?&&?StringUtils.equals("123456",?password))?{return?"登錄成功";}return?"賬戶名或密碼錯誤";}}

LoginLimitApplication.java

package?com.example.loginlimit;import?org.springframework.boot.SpringApplication;
import?org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public?class?LoginLimitApplication?{public?static?void?main(String[]?args)?{SpringApplication.run(LoginLimitApplication.class,?args);}}

4演示一下效果

圖片

圖片

上面這套限流的邏輯感覺用在小型或中型的項目上應該問題不大,不過目前的登錄很少有直接鎖定賬號不能輸入的,一般都是彈出一個驗證碼框,讓你輸入驗證碼再提交。我覺得用我這套邏輯改改應該不成問題,核心還是接口嘗試次數的限制嘛!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/166187.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/166187.shtml
英文地址,請注明出處:http://en.pswp.cn/news/166187.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Mobaxterm 使用lrzsz傳輸文件(rz/sz)

Mobaxterm 使用lrzsz傳輸文件報錯 1. 現象 最近從xshell切換到Mobaxterm其他一切正常,就是使用rz傳輸文件時會出現錯誤,比較苦惱. 會出現以下錯誤 [rootcentos7 rpmbuild]# rz ?CCCCCCCCCCC23be50ive.**B0100000023be502. 解決方法 去官網(https://mobaxterm.mobatek.net…

2021年03月 Scratch(三級)真題解析#中國電子學會#全國青少年軟件編程等級考試

Scratch等級考試(1~4級)全部真題?點這里 一、單選題(共25題,每題2分,共50分) 第1題 在《采礦》游戲中,當角色撿到黃金時財富值加1分,撿到鉆石時財富值加2分,下面哪個程序實現這個功能? A: B: C: D: 答案:D A將變量值固定,BC為雙重判斷

練習七-在Verilog中使用任務task

在Verilog中使用任務task 1&#xff0c;任務目的2&#xff0c;RTL代碼&#xff0c;交換3&#xff0c;測試代碼4&#xff0c;波形顯示 1&#xff0c;任務目的 &#xff08;1&#xff09;掌握任務在verilog模塊設計中的應用&#xff1b; &#xff08;2&#xff09;學會在電平敏感…

Android Studio記錄一個錯誤:Execution failed for task ‘:app:lintVitalRelease‘.

Android出現Execution failed for task :app:lintVitalRelease.> Lint found fatal errors while assembling a release target. Execution failed for task :app:lintVitalRelease解決方法 Execution failed for task ‘:app:lintVitalRelease’ build project 可以正常執…

〖大前端 - 基礎入門三大核心之JS篇?〗- DOM事件對象及它的屬性

說明&#xff1a;該文屬于 大前端全棧架構白寶書專欄&#xff0c;目前階段免費&#xff0c;如需要項目實戰或者是體系化資源&#xff0c;文末名片加V&#xff01;作者&#xff1a;不渴望力量的哈士奇(哈哥)&#xff0c;十余年工作經驗, 從事過全棧研發、產品經理等工作&#xf…

進程已結束,退出代碼-1073741571 (0xC00000FD)

今天遇到了一個很邪門的問題&#xff0c;沒有報錯&#xff0c;只是提示“進程已結束,退出代碼-1073741571 (0xC00000FD)”。后來查資料說是棧溢出。 出問題的應該是上面這段代碼。 這里我想把一個128*128的矩陣進行剪枝操作。 傳入的128*128的矩陣太大了&#xff0c;兩組for循…

介紹GLFW庫和OpenGL和GLEW庫三者之間的關系

具體來說&#xff0c;OpenGL是一個開放的圖形庫&#xff0c;它規定了每個函數應該如何執行&#xff0c;以及它們的輸出值&#xff0c;但沒有具體實現。它提供了渲染2D和3D圖形的標準或規范。 GLEW&#xff0c;全稱OpenGL Extension Wrangler Library&#xff0c;是一個用于管理…

【Flink】狀態管理

目錄 1、狀態概述 1.1 無狀態算子 1.2 有狀態算子 2、狀態分類 ?編輯 2.1 算子狀態 2.1.1 列表狀態&#xff08;ListState&#xff09; 2.1.2 聯合列表狀態&#xff08;UnionListState&#xff09; 2.1.3 廣播狀態&#xff08;BroadcastState&#xff09; 2.2 按鍵分…

Redis Transaction事務

Redis 事務的目的是方便用戶一次執行多個命令。執行 Redis 事務可分為三個階段&#xff1a; 開始事務命令入隊執行事務 Redis事務特性 Redis 事務具有兩個重要特性&#xff1a; 1) 單獨的隔離操作 事務中的所有命令都會被序列化&#xff0c;它們將按照順序執行&#xff0c…

圖像標記上線,描點信息盡在掌握丨三疊云

圖像標記 路徑 表單設計 >> 組件 >> 增強組件 功能簡介 「圖像標記」字段是「增強字段」類型字段。用戶通過上傳圖片的方式構建一個背景圖片&#xff0c;并在構建的圖片背景上添加描點信息。搭配「儀表盤」中的「圖像軌跡」&#xff0c;可繪制出相應的數據軌跡…

界面組件DevExpress Reporting v23.1 - Web報表設計器功能升級

DevExpress Reporting是.NET Framework下功能完善的報表平臺&#xff0c;它附帶了易于使用的Visual Studio報表設計器和豐富的報表控件集&#xff0c;包括數據透視表、圖表&#xff0c;因此您可以構建無與倫比、信息清晰的報表 界面組件DevExpress Reporting v23.1已經發布一段…

基于JavaWeb+SSM+Vue微信閱讀小程序的設計和實現

基于JavaWebSSMVue微信閱讀小程序的設計和實現 源碼獲取入口Lun文目錄前言主要技術系統設計功能截圖訂閱經典源碼專欄[Java 源碼獲取 源碼獲取入口 Lun文目錄 第1章 緒論 1 1.1 課題背景 1 1.2 課題意義 1 1.3 研究內容 1 第2章 開發環境與技術 3 2.1 MYSQL數據庫 3 2.2 JSP技…

2016年8月15日 Go生態洞察:Go 1.7版本發布

&#x1f337;&#x1f341; 博主貓頭虎&#xff08;&#x1f405;&#x1f43e;&#xff09;帶您 Go to New World?&#x1f341; &#x1f984; 博客首頁——&#x1f405;&#x1f43e;貓頭虎的博客&#x1f390; &#x1f433; 《面試題大全專欄》 &#x1f995; 文章圖文…

解決traefik/nginx-ingress-controller配置正確的情況訪問域名仍然報錯: Connection Refused的問題

最近碰到一個很奇怪的問題&#xff1a; traefik/nginx-ingress-controller配置正確&#xff0c;但是訪問ingress配置的host域名就是死活報錯&#xff1a; Connection Refused 這樣怎么也找不到原因&#xff0c;然后一咬牙直接在其中一臺節點yum安裝nginx, 通過直接反向代理的方…

微信小程序開發資源匯總

本文收集了微信小程序開發過程中會使用到的資料、問題以及第三方組件庫。本文不是一篇關于如何學習微信小程序的入門指南&#xff0c;也非參考手冊&#xff0c;只是一些資料的整理。 本倉庫中的資料整理自網絡&#xff0c;也有一些來自網友的推薦。 官方文檔 小程序設計指南…

UE5 UI教程學習筆記

參考資料&#xff1a;https://item.taobao.com/item.htm?spma21n57.1.0.0.2b4f523cAV5i43&id716635137219&ns1&abbucket15#detail 基礎工程&#xff1a;https://download.csdn.net/download/qq_17523181/88559312 1. 介紹 工程素材 2. 創建Widget UE5 UI系統的…

那些被玩爛了的設計模式

單例模式 單例模式是指一個類在一個進程中只有一個實例對象&#xff08;但也不一定&#xff0c;比如Spring中的Bean的單例是指在一個容器中是單例的&#xff09; 單例模式創建分為餓漢式和懶漢式&#xff0c;總共大概有8種寫法。但是在開源項目中使用最多的主要有兩種寫法&am…

electron實現截圖的功能

Electron是一種跨平臺的桌面應用程序開發框架&#xff0c;可以使用HTML、CSS和JavaScript等Web技術構建桌面應用程序。下面是一種使用Electron實現截圖的簡單方法&#xff1a; 安裝Electron和截圖庫 首先&#xff0c;需要安裝Electron和一個截圖庫&#xff0c;例如electron-sc…

替換jar文件中的jar文件中的class

文件格式 testjar.jar在ruoyi.jar中。 AssetServiceImpl.class在testjar.jar 查找testjar.jar路徑 jar -tvf ruoyi.jar | grep testjar.jar 解析testjar.jar jar -xvf ruoyi.jar BOOT-INF/lib/testjar.jar 查找class文件路徑 jar -tvf testjar.jar | grep AssetServiceImp…

ELK: logstash gork filter 多個模式(pattern)匹配規則語法和多行日志匹配設置

項目里用logstash分析日志&#xff0c;由于有多種模式&#xff08;pattern&#xff09;需要匹配&#xff0c;網上搜了很多示例&#xff0c;發現這些都是老的寫法&#xff0c;都會報錯&#xff0c;后來查閱了官方文檔&#xff0c;才發現&#xff0c;新版本只支持新語法。 錯誤的…