SpringBoot-接口防止重复提交

        在Spring Boot应用中,防止接口重复提交是一个常见的问题,特别是在处理表单提交或关键业务操作时。重复提交可能会导致数据重复插入、资源浪费或业务逻辑错误。以下是一些常见的方法来防止接口重复提交

前端

1.前端防抖/节流

        前端可以通过JavaScript实现防抖(Debounce)或节流(Throttle)机制,控制用户在短时间内不能多次点击提交按钮。

// 简单的防抖函数示例
function debounce(func, wait) {
    let timeout;
    return function(...args) {
        clearTimeout(timeout);
        timeout = setTimeout(() => {
            func.apply(this, args);
        }, wait);
    };
}

// 使用防抖函数包装提交事件
document.getElementById('submitBtn').addEventListener('click', debounce(function() {
    // 提交表单的逻辑
}, 1000));

2.禁用提交按钮

这个就比较简单了,在用户点击提交按钮后,立即禁用按钮,防止用户再次点击。

<button type="submit" id="submitBtn" onclick="this.disabled=true;this.form.submit();">提交</button>

后端

1.后端幂等性校验

        确保后端接口的幂等性,即同一个请求被多次执行后,产生的结果是相同的。

1.1 使用唯一请求标识(如UUID)

        每次请求生成一个唯一的标识(如UUID),并在前端和后端之间传递这个标识。后端在处理请求时,首先检查这个标识是否已经处理过,若已处理则直接返回结果,不再执行后续逻辑。

@RestController
public class MyController {

    private Set<String> requestIds = ConcurrentHashMap.newKeySet();

    @PostMapping("/submit")
    public ResponseEntity<String> submit(@RequestParam("requestId") String requestId, ...) {
        if (requestIds.contains(requestId)) {
            return ResponseEntity.status(HttpStatus.CONFLICT).body("Duplicate request");
        }
        requestIds.add(requestId);
        // 处理业务逻辑
        return ResponseEntity.ok("Success");
    }
}

1.2使用分布式锁

对于分布式系统,可以使用分布式锁(如Redis分布式锁)来确保同一时间只有一个请求在处理。

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@PostMapping("/submit")
public ResponseEntity<String> submit(...) {
    String lockKey = "lock_key_" + someUniqueId;
    Boolean success = redisTemplate.opsForValue().trySetIfAbsent(lockKey, "locked", 10, TimeUnit.SECONDS);
    
    if (!success) {
        return ResponseEntity.status(HttpStatus.CONFLICT).body("Duplicate request");
    }
    try {
        // 处理业务逻辑
        return ResponseEntity.ok("Success");
    } finally {
        redisTemplate.delete(lockKey);
    }
}

2. 乐观锁/悲观锁

悲观锁(Pessimistic Locking)
定义:悲观锁假设最坏的情况,认为每次访问数据时都可能会发生冲突,因此在整个数据处理过程中都会锁定数据,直到事务结束才会释放锁。
实现:通常通过数据库的 SELECT ... FOR UPDATE 语句来实现。
优点:
在高并发情况下,能够有效防止数据冲突。
缺点:
锁定时间长,可能导致其他事务长时间等待,影响性能。

BEGIN;
SELECT * FROM table_name WHERE id = 1 FOR UPDATE;
-- 处理数据
UPDATE table_name SET column1 = value1 WHERE id = 1;
COMMIT;

 

乐观锁(Optimistic Locking)
定义:乐观锁假设最好的情况,认为每次访问数据时不会发生冲突,因此在提交更新时才会检查是否有冲突,如果有冲突则回滚事务。
实现:通常通过版本号(Version)或时间戳(Timestamp)来实现。在更新数据时,会检查当前版本号是否与上次读取时的版本号一致,如果不一致则认为数据已被其他事务修改,事务失败。
优点:
减少了锁的使用,提高了并发性能。
缺点:
在高并发情况下,可能会导致频繁的事务回滚,影响性能。 

BEGIN;
SELECT version, column1 FROM table_name WHERE id = 1;
-- 假设读取到的版本号为1
-- 处理数据
UPDATE table_name SET column1 = value1, version = version + 1 WHERE id = 1 AND version = 1;
IF ROW_COUNT() = 0 THEN
  ROLLBACK; -- 数据已被其他事务修改,事务失败
ELSE
  COMMIT;
END IF;

 可以使用mybatisplus的@Version

import com.baomidou.mybatisplus.annotation.Version;

public class YourEntity {
    @Version
    private Integer version;
    // 其他字段...
}

3.拦截器/过滤器

我们可以自定义一个注解,在方法上使用,来配置接口是否可以重复提交。

创建自定义注解

/**
 * RepeatSubmit注解用于防止接口在短时间内被重复提交
 * 它通过指定的时间间隔来判断是否为重复提交,并提供了一个默认的提示消息
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit
{
    /**
     * 间隔时间(ms),小于此时间视为重复提交
     */
    public int interval() default 5000;

    /**
     * 提示消息
     */
    public String message() default "不允许重复提交,请稍后再试";
}

 编写拦截器

