使用Spring Boot、Redis和Lua脚本实现订单系统

发布于:2025-03-04 ⋅ 阅读:(20) ⋅ 点赞:(0)

在现代的分布式系统中,订单系统是一个常见的业务场景。为了确保订单处理的高效性和一致性,我们可以使用Redis作为缓存和数据存储,并结合Lua脚本来实现原子操作。本文将介绍如何使用Spring Boot、Redis和Lua脚本实现一个简单的订单系统。

1. 项目概述

我们将实现一个简单的订单系统,主要功能包括:

  • 创建订单
  • 查询订单
  • 取消订单

为了确保订单操作的原子性,我们将使用Redis的Lua脚本来处理订单的创建和取消操作。

2. 环境准备

在开始之前,确保你已经安装了以下环境:

  • JDK 1.8+
  • Maven 3.x
  • Redis 5.x+

3. 创建Spring Boot项目

首先,使用Spring Initializr创建一个新的Spring Boot项目,添加以下依赖:

  • Spring Web
  • Spring Data Redis

生成的pom.xml文件应该包含以下依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

4. 配置Redis

application.properties文件中配置Redis连接信息:

spring.redis.host=localhost
spring.redis.port=6379

5. 创建订单实体类

创建一个简单的订单实体类Order

public class Order {
    private String id;
    private String userId;
    private String productId;
    private int quantity;
    private double price;
    private String status;

    // Getters and Setters
}

6. 实现订单服务

接下来,我们实现订单服务OrderService,使用Redis和Lua脚本来处理订单的创建和取消操作。

6.1 创建订单

我们使用Lua脚本来确保创建订单的原子性。Lua脚本如下:

local orderId = KEYS[1]
local userId = ARGV[1]
local productId = ARGV[2]
local quantity = tonumber(ARGV[3])
local price = tonumber(ARGV[4])

local orderKey = "order:" .. orderId
local userOrdersKey = "user:orders:" .. userId

-- 检查订单是否已存在
if redis.call("EXISTS", orderKey) == 1 then
    return 0
end

-- 创建订单
redis.call("HSET", orderKey, "userId", userId, "productId", productId, "quantity", quantity, "price", price, "status", "CREATED")
redis.call("SADD", userOrdersKey, orderId)

return 1

OrderService中,我们使用RedisTemplate来执行Lua脚本:

@Service
public class OrderService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    private static final String CREATE_ORDER_SCRIPT = "local orderId = KEYS[1]\n" +
            "local userId = ARGV[1]\n" +
            "local productId = ARGV[2]\n" +
            "local quantity = tonumber(ARGV[3])\n" +
            "local price = tonumber(ARGV[4])\n" +
            "local orderKey = \"order:\" .. orderId\n" +
            "local userOrdersKey = \"user:orders:\" .. userId\n" +
            "if redis.call(\"EXISTS\", orderKey) == 1 then\n" +
            "    return 0\n" +
            "end\n" +
            "redis.call(\"HSET\", orderKey, \"userId\", userId, \"productId\", productId, \"quantity\", quantity, \"price\", price, \"status\", \"CREATED\")\n" +
            "redis.call(\"SADD\", userOrdersKey, orderId)\n" +
            "return 1";

    public boolean createOrder(Order order) {
        DefaultRedisScript<Long> script = new DefaultRedisScript<>(CREATE_ORDER_SCRIPT, Long.class);
        List<String> keys = Collections.singletonList(order.getId());
        String[] args = {order.getUserId(), order.getProductId(), String.valueOf(order.getQuantity()), String.valueOf(order.getPrice())};
        Long result = redisTemplate.execute(script, keys, args);
        return result != null && result == 1;
    }
}

6.2 取消订单

同样地,我们使用Lua脚本来确保取消订单的原子性。Lua脚本如下:

local orderId = KEYS[1]
local userId = ARGV[1]

local orderKey = "order:" .. orderId
local userOrdersKey = "user:orders:" .. userId

-- 检查订单是否存在
if redis.call("EXISTS", orderKey) == 0 then
    return 0
end

-- 取消订单
redis.call("HSET", orderKey, "status", "CANCELLED")
redis.call("SREM", userOrdersKey, orderId)

return 1

OrderService中,我们使用RedisTemplate来执行Lua脚本:

@Service
public class OrderService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    private static final String CANCEL_ORDER_SCRIPT = "local orderId = KEYS[1]\n" +
            "local userId = ARGV[1]\n" +
            "local orderKey = \"order:\" .. orderId\n" +
            "local userOrdersKey = \"user:orders:\" .. userId\n" +
            "if redis.call(\"EXISTS\", orderKey) == 0 then\n" +
            "    return 0\n" +
            "end\n" +
            "redis.call(\"HSET\", orderKey, \"status\", \"CANCELLED\")\n" +
            "redis.call(\"SREM\", userOrdersKey, orderId)\n" +
            "return 1";

    public boolean cancelOrder(String orderId, String userId) {
        DefaultRedisScript<Long> script = new DefaultRedisScript<>(CANCEL_ORDER_SCRIPT, Long.class);
        List<String> keys = Collections.singletonList(orderId);
        String[] args = {userId};
        Long result = redisTemplate.execute(script, keys, args);
        return result != null && result == 1;
    }
}

6.3 查询订单

查询订单的操作相对简单,直接从Redis中获取订单信息即可:

@Service
public class OrderService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    public Order getOrder(String orderId) {
        String orderKey = "order:" + orderId;
        Map<Object, Object> orderMap = redisTemplate.opsForHash().entries(orderKey);
        if (orderMap.isEmpty()) {
            return null;
        }
        Order order = new Order();
        order.setId(orderId);
        order.setUserId((String) orderMap.get("userId"));
        order.setProductId((String) orderMap.get("productId"));
        order.setQuantity(Integer.parseInt((String) orderMap.get("quantity")));
        order.setPrice(Double.parseDouble((String) orderMap.get("price")));
        order.setStatus((String) orderMap.get("status"));
        return order;
    }
}

7. 创建控制器

最后,我们创建一个简单的控制器OrderController来处理HTTP请求:

@RestController
@RequestMapping("/orders")
public class OrderController {

    @Autowired
    private OrderService orderService;

    @PostMapping
    public ResponseEntity<String> createOrder(@RequestBody Order order) {
        boolean result = orderService.createOrder(order);
        if (result) {
            return ResponseEntity.ok("Order created successfully");
        } else {
            return ResponseEntity.status(HttpStatus.CONFLICT).body("Order already exists");
        }
    }

    @GetMapping("/{orderId}")
    public ResponseEntity<Order> getOrder(@PathVariable String orderId) {
        Order order = orderService.getOrder(orderId);
        if (order != null) {
            return ResponseEntity.ok(order);
        } else {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
        }
    }

    @PostMapping("/{orderId}/cancel")
    public ResponseEntity<String> cancelOrder(@PathVariable String orderId, @RequestParam String userId) {
        boolean result = orderService.cancelOrder(orderId, userId);
        if (result) {
            return ResponseEntity.ok("Order cancelled successfully");
        } else {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Order not found");
        }
    }
}

8. 测试

启动Spring Boot应用程序后,可以使用Postman或curl等工具测试API:

  • 创建订单:POST /orders
  • 查询订单:GET /orders/{orderId}
  • 取消订单:POST /orders/{orderId}/cancel?userId={userId}

9. 总结

通过本文,我们实现了一个简单的订单系统,使用Redis作为数据存储,并结合Lua脚本来确保订单操作的原子性。Spring Boot提供了与Redis的便捷集成,使得开发过程更加高效。希望本文对你理解如何在Spring Boot项目中使用Redis和Lua脚本有所帮助。