防重复提交详解:从前端Vue到后端Java的全面解决方案

news2025/3/17 6:08:10

防重复提交详解:从前端Vue到后端Java的全面解决方案

一、重复提交问题概述

在Web应用开发中,表单重复提交是一个常见问题,可能导致:

  • 数据库中出现重复记录
  • 重复执行业务逻辑(如多次扣款)
  • 系统资源浪费
  • 用户体验下降

本文将从前端Vue和后端Java两个层面,详细介绍防止重复提交的多种解决方案。

二、前端防重复提交(Vue.js)

1. 禁用提交按钮方案

最基本的防重复提交方法是在表单提交后禁用提交按钮,直到请求完成。

案例实现:

<template>
  <div class="max-w-md mx-auto p-6 bg-white rounded-lg shadow-md">
    <h2 class="text-xl font-bold mb-4">方案一:禁用提交按钮</h2>
    
    <form @submit.prevent="submitForm" class="space-y-4">
      <div>
        <label for="username" class="block text-sm font-medium mb-1">用户名</label>
        <input 
          id="username" 
          v-model="formData.username" 
          type="text" 
          class="w-full px-3 py-2 border border-gray-300 rounded-md"
          required
        />
      </div>
      
      <div>
        <label for="email" class="block text-sm font-medium mb-1">邮箱</label>
        <input 
          id="email" 
          v-model="formData.email" 
          type="email" 
          class="w-full px-3 py-2 border border-gray-300 rounded-md"
          required
        />
      </div>
      
      <div>
        <button 
          type="submit" 
          class="w-full py-2 px-4 bg-green-600 hover:bg-green-700 text-white font-medium rounded-md transition-colors"
          :disabled="isSubmitting"
        >
          <span v-if="isSubmitting">提交中...</span>
          <span v-else>提交</span>
        </button>
      </div>
      
      <div v-if="message" :class="[success ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800', 'p-3 rounded-md']">
        {{ message }}
      </div>
    </form>
  </div>
</template>

<script setup>
import { ref, reactive } from 'vue';

const formData = reactive({
  username: '',
  email: ''
});

const isSubmitting = ref(false);
const message = ref('');
const success = ref(false);

async function submitForm() {
  // 如果已经在提交中,直接返回
  if (isSubmitting.value) {
    return;
  }
  
  try {
    // 设置提交状态为true
    isSubmitting.value = true;
    message.value = '';
    
    // 模拟API请求
    await new Promise(resolve => setTimeout(resolve, 2000));
    
    // 请求成功
    success.value = true;
    message.value = '表单提交成功!';
    
    // 重置表单
    formData.username = '';
    formData.email = '';
    
  } catch (error) {
    // 请求失败
    success.value = false;
    message.value = '提交失败:' + (error.message || '未知错误');
  } finally {
    // 无论成功失败,都将提交状态设为false
    isSubmitting.value = false;
  }
}
</script>

优点:

  • 实现简单,适用于大多数场景
  • 用户体验良好,提供明确的视觉反馈

缺点:

  • 如果用户刷新页面,状态会丢失
  • 不能防止用户通过其他方式(如API工具)重复提交

2. 提交状态与加载指示器方案

增强用户体验,添加加载指示器,让用户知道请求正在处理中。

案例实现:

<template>
  <div class="max-w-md mx-auto p-6 bg-white rounded-lg shadow-md">
    <h2 class="text-xl font-bold mb-4">方案二:提交状态与加载指示器</h2>
    
    <form @submit.prevent="submitForm" class="space-y-4">
      <div>
        <label for="title" class="block text-sm font-medium mb-1">标题</label>
        <input 
          id="title" 
          v-model="formData.title" 
          type="text" 
          class="w-full px-3 py-2 border border-gray-300 rounded-md"
          required
        />
      </div>
      
      <div>
        <label for="content" class="block text-sm font-medium mb-1">内容</label>
        <textarea 
          id="content" 
          v-model="formData.content" 
          class="w-full px-3 py-2 border border-gray-300 rounded-md"
          rows="4"
          required
        ></textarea>
      </div>
      
      <div>
        <button 
          type="submit" 
          class="w-full py-2 px-4 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-md transition-colors relative"
          :disabled="isSubmitting"
        >
          <span v-if="isSubmitting" class="flex items-center justify-center">
            <svg class="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
              <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
              <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
            </svg>
            处理中...
          </span>
          <span v-else>发布文章</span>
        </button>
      </div>
      
      <div v-if="submitStatus.show" :class="[submitStatus.success ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800', 'p-3 rounded-md']">
        {{ submitStatus.message }}
      </div>
    </form>
    
    <!-- 全屏加载遮罩 -->
    <div v-if="isSubmitting" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
      <div class="bg-white p-6 rounded-lg shadow-lg text-center">
        <svg class="animate-spin h-10 w-10 text-blue-600 mx-auto mb-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
          <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
          <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
        </svg>
        <p class="text-gray-700">正在提交您的文章,请稍候...</p>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, reactive } from 'vue';

const formData = reactive({
  title: '',
  content: ''
});

const isSubmitting = ref(false);
const submitStatus = reactive({
  show: false,
  success: false,
  message: ''
});

async function submitForm() {
  if (isSubmitting.value) {
    return;
  }
  
  try {
    isSubmitting.value = true;
    submitStatus.show = false;
    
    // 模拟API请求
    await new Promise(resolve => setTimeout(resolve, 3000));
    
    // 请求成功
    submitStatus.success = true;
    submitStatus.message = '文章发布成功!';
    submitStatus.show = true;
    
    // 重置表单
    formData.title = '';
    formData.content = '';
    
  } catch (error) {
    // 请求失败
    submitStatus.success = false;
    submitStatus.message = '发布失败:' + (error.message || '服务器错误');
    submitStatus.show = true;
  } finally {
    isSubmitting.value = false;
  }
}
</script>

优点:

  • 提供更丰富的视觉反馈
  • 防止用户在请求处理过程中进行其他操作

缺点:

  • 仍然不能防止用户刷新页面后重新提交
  • 不能防止恶意用户通过其他方式重复提交

3. 表单令牌方案

使用唯一令牌标识每个表单实例,确保同一表单只能提交一次。

案例实现:

<template>
  <div class="max-w-md mx-auto p-6 bg-white rounded-lg shadow-md">
    <h2 class="text-xl font-bold mb-4">方案三:表单令牌</h2>
    
    <form @submit.prevent="submitForm" class="space-y-4">
      <div>
        <label for="name" class="block text-sm font-medium mb-1">姓名</label>
        <input 
          id="name" 
          v-model="formData.name" 
          type="text" 
          class="w-full px-3 py-2 border border-gray-300 rounded-md"
          required
        />
      </div>
      
      <div>
        <label for="phone" class="block text-sm font-medium mb-1">电话</label>
        <input 
          id="phone" 
          v-model="formData.phone" 
          type="tel" 
          class="w-full px-3 py-2 border border-gray-300 rounded-md"
          required
        />
      </div>
      
      <div>
        <label for="address" class="block text-sm font-medium mb-1">地址</label>
        <input 
          id="address" 
          v-model="formData.address" 
          type="text" 
          class="w-full px-3 py-2 border border-gray-300 rounded-md"
          required
        />
      </div>
      
      <!-- 隐藏的表单令牌 -->
      <input type="hidden" name="formToken" :value="formToken" />
      
      <div>
        <button 
          type="submit" 
          class="w-full py-2 px-4 bg-purple-600 hover:bg-purple-700 text-white font-medium rounded-md transition-colors"
          :disabled="isSubmitting"
        >
          <span v-if="isSubmitting" class="flex items-center justify-center">
            <svg class="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
              <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
              <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
            </svg>
            提交中...
          </span>
          <span v-else>提交订单</span>
        </button>
      </div>
      
      <div v-if="resultMessage" :class="[isSuccess ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800', 'p-3 rounded-md']">
        {{ resultMessage }}
      </div>
      
      <div v-if="isTokenUsed" class="p-3 bg-yellow-100 text-yellow-800 rounded-md">
        <p>检测到此表单已提交过,请勿重复提交!</p>
        <button 
          @click="resetForm" 
          class="mt-2 px-4 py-2 bg-yellow-500 hover:bg-yellow-600 text-white rounded-md"
        >
          重置表单
        </button>
      </div>
    </form>
  </div>
</template>

<script setup>
import { ref, reactive, onMounted } from 'vue';

const formData = reactive({
  name: '',
  phone: '',
  address: ''
});

const isSubmitting = ref(false);
const resultMessage = ref('');
const isSuccess = ref(false);
const isTokenUsed = ref(false);
const formToken = ref('');

// 生成唯一令牌
function generateToken() {
  return Date.now().toString(36) + Math.random().toString(36).substring(2);
}

// 检查令牌是否已使用
function checkTokenUsed(token) {
  const usedTokens = JSON.parse(localStorage.getItem('usedFormTokens') || '[]');
  return usedTokens.includes(token);
}

// 标记令牌为已使用
function markTokenAsUsed(token) {
  const usedTokens = JSON.parse(localStorage.getItem('usedFormTokens') || '[]');
  usedTokens.push(token);
  localStorage.setItem('usedFormTokens', JSON.stringify(usedTokens));
}

// 重置表单和令牌
function resetForm() {
  formData.name = '';
  formData.phone = '';
  formData.address = '';
  formToken.value = generateToken();
  isTokenUsed.value = false;
  resultMessage.value = '';
}

async function submitForm() {
  // 检查令牌是否已使用
  if (checkTokenUsed(formToken.value)) {
    isTokenUsed.value = true;
    return;
  }
  
  if (isSubmitting.value) {
    return;
  }
  
  try {
    isSubmitting.value = true;
    resultMessage.value = '';
    
    // 模拟API请求
    await new Promise(resolve => setTimeout(resolve, 2000));
    
    // 标记令牌为已使用
    markTokenAsUsed(formToken.value);
    
    // 请求成功
    isSuccess.value = true;
    resultMessage.value = '订单提交成功!';
    
  } catch (error) {
    // 请求失败
    isSuccess.value = false;
    resultMessage.value = '提交失败:' + (error.message || '服务器错误');
  } finally {
    isSubmitting.value = false;
  }
}

onMounted(() => {
  // 组件挂载时生成令牌
  formToken.value = generateToken();
});
</script>

优点:

  • 可以防止同一表单多次提交
  • 即使用户刷新页面,也能检测到表单已提交

缺点:

  • 本地存储的令牌可能被清除
  • 需要后端配合验证令牌

4. 防抖与节流方案

使用防抖(debounce)或节流(throttle)技术防止用户快速多次点击提交按钮。

案例实现:

<template>
  <div class="max-w-md mx-auto p-6 bg-white rounded-lg shadow-md">
    <h2 class="text-xl font-bold mb-4">方案四:防抖与节流</h2>
    
    <form @submit.prevent class="space-y-4">
      <div>
        <label for="search" class="block text-sm font-medium mb-1">搜索关键词</label>
        <input 
          id="search" 
          v-model="searchTerm" 
          type="text" 
          class="w-full px-3 py-2 border border-gray-300 rounded-md"
          placeholder="输入关键词..."
        />
      </div>
      
      <div class="grid grid-cols-2 gap-4">
        <div>
          <button 
            @click="normalSubmit"
            class="w-full py-2 px-4 bg-red-600 hover:bg-red-700 text-white font-medium rounded-md transition-colors"
          >
            普通提交
          </button>
          <div class="mt-2 text-xs text-gray-500">
            点击次数: {{ normalClickCount }}
          </div>
        </div>
        
        <div>
          <button 
            @click="debouncedSubmit"
            class="w-full py-2 px-4 bg-green-600 hover:bg-green-700 text-white font-medium rounded-md transition-colors"
          >
            防抖提交
          </button>
          <div class="mt-2 text-xs text-gray-500">
            实际提交次数: {{ debounceSubmitCount }}
          </div>
        </div>
      </div>
      
      <div class="grid grid-cols-2 gap-4 mt-4">
        <div>
          <button 
            @click="throttledSubmit"
            class="w-full py-2 px-4 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-md transition-colors"
          >
            节流提交
          </button>
          <div class="mt-2 text-xs text-gray-500">
            实际提交次数: {{ throttleSubmitCount }}
          </div>
        </div>
        
        <div>
          <button 
            @click="resetCounts"
            class="w-full py-2 px-4 bg-gray-600 hover:bg-gray-700 text-white font-medium rounded-md transition-colors"
          >
            重置计数
          </button>
        </div>
      </div>
      
      <div class="mt-4 p-3 bg-gray-100 rounded-md">
        <h3 class="font-medium mb-2">日志:</h3>
        <div class="h-40 overflow-y-auto text-sm">
          <div v-for="(log, index) in logs" :key="index" class="mb-1">
            {{ log }}
          </div>
        </div>
      </div>
    </form>
  </div>
