当前位置: 首页 > news >正文

JAVA | 通过自定义注解与AOP防止接口重复提交

关注:CodingTechWork

引言

  在Web应用开发中,特别是在处理表单提交或API调用时,可能会遇到用户因网络延迟、按钮多次点击等原因导致的重复提交问题。为了解决这一问题,通常的做法是在前端禁用提交按钮,或者在后端使用唯一令牌(Token)机制来确保请求的唯一性。然而,这些方法往往需要针对每个可能的重复提交场景单独处理,增加了代码的复杂性和维护成本。
  本文将介绍一种更加优雅和通用的解决方案:通过Java自定义注解和面向切面编程(AOP),结合Redis实现防重操作。这种方法不仅可以有效地防止短时间内重复提交,而且可以通过配置灵活调整防重策略,适用于各种业务场景。接下来,我们将详细介绍该方案的设计思路、具体实现以及最佳实践。

方案概述

自定义注解

  为了简化防重逻辑的实现,我们首先定义了一个名为@Resubmit的自定义注解,它用于标记那些需要防止重复调用的方法。此注解包含了几个重要的属性:

  • keyPrefix():指定生成防重Key的前缀规则。
  • key():允许开发者自定义Key值,或者从请求参数中动态获取。
  • limitation():设定两次相同请求之间最短时间间隔,单位为秒。
  • timeout():设置最长限制时间,以避免由于系统原因导致无法再次调用的问题,默认300秒。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/***为了避免短时间内重复调用同一方法,特别是相同参数的调用,我们通过一个 key 来判断是否为重复请求。key 由调用方提供,用于标识每次调用。只有当两次调用的 key 相同,才会被视为重复请求。*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Resubmit {/*** key 的前缀类型*/ResubmitKeyPrefixTypeEnum keyPrefix() default ResubmitKeyPrefixTypeEnum.URL_PATH;/*** 如果指定 key ,则通过${}方式从参数中进行取值;如果未指定 key,则从 ApiRequestKey#getKey 中取值*/String key() default "";/*** 允许两次相同调用的最短时间间隔,单位 - 秒*/long limitation() default 0;/*** 超时时间,单位 - 秒;超过这个时间,解除限制,默认 300 秒*/long timeout() default 300;
}

ResubmitKeyPrefixTypeEnum

  为了简化keyPrefix的配置,我们还定义了枚举类型ResubmitKeyPrefixTypeEnum,提供了三种不同的前缀生成方式:URL路径、类名以及类加方法名。


public enum ResubmitKeyPrefixTypeEnum {/*** url 路径*/URL_PATH,/*** 类名*/CLASS,/*** 类 + 方法名*/CLASS_METHOD;
}

