SpringBoot:基于 Redis 自定義注解實現后端接口防重復提交校驗(冪等操作)

SpringBoot:基于 Redis 自定義注解實現后端接口防重復提交校驗(冪等操作)


可基于 時間間隔用于冪等判斷的參數名稱 實現防重復提交校驗

客戶端發送請求 ↓
[Spring Boot 應用入口]↓
┌─────────────────────────────────────────┐
│        CacheRequestFilter        		│ │ // 第一步:請求體緩存過濾器
│  ┌────────────────────────────────────┐ │
│  │ 判斷請求類型:              	        │ │
│  │ - 非JSON類型請求 → 直接放行			│ │
│  │ - JSON類型請求 (POST/PUT等)   		│ │
│  │   → 用CacheRequestWrapper包裝 		│ │
│  │   → 緩存請求體到內存(支持重復讀取)	│ │
│  └────────────────────────────────────┘ │
└─────────────────────────────────────────┘↓
[DispatcherServlet 路由分發]  // 匹配到標注 @NoDuplicateSubmit 的控制器方法↓
┌─────────────────────────────────────────────────┐
│    NoDuplicateSubmitAspect     			    │ │ // 第二步:AOP切面攔截
│  ┌────────────────────────────────────────────┐ │
│  │ 構建Redis防重校驗Key:       				│ │
│  │ 1. 前綴(@NoDuplicateSubmit.prefix)		│ │
│  │ 2. 請求方法+路徑(如POST:/api/submit)		│ │
│  │ 3. 當前用戶ID(SecurityContextHolder獲取)	│ │
│  │ 4. 參數哈希值:            				    │ │
│  │    - 若指定paramNames → 用SpEL提取對應參數	│ │
│  │    - 若未指定但allParamVerify=true → 所有參數	│ │
│  │    - 否則 → 固定標識"none"					│ │
│  │    → 用MurmurHash計算哈希并轉Base64			│ │
│  └────────────────────────────────────────────┘ │
│  ┌────────────────────────────────────────────┐ │
│  │ Redis重復校驗:								│ │
│  │ - 執行setIfAbsent(原子操作)				    │ │
│  │   → 若Key不存在 → 正常執行   				    │ │
│  │     (設置Key并指定過期時間)				    │ │
│  │   → 若Key已存在 → 重復提交   				    │ │
│  └────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘├─ 重復提交 → 拋出ServerException│       ↓│  ┌─────────────────────────┐│  │ GlobalExceptionHandler  │  // 捕獲異常,返回友好提示│  └─────────────────────────┘│       ↓│  客戶端收到"請勿重復提交"錯誤│└─ 正常提交 → 執行控制器方法↓控制器處理業務邏輯(可重復讀取請求體)↓客戶端收到處理結果

具體操作如下:


Spring Boot 2.x + Spring Framework 5.x 版本

一、添加依賴

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- AOP --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId><version>2.0.36</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.24</version></dependency><dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>30.0-jre</version></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.14.0</version></dependency>

二、 構建可重復讀取inputStream的request

HTTP 請求體的輸入流 ( ServletInputStream ) 只能被讀取一次。當 AOP 攔截器(如日志切面、參數校驗切面)和控制器都需要讀取請求體時,如果不做處理,后續讀取會拋出異常

import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.MediaType;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;/*** HttpServletReqeust使請求輸入流支持二次讀取*/
public class CacheRequestFilter implements Filter {@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {ServletRequest requestWrapper = null;if (request instanceof HttpServletRequest&& StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) {requestWrapper = new CacheRequestWrapper((HttpServletRequest) request);}if (null == requestWrapper) {chain.doFilter(request, response);} else {chain.doFilter(requestWrapper, response);}}public static class CacheRequestWrapper extends HttpServletRequestWrapper {private final String requestBody;public CacheRequestWrapper(HttpServletRequest request) throws IOException {super(request);StringBuilder sb = new StringBuilder();try (BufferedReader reader = request.getReader()) {String line;while ((line = reader.readLine()) != null) {sb.append(line);}}this.requestBody = sb.toString();}public String getRequestBody() {return this.requestBody;}@Overridepublic ServletInputStream getInputStream() throws IOException {ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(requestBody.getBytes());return new ServletInputStream() {@Overridepublic boolean isFinished() {return byteArrayInputStream.available() == 0;}@Overridepublic boolean isReady() {return true;}@Overridepublic void setReadListener(ReadListener readListener) {}@Overridepublic int read() throws IOException {return byteArrayInputStream.read();}};}@Overridepublic BufferedReader getReader() throws IOException {return new BufferedReader(new InputStreamReader(this.getInputStream()));}}}