</template>

<script setup>
import { ref, onUnmounted } from 'vue';

const searchTerm = ref('');
const normalClickCount = ref(0);
const debounceSubmitCount = ref(0);
const throttleSubmitCount = ref(0);
const logs = ref([]);

// 添加日志
function addLog(message) {
  const now = new Date();
  const timeStr = `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}.${now.getMilliseconds()}`;
  logs.value.unshift(`[${timeStr}] ${message}`);
}

// 普通提交
function normalSubmit() {
  normalClickCount.value++;
  addLog(`普通提交被触发,搜索词: ${searchTerm.value}`);
}

// 防抖函数
function debounce(func, delay) {
  let timer = null;
  return function(...args) {
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => {
      func.apply(this, args);
    }, delay);
  };
}

// 节流函数
function throttle(func, limit) {
  let inThrottle = false;
  return function(...args) {
    if (!inThrottle) {
      func.apply(this, args);
      inThrottle = true;
      setTimeout(() => {
        inThrottle = false;
      }, limit);
    }
  };
}

// 防抖提交处理函数
function handleDebouncedSubmit() {
  debounceSubmitCount.value++;
  addLog(`防抖提交被触发,搜索词: ${searchTerm.value}`);
}

// 节流提交处理函数
function handleThrottledSubmit() {
  throttleSubmitCount.value++;
  addLog(`节流提交被触发,搜索词: ${searchTerm.value}`);
}

// 创建防抖和节流版本的提交函数
const debouncedSubmit = debounce(handleDebouncedSubmit, 1000); // 1秒防抖
const throttledSubmit = throttle(handleThrottledSubmit, 2000); // 2秒节流

// 重置计数
function resetCounts() {
  normalClickCount.value = 0;
  debounceSubmitCount.value = 0;
  throttleSubmitCount.value = 0;
  logs.value = [];
  addLog('计数已重置');
}

// 组件卸载时清除定时器
onUnmounted(() => {
  // 这里应该清除定时器,但由于我们的防抖和节流函数是闭包形式,
  // 实际项目中应该使用更完善的实现方式,确保定时器被正确清除
});
</script>

优点:

  • 有效防止用户快速多次点击
  • 减轻服务器负担
  • 适用于搜索、自动保存等场景

缺点:

  • 不适用于所有场景,如支付等需要精确控制的操作
  • 需要合理设置延迟时间

三、后端防重复提交(Java)

1. 表单令牌验证方案

后端验证前端提交的表单令牌,确保同一令牌只能使用一次。

案例实现:

// Controller层
@RestController
@RequestMapping("/api")
public class FormController {
    
    private final FormTokenService tokenService;
    private final FormService formService;
    
    public FormController(FormTokenService tokenService, FormService formService) {
        this.tokenService = tokenService;
        this.formService = formService;
    }
    
    @PostMapping("/submit")
    public ResponseEntity<?> submitForm(@RequestBody FormRequest request,
                                       @RequestHeader("X-Form-Token") String token) {
        
        // 验证令牌是否有效
        if (!tokenService.isValidToken(token)) {
            return ResponseEntity
                .status(HttpStatus.BAD_REQUEST)
                .body(new ApiResponse(false, "无效的表单令牌"));
        }
        
        // 验证令牌是否已使用
        if (tokenService.isTokenUsed(token)) {
            return ResponseEntity
                .status(HttpStatus.TOO_MANY_REQUESTS)
                .body(new ApiResponse(false, "表单已提交,请勿重复提交"));
        }
        
        try {
            // 标记令牌为已使用(在处理业务逻辑前)
            tokenService.markTokenAsUsed(token);
            
            // 处理表单提交
            String formId = formService.processForm(request);
            
            return ResponseEntity.ok(new ApiResponse(true, "表单提交成功", formId));
            
        } catch (Exception e) {
            // 发生异常时,可以选择是否将令牌标记为未使用
            // tokenService.invalidateToken(token);
            
            return ResponseEntity
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(new ApiResponse(false, "表单提交失败: " + e.getMessage()));
        }
    }
}

// 令牌服务接口
public interface FormTokenService {
    boolean isValidToken(String token);
    boolean isTokenUsed(String token);
    void markTokenAsUsed(String token);
    void invalidateToken(String token);
}

// 令牌服务实现(使用内存缓存)
@Service
public class FormTokenServiceImpl implements FormTokenService {
    
    // 使用Caffeine缓存库
    private final Cache<String, Boolean> usedTokens;
    
    public FormTokenServiceImpl() {
        // 创建缓存,24小时后过期
        this.usedTokens = Caffeine.newBuilder()
            .expireAfterWrite(24, TimeUnit.HOURS)
            .maximumSize(10_000)
            .build();
    }
    
    @Override
    public boolean isValidToken(String token) {
        // 简单验证:非空且长度合适
        return token != null && token.length() >= 8;
    }
    
    @Override
    public boolean isTokenUsed(String token) {
        return usedTokens.getIfPresent(token) != null;
    }
    
    @Override
    public void markTokenAsUsed(String token) {
        usedTokens.put(token, Boolean.TRUE);
    }
    
    @Override
    public void invalidateToken(String token) {
        usedTokens.invalidate(token);
    }
}

// 请求和响应类
public class FormRequest {
    private String name;
    private String email;
    private String content;
    
    // getters and setters
}

public class ApiResponse {
    private boolean success;
    private String message;
    private Object data;
    
    public ApiResponse(boolean success, String message) {
        this.success = success;
        this.message = message;
    }
    
    public ApiResponse(boolean success, String message, Object data) {
        this.success = success;
        this.message = message;
        this.data = data;
    }
    
    // getters
}

优点:

  • 可靠地防止重复提交
  • 可以设置令牌过期时间
  • 适用于各种表单提交场景

缺点:

  • 需要前后端配合
  • 缓存管理可能增加系统复杂性

2. 数据库唯一约束方案

利用数据库唯一约束防止重复数据插入。

案例实现:

// 实体类
@Entity
@Table(name = "orders", 
       uniqueConstraints = @UniqueConstraint(columnNames = {"order_number"}))
public class Order {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "order_number", unique = true, nullable = false)
    private String orderNumber;
    
    @Column(name = "customer_name")
    private String customerName;
    
    @Column(name = "amount")
    private BigDecimal amount;
    
    @Column(name = "created_at")
    private LocalDateTime createdAt;
    
    // getters and setters
}