/**
 * 防止重复提交拦截器
 * 
 * 该拦截器用于检查HTTP请求是否为重复提交,以防止例如表单重复提交等问题
 * 它通过检查请求参数或请求头中的特定值来确定请求是否重复
 * 如果检测到重复提交,将直接返回错误信息,否则允许请求继续执行
 */
@Component
public abstract class RepeatSubmitInterceptor implements HandlerInterceptor
{
    /**
     * 在请求处理之前进行拦截
     * 
     * @param request 请求对象,包含请求的所有信息
     * @param response 响应对象,用于向客户端发送响应
     * @param handler 请求处理对象,可以判断是否为方法处理请求
     * @return 返回true表示请求继续执行,返回false表示请求被拦截终止
     * @throws Exception 如果处理过程中发生异常
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
    {
        // 判断handler是否为HandlerMethod类型,即是否由方法处理请求
        if (handler instanceof HandlerMethod)
        {
            // 转换为HandlerMethod类型,获取执行的方法
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();
            // 获取方法上的RepeatSubmit注解
            RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
            // 判断方法上是否有RepeatSubmit注解
            if (annotation != null)
            {
                // 调用抽象方法判断是否重复提交
                if (this.isRepeatSubmit(request, annotation))
                {
                    // 如果是重复提交,返回错误信息并终止请求
                    AjaxResult ajaxResult = AjaxResult.error(annotation.message());
                    ServletUtils.renderString(response, JSON.marshal(ajaxResult));
                    return false;
                }
            }
            // 不是重复提交,允许请求继续执行
            return true;
        }
        else
        {
            // 如果handler不是HandlerMethod类型,直接允许请求继续执行
            return true;
        }
    }

    /**
     * 验证是否重复提交由子类实现具体的防重复提交的规则
     * 
     * @param request 请求对象,包含请求的所有信息
     * @param annotation 防复注解,包含防重复提交的配置信息
     * @return 返回true表示重复提交,返回false表示不是重复提交
     * @throws Exception 如果验证过程中发生异常
     */
    public abstract boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation) throws Exception;
}
// 自定义的同一个URL数据拦截器,继承自RepeatSubmitInterceptor
@Component
public class SameUrlDataInterceptor extends RepeatSubmitInterceptor
{
    // 用于在session中存储重复提交数据的键
    public final String SESSION_REPEAT_KEY = "repeatData";

    // 警惕:unchecked表示此处存在未经检查的转换
    @SuppressWarnings("unchecked")
    @Override
    public boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation) throws Exception
    {
        // 本次参数及系统时间
        String nowParams = JSON.marshal(request.getParameterMap());
        Map<String, Object> nowDataMap = new HashMap<String, Object>();
        nowDataMap.put(REPEAT_PARAMS, nowParams);
        nowDataMap.put(REPEAT_TIME, System.currentTimeMillis());

        // 请求地址(作为存放session的key值)
        String url = request.getRequestURI();

        HttpSession session = request.getSession();
        Object sessionObj = session.getAttribute(SESSION_REPEAT_KEY);
        if (sessionObj != null)
        {
            Map<String, Object> sessionMap = (Map<String, Object>) sessionObj;
            if (sessionMap.containsKey(url))
            {
                Map<String, Object> preDataMap = (Map<String, Object>) sessionMap.get(url);
                // 比较参数和时间来判断是否重复提交
                if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap, annotation.interval()))
                {
                    return true;
                }
            }
        }
        // 如果不是重复提交,则将当前数据保存到session中
        Map<String, Object> sessionMap = new HashMap<String, Object>();
        sessionMap.put(url, nowDataMap);
        session.setAttribute(SESSION_REPEAT_KEY, sessionMap);
        return false;
    }

    /**
     * 判断参数是否相同
     */
    private boolean compareParams(Map<String, Object> nowMap, Map<String, Object> preMap)
    {
        String nowParams = (String) nowMap.get(REPEAT_PARAMS);
        String preParams = (String) preMap.get(REPEAT_PARAMS);
        return nowParams.equals(preParams);
    }

    /**
     * 判断两次间隔时间
     */
    private boolean compareTime(Map<String, Object> nowMap, Map<String, Object> preMap, int interval)
    {
        long time1 = (Long) nowMap.get(REPEAT_TIME);
        long time2 = (Long) preMap.get(REPEAT_TIME);
        if ((time1 - time2) < interval)
        {
            return true;
        }
        return false;
    }
}

        防止接口重复提交需要结合前端和后端的多种手段来实现。前端可以通过防抖、节流和禁用按钮来减少重复提交的可能性;后端则需要通过幂等性校验、数据库约束、分布式锁和拦截器/过滤器等手段来确保接口的幂等性和数据的正确性。

