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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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 框…