// 仓库接口
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
    boolean existsByOrderNumber(String orderNumber);
}

// 服务实现
@Service
public class OrderServiceImpl implements OrderService {
    
    private final OrderRepository orderRepository;
    
    public OrderServiceImpl(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }
    
    @Override
    @Transactional
    public String createOrder(OrderRequest request) {
        // 生成订单号
        String orderNumber = generateOrderNumber();
        
        // 检查订单号是否已存在
        if (orderRepository.existsByOrderNumber(orderNumber)) {
            throw new DuplicateOrderException("订单号已存在");
        }
        
        // 创建订单
        Order order = new Order();
        order.setOrderNumber(orderNumber);
        order.setCustomerName(request.getCustomerName());
        order.setAmount(request.getAmount());
        order.setCreatedAt(LocalDateTime.now());
        
        try {
            orderRepository.save(order);
            return orderNumber;
        } catch (DataIntegrityViolationException e) {
            // 捕获唯一约束违反异常
            throw new DuplicateOrderException("创建订单失败,可能是重复提交", e);
        }
    }
    
    private String generateOrderNumber() {
        // 生成唯一订单号的逻辑
        return "ORD" + System.currentTimeMillis() + 
               String.format("%04d", new Random().nextInt(10000));
    }
}

// 控制器
@RestController
@RequestMapping("/api/orders")
public class OrderController {
    
    private final OrderService orderService;
    
    public OrderController(OrderService orderService) {
        this.orderService = orderService;
    }
    
    @PostMapping
    public ResponseEntity<?> createOrder(@RequestBody OrderRequest request) {
        try {
            String orderNumber = orderService.createOrder(request);
            return ResponseEntity.ok(new ApiResponse(true, "订单创建成功", orderNumber));
        } catch (DuplicateOrderException e) {
            return ResponseEntity
                .status(HttpStatus.CONFLICT)
                .body(new ApiResponse(false, e.getMessage()));
        } catch (Exception e) {
            return ResponseEntity
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(new ApiResponse(false, "创建订单失败: " + e.getMessage()));
        }
    }
}

// 异常类
public class DuplicateOrderException extends RuntimeException {
    public DuplicateOrderException(String message) {
        super(message);
    }
    
    public DuplicateOrderException(String message, Throwable cause) {
        super(message, cause);
    }
}

优点:

  • 在数据库层面保证数据唯一性
  • 即使应用服务器出现问题,也能保证数据一致性
  • 适用于关键业务数据

缺点:

  • 只能防止数据重复,不能防止业务逻辑重复执行
  • 可能导致用户体验不佳(如果没有适当的错误处理)

3. 事务隔离与锁机制方案

使用数据库事务隔离级别和锁机制防止并发提交。

案例实现:

// 服务实现
@Service
public class PaymentServiceImpl implements PaymentService {
    
    private final PaymentRepository paymentRepository;
    private final AccountRepository accountRepository;
    
    public PaymentServiceImpl(PaymentRepository paymentRepository, 
                             AccountRepository accountRepository) {
        this.paymentRepository = paymentRepository;
        this.accountRepository = accountRepository;
    }
    
    @Override
    @Transactional(isolation = Isolation.SERIALIZABLE)
    public String processPayment(PaymentRequest request) {
        // 检查是否存在相同的支付请求
        if (paymentRepository.existsByTransactionId(request.getTransactionId())) {
            throw new DuplicatePaymentException("该交易已处理,请勿重复支付");
        }
        
        // 获取账户(使用悲观锁)
        Account account = accountRepository.findByIdWithLock(request.getAccountId())
            .orElseThrow(() -> new AccountNotFoundException("账户不存在"));
        
        // 检查余额
        if (account.getBalance().compareTo(request.getAmount()) < 0) {
            throw new InsufficientBalanceException("账户余额不足");
        }
        
        // 扣减余额
        account.setBalance(account.getBalance().subtract(request.getAmount()));
        accountRepository.save(account);
        
        // 创建支付记录
        Payment payment = new Payment();
        payment.setTransactionId(request.getTransactionId());
        payment.setAccountId(request.getAccountId());
        payment.setAmount(request.getAmount());
        payment.setStatus("SUCCESS");
        payment.setCreatedAt(LocalDateTime.now());
        
        paymentRepository.save(payment);
        
        return payment.getTransactionId();
    }
}

// 仓库接口
@Repository
public interface AccountRepository extends JpaRepository<Account, Long> {
    
    // 使用悲观锁查询账户
    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query("SELECT a FROM Account a WHERE a.id = :id")
    Optional<Account> findByIdWithLock(@Param("id") Long id);
}

@Repository
public interface PaymentRepository extends JpaRepository<Payment, Long> {
    boolean existsByTransactionId(String transactionId);
}

// 控制器
@RestController
@RequestMapping("/api/payments")
public class PaymentController {
    
    private final PaymentService paymentService;
    
    public PaymentController(PaymentService paymentService) {
        this.paymentService = paymentService;
    }
    
    @PostMapping
    public ResponseEntity<?> processPayment(@RequestBody PaymentRequest request) {
        try {
            String transactionId = paymentService.processPayment(request);
            return ResponseEntity.ok(new ApiResponse(true, "支付成功", transactionId));
        } catch (DuplicatePaymentException e) {
            return ResponseEntity
                .status(HttpStatus.CONFLICT)
                .body(new ApiResponse(false, e.getMessage()));
        } catch (Exception e) {
            return ResponseEntity
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(new ApiResponse(false, "支付处理失败: " + e.getMessage()));
        }
    }
}

优点:

  • 可以有效防止并发情况下的重复提交
  • 保证数据一致性
  • 适用于金融交易等高敏感度场景

缺点:

  • 高隔离级别可能影响系统性能
  • 锁机制可能导致死锁
  • 实现复杂度较高

4. 分布式锁方案

在分布式系统中使用分布式锁防止重复提交。

案例实现(使用Redis实现分布式锁):

// 分布式锁服务接口
public interface DistributedLockService {
    boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit);
    void unlock(String lockKey);
    boolean isLocked(String lockKey);
}

// Redis实现的分布式锁服务
@Service
public class RedisDistributedLockService implements DistributedLockService {
    
    private final RedissonClient redissonClient;
    
    public RedisDistributedLockService(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }
    