### 回答1: Python机器学习在图像识别领域具有广泛的应用。机器学习可以通过训练模型来自动地从图像中识别出特定的对象、场景或属性。在Python中,有多种强大的机器学习库可以用于图像识别。以下是关于Python机器学习图像识别的一些重要概念和方法: 1. 特征提取:机器学习模型需要在图像中找到特定的可识别特征。Python提供了多种用于图像特征提取的库,如OpenCV和Scikit-learn,它们可以提取图像中的边缘、纹理、色彩等特征。 2. 分类算法:在图像识别中,常用的机器学习算法有支持向量机(SVM)、随机森林(Random Forest)和卷积神经网络(CNN)等。Python中有多个库可用于实现这些算法,如Scikit-learn和Keras等。 3. 数据集和标注:图像识别通常需要大量的标注图像来训练模型。Python提供了一些用于处理和增强图像数据集的库,如PIL和Scikit-image。此外,还有许多公开的图像数据集可供学习和研究,如MNIST和CIFAR-10等。 4. 模型评估:为了评估模型的识别性能,可以使用各种评价指标,如准确率、召回率和F1-score等。Python中的Scikit-learn库提供了用于模型评估的函数和工具。 5. 迁移学习:对于计算资源有限的情况,迁移学习是一种常用的方法。通过使用在大型图像数据集上预训练的模型,可以将它们迁移到需要解决的具体问题上。Python中的Keras和TensorFlow等库支持迁移学习。 总结而言,Python机器学习在图像识别领域提供了丰富的工具和库,可以帮助我们实现从图像中识别和理解信息的自动化过程。无论是从事研究还是应用开发,使用Python进行图像识别都是一个很好的选择。 ### 回答2: Python 是一种流行的编程语言,它在机器学习领域得到了广泛应用,其中包括图像识别。图像识别是指使用机器学习算法识别和分类图像的能力。 Python 机器学习库中最著名且常用的是 TensorFlow 和 Keras。TensorFlow 是由 Google 开发的开源库,提供了一种构建和训练神经网络的框架。Keras 是一种高级神经网络库,它建立在 TensorFlow 之上,提供了简单易用的接口。使用这些库,我们可以使用 Python 编写代码来创建、训练和测试图像识别模型。 对于图像识别任务,我们通常会使用卷积神经网络(Convolutional Neural Network,CNN)。CNN 是一种深度学习模型,专门用于处理图像数据。该模型通过卷积层、池化层和全连接层等组件来提取图像中的特征,并进行分类或识别。 在使用 Python 进行图像识别时,我们需要一些预处理步骤。首先,我们需要准备训练数据集和测试数据集。然后,我们可以使用 TensorFlow 或 Keras 中的函数来加载和处理图像数据。这些函数可以帮助我们将图像转换为数值矩阵,以便模型能够处理。 接下来,我们可以构建 CNN 模型。使用 TensorFlow 和 Keras,我们可以轻松地定义卷积层、池化层和全连接层,以及它们之间的连接。还可以选择不同的激活函数、优化算法和损失函数,以进一步改进模型的性能。 一旦我们定义好了模型,就可以将训练数据送入模型进行训练。通常,我们使用梯度下降等优化算法来最小化模型的损失函数,并反复迭代调整模型参数。训练完成后,我们可以使用测试数据评估模型的准确性。 总结来说,Python 机器学习库提供了强大的工具和函数,使我们能够用 CNN 模型进行图像识别。通过使用 TensorFlow 和 Keras 等库,我们可以更容易地构建、训练和测试图像识别模型,从而在图像分类和识别等任务中取得更好的效果。 ### 回答3: Python机器学习在图像识别中得到了广泛应用。图像识别是计算机视觉的一个重要研究方向,旨在使计算机能够自动识别和理解图像信息。 利用Python机器学习技术进行图像识别,主要涉及以下几个方面。首先,收集并准备图像数据集。这可以通过网络爬虫、数据库等方式获取大量标注的图像数据,然后将其转化为可供机器学习算法进行处理的格式。 其次,需要选择适当的机器学习算法。常用的图像识别算法包括卷积神经网络(CNN)、支持向量机(SVM)和决策树等。Python的机器学习库如Scikit-learn、TensorFlow和Keras都提供了这些经典算法的实现。 然后,使用选择的算法对图像进行训练和测试。训练过程中,通过输入大量已标注的图像样本,利用机器学习算法提取特征和学习模式,从而使机器能够学会识别图像中的目标。测试过程中,将未知的图像样本输入训练好的模型,判断其属于哪个类别。 最后,评估和优化模型的性能。通过计算模型的准确率、召回率、精确度等指标,评估模型的性能。如果模型性能不理想,可以尝试调整算法参数、增加训练样本数量或改进特征提取方法等来优化模型。 总而言之,Python机器学习在图像识别中有着广泛的应用前景。随着深度学习和人工智能的不断发展,我们将会看到更加强大和智能的图像识别系统的出现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咕德猫宁丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值