AOP切面逻辑

  接下来是核心部分——AOP切面逻辑。ResubmitAspect类负责拦截所有带有@Resubmit注解的方法,并根据配置执行相应的防重检查。主要功能包括:

  1. 前置处理:在方法执行之前,构造出唯一的Key,并检查该Key是否已经存在于Redis中。如果存在,则抛出异常阻止方法继续执行;否则,将Key存入Redis并设置过期时间。
  2. 后置处理:当方法正常执行完毕后,清理ThreadLocal中的数据,确保不会影响其他线程。对于不限制调用次数的情况(即limitation设为0),还会立即删除Redis中的Key。
    此外,还有辅助方法用于解析请求参数、构建完整的Key字符串等。
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
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.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;@Aspect
@Component
@Slf4j
public class ResubmitAspect {private static final ThreadLocal<String> KEY_HOLDER = new ThreadLocal<>();private static final String COMMON_KEY_PREFIX = "XXXWEB:BIZ:RESUBMIT";private static final String COLON = ":";@Autowiredprivate RedisService redisService;/*** 定义切入点,匹配所有带有 {@link Resubmit} 注解的方法。*/@Pointcut("@annotation(com.demo.commons.annotations.Resubmit)")public void resubmitPointcut() {// 空方法体,仅用于定义切入点}/*** 在方法执行前检查是否为重复调用。如果检测到重复调用,则抛出异常阻止方法执行。** @param joinPoint 切入点信息* @param resubmit  防止重提交的注解配置*/@Before("resubmitPointcut() && @annotation(resubmit)")public void before(JoinPoint joinPoint, Resubmit resubmit) {try {// 构造唯一key,并添加公共前缀String key = constructUniqueKey(joinPoint, resubmit);log.info("Resubmit check with key {}", key);// 检查Redis中是否存在相同key,若存在则拒绝请求if (redisService.exists(key)) {throw new BusinessException("请不要频繁操作!");}// 将key存入Redis并设置过期时间(以秒为单位)int expireTime = Math.max(resubmit.limitation(), resubmit.timeout());redisService.setExpiry(key, expireTime);KEY_HOLDER.set(key);} catch (Exception e) {log.error("Error during resubmit check", e);}}/*** 方法执行后清理线程局部变量中的key。** @param resubmit 防止重提交的注解配置*/@After("resubmitPointcut() && @annotation(resubmit)")public void after(Resubmit resubmit) {String key = KEY_HOLDER.get();if (key == null || !resubmit.limitation() > 0) return;// 如果没有限制调用次数,则立即删除keyif (resubmit.limitation() == 0) {redisService.delete(key);}KEY_HOLDER.remove();}/*** 构造唯一的key值,用于标识一次请求。** @param joinPoint 切入点信息* @param resubmit  防止重提交的注解配置* @return 唯一的key字符串*/private String constructUniqueKey(JoinPoint joinPoint, Resubmit resubmit) {String customKey = extractCustomKey(joinPoint, resubmit);String prefix = buildKeyPrefix(joinPoint, resubmit);// 如果自定义key为空,则返回空字符串if (customKey == null || customKey.isEmpty()) {return "";}// 组装完整的key,避免重复拼接符号return COMMON_KEY_PREFIX + COLON + prefix + COLON + customKey;}/*** 提取自定义key或从参数中获取。** @param joinPoint 切入点信息* @param resubmit  防止重提交的注解配置* @return 自定义key字符串*/private String extractCustomKey(JoinPoint joinPoint, Resubmit resubmit) {// 优先尝试解析注解中的key表达式if (!resubmit.key().isEmpty()) {return parseExpressionKey(joinPoint, resubmit.key());}// 否则从参数中查找实现了ApiRequestKey接口的对象for (Object arg : joinPoint.getArgs()) {if (arg instanceof ApiRequestKey) {return DigestUtil.md5Hex(((ApiRequestKey) arg).getKey());}}return "";}/*** 根据注解配置构建key的前缀部分。** @param joinPoint 切入点信息* @param resubmit  防止重提交的注解配置* @return key前缀字符串*/private String buildKeyPrefix(JoinPoint joinPoint, Resubmit resubmit) {switch (resubmit.keyPrefix()) {case URL_PATH:HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();return request.getRequestURI().replace("/", COLON) + COLON + request.getMethod().toLowerCase();case CLASS:return joinPoint.getSignature().getDeclaringTypeName().toLowerCase();case CLASS_METHOD:MethodSignature signature = (MethodSignature) joinPoint.getSignature();return signature.getDeclaringTypeName().toLowerCase() + COLON + signature.getName().toLowerCase();default:return "";}}/*** 解析key表达式,支持从方法参数中提取属性值。** @param joinPoint      切入点信息* @param keyExpression  key表达式字符串* @return 解析后的key字符串*/private String parseExpressionKey(JoinPoint joinPoint, String keyExpression) {Pattern pattern = Pattern.compile("\\$\\{(.*?)\\}");Matcher matcher = pattern.matcher(keyExpression);if (matcher.find()) {String groupName = matcher.group(1);Object value = BeanUtil.getProperty(obtainMethodArgumentsMap(joinPoint), groupName);return value != null ? value.toString() : null;}return null;}/*** 获取方法参数并转换为Map形式,便于后续处理。** @param joinPoint 切入点信息* @return 参数映射表*/private Map<String, Object> obtainMethodArgumentsMap(JoinPoint joinPoint) {MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();String[] argNames = methodSignature.getParameterNames();Object[] argValues = joinPoint.getArgs();Map<String, Object> args = Maps.newHashMapWithExpectedSize(argValues.length);for (int i = 0; i < argNames.length; i++) {args.put(argNames[i], shouldIgnoreArgument(argValues[i]) ? "[ignore]" : argValues[i]);}return args;}/*** 判断是否应该忽略某些类型的参数,例如文件上传、HTTP请求/响应等。** @param object 参数对象* @return 是否忽略该参数*/private boolean shouldIgnoreArgument(Object object) {if (object == null) return false;Class<?> clazz = object.getClass();return clazz.isArray() && IntStream.range(0, Array.getLength(object)).anyMatch(index -> shouldIgnoreArgument(Array.get(object, index)))|| Collection.class.isAssignableFrom(clazz) && ((Collection<?>) object).stream().anyMatch(this::shouldIgnoreArgument)|| Map.class.isAssignableFrom(clazz) && shouldIgnoreArgument(((Map<?, ?>) object).values())|| object instanceof MultipartFile|| object instanceof HttpServletRequest|| object instanceof HttpServletResponse|| object instanceof BindingResult;}
}

Controller层应用示例

  为了展示如何在Spring MVC的Controller层中使用@Resubmit注解来防止接口重复提交,我们将创建一个简单的例子。这个例子假设我们有一个用户注册的功能,为了避免用户多次点击“注册”按钮导致重复注册的问题,我们可以使用@Resubmit注解。

定义业务模型

首先,我们需要定义一个用于表单提交的数据传输对象(DTO),例如UserRegistrationDTO,它包含了用户注册所需的信息。

package com.example.demo.dto;import lombok.Data;@Data
public class UserRegistrationDTO {private String username;private String password;private String confirmPassword;
}

创建Controller

接下来,我们在Controller中添加一个处理用户注册请求的方法,并为其添加@Resubmit注解。

package com.example.demo.controller;import com.demo.commons.annotations.Resubmit;
import com.example.demo.dto.UserRegistrationDTO;
import org.springframework.web.bind.annotation.*;@RestController
@RequestMapping("/api/v1")
public class UserController {/*** 用户注册接口。** @param userRegistrationDTO 用户注册信息* @return 返回注册结果*/@PostMapping("/register")@Resubmit(keyPrefix = ResubmitKeyPrefixTypeEnum.CLASS_METHOD, key = "${username}", limitation = 60, timeout = 300)public ResponseEntity<String> register(@RequestBody UserRegistrationDTO userRegistrationDTO) {// 这里可以加入实际的业务逻辑,比如保存用户信息到数据库等// 为简化示例,直接返回成功消息return ResponseEntity.ok("注册成功!");}
}

解释注解配置

  1. keyPrefix = ResubmitKeyPrefixTypeEnum.CLASS_METHOD:指定了生成防重Key时使用的前缀是类名加方法名。这有助于确保不同控制器中的同名方法不会相互影响。
  2. key = " u s e r n a m e " :这里使用了表达式 {username}":这里使用了表达式 username":这里使用了表达式{username},表示从请求体中的UserRegistrationDTO对象提取username属性作为唯一标识的一部分。这意味着同一个用户名在同一时间段内不能再次调用此方法。
  3. limitation = 60:设置两次相同调用之间最短时间间隔为60秒。如果用户在60秒内尝试再次注册相同的用户名,则会收到提示信息:“请不要频繁操作!”。
  4. timeout = 300:设定最长限制时间为5分钟(300秒)。超过这个时间后,即使没有新的调用,旧的限制也会被自动解除,允许再次执行该方法。

测试接口

当启动应用程序并访问上述API端点时,如果尝试在60秒内以相同的用户名发起多次注册请求,将会看到如下的响应:

{"timestamp": "2025-01-07T06:09:00.000+00:00","status": 500,"error": "Internal Server Error","message": "请不要频繁操作!","path": "/api/v1/register"
}

而首次调用或是在限制时间之外的请求将正常工作,并返回成功的响应:

{"status": 200,"body": "注册成功!"
}

通过这种方式,我们可以有效地避免由于用户的误操作或者网络延迟等原因造成的重复提交问题,同时保持代码的简洁性和可维护性。此外,还可以根据具体的业务需求调整@Resubmit注解的参数,灵活地控制防重策略。

结论

  通过引入自定义注解和AOP技术,我们可以轻松地为项目添加防止接口重复提交的功能,同时保持代码的简洁性和可维护性。这种方式不仅能够有效减少不必要的数据库操作和其他资源消耗,还能提高用户体验,避免因为误操作而引发的数据不一致性问题。

相关文章:

JAVA | 通过自定义注解与AOP防止接口重复提交

关注&#xff1a;CodingTechWork 引言 在Web应用开发中&#xff0c;特别是在处理表单提交或API调用时&#xff0c;可能会遇到用户因网络延迟、按钮多次点击等原因导致的重复提交问题。为了解决这一问题&#xff0c;通常的做法是在前端禁用提交按钮&#xff0c;或者在后端使用唯…...

从零手写实现redis(四)添加监听器

1、删除监听器 /*** 删除监听器接口** author binbin.hou* since 0.0.6* param <K> key* param <V> value*/ public interface ICacheRemoveListener<K,V> {/*** 监听* param context 上下文* since 0.0.6*/void listen(final ICacheRemoveListenerContext&…...

Spring Boot项目中使用单一动态SQL方法可能带来的问题

1. 查询计划缓存的影响 深入分析 数据库系统通常会对常量SQL语句进行编译并缓存其执行计划以提高性能。对于动态生成的SQL语句&#xff0c;由于每次构建的SQL字符串可能不同&#xff0c;这会导致查询计划无法被有效利用&#xff0c;从而需要重新解析、优化和编译&#xff0c;…...

51单片机——中断(重点)

学习51单片机的重点及难点主要有中断、定时器、串口等内容&#xff0c;这部分内容一定要认真掌握&#xff0c;这部分没有学好就不能说学会了51单片机 1、中断系统 1.1 概念 中断是为使单片机具有对外部或内部随机发生的事件实时处理而设置的&#xff0c;中断功能的存在&#…...

MySQL insert or update方式性能比较

MySQL中&#xff0c;有如下两种方式&#xff0c;哪种方式比较好&#xff1f; 1、先使用enterprise_id字段查询数据表&#xff0c;如果表中存在记录&#xff0c;则更新记录&#xff1b;如果不存在&#xff0c;则插入记录&#xff1b; 2、使用“INSERT INTO XXX ON DUPLICATE K…...

Linux下常用命令

本文以笔记的形式记录Linux下常用命令。 注1&#xff1a;限于研究水平&#xff0c;阐述难免不当&#xff0c;欢迎批评指正。 注2&#xff1a;文章内容会不定期更新。 一、Ubuntu 添加账号 useradd -m -s /bin/bash -d /home/newuser newuser:newuser passwd newuser 二、 Ce…...

计算机网络、嵌入式等常见问题简答

1.嵌入式系统中经常要用到无限循环&#xff0c;如何用C编写死循环 答&#xff1a;while(1){}或者for(;;) 2.程序的局部变量存在于哪里&#xff0c;全局变量存在于哪里&#xff0c;动态申请数据存在于哪里。 答&#xff1a;程序的局部变量存在于栈区&#xff1b;全局变量存在…...

嵌入式中QT实现文本与线程控制方法

第一:利用QT进行文件读写实现 利用QT进行读写文本的时候进行读写,读取MP3歌词的文本,对这个文件进行读写操作。 实例代码,利用Qfile,对文件进行读写。 //读取对应文件文件,头文件的实现。 #ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QMainWindow> #incl…...

141.环形链表 142.环形链表II

141.环形链表 & 142.环形链表II 141.环形链表 思路&#xff1a;快慢指针 or 哈希表 快慢指针代码&#xff1a; class Solution { public:bool hasCycle(ListNode *head) {if(headnullptr||head->nextnullptr)return false;ListNode *fasthead->next; //不能设置成…...

计算机网络掩码、最小地址、最大地址计算、IP地址个数

一、必备知识 1.无分类地址IPV4地址网络前缀主机号 2.每个IPV4地址由32位二进制数组成 3. /15这个地址表示网络前缀有15位&#xff0c;那么主机号32-1517位。 4.IP地址的个数&#xff1a;2**n (n表示主机号的位数) 5.可用&#xff08;可分配&#xff09;IP地址个数&#x…...

第3章——HTTP报文内的HTTP信息

第3章——HTTP报文内的HTTP信息 HTTP报文 ​ 用于HTTP协议交互的信息被称为HTTP报文&#xff0c;分为请求报文和响应报文。分为Head&#xff0c;Body 结构&#xff1a; 请求行&#xff1a;包含用于请求的方法&#xff0c;请求URI和HTTP版本。 状态行&#xff1a;包含表明响应…...

Minio-Linux-安装

文章目录 1.Linux安装1.下载源码包2.上传到/usr/local/minio1.进入目录2.上传 3.开放执行权限4.创建minio文件存储目录及日志目录5.编写启动的shell脚本1.脚本编写2.赋予执行权限 6.启动&#xff01;1.执行run脚本2.查看日志3.开放9001和9000端口1.服务器2.安全组3.访问&#x…...

面试高阶问题:对称加密与非对称加密的原理及其应用场景

目录 第一章 对称加密原理及算法实现 第二章 非对称加密原理及算法实现 第三章 对称加密与非对称加密的应用场景 第四章 对称加密与非对称加密的应用实例 第五章 对称加密与非对称加密的对比分析 第一章 对称加密原理及算法实现 1.1 对称加密的原理 对称加密,又称私钥加密…...

报错 - decord 在 macOS Silicon 安装失败

问题&#xff1a;在 macOS M2 上 pip 安装 decord 出错&#xff1a; ERROR: Could not find a version that satisfies the requirement decord (from versions: none) ERROR: No matching distribution found for decord使用 decord 源码编译&#xff0c;make 也会出很多问题 …...

英伟达 RTX 5090 显卡赋能医疗大模型:变革、挑战与展望

一、英伟达 RTX 5090 与 RTX 4090 技术参数对比 1.1 核心架构与制程工艺 在探讨英伟达 RTX 4090 与 RTX 5090 的差异时&#xff0c;核心架构与制程工艺无疑是最为关键的基础要素&#xff0c;它们从根本上决定了两款显卡的性能上限与应用潜力。 1.1.1 核心架构差异 RTX 4090…...

PyCharm简单调试

本文简单讲述一下PyCharm中经常用到的调试操作。 示例代码如下&#xff1a; for i in range(10):print("hello", i)if i > 2:print("ok!")在代码前面打上断点&#xff0c;如下图所示&#xff1a; 单机调试按钮Debug 单机Resume Program按钮&#xf…...

快速入门Spring Cloud Alibaba,轻松玩转微服务

​ 1 快速入门Spring Cloud Alibaba&#xff0c;轻松玩转微服务 1.1 架构 架构图&#xff1a; 1.2 项目结构 1.2.1 系统框架版本 版本适配查看&#xff1a;https://sca.aliyun.com/docs/2023/overview/version-explain/ Spring Boot Version &#xff1a;3.2.4 Spring Clo…...

浅尝Appium自动化框架

浅尝Appium自动化框架 Appium自动化框架介绍Appium原理Appium使用安装平台驱动 Appium自动化框架介绍 Appium 是一个开源的自动化测试框架&#xff0c;最初设计用于移动应用的测试&#xff0c;但现在它也扩展了对桌面端应用的支持。Appium 使得自动化测试变得更加简单&#xf…...

poi-tl+kkviewfile实现生成pdf业务报告

需求背景&#xff0c;需要把ai生成的一些业务数据&#xff0c;生成一份pdf报告 需求分析 简单来说&#xff0c;就是json生成pdf的方案。 直接生成pdf。适合一些pdf样式简单的场景&#xff0c;一般就是纯文本按序渲染&#xff0c;或者是纯表格。如果需要一些复杂的排布&#x…...

python导入模块失败

运行下面代码模块&#xff0c;出现报错&#xff0c;导入模块失败 import torch from layers.Embed import DataEmbedding from layers.Conv_Blocks import Inception_Block_V1 将你自己的目录添加到 sys.path&#xff0c;假设你的目录位置是D://winhzq//桌面//pydemo//…...

Vulkan 学习(12)---- Vulkan pipeline 创建

目录 Vulkan 渲染管线顶点输入阶段输入装配阶段顶点着色器阶段细分控制、评估着色器阶段(可选)几何着色器阶段(可选)图元装配阶段光栅化阶段片段着色器片段测试阶段混合阶段 Vulkan 渲染管线 渲染管线可以看作是一条生产流水线&#xff0c;定义了从输入顶点到输出图像的所有步…...

BloombergGPT: A Large Language Model for Finance——面向金融领域的大语言模型

这篇文章介绍了BloombergGPT&#xff0c;一个专门为金融领域设计的大语言模型&#xff08;LLM&#xff09;。以下是文章的主要内容总结&#xff1a; 背景与动机&#xff1a; 大语言模型&#xff08;如GPT-3&#xff09;在多个任务上表现出色&#xff0c;但尚未有针对金融领域的…...

来说数据库

什么是数据库&#xff1f; 是部署在操作系统上&#xff0c;把数据按一定的数据模型组织、永久存储&#xff0c;并可以被用户共享的软件系统。 其实数据库&#xff0c;可以理解为&#xff0c;把数据都存成文件&#xff0c;有很多的文件和很多的目录&#xff0c;不好管理&#xf…...

教程:从pycharm基于anaconda构建机器学习环境并运行第一个 Python 文件

1. 安装 PyCharm 访问 PyCharm 官方网站&#xff1a;https://www.jetbrains.com/pycharm/。下载社区版&#xff08;免费&#xff09;或专业版&#xff08;收费&#xff0c;提供更多功能&#xff09;。按照操作系统的安装指导安装 PyCharm。安装后打开 PyCharm&#xff0c;并根…...

嵌入式驱动开发详解11(INPUT子系统)

文章目录 前言input子系统简介主要结构体API函数input子系统驱动框架上报事件后续设备树配置方式参考文献 前言 按键、鼠标、键盘、触摸屏等都属于输入(input)设备&#xff0c;Linux 内核为此专门做了一个叫做 input 子系统的框架来处理输入事件。输入设备本质上还是字符设备&…...

动态规划解决目标和问题

代码随想录链接:代码随想录 思路: 可以将数组分为两部分&#xff0c;其中一部分记作left&#xff0c;其中数字的符号全为,而另外一部分记作right&#xff0c;其中数字的符号全为-。这里全为-的意思不是真正的符号为-&#xff0c;而表示这一堆数字在计算时取值为负 因此有如下…...

【漏洞分析】UDF提权漏洞——CVE-2016-6662-MySQL ‘malloc_lib’变量重写命令执行

0x00 前言 最近在做渗透笔记&#xff0c;其中有一个靶机在getshell后&#xff0c;需要进行提权。发现靶机使用root启动的mysql服务&#xff0c;那么尝试使用UDF提权。于是在提权成功后&#xff0c;花了一天时间特意搜了一下整个UDF提权的漏洞原理和利用&#xff0c;加深理解。…...

特种设备安全管理人员免费题库限时练习(判断题)

56.(判断题)特别重大事故、重大事故、较大事故和一般事故,负责事故调查的人民政府应当自收到事故调查报告之日起15日内做出批复。 A.正确 B.错误 答案:错误 57.(判断题)每一类事故灾难的应急救援措施可能千差万别,因此其基本应急模式是不一致的。 A.正确 B.错误 答案:错…...

linux-25 文件管理(三)复制、移动文件,cp,mv

命令cp是copy的简写&#xff0c;而mv则是move的简写。那既然copy是用于实现复制文件的&#xff0c;那通常一般我们要指定其要复制的是谁&#xff1f;而且复制完以后保存在什么地方&#xff0c;对吧&#xff1f;那因此它的使用格式很简单&#xff0c;那就是cp srcfile dest&…...

中国科技统计年鉴EXCEL版(2021-2023年)-社科数据

中国科技统计年鉴EXCEL版&#xff08;2021-2023年&#xff09;-社科数据https://download.csdn.net/download/paofuluolijiang/90028724 https://download.csdn.net/download/paofuluolijiang/90028724 中国科技统计年鉴提供了从2021至2023年的详尽数据&#xff0c;覆盖了科技…...

Idea(中文版) 项目结构/基本设置/设计背景

目录 1. Idea 项目结构 1.1 新建项目 1.2 新建项目的模块 1.3 新建项目模块的包 1.4 新建项目模块包的类 2. 基本设置 2.1 设置主题 2.2 设置字体 2.3 设置注释 2.4 自动导包 2.5 忽略大小写 2.6 设置背景图片 3. 项目与模块操作 3.1 修改类名 3.2 关闭项目 1. I…...

jenkins入门--安装jenkins

下载地址https://www.jenkins.io/ jdk 安装 &#xff1a;Jenkins需要安装对应版本的jdk,我在安装过程中显示需要21,17 Java Downloads | Oracle jenkins安装过程参考全网最清晰Jenkins安装教程-windows_windows安装jenkins-CSDN博客 安装完成后&#xff0c;浏览器输入127.0.…...

基于Springboot + vue实现的小型养老院管理系统

&#x1f942;(❁◡❁)您的点赞&#x1f44d;➕评论&#x1f4dd;➕收藏⭐是作者创作的最大动力&#x1f91e; &#x1f496;&#x1f4d5;&#x1f389;&#x1f525; 支持我&#xff1a;点赞&#x1f44d;收藏⭐️留言&#x1f4dd;欢迎留言讨论 &#x1f525;&#x1f525;&…...

shell基础使用及vim的常用快捷键

一、shell简介 参考博文1 参考博文2——shell语法及应用 参考博文3——vi的使用 在linux中有很多类型的shell&#xff0c;不同的shell具备不同的功能&#xff0c;shell还决定了脚本中函数的语法&#xff0c;Linux中默认的shell是 / b in/ b a s h &#xff0c;流行的shell…...

Mac 安装psycopg2出错:Error:pg_config executable not found的解决

在mac 上执行pip3 install psycopg2-binary出现如下错误&#xff1a; Error:pg_config executable not found然后我又到终端里执行 brew install postgresql16 显示 Warning: You are using macOS 15. We do not provide support for this pre-release version. It is expe…...

UniApp | 从入门到精通:开启全平台开发的大门

UniApp | 从入门到精通:开启全平台开发的大门 一、前言二、Uniapp 基础入门2.1 什么是 Uniapp2.2 开发环境搭建三、Uniapp 核心语法与组件3.1 模板语法3.2 组件使用四、页面路由与导航4.1 路由配置4.2 导航方法五、数据请求与处理5.1 发起请求5.2 数据缓存六、样式与布局6.1 样…...

Kafka3.x KRaft 模式 (没有zookeeper) 常用命令

版本号&#xff1a;kafka_2.12-3.7.0 说明&#xff1a;如有多个地址&#xff0c;用逗号分隔 创建主题 bin/kafka-topics.sh --bootstrap-server localhost:9092 --create --topic demo --partitions 1 --replication-factor 1删除主题 bin/kafka-topics.sh --delete --boots…...

【竞技宝】CS2:NertZ离队Liquid光速加盟!

2025年1月7日&#xff0c;目前CS2的赛事正处于空窗期中&#xff0c;很多队伍在近期都在进行阵容上的调整&#xff0c;其中出现了很多震惊观众的转会消息。今日凌晨&#xff0c;HEROIC官宣队内的NertZ选手正式离队&#xff0c;此后Liquid很快发布消息宣布了NertZ的加盟。 今日凌…...

PDFMathTranslate: Star13.8k,一款基于AI的PDF文档全文双语翻译PDF文档全文双语翻译,保留格式神器,你应该需要它

嗨&#xff0c;大家好&#xff0c;我是小华同学&#xff0c;关注我们获得“最新、最全、最优质”开源项目和高效工作学习方法 PDFMathTranslate是一个开源项目&#xff0c;旨在为用户提供便捷的PDF科学论文翻译解决方案。它不仅能够翻译文本&#xff0c;还能保留公式、图表、目…...

滑动窗口——最小覆盖子串

一.题目描述 76. 最小覆盖子串 - 力扣&#xff08;LeetCode&#xff09; 二.题目解析 题目还是很好理解的&#xff0c;就是在字符串s中找到一个子串&#xff0c;该子串包含字符串t的所有字符。返回最短的子串。如果s中不包含这样的子串就返回一个空串。 需要注意的是&#…...

2012mfc,几种串

串,即是由符组成的串,在标准C,标准C,MFC中串这一功能的实现是不相同的,C完全兼容了C. 1.标准C中的串 在标准C中没有串数据类型,C中的串是有符类型的符数组或符类型的符指针来实现的.如: char name[26]"This is a Cstyle string"; //或char *name"This is a…...

基于SpringBoot的乐器商城购物推荐系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…...

Jurgen提出的Highway Networks:LSTM时间维方法应用到深度维

Jurgen提出的Highway Networks&#xff1a;LSTM时间维方法应用到深度维 具体实例与推演 假设我们有一个离散型随机变量 X X X&#xff0c;它表示掷一枚骰子得到的点数&#xff0c;求 X X X 的期望。 步骤&#xff1a; 列出 X X X 的所有可能取值 x i x_i xi​&#xff08;…...

asp.net core中的 Cookie 和 Session

在 Web 开发中&#xff0c;用户会话管理是非常重要的&#xff0c;尤其是在需要保持用户状态和身份验证的应用中。ASP.NET Core 提供了多种状态管理技术&#xff0c;如 Cookie 和 Session&#xff0c;它们可以帮助你管理用户会话、存储数据并实现用户身份验证等功能。下面将详细…...

【STM32+CubeMX】 新建一个工程(STM32F407)

相关文章&#xff1a; 【HAL库】 STM32CubeMX 教程 1 --- 下载、安装 目录 第一部分、新建工程 第二部分、工程文件解释 第三部分、编译验证工程 友情约定&#xff1a;本系列的前五篇&#xff0c;为了方便新手玩家熟悉CubeMX、Keil的使用&#xff0c;会详细地截图每一步Cu…...

IO进程day1

一、思维导图...

剧本字幕自己看

Hello English learners! Welcome back to my channel! My name is Ethan, and today we’re diving into a topic we deal with every day—traffic. 大家好,英语学习者们!欢迎回到我的频道!我是Ethan,今天我们要聊一个每天都会遇到的话题——交通。 When I drive somewh…...

Java排序

Map Stream 排序 最簡單的排序方式 Map<String,String> _lineMap = _itRow.next();_lineMap = _lineMap.entrySet().stream().sorted((i1,i2)>i1.getKey().compareTo(i2.getKey())).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,(e1,e2)->e…...

Geoserver修行记-后端调用WMS/WMTS服务无找不到图层Could not find layer

项目场景 调用geoserver地图服务WMS,找不到图层 我在进行地图服务调用的时候&#xff0c;总是提示我找不多图层 Could not find layer&#xff0c;重点是这个图层我明明是定义了&#xff0c;发布了&#xff0c;且还能够正常查看图层的wms的样式&#xff0c;但是在调用后端调用…...

JavaScript代码片段二

见过不少人、经过不少事、也吃过不少苦&#xff0c;感悟世事无常、人心多变&#xff0c;靠着回忆将往事串珠成链&#xff0c;聊聊感情、谈谈发展&#xff0c;我慢慢写、你一点一点看...... JavaScript统计文字个数、特殊字符转义、动态插入js代码、身份证验证 统计文字个数 f…...