三、使自定義的Filter生效

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class FilterConfig {@Beanpublic FilterRegistrationBean someFilterRegistration() {FilterRegistrationBean registration = new FilterRegistrationBean();registration.setFilter(new CacheRequestFilter());registration.addUrlPatterns("/*");registration.setName("cacheRequestFilter");registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);return registration;}
}

四、防重復提交常量類


/*** 防重復提交常量類*/
public final class NoDuplicateSubmitConstant {public static final String RESUBMIT_MSG = "請勿重復提交數據";public static final String REDIS_SEPARATOR = ":";public static final String RESUBMIT_CHECK_KEY_PREFIX = "no-duplicate-submit";}

五、創建防重復提交注解

創建一個自定義注解 @NoDuplicateSubmit ,用于標識需要防重復提交的方法


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;/*** 冪等注解,防止用戶重復提交表單信息*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NoDuplicateSubmit {/*** 觸發冪等失敗邏輯時,返回的錯誤提示信息*/String message() default NoDuplicateSubmitConstant.RESUBMIT_MSG;/*** 防重復提交校驗的時間間隔*/long interval() default 10;/*** 防重復提交校驗的時間間隔的單位*/TimeUnit timeUnit() default TimeUnit.SECONDS;/*** 自定義 Redis Key 前綴*/String prefix() default NoDuplicateSubmitConstant.RESUBMIT_CHECK_KEY_PREFIX;/*** 指定參與冪等判斷的參數名稱* 例如:Param傳參   @NoDuplicateSubmit(paramNames = {"#name"})   表示只使用name參數計算哈希*      Body傳參    @NoDuplicateSubmit(paramNames = {"#user.name"})  表示只使用user對象下的name參數計算哈希*/String[] paramNames() default {};/*** 僅當 {@link #paramNames()} 為空時, 開啟此開關,選擇是否校驗全部參數*/boolean allParamVerify() default false;}

六、創建防止重復提交攔截器

創建一個AOP切面類,用于攔截標注了 @NoDuplicateSubmit 注解的方法,并檢查是否重復提交

import com.alibaba.fastjson2.JSON;
import com.google.common.hash.Hashing;
import lombok.RequiredArgsConstructor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.rmi.ServerException;
import java.util.*;
import java.util.concurrent.TimeUnit;/*** 防止用戶重復提交表單信息切面控制器*/
@Aspect
@RequiredArgsConstructor
@Component
public final class NoDuplicateSubmitAspect {private static final Logger log = LoggerFactory.getLogger(NoDuplicateSubmitAspect.class);private final RedisTemplate<String, Object> redisTemplate;/*** 增強方法標記 {@link NoDuplicateSubmit} 注解邏輯*/@Around("@annotation(noDuplicateSubmit)")public Object noDuplicateSubmit(ProceedingJoinPoint joinPoint, NoDuplicateSubmit noDuplicateSubmit) throws Throwable {final String lockKey = buildLockKey(joinPoint, noDuplicateSubmit);final String message = noDuplicateSubmit.message();final long interval = noDuplicateSubmit.interval();final TimeUnit timeUnit = noDuplicateSubmit.timeUnit();// 原子操作:如果 key 不存在,則設置 key 并過期;如果存在,直接返回 falseBoolean isAbsent = redisTemplate.opsForValue().setIfAbsent(lockKey, "submit", interval, timeUnit);if (Boolean.FALSE.equals(isAbsent)) {// key 已存在 → 重復提交,拋異常throw new ServerException(message);}// key 不存在 → 正常執行方法(無需手動刪除 key,過期后自動刪除)return joinPoint.proceed();}/*** @param joinPoint* @return 構建重復提交的key*/private String buildLockKey(ProceedingJoinPoint joinPoint, @NonNull NoDuplicateSubmit noDuplicateSubmit) {StringBuilder keyBuilder =new StringBuilder(noDuplicateSubmit.prefix()).append(NoDuplicateSubmitConstant.REDIS_SEPARATOR).append(getMethodAndServletPath()).append(NoDuplicateSubmitConstant.REDIS_SEPARATOR).append(getCurrentUserId()).append(NoDuplicateSubmitConstant.REDIS_SEPARATOR).append(calcArgsMurmurHash(joinPoint, noDuplicateSubmit));return keyBuilder.toString();}/*** @return 獲取當前線程上下文 Method + ServletPath*/private String getMethodAndServletPath() {HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();return request.getMethod() + NoDuplicateSubmitConstant.REDIS_SEPARATOR + request.getServletPath();}/*** @return 當前操作用戶 ID*/private Long getCurrentUserId() {return SecurityContextHolder.getUserId();}/*** @return joinPoint 采用google的MurmurHash算法計算哈希做校驗*/private String calcArgsMurmurHash(ProceedingJoinPoint joinPoint, NoDuplicateSubmit noDuplicateSubmit) {final String[] paramNames = noDuplicateSubmit.paramNames();final boolean allParamVerify = noDuplicateSubmit.allParamVerify();MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();Method method = methodSignature.getMethod();Object[] args = joinPoint.getArgs();// 處理 paramNames 為空的場景if (paramNames.length == 0) {if (allParamVerify) {byte[] hashBytes = Hashing.murmur3_128().hashBytes(JSON.toJSONBytes(args)).asBytes();return Base64.getUrlEncoder().withoutPadding().encodeToString(hashBytes);} else {// 不校驗參數,返回固定標識return "none";}}Object[] argsForKey = ExpressionUtils.getExpressionValueAliasAble(args, method, paramNames);// 使用 Google Guava 的 Hashing 生成 128 位哈希byte[] hashBytes = Hashing.murmur3_128().hashBytes(JSON.toJSONBytes(argsForKey)).asBytes();// 轉為 Base64 編碼return Base64.getUrlEncoder().withoutPadding().encodeToString(hashBytes);}
}

在這個切面類中,我們通過@Around注解攔截所有標注了 @NoDuplicateSubmit 注解的方法。通過Redis,我們為每個請求生成一個唯一的key,并設置一個過期時間。如果在過期時間內再次提交相同的請求,就會被攔截。

七、SpEL工具類

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;public class ExpressionUtils {private static final Map<String, Expression> EXPRESSION_CACHE = new ConcurrentHashMap<>(64);private static final ExpressionParser SPEL_PARSER = new SpelExpressionParser();/*** 可以通過別名獲取表達式的值,類似于spring cache的用法 可以給參數指定別名** @param arguments         方法* @param method            參數* @param expressionsString Spring EL表達式字符串* @param <T>               類型* @return 結果集*/@Nullablepublic static <T> T[] getExpressionValueAliasAble(@Nullable Object[] arguments, @NonNull Method method, String... expressionsString) {if (ArrayUtils.isEmpty(arguments) || ArrayUtils.isEmpty(expressionsString)) {return null;}Object[] result = new Object[expressionsString.length];for (int i = 0; i < result.length; i++) {result[i] = getExpressionValueAliasAble(arguments, method, expressionsString[i]);}//noinspection uncheckedreturn (T[]) result;}/*** 可以通過別名獲取表達式的值,類似于spring cache的用法 可以給參數指定別名** @param arguments        參數* @param method           方法* @param expressionString Spring EL表達式字符串* @param <T>              類型* @return 結果*/@Nullablepublic static <T> T getExpressionValueAliasAble(@Nullable Object[] arguments, @NonNull Method method, String expressionString) {if (ArrayUtils.isEmpty(arguments) || StringUtils.isBlank(expressionString)) {return null;}Expression expression = getExpression(expressionString);if (expression == null) {return null;}MethodBasedEvaluationContext evaluationContext = getEvaluationContextAliasAble(arguments, method);return (T) expression.getValue(evaluationContext);}/*** 獲取Expression對象** @param expressionString Spring EL 表達式字符串 例如 #{param.id}* @return Expression*/@Nullablepublic static Expression getExpression(@Nullable String expressionString) {if (StringUtils.isBlank(expressionString)) {return null;}if (EXPRESSION_CACHE.containsKey(expressionString)) {return EXPRESSION_CACHE.get(expressionString);}Expression expression = SPEL_PARSER.parseExpression(expressionString);EXPRESSION_CACHE.put(expressionString, expression);return expression;}/*** 獲取可以通過別名查找的EvaluationContext,類似于spring cache的用法 #a0.id,#p1.name** @param arguments 方法入參* @param method    方法* @return MethodBasedEvaluationContext*/@NonNullpublic static MethodBasedEvaluationContext getEvaluationContextAliasAble(@NonNull Object[] arguments, @NonNull Method method) {return new MethodBasedEvaluationContext(arguments, method, arguments, new LocalVariableTableParameterNameDiscoverer());}}

八、自定義異常處理

為防重復提交功能添加自定義異常處理,使其返回更加友好的錯誤信息:


/*** 全局異常捕獲類*/
@RestControllerAdvice
public class GlobalExceptionHandler {private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);@ExceptionHandler(value = ServerException.class)public ResultInfo abstractExceptionHandle(HttpServletRequest request, AbstractException ex) {logger.error("========================================== ServerException-Start ==========================================");String params = getRequestParams(request);logger.error("RequestURL     : {}", request.getRequestURL());logger.error("HTTP Method    : {}", request.getMethod());logger.error("Params         : {}", params);logger.error("IP             : {}", request.getRemoteAddr());logger.error("Cause          : ", ex);logger.error("ExMessage      : {}", ex.getMessage());logger.info("=========================================== ServerException-End ===========================================");return ResultInfo.error(ex);}


Spring Boot 3.x + Spring Framework6.x版本

只需更新 ExpressionUtils SpEL工具類 ,其他的方法和上面一樣

LocalVariableTableParameterNameDiscovererSpring 6.0.1 中被標記為 deprecated(過時) 并計劃移除,主要原因是 Spring 引入了更高效的參數名發現機制 StandardReflectionParameterNameDiscoverer
以下是替代方案:


import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.StandardReflectionParameterNameDiscoverer;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;public class ExpressionUtils {private static final Map<String, Expression> EXPRESSION_CACHE = new ConcurrentHashMap<>(64);private static final ExpressionParser SPEL_PARSER = new SpelExpressionParser();/*** 可以通過別名獲取表達式的值,類似于spring cache的用法 可以給參數指定別名** @param arguments         方法* @param method            參數* @param expressionsString Spring EL表達式字符串* @param <T>               類型* @return 結果集*/@Nullablepublic static <T> T[] getExpressionValueAliasAble(@Nullable Object[] arguments, @NonNull Method method, String... expressionsString) {if (ArrayUtils.isEmpty(arguments) || ArrayUtils.isEmpty(expressionsString)) {return null;}Object[] result = new Object[expressionsString.length];for (int i = 0; i < result.length; i++) {result[i] = getExpressionValueAliasAble(arguments, method, expressionsString[i]);}//noinspection uncheckedreturn (T[]) result;}/*** 可以通過別名獲取表達式的值,類似于spring cache的用法 可以給參數指定別名** @param arguments        參數* @param method           方法* @param expressionString Spring EL表達式字符串* @param <T>              類型* @return 結果*/@Nullablepublic static <T> T getExpressionValueAliasAble(@Nullable Object[] arguments, @NonNull Method method, String expressionString) {if (ArrayUtils.isEmpty(arguments) || StringUtils.isBlank(expressionString)) {return null;}Expression expression = getExpression(expressionString);if (expression == null) {return null;}MethodBasedEvaluationContext evaluationContext = getEvaluationContextAliasAble(arguments, method);return (T) expression.getValue(evaluationContext);}/*** 獲取Expression對象** @param expressionString Spring EL 表達式字符串 例如 #{param.id}* @return Expression*/@Nullablepublic static Expression getExpression(@Nullable String expressionString) {if (StringUtils.isBlank(expressionString)) {return null;}if (EXPRESSION_CACHE.containsKey(expressionString)) {return EXPRESSION_CACHE.get(expressionString);}Expression expression = SPEL_PARSER.parseExpression(expressionString);EXPRESSION_CACHE.put(expressionString, expression);return expression;}/*** 獲取可以通過別名查找的EvaluationContext,類似于spring cache的用法 #a0.id,#p1.name** @param arguments 方法入參* @param method    方法* @return MethodBasedEvaluationContext*/@NonNullpublic static MethodBasedEvaluationContext getEvaluationContextAliasAble(@NonNull Object[] arguments, @NonNull Method method) {return new MethodBasedEvaluationContext(arguments, method, arguments, new StandardReflectionParameterNameDiscoverer());}}

創建示例Controller

創建一個簡單的Controller,用于測試防重復提交功能

@RestController
@RequestMapping()
public class TestController {@PostMapping("/test1")@NoDuplicateSubmit(message = "不要在提交了", interval = 120, paramNames = {"#name"})public ResultInfo<String> test1(@RequestParam String name) {return ResultInfo.success();}@PostMapping("/test2")@NoDuplicateSubmit(message = "不要在提交了", interval = 120, paramNames = {"#user.name"})public ResultInfo<String> test2(@RequestBody User user) {return ResultInfo.success();}public static class User {private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}}
}

流程圖解

非JSON類型請求
JSON類型請求(POST/PUT等)
指定paramNames
未指定但allParamVerify=true
其他
是(重復提交)
否(正常提交)
客戶端發送請求
Spring Boot應用入口
CacheRequestFilter過濾器
直接放行
用CacheRequestWrapper包裝請求
緩存請求體到內存(支持重復讀取)
DispatcherServlet路由分發
匹配到@NoDuplicateSubmit注解方法?
正常執行控制器方法
NoDuplicateSubmitAspect切面攔截
構建Redis防重校驗Key
前綴(@NoDuplicateSubmit.prefix)
請求方法+路徑(如POST:/api/submit)
當前用戶ID(SecurityContextHolder獲取)
參數哈希值
參數規則?
用SpEL提取對應參數
所有參數參與計算
固定標識'none'
MurmurHash計算哈希→轉Base64
Redis執行setIfAbsent(原子操作)
Key是否存在?
拋出ServerException
設置Key及過期時間→執行控制器方法
GlobalExceptionHandler捕獲異常
返回'請勿重復提交'錯誤信息
客戶端接收錯誤響應
控制器處理業務邏輯(可重復讀請求體)
返回處理結果
客戶端接收成功響應

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

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

相關文章

【語音技術】意圖與語料

目錄 1. 意圖 1.1. 意圖分類 1.1.1 入口意圖&#xff08;Entry Intent&#xff09; 1.1.2 對話意圖&#xff08;Dialog Intent&#xff09; 1.2. 意圖類型切換操作步驟 2. 語料 2.1 語料分類詳解 2.2 語料編寫規范詳解 2.3 標簽符號深度說明 3. 詞槽 3.1 符類型要求 …

【MySQL集群架構與實踐5】使用Docker實現水平分片

目錄 一. 在Docker中安裝ShardingSphere 二. 實踐&#xff1a;水平分片 2.1 應用場景 2.2 架構圖 2.3 服務器規劃 2.4 創建server-user容器 2.5 創建server-order0和server-order1容器 2.6.日志配置 2.7 數據節點配置 2.8.測試數據節點 2.8.1.測試server_order0.t_or…

視覺圖像處理中級篇 [1]—— 彩色照相機的效果與預處理

在工業檢測中&#xff0c;黑白相機雖應用廣泛&#xff0c;但在應對顏色差異檢測時往往力不從心。彩色照相機憑借其對色彩信息的精準捕捉&#xff0c;成為復雜場景下的理想選擇&#xff0c;而預處理技術則進一步釋放了其性能潛力。一、彩色照相機的效果檢查蓋子上的金色標簽可以…

使用 BERT 的 NSP 實現語義感知切片 —— 提升 RAG 系統的檢索質量

在構建 Retrieval-Augmented Generation&#xff08;RAG&#xff09;系統時&#xff0c;文檔的切片方式至關重要。我們需要將長文本切分成合適的段落&#xff08;chunks&#xff09;&#xff0c;然后存入向量數據庫進行召回。如果切得太粗&#xff0c;會丟失上下文細節&#xf…

使用STM32CubeMX生成的STM32CubeIDE工程在更改工程名后編譯失敗問題解決

0 問題描述 使用STM32CubeMX生成STM32CubeIDE工程,然后使用STM32CubeIDE改名后編譯提示如下錯誤: 1 問題原因及解決辦法 1.1 問題原因 原因在于更名后STM32CubeIDE沒有自動更新引用關系,這是因為我們使用STM32CubeMX生成代碼時沒有勾選在根目錄下生成: 取消勾選在根目…

8月3日星期日今日早報簡報微語報早讀

8月3日星期日&#xff0c;農歷閏六月初十&#xff0c;早報#微語早讀。1、廣西防城港&#xff1a;奔馳女司機身份已查清&#xff0c;結果將統一對外發布&#xff1b;2、陳藝文、陳佳包攬游泳世錦賽女子跳水三米板金銀牌&#xff1b;3、九省份保險業已賠付暴雨災害損失5.2億元&am…

wxPython 實踐(六)對話框

wxPython 實踐&#xff08;一&#xff09;概述 wxPython 實踐&#xff08;二&#xff09;基礎控件 wxPython 實踐&#xff08;三&#xff09;頁面布局 wxPython 實踐&#xff08;四&#xff09;事件響應 wxPython 實踐&#xff08;五&#xff09;高級控件 wxPython 實踐&#x…

MATLAB科研數據可視化技術

互聯網的飛速發展伴隨著海量信息的產生&#xff0c;而海量信息的背后對應的則是海量數據。如何從這些海量數據中獲取有價值的信息來供人們學習和工作使用&#xff0c;這就不得不用到大數據挖掘和分析技術。數據可視化分析作為大數據技術的核心一環&#xff0c;其重要性不言而喻…

文明存續的時間博弈:論地球資源枯竭臨界期的技術突圍與行動緊迫性

摘要當地球資源消耗以指數級速度逼近生態承載力極限&#xff0c;人類文明正面臨“存續還是消亡”的終極抉擇。本文基于地球資源枯竭的實證數據與技術突破的可行性分析&#xff0c;揭示文明存續的時間窗口已進入不可逆臨界期&#xff08;2040-2070年&#xff09;&#xff0c;論證…

Elasticsearch 8.19.0 和 9.1.0 中 LogsDB 和 TSDS 的性能與存儲改進

作者&#xff1a;來自 Elastic Martijn Van Groningen 探索 TSDS 和 LogsDB 的最新增強功能&#xff0c;包括優化 I/O、提升合并性能等。 Elasticsearch 帶來了許多新功能&#xff0c;幫助你為你的使用場景構建最佳搜索解決方案。通過我們的示例筆記本深入學習&#xff0c;開始…

cs336之注意pytorch的tensor在哪里?(assert的使用)

問題 記住&#xff1a;無論何時你在pytorch中有一個張量tensor&#xff0c;你應該始終問一個問題&#xff1a;它當前位于哪里&#xff1f; 注意它在CPU還是在GPU中。要判斷它在哪里&#xff0c;可以使用python的assert斷言語句。 assert斷言 在 Python 中&#xff0c;assert 是…

Mysql 分區表

分區表是將一張表分成多張獨立子表&#xff0c;每個子表是一個區&#xff0c;目的是提高查詢效率。 從 server 層來看&#xff0c;只有一張表。但是從引擎層來看&#xff0c;是多張表&#xff0c;對應多個.idb文件。引擎層訪問數據只訪問特定分區表&#xff0c;也只對特定分區表…

Makefile 入門與實踐指南

Makefile 是用于 make 工具的配置文件&#xff0c;它定義了如何編譯和鏈接你的項目&#xff0c;讓構建過程自動化。一、核心概念 make 的核心思想是 “目標”&#xff08;Target&#xff09; 和 “依賴”&#xff08;Dependencies&#xff09;&#xff1a; 目標 (Target)&#…

分布式微服務--Nacos作為配置中心(補)關于bosststrap.yml與@RefreshScope

一、關于bosststrap.yml? bootstrap.yml 和 application.yml 的區別對比項bootstrap.ymlapplication.yml加載時機優先于 application.yml 加載&#xff08;啟動早期&#xff09;程序初始化完成后加載主要用途設置應用的外部配置源、注冊中心信息等設置應用內部配置&#xff0c…

[Qt]QString 與Sqlite3 字符串互動[漢字不亂碼]

環境&#xff1a;Qt C&#xff08;msvc c&#xff09;1.將與數據庫交互的代碼文件編碼轉換為utf-8-bom編碼&#xff0c;&#xff08;可使用notepad 進行轉換&#xff09;2.在代碼文件頭文件中加上下面代碼。//vs2010 版本是 1600 #if defined(_MSC_VER) && (_MSC_VER &…

SpringBoot啟動項目詳解

SpringBoot 的啟動過程是一個整合 Spring 核心容器、自動配置、嵌入式服務器等功能的復雜流程&#xff0c;核心目標是 “簡化配置、快速啟動”。下面從入口類開始&#xff0c;逐步拆解其詳細啟動步驟&#xff1a;一、啟動入口&#xff1a;SpringBootApplication與main方法Sprin…

PCB 控深槽如何破解 5G 基站 120℃高熱魔咒?

5G 基站在高頻通信下的功耗較 4G 基站提升 3-4 倍&#xff0c;射頻模塊、電源單元等核心部件的工作溫度常突破 120℃&#xff0c;遠超設備安全閾值&#xff08;≤85℃&#xff09;&#xff0c;形成制約通信穩定性的 “高熱魔咒”。印制線路板&#xff08;PCB&#xff09;作為熱…

NEXT.js 打包部署到服務器

在網上查了一下&#xff0c;記錄一下1.首先執行打包命令&#xff0c;我這個項目是用的pnpm&#xff0c;可以根據項目需求使用 npm 或者別的pnpm run build2.打包完成后會有一個 .next 的文件夾&#xff0c;需要把下圖的這些文件放到服務器。服務器需要有node環境之后就需要執行…

【AI分析】uv庫自動安裝腳本uv-installer-0.8.3.ps1分析

目錄uv 安裝腳本完整分析報告1. 腳本概述2. 參數解析3. 環境變量控制4. 核心函數詳解a. Install-Binary&#xff08;主控函數&#xff09;b. Get-TargetTriple&#xff08;架構檢測&#xff09;c. Download&#xff08;下載處理&#xff09;d. Invoke-Installer&#xff08;安裝…

etcd 的安裝與使用

介紹 Etcd 是一個 golang 編寫的分布式、高可用的一致性鍵值存儲系統&#xff0c;用于配置共享和服 務發現等。它使用 Raft 一致性算法來保持集群數據的一致性&#xff0c;且客戶端通過長連接 watch 功能&#xff0c;能夠及時收到數據變化通知&#xff0c;相較于 Zookeeper 框…