    @Override
    public boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit) {
        RLock lock = redissonClient.getLock(lockKey);
        try {
            return lock.tryLock(waitTime, leaseTime, unit);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }
    }
    
    @Override
    public void unlock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        if (lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }
    
    @Override
    public boolean isLocked(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        return lock.isLocked();
    }
}

// 使用分布式锁的服务实现
@Service
public class RegistrationServiceImpl implements RegistrationService {
    
    private final DistributedLockService lockService;
    private final UserRepository userRepository;
    
    public RegistrationServiceImpl(DistributedLockService lockService,
                                  UserRepository userRepository) {
        this.lockService = lockService;
        this.userRepository = userRepository;
    }
    
    @Override
    public String registerUser(UserRegistrationRequest request) {
        // 创建锁键(基于用户名或邮箱)
        String lockKey = "user_registration:" + request.getEmail();
        
        boolean locked = false;
        try {
            // 尝试获取锁,等待5秒,锁定30秒
            locked = lockService.tryLock(lockKey, 5, 30, TimeUnit.SECONDS);
            
            if (!locked) {
                throw new ConcurrentOperationException("操作正在处理中,请稍后再试");
            }
            
            // 检查用户是否已存在
            if (userRepository.existsByEmail(request.getEmail())) {
                throw new DuplicateUserException("该邮箱已注册");
            }
            
            // 创建用户
            User user = new User();
            user.setUsername(request.getUsername());
            user.setEmail(request.getEmail());
            user.setPassword(encryptPassword(request.getPassword()));
            user.setCreatedAt(LocalDateTime.now());
            
            userRepository.save(user);
            
            return user.getId().toString();
            
        } finally {
            // 释放锁
            if (locked) {
                lockService.unlock(lockKey);
            }
        }
    }
    
    private String encryptPassword(String password) {
        // 密码加密逻辑
        return BCrypt.hashpw(password, BCrypt.gensalt());
    }
}

// 控制器
@RestController
@RequestMapping("/api/users")
public class UserController {
    
    private final RegistrationService registrationService;
    
    public UserController(RegistrationService registrationService) {
        this.registrationService = registrationService;
    }
    
    @PostMapping("/register")
    public ResponseEntity<?> registerUser(@RequestBody UserRegistrationRequest request) {
        try {
            String userId = registrationService.registerUser(request);
            return ResponseEntity.ok(new ApiResponse(true, "用户注册成功", userId));
        } catch (DuplicateUserException e) {
            return ResponseEntity
                .status(HttpStatus.CONFLICT)
                .body(new ApiResponse(false, e.getMessage()));
        } catch (ConcurrentOperationException e) {
            return ResponseEntity
                .status(HttpStatus.TOO_MANY_REQUESTS)
                .body(new ApiResponse(false, e.getMessage()));
        } catch (Exception e) {
            return ResponseEntity
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(new ApiResponse(false, "注册失败: " + e.getMessage()));
        }
    }
}

优点:

  • 适用于分布式系统环境
  • 可以跨服务器防止重复提交
  • 灵活的锁定策略

缺点:

  • 依赖外部系统(如Redis)
  • 实现复杂度高
  • 需要处理锁超时和失效情况

四、前后端结合的完整解决方案

完整案例:订单提交系统

下面是一个结合前端Vue和后端Java的完整订单提交系统,综合运用多种防重复提交技术。

前端实现(Vue.js):

<template>
  <div class="max-w-2xl mx-auto p-6 bg-white rounded-lg shadow-md">
    <h1 class="text-2xl font-bold mb-6 text-gray-800">订单提交系统</h1>
    
    <form @submit.prevent="submitOrder" class="space-y-6">
      <!-- 客户信息 -->
      <div class="bg-gray-50 p-4 rounded-md">
        <h2 class="text-lg font-medium mb-3">客户信息</h2>
        <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
          <div>
            <label for="customerName" class="block text-sm font-medium mb-1">客户姓名</label>
            <input 
              id="customerName" 
              v-model="orderData.customerName" 
              type="text" 
              class="w-full px-3 py-2 border border-gray-300 rounded-md"
              required
            />
          </div>
          <div>
            <label for="phone" class="block text-sm font-medium mb-1">联系电话</label>
            <input 
              id="phone" 
              v-model="orderData.phone" 
              type="tel" 
              class="w-full px-3 py-2 border border-gray-300 rounded-md"
              required
            />
          </div>
        </div>
      </div>
      
      <!-- 订单信息 -->
      <div class="bg-gray-50 p-4 rounded-md">
        <h2 class="text-lg font-medium mb-3">订单信息</h2>
        <div class="space-y-4">
          <div>
            <label for="productId" class="block text-sm font-medium mb-1">产品选择</label>
            <select 
              id="productId" 
              v-model="orderData.productId" 
              class="w-full px-3 py-2 border border-gray-300 rounded-md"
              required
            >
              <option value="">请选择产品</option>
              <option value="1">产品A - ¥100</option>
              <option value="2">产品B - ¥200</option>
              <option value="3">产品C - ¥300</option>
            </select>
          </div>
          
          <div>
            <label for="quantity" class="block text-sm font-medium mb-1">数量</label>
            <input 
              id="quantity" 
              v-model.number="orderData.quantity" 
              type="number" 
              min="1"
              class="w-full px-3 py-2 border border-gray-300 rounded-md"
              required
            />
          </div>
          
          <div>
            <label for="address" class="block text-sm font-medium mb-1">收货地址</label>
            <textarea 
              id="address" 
              v-model="orderData.address" 
              class="w-full px-3 py-2 border border-gray-300 rounded-md"
              rows="2"
              required
            ></textarea>
          </div>
        </div>
      </div>
      
      <!-- 订单摘要 -->
      <div class="bg-gray-50 p-4 rounded-md">
        <h2 class="text-lg font-medium mb-3">订单摘要</h2>
        <div class="flex justify-between mb-2">
          <span>产品价格:</span>
          <span>¥{{ productPrice }}</span>
        </div>
        <div class="flex justify-between mb-2">
          <span>数量:</span>
          <span>{{ orderData.quantity || 0 }}</span>
        </div>
        <div class="flex justify-between font-bold">
          <span>总计:</span>
          <span>¥{{ totalPrice }}</span>
        </div>
      </div>
      
      <!-- 隐藏的表单令牌 -->
      <input type="hidden" name="orderToken" :value="orderToken" />
      
      <!-- 提交按钮 -->
      <div>
        <button 
          type="submit" 
          class="w-full py-3 px-4 bg-green-600 hover:bg-green-700 text-white font-medium rounded-md transition-colors"
          :disabled="isSubmitting || isOrderSubmitted"
        >
          <span v-if="isSubmitting" class="flex items-center justify-center">
            <svg class="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
              <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
              <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
            </svg>
            订单提交中...
          </span>
          <span v-else-if="isOrderSubmitted">订单已提交</span>
          <span v-else>提交订单</span>
        </button>
      </div>
      
      <!-- 结果消息 -->
      <div v-if="resultMessage" :class="[isSuccess ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800', 'p-4 rounded-md']">
        <p class="font-medium">{{ resultMessage }}</p>
        <p v-if="orderNumber" class="mt-2">
          订单号: <span class="font-mono font-bold">{{ orderNumber }}</span>
        </p>
      </div>
    </form>
    
    <!-- 确认对话框 -->
    <div v-if="showConfirmDialog" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
      <div class="bg-white p-6 rounded-lg shadow-lg max-w-md w-full">
        <h3 class="text-xl font-bold mb-4">确认提交订单</h3>
        <p class="mb-4">您确定要提交此订单吗?提交后将无法修改。</p>
        <div class="flex justify-end space-x-4">
          <button 
            @click="showConfirmDialog = false" 
            class="px-4 py-2 bg-gray-200 hover:bg-gray-300 rounded-md"
          >
            取消
          </button>
          <button 
            @click="confirmSubmit" 
            class="px-4 py-2 bg-green-600 hover:bg-green-700 text-white rounded-md"
          >
            确认提交
          </button>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, reactive, computed, onMounted } from 'vue';

// 订单数据
const orderData = reactive({
  customerName: '',
  phone: '',
  productId: '',
  quantity: 1,
  address: ''
});

// 状态变量
const isSubmitting = ref(false);
const isOrderSubmitted = ref(false);
const resultMessage = ref('');
const isSuccess = ref(false);
const orderNumber = ref('');
const orderToken = ref('');
const showConfirmDialog = ref(false);

// 计算属性
const productPrice = computed(() => {
  switch (orderData.productId) {
    case '1': return 100;
    case '2': return 200;
    case '3': return 300;
    default: return 0;
  }
});

const totalPrice = computed(() => {
  return productPrice.value * (orderData.quantity || 0);
});

// 生成唯一令牌
function generateToken() {
  return Date.now().toString(36) + Math.random().toString(36).substring(2);
}

// 防抖函数
function debounce(func, delay) {
  let timer = null;
  return function(...args) {
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => {
      func.apply(this, args);
    }, delay);
  };
}

// 提交订单(显示确认对话框)
function submitOrder() {
  // 如果已提交或正在提交,直接返回
  if (isSubmitting.value || isOrderSubmitted.value) {
    return;
  }
  
  // 显示确认对话框
  showConfirmDialog.value = true;
}

// 确认提交(实际提交逻辑)
const confirmSubmit = debounce(async function() {
  showConfirmDialog.value = false;
  
  if (isSubmitting.value || isOrderSubmitted.value) {
    return;
  }
  
  try {
    isSubmitting.value = true;
    resultMessage.value = '';
    
    // 准备提交数据
    const payload = {
      ...orderData,
      totalPrice: totalPrice.value,
      _token: orderToken.value
    };
    
    // 发送到后端
    const response = await fetch('/api/orders', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Order-Token': orderToken.value
      },
      body: JSON.stringify(payload)
    });
    
    const data = await response.json();
    
    if (!response.ok) {
      throw new Error(data.message || '订单提交失败');
    }
    
    // 提交成功
    isSuccess.value = true;
    resultMessage.value = '订单提交成功!';
    orderNumber.value = data.data; // 订单号
    isOrderSubmitted.value = true;
    
    // 生成新令牌(以防用户想再次提交)
    orderToken.value = generateToken();
    
  } catch (error) {
    // 提交失败
    isSuccess.value = false;
    resultMessage.value = error.message;
  } finally {
    isSubmitting.value = false;
  }
}, 300);

onMounted(() => {
  // 组件挂载时生成令牌
  orderToken.value = generateToken();
});
</script>

后端实现(Java Spring Boot):

// 订单实体
@Entity
@Table(name = "orders", 
       uniqueConstraints = @UniqueConstraint(columnNames = {"order_number"}))
public class Order {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "order_number", unique = true, nullable = false)
    private String orderNumber;
    
    @Column(name = "customer_name")
    private String customerName;
    
    @Column(name = "phone")
    private String phone;
    
    @Column(name = "product_id")
    private Long productId;
    
    @Column(name = "quantity")
    private Integer quantity;
    
    @Column(name = "address")
    private String address;
    
    @Column(name = "total_price")
    private BigDecimal totalPrice;
    
    @Column(name = "status")
    private String status;
    
    @Column(name = "created_at")
    private LocalDateTime createdAt;
    
    // getters and setters
}

// 订单服务接口
public interface OrderService {
    String createOrder(OrderRequest request);
}

// 订单服务实现
@Service
@Transactional
public class OrderServiceImpl implements OrderService {
    
    private final OrderRepository orderRepository;
    private final OrderTokenService tokenService;
    
    public OrderServiceImpl(OrderRepository orderRepository, 
                           OrderTokenService tokenService) {
        this.orderRepository = orderRepository;
        this.tokenService = tokenService;
    }
    
    @Override
    @Transactional(isolation = Isolation.SERIALIZABLE)
    public String createOrder(OrderRequest request) {
        // 验证令牌
        String token = request.getToken();
        if (tokenService.isTokenUsed(token)) {
            throw new DuplicateOrderException("订单已提交,请勿重复提交");
        }
        
        try {
            // 标记令牌为已使用
            tokenService.markTokenAsUsed(token);
            
            // 生成订单号
            String orderNumber = generateOrderNumber();
            
            // 创建订单
            Order order = new Order();
            order.setOrderNumber(orderNumber);
            order.setCustomerName(request.getCustomerName());
            order.setPhone(request.getPhone());
            order.setProductId(request.getProductId());
            order.setQuantity(request.getQuantity());
            order.setAddress(request.getAddress());
            order.setTotalPrice(request.getTotalPrice());
            order.setStatus("PENDING");
            order.setCreatedAt(LocalDateTime.now());
            
            orderRepository.save(order);
            
            // 异步处理订单(示例)
            processOrderAsync(order);
            
            return orderNumber;
            
        } catch (DataIntegrityViolationException e) {
            // 捕获数据库唯一约束异常
            throw new DuplicateOrderException("订单创建失败,可能是重复提交", e);
        }
    }
    
    private String generateOrderNumber() {
        return "ORD" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + 
               String.format("%04d", new Random().nextInt(10000));
    }
    
    @Async
    public void processOrderAsync(Order order) {
        // 异步处理订单的逻辑
        try {
            // 模拟处理时间
            Thread.sleep(5000);
            
            // 更新订单状态
            order.setStatus("PROCESSED");
            orderRepository.save(order);
            
        } catch (Exception e) {
            // 处理异常
            order.setStatus("ERROR");
            orderRepository.save(order);
        }
    }
}

// 令牌服务实现
@Service
public class OrderTokenServiceImpl implements OrderTokenService {
    
    private final RedisTemplate<String, Boolean> redisTemplate;
    
    public OrderTokenServiceImpl(RedisTemplate<String, Boolean> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
    
    @Override
    public boolean isTokenUsed(String token) {
        Boolean used = redisTemplate.opsForValue().get("order_token:" + token);
        return used != null && used;
    }
    
    @Override
    public void markTokenAsUsed(String token) {
        redisTemplate.opsForValue().set("order_token:" + token, true, 24, TimeUnit.HOURS);
    }
    
    @Override
    public void invalidateToken(String token) {
        redisTemplate.delete("order_token:" + token);
    }
}

// 控制器
@RestController
@RequestMapping("/api/orders")
public class OrderController {
    
    private final OrderService orderService;
    private static final Logger logger = LoggerFactory.getLogger(OrderController.class);
    
    public OrderController(OrderService orderService) {
        this.orderService = orderService;
    }
    
    @PostMapping
    public ResponseEntity<?> createOrder(@RequestBody OrderRequest request,
                                        @RequestHeader("X-Order-Token") String token) {
        // 设置令牌(以防请求体中没有)
        request.setToken(token);
        
        try {
            // 记录请求日志
            logger.info("Received order request with token: {}", token);
            
            // 创建订单
            String orderNumber = orderService.createOrder(request);
            
            // 记录成功日志
            logger.info("Order created successfully: {}", orderNumber);
            
            return ResponseEntity.ok(new ApiResponse(true, "订单提交成功", orderNumber));
            
        } catch (DuplicateOrderException e) {
            // 记录重复提交日志
            logger.warn("Duplicate order submission: {}", e.getMessage());
            
            return ResponseEntity
                .status(HttpStatus.CONFLICT)
                .body(new ApiResponse(false, e.getMessage()));
                
        } catch (Exception e) {
            // 记录错误日志
            logger.error("Error creating order", e);
            
            return ResponseEntity
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(new ApiResponse(false, "订单提交失败: " + e.getMessage()));
        }
    }
}

五、最佳实践与总结

最佳实践

  1. 多层防护

  2. 前端:禁用按钮 + 视觉反馈 + 表单令牌

  3. 后端:令牌验证 + 数据库约束 + 事务隔离

  4. 分布式系统:分布式锁 + 幂等性设计

  5. 前端防护

  6. 禁用提交按钮,防止用户多次点击

  7. 提供明确的加载状态反馈

  8. 使用防抖/节流限制快速点击

  9. 添加确认对话框增加用户确认步骤

  10. 生成并使用表单令牌

  11. 后端防护

  12. 验证前端提交的令牌

  13. 使用数据库唯一约束

  14. 选择合适的事务隔离级别

  15. 实现幂等性API设计

  16. 使用分布式锁(在分布式系统中)

  17. 记录详细日志,便于问题排查

  18. 异常处理

  19. 前端友好展示错误信息

  20. 后端返回明确的错误状态码和信息

  21. 区分不同类型的错误(如重复提交、服务器错误等)

  22. 性能考虑

  23. 避免过度使用高隔离级别事务

  24. 合理设置锁超时时间

  25. 使用异步处理长时间运行的任务

总结

防止表单重复提交是Web应用开发中的重要环节,需要前后端协同配合。本文详细介绍了多种防重复提交的解决方案:

  1. 前端Vue.js解决方案

  2. 禁用提交按钮

  3. 提交状态与加载指示器

  4. 表单令牌

  5. 防抖与节流

  6. 后端Java解决方案

  7. 表单令牌验证

  8. 数据库唯一约束

  9. 事务隔离与锁机制

  10. 分布式锁

  11. 综合解决方案

  12. 结合前后端多种技术

  13. 多层次防护机制

  14. 完善的异常处理

  15. 良好的用户体验

通过合理选择和组合这些技术,可以有效防止表单重复提交问题,保证系统数据一致性和用户体验。在实际应用中,应根据业务场景和系统架构选择最适合的解决方案。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2316445.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

同一子网通信

添加交换机后的通信流程 1. 同一子网内&#xff08;使用交换机&#xff09; 判断是否在同一子网&#xff1a; 主机A通过子网掩码判断主机B的IP地址是否属于同一子网。若在同一子网&#xff0c;主机A需要通过ARP获取主机B的MAC地址。 ARP请求&#xff08;广播&#xff09;&…

IntelliJ IDEA 快捷键系列:重命名快捷键详解

目录 引言一、默认重命名快捷键1. Windows 系统‌2. Mac 系统‌ 二、操作步骤与技巧1. 精准选择重命名范围‌2. 智能过滤无关内容‌ 三、总结 引言 在代码重构中&#xff0c;‌重命名变量、类、方法‌ 是最常用的操作之一。正确使用快捷键可以极大提升开发效率。本文针对 ‌Ma…

零基础掌握分布式ID生成:从理论到实战的完整指南 [特殊字符]

一、为什么需要分布式ID&#xff1f; &#x1f914; 在单机系统中&#xff0c;使用数据库自增ID就能满足需求。但在分布式系统中&#xff0c;多个服务节点同时生成ID时会出现以下问题&#xff1a; ID冲突&#xff1a;不同节点生成相同ID 扩展困难&#xff1a;数据库自增ID无法…

使用python反射,实现pytest读取yaml并发送请求

pytest yaml yaml - feature: 用户模块story: 登录title: 添加用户request:method: POSTurl: /system/user/listheaders: nullparams: nullvalidate: nullread_yaml_all def read_yaml_all(path):with open(path, r, encodingutf-8) as f:value yaml.safe_load(f)return v…

Matlab 汽车悬架系统动力学建模与仿真

1、内容简介 略 Matlab 170-汽车悬架系统动力学建模与仿真 可以交流、咨询、答疑 2、内容说明 略 本文对题目给定的1/2汽车四自由度模型&#xff0c;建立状态空间模型进行系统分析&#xff0c;并通过MATLAB仿真对系统进行稳定性、可控可观测性分析&#xff0c;对得的结果进行…

专访数势科技谭李:智能分析 Agent 打通数据平权的最后一公里

作者|斗斗 编辑|皮爷 出品|产业家 伦敦塔桥下的泰晤士河底&#xff0c;埋藏着工业革命的隐秘图腾——布鲁内尔设计的隧道盾构机。在19世纪城市地下轨道建设的过程中&#xff0c;这个直径11米的钢铁巨兽没有选择拓宽河道&#xff0c;而是开创了地下通行的新维度。 “我们不…

2、操作系统之软件基础

一、硬件支持系统 &#xff0c;系统管理硬件 操作系统核心功能可以分为&#xff1a; 守护者&#xff1a;对硬件和软件资源的管理协调者&#xff1a;通过机制&#xff0c;将各种各样的硬件资源适配给软件使用。 所以为了更好的管理硬件&#xff0c;操作系统引进了软件。其中3大…

STC89C52单片机学习——第20节: [8-2]串口向电脑发送数据电脑通过串口控制LED

写这个文章是用来学习的,记录一下我的学习过程。希望我能一直坚持下去,我只是一个小白,只是想好好学习,我知道这会很难&#xff0c;但我还是想去做&#xff01; 本文写于&#xff1a;2025.03.15 51单片机学习——第20节: [8-2]串口向电脑发送数据&电脑通过串口控制LED 前言…

K8S下nodelocaldns crash问题导致域名请求响应缓慢

前言 最近做项目&#xff0c;有业务出现偶发的部署导致响应很慢的情况&#xff0c;据了解&#xff0c;业务使用域名访问&#xff0c;相同的nginx代理&#xff0c;唯一的区别就是K8S重新部署了。那么问题大概率出现在容器平台&#xff0c;毕竟业务是重启几次正常&#xff0c;偶…

CVPR2024 | TT3D | 物理世界中可迁移目标性 3D 对抗攻击

Towards Transferable Targeted 3D Adversarial Attack in the Physical World 速览总结摘要-Abstract引言-Introduction相关工作-Related Work方法-MethodologyPreliminray-预备知识问题表述-Problem FormulationNeRF参数空间中的双重优化-Dual Optimization in NeRF Paramete…

全面对比分析:HDMI、DP、DVI、VGA、Type-C、SDI视频接口特点详解

在当今的多媒体时代&#xff0c;视频接口的选择对于设备连接和显示效果至关重要。不同的视频接口在传输质量、兼容性、带宽等方面各有优劣。本文将全面对比分析常用的视频接口HDMI、DP、DVI、VGA、Type-C、SDI&#xff0c;帮助读者更好地理解它们的特点和适用场景。 一、HDMI&…

传输层自学

传输实体&#xff1a;完成传输层任务的硬件或软件 可能位于&#xff1a; 操作系统内核独立的用户进程绑定在网络应用中的链接库网络接口卡 1.功能&#xff1a; 网络层与传输层作用范围比较&#xff1f; 网络层负责把数据从源机送达到目的机 传输层负责把数据送达到具体的应…

微服务架构下前端如何配置 OpenAPI 接口

在微服务架构中&#xff0c;后端通常由多个独立的服务组成&#xff0c;每个服务可能提供自己的 API 接口。为了在前端项目中高效地调用这些 API&#xff0c;可以使用 OpenAPI 规范生成客户端代码。以下是详细的配置步骤和最佳实践&#xff1a; 1. 理解 OpenAPI 规范 OpenAPI 是…

FreeRTOS源码概述

FreeRTOS源码概述 1 FreeRTOS目录结构 使用 STM32CubeMX 创建的 FreeRTOS 工程中&#xff0c;FreeRTOS 相关的源码如下&#xff1a; 主要涉及2个目录&#xff1a; Core Inc 目录下的 FreeRTOSConfig.h 是配置文件Src 目录下的 freertos.c 是 STM32CubeMX 创建的默认任务 Mi…

日志统计(C++,模拟,双指针)

题目要我们求在某个时间段中&#xff0c;帖子点赞数达到K的帖子数 遍历方式一 我们可以先对所有帖子根据时间&#xff0c;升序排序 枚举每一条帖子&#xff0c;枚举后续每一条帖子&#xff0c;如果id相同且时间差小于d&#xff0c;那么就记录起来&#xff0c;如果记录数量cn…

加固脱壳技术:DEX动态加载对抗

1. 加固技术原理剖析 1.1 DEX保护演进路线 加固方案发展历程&#xff1a; graph LR A[2015 代码混淆] --> B[2017 DEX动态加载] B --> C[2019 VMP指令虚拟化] C --> D[2022 全链路加密] 1.1.1 主流加固方案对比 厂商核心防护技术弱点分析梆梆加固DEX文件分片…

C++之list类(超详细)

在上一节中我们学习了STL中的vector这个容器&#xff0c;这节我们来学习一下另外一个常用的容器——list。 文章目录 前言 一、list的介绍 二、list的使用及相关接口 1.list的使用 2.list的迭代器使用 3.list的相关接口 3.1 list capacity 3.2 list element access 3.3…

强化学习的一些概念

目录 强化学习 打个比方 核心要素 State Action Reward 几个代码demo 学习目标 强化学习 强化学习&#xff08;Reinforcement Learning, RL&#xff09;是机器学习的一个分支&#xff0c;旨在让智能体&#xff08;Agent&#xff09;通过与环境的交互学习最优策略&#xff0c;以…

MambaTab:表格数据处理的新利器

——基于结构化状态空间模型的特征增量学习框架 摘要 本文提出MambaTab&#xff0c;一种基于结构化状态空间模型&#xff08;SSM&#xff09;的表格数据处理框架。通过创新的嵌入稳定化设计与轻量化SSM架构&#xff0c;MambaTab在普通监督学习和特征增量学习场景中均表现优异&…

Kafka的流量控制机制

Kafka的流量控制机制 Kafka 作为一款高吞吐量的消息队列系统&#xff0c;能够在海量数据场景下提供稳定的消息生产和消费能力&#xff0c;其背后的流量控制机制功不可没。我们需要认识到&#xff0c;Kafka 的流量控制并非仅仅是为了防止系统过载或崩溃&#xff0c;它的目标是实…