Preventing Duplicate Order Submissions with Token Mechanism and Redis in Spring Boot
This article explains how to prevent duplicate order submissions in e‑commerce systems by using a token mechanism and Redis‑based distributed locks within a Spring Boot backend, detailing the workflow, code implementation, and best‑practice optimizations for idempotent processing.
In e‑commerce or any order‑processing scenario, repeatedly clicking the "Submit Order" button can cause duplicate orders, leading to data redundancy, inventory issues, and poor user experience. Preventing duplicate submissions is therefore a common requirement.
Common Duplicate Submission Scenarios
Network latency – users click repeatedly because they do not receive confirmation.
Page refresh – refreshing after submission triggers another request.
User error – accidental multiple clicks.
Requirements for Prevention
Idempotency – the same request must be processed only once.
User experience – avoid perceived delays or errors caused by duplicates.
Typical Solutions
Frontend protection : disable the submit button or lock it after the first click.
Backend idempotency handling :
Token mechanism – generate a unique token per order and allow it to be used only once.
Database unique index – enforce uniqueness on order fields.
Distributed lock – use Redis or similar to ensure only one request processes an order at a time.
Implementation Using Spring Boot, Token, and Redis
The following sections show a practical implementation.
Token Mechanism
Redis atomic operations guarantee that concurrent requests do not conflict. The workflow includes token generation, validation, and destruction.
Token Generation
When a user initiates an order, the server creates a unique OrderToken and stores it in Redis.
Token Validation
The client sends the OrderToken back with the order; the server checks its validity.
Token Destruction
After successful validation, the token is immediately removed to prevent reuse.
Redis Distributed Lock
In a multi‑instance environment, the token can be stored with a TTL (e.g., 10 minutes) and deleted atomically after verification.
Process Design
User requests an order token; the backend generates a UUID and stores it in Redis.
The frontend includes the token when submitting the order.
The backend validates the token, creates the order, and deletes the token.
If the token is missing, expired, or already used, an error is returned.
Code Implementation
pom.xml dependencies :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>application.properties (Redis configuration and Thymeleaf settings):
# Thymeleaf settings
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.cache=false
# Redis connection
spring.redis.host=127.0.0.1
spring.redis.port=23456
spring.redis.password=pwdOrderTokenService.java – generates and validates tokens:
package com.example.demo.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Service
@Slf4j
public class OrderTokenService {
@Autowired
private RedisTemplate
redisTemplate;
// Generate order token
public String generateOrderToken(String userId) {
String token = UUID.randomUUID().toString();
redisTemplate.opsForValue().set("orderToken:" + userId, token, 10, TimeUnit.MINUTES);
return token;
}
// Validate order token
public boolean validateOrderToken(String userId, String token) {
String redisToken = redisTemplate.opsForValue().get("orderToken:" + userId);
log.info("@@ Redis token: {} vs request token: {}", redisToken, token);
if (token.equals(redisToken)) {
redisTemplate.delete("orderToken:" + userId);
return true;
}
return false;
}
}OrderController.java – exposes endpoints for token retrieval and order submission:
package com.example.demo.controller;
import com.example.demo.entity.Order;
import com.example.demo.service.OrderTokenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderTokenService orderTokenService;
@GetMapping("/getOrderToken")
public ResponseEntity
getOrderToken(@RequestParam String userId) {
String token = orderTokenService.generateOrderToken(userId);
return ResponseEntity.ok(token);
}
@PostMapping("/submitOrder")
public ResponseEntity
submitOrder(Order order) {
if (!orderTokenService.validateOrderToken(order.getUserId(), order.getOrderToken())) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("订单重复提交,请勿重复操作");
}
// Order processing logic ...
return ResponseEntity.ok("订单提交成功");
}
}Frontend script – obtains a token before submitting the form:
<script>
document.getElementById('orderForm').addEventListener('submit', function(event) {
event.preventDefault();
const userId = document.getElementById('userId').value;
if (!userId) { alert("请填写用户ID"); return; }
fetch(`/order/getOrderToken?userId=${userId}`)
.then(r => r.text())
.then(token => {
document.getElementById('orderToken').value = token;
const formData = new FormData(document.getElementById('orderForm'));
fetch('/order/submitOrder', { method: 'POST', body: formData })
.then(r => r.text())
.then(result => { document.getElementById('message').textContent = result; })
.catch(() => { document.getElementById('message').textContent = '订单提交失败,请重试'; });
})
.catch(() => { document.getElementById('message').textContent = '获取Token失败'; });
});
</script>Technical Choices and Optimizations
Using Redis with a token mechanism ensures idempotent order processing, while UUID guarantees token uniqueness and TTL limits token lifespan to avoid resource waste. The distributed lock provided by Redis handles high‑concurrency scenarios without token reuse.
Conclusion
The key to preventing duplicate order submissions lies in the uniqueness and timely expiration of tokens, atomic validation and deletion of tokens, and leveraging Redis for efficient storage and distributed locking, offering a simple, scalable solution for high‑traffic e‑commerce systems.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.