

1.不可重复下单。
java
@Override
public Result seckillVoucher(Long voucherId) {
//1.查询优惠券
SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
//2.判断秒杀是否开始
if(voucher.getBeginTime().isAfter(LocalDateTime.now())){
//未开始
return Result.fail("秒杀尚未开始");
}
//3.判断秒杀是否结束
if(voucher.getEndTime().isBefore(LocalDateTime.now())){
return Result.fail("秒杀已结束");
}
//4.库存是否充足
if (voucher.getStock() < 1){
return Result.fail("库存不足");
}
Long userId = UserHolder.getUser().getId();
//创建锁
SimpleRedisLock lock = new SimpleRedisLock("order:"+userId, stringRedisTemplate);
//6一人一单
boolean isLock = lock.tryLock(1200L);
if(!isLock){
//失败
return Result.fail("请勿重复下单");
}
try {
//获取代理对象(事务)
IVoucherOrderService proxy = (IVoucherOrderService)AopContext.currentProxy();
return proxy.creatVoucherOrder(voucherId);
} finally {
//释放锁
lock.unlock();
}
}
@Transactional
public Result creatVoucherOrder(Long voucherId) {
//6一人一单
Long userId = UserHolder.getUser().getId();
//6.1查询订单
Integer count = query().eq("user_id",userId).eq("voucher_id", voucherId).count();
//6.2判断是否存在
if(count > 0){
return Result.fail("不能重复下单");
}
//5.扣减库存
boolean success = seckillVoucherService.update()
.setSql("stock = stock -1")
.eq("voucher_id", voucherId).gt("stock",0)
.update();
if(!success){
return Result.fail("库存不足");
}
//6.创建订单
VoucherOrder voucherOrder = new VoucherOrder();
//6.1订单id 用户id 代金券id
long orderId = redisIdWorker.nextId("order");
voucherOrder.setId(orderId);
voucherOrder.setUserId(userId);
voucherOrder.setVoucherId(voucherId);
save(voucherOrder);
//7.返回订单id
return Result.ok(orderId);
}
锁
java
package com.hmdp.utils;
import org.springframework.data.redis.core.StringRedisTemplate;
import java.util.concurrent.TimeUnit;
public class SimpleRedisLock implements ILock{
private StringRedisTemplate stringRedisTemplate;
private String name;
public SimpleRedisLock( String name,StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
this.name = name;
}
private static final String KEY_PREFIX = "lock:";
@Override
public boolean tryLock(long timeoutSec) {
//获取线程标示
long threadId = Thread.currentThread().getId();
//获取锁
Boolean success = stringRedisTemplate.opsForValue()
.setIfAbsent(KEY_PREFIX + name,threadId+"", timeoutSec, TimeUnit.SECONDS);
return Boolean.TRUE.equals(success);
}
@Override
public void unlock() {
}
}

在 Redis 中使用 Lua 脚本的主要原因是 原子性 和 性能优化。Redis 是一个高性能的键值存储系统,通常用于缓存、消息队列等场景。在这些场景中,确保操作的原子性和高效性是非常重要的。Lua 脚本可以帮助你实现这些目标。以下是具体的原因和好处:
1. 原子性
Redis 的 Lua 脚本执行是原子性的,这意味着在脚本执行期间,其他客户端的请求不会中断脚本的执行。这确保了脚本中的操作序列是不可分割的,从而避免了并发问题。
2. 性能优化
使用 Lua 脚本可以减少网络往返次数。如果你需要在 Redis 中执行多个操作,每次操作都需要与 Redis 服务器进行一次网络交互。通过将这些操作封装在 Lua 脚本中,可以一次性发送脚本并执行,从而减少网络延迟。
3. 复杂逻辑
Lua 脚本可以实现复杂的逻辑,而不需要在客户端进行多次交互。这使得你可以将复杂的业务逻辑封装在 Redis 中,减少客户端的负担。
4. 减少锁的使用
在某些情况下,使用 Lua 脚本可以减少锁的使用。通过将多个操作封装在 Lua 脚本中,可以避免在客户端使用锁来确保操作的原子性。

Lua
java
---
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Created by zhaoming.
--- DateTime: 2025/10/27 10:36
---
--比较线程标识与锁中是否一致
if(redis.call('get',KEY[1]) == ARGV[1]) then
--释放锁
return redis.call('del',KEY[1])
end
return 0
SimpleRedisLock
java
public class SimpleRedisLock implements ILock{
private StringRedisTemplate stringRedisTemplate;
private String name;
public SimpleRedisLock( String name,StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
this.name = name;
}
private static final String KEY_PREFIX = "lock:";
private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";
private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;
static {
UNLOCK_SCRIPT = new DefaultRedisScript<>();
UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));
UNLOCK_SCRIPT.setResultType(Long.class);
}
@Override
public boolean tryLock(long timeoutSec) {
//获取线程标示
long threadId = Thread.currentThread().getId();
//获取锁
Boolean success = stringRedisTemplate.opsForValue()
.setIfAbsent(KEY_PREFIX + name,threadId+"", timeoutSec, TimeUnit.SECONDS);
return Boolean.TRUE.equals(success);
}
@Override
public void unlock() {
//调用lua脚本
stringRedisTemplate.execute(UNLOCK_SCRIPT,
Collections.singletonList(KEY_PREFIX + name),
ID_PREFIX + Thread.currentThread().getId());
}