好的,下面是关于PHP实现幂等性,特别是在API层防御POST/PUT请求重复提交的技术文章。
PHP API幂等性实现:防御POST/PUT请求重复提交
大家好,今天我们来探讨一个非常重要的API设计原则:幂等性。特别是在处理POST和PUT请求时,如何防御重复提交,保证数据的一致性。幂等性是指一个操作,无论执行多少次,其结果都相同。对于API来说,这意味着多次调用同一个接口,产生的影响应该与调用一次相同。
为什么需要幂等性?
在高并发、网络不稳定的环境下,客户端可能会因为超时、网络抖动等原因多次发送相同的请求。如果没有幂等性保证,这些重复请求可能会导致数据错误,例如:
- 重复创建订单
- 重复增加库存
- 重复扣款
幂等性实现策略
实现幂等性的方法有很多,针对不同的场景需要选择合适的策略。以下是一些常用的策略,以及如何在PHP API中实现它们:
- Token机制(推荐)
- 唯一性约束
- 乐观锁
- 悲观锁
- 状态机
- 记录请求日志
接下来,我们将逐一详细介绍这些策略,并提供PHP代码示例。
1. Token机制
Token机制是最常用且推荐的幂等性实现方式。客户端在发起请求前,先从服务器获取一个唯一的Token,然后在请求体中携带这个Token。服务器端接收到请求后,首先验证Token是否有效,然后执行业务逻辑。如果Token有效,则执行操作,并将Token标记为已使用。如果Token无效,则拒绝请求。
-
流程:
- 客户端向服务器请求Token。
- 服务器生成唯一Token,并保存(例如Redis),设置过期时间。
- 客户端携带Token发起POST/PUT请求。
- 服务器验证Token:
- Token存在且未被使用:执行业务逻辑,将Token标记为已使用(或删除),返回成功。
- Token不存在或已被使用:拒绝请求,返回错误。
-
PHP代码示例:
<?php class IdempotencyService { private $redis; private $tokenPrefix = 'idempotency_token:'; private $tokenTTL = 60; // Token有效期,单位秒 public function __construct() { // 假设你已经配置好了Redis连接 $this->redis = new Redis(); $this->redis->connect('127.0.0.1', 6379); } /** * 生成Token * @return string */ public function generateToken(): string { $token = uniqid(); //或者使用uuid $key = $this->tokenPrefix . $token; $this->redis->setex($key, $this->tokenTTL, 1); // 1代表存在 return $token; } /** * 验证Token * @param string $token * @return bool */ public function validateToken(string $token): bool { $key = $this->tokenPrefix . $token; if ($this->redis->exists($key)) { // 使用Redis的原子操作 del 来保证并发安全 if ($this->redis->del($key)) { //如果能删除,说明token存在且未被使用 return true; } else { // 并发情况下可能删除失败,说明已被使用 return false; } } return false; } /** * 示例API接口 * @param array $data * @return array */ public function processRequest(array $data): array { if (!isset($data['token'])) { return ['status' => 'error', 'message' => 'Token is required']; } $token = $data['token']; if (!$this->validateToken($token)) { return ['status' => 'error', 'message' => 'Invalid or expired token']; } // 模拟业务逻辑处理 // ... 你的业务逻辑代码 ... $result = $this->performBusinessLogic($data); return ['status' => 'success', 'message' => 'Request processed successfully', 'data' => $result]; } private function performBusinessLogic(array $data): array { // 模拟业务逻辑 // 假设这里是将数据保存到数据库 // 为了简单起见,我们直接返回数据 return $data; } } // 使用示例 $idempotencyService = new IdempotencyService(); // 1. 客户端首先获取Token $token = $idempotencyService->generateToken(); echo "Generated Token: " . $token . "n"; // 2. 客户端携带Token发起请求 $requestData = [ 'token' => $token, 'name' => 'Product A', 'price' => 100, ]; // 3. 服务器处理请求 $response = $idempotencyService->processRequest($requestData); print_r($response); // 4. 模拟重复请求 echo "nSimulating a duplicate request...n"; $response2 = $idempotencyService->processRequest($requestData); print_r($response2); ?>代码解释:
generateToken(): 生成唯一的Token,并将其存储在Redis中,设置过期时间。validateToken(): 验证Token是否存在,如果存在则删除,并返回true;否则返回false。 这里使用了redis的del命令,它本身是原子性的,避免了并发问题。processRequest(): 接收请求,验证Token,然后执行业务逻辑。- 注意:实际生产环境中,需要替换
performBusinessLogic()中的模拟业务逻辑为真正的业务逻辑代码。
优点:
- 简单易实现。
- 适用于各种场景,包括创建、更新等操作。
- 可以设置Token的过期时间,防止Token无限期占用资源。
缺点:
- 需要额外的存储空间来存储Token(例如Redis)。
- 客户端需要先请求Token,增加了一次网络请求。
- 唯一性约束
如果你的业务场景涉及到数据库操作,并且数据库中有唯一性约束的字段,那么可以利用唯一性约束来实现幂等性。例如,订单号是唯一的,那么在创建订单时,如果订单号已经存在,则直接返回成功,而不是重复创建订单。
-
流程:
- 客户端发起POST/PUT请求,携带唯一标识符(例如订单号)。
- 服务器端接收到请求后,尝试将数据插入数据库。
- 如果插入成功,则返回成功。
- 如果插入失败,并且错误原因是唯一性约束冲突,则返回成功(表示已经执行过相同的操作)。
- 如果插入失败,并且错误原因不是唯一性约束冲突,则返回错误。
-
PHP代码示例:
<?php class OrderService { private $pdo; public function __construct(PDO $pdo) { $this->pdo = $pdo; } /** * 创建订单 * @param string $orderId 订单ID,作为唯一标识 * @param int $userId 用户ID * @param float $amount 订单金额 * @return array */ public function createOrder(string $orderId, int $userId, float $amount): array { try { $sql = "INSERT INTO orders (order_id, user_id, amount, created_at) VALUES (:order_id, :user_id, :amount, NOW())"; $stmt = $this->pdo->prepare($sql); $stmt->execute([ 'order_id' => $orderId, 'user_id' => $userId, 'amount' => $amount, ]); return ['status' => 'success', 'message' => 'Order created successfully']; } catch (PDOException $e) { // 检查是否是唯一性约束冲突 if ($e->getCode() == '23000' && strpos($e->getMessage(), 'order_id') !== false) { // 唯一性约束冲突,表示订单已经存在,返回成功 return ['status' => 'success', 'message' => 'Order already exists']; } else { // 其他错误,返回错误信息 return ['status' => 'error', 'message' => 'Failed to create order: ' . $e->getMessage()]; } } } } // 使用示例 try { // 假设你已经配置好了PDO连接 $pdo = new PDO("mysql:host=localhost;dbname=test", "username", "password"); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $orderService = new OrderService($pdo); // 1. 第一次创建订单 $orderId = uniqid('order_'); // 生成唯一订单ID $response = $orderService->createOrder($orderId, 123, 100.00); print_r($response); // 2. 模拟重复请求,使用相同的订单ID echo "nSimulating a duplicate request...n"; $response2 = $orderService->createOrder($orderId, 123, 100.00); print_r($response2); } catch (PDOException $e) { echo "Database connection failed: " . $e->getMessage(); } ?>数据库表结构:
CREATE TABLE orders ( id INT AUTO_INCREMENT PRIMARY KEY, order_id VARCHAR(255) UNIQUE NOT NULL, -- 唯一索引 user_id INT NOT NULL, amount DECIMAL(10, 2) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );代码解释:
createOrder(): 尝试将订单数据插入数据库。- 捕获
PDOException异常,判断是否是唯一性约束冲突(错误代码为23000,并且错误信息包含order_id)。 - 如果是唯一性约束冲突,则返回成功;否则返回错误。
- 注意:你需要根据你使用的数据库类型,修改唯一性约束冲突的判断逻辑。
优点:
- 实现简单,不需要额外的存储空间。
- 性能较好,直接利用数据库的索引。
缺点:
- 只适用于创建操作,不适用于更新操作。
- 需要数据库支持唯一性约束。
- 错误判断逻辑可能依赖于具体的数据库实现。
- 乐观锁
乐观锁是一种并发控制机制,它假设在操作期间不会发生冲突。在更新数据时,先读取数据的版本号,然后在更新时,比较版本号是否一致。如果版本号一致,则更新成功;否则更新失败,表示数据已被其他线程修改。
-
流程:
- 客户端发起PUT请求,携带要更新的数据和版本号。
- 服务器端接收到请求后,先读取数据库中对应数据的版本号。
- 比较客户端传递的版本号和数据库中的版本号是否一致。
- 如果一致,则更新数据,并将版本号加1。
- 如果不一致,则拒绝请求,返回错误。
-
PHP代码示例:
<?php class ProductService { private $pdo; public function __construct(PDO $pdo) { $this->pdo = $pdo; } /** * 更新产品信息 * @param int $productId 产品ID * @param string $name 产品名称 * @param float $price 产品价格 * @param int $version 版本号 * @return array */ public function updateProduct(int $productId, string $name, float $price, int $version): array { try { $sql = "UPDATE products SET name = :name, price = :price, version = version + 1 WHERE id = :id AND version = :version"; $stmt = $this->pdo->prepare($sql); $stmt->execute([ 'id' => $productId, 'name' => $name, 'price' => $price, 'version' => $version, ]); // 检查更新是否成功 if ($stmt->rowCount() > 0) { return ['status' => 'success', 'message' => 'Product updated successfully']; } else { return ['status' => 'error', 'message' => 'Product update failed: version mismatch']; } } catch (PDOException $e) { return ['status' => 'error', 'message' => 'Failed to update product: ' . $e->getMessage()]; } } /** * 获取产品信息(包含版本号) * @param int $productId * @return array|null */ public function getProduct(int $productId): ?array { $sql = "SELECT id, name, price, version FROM products WHERE id = :id"; $stmt = $this->pdo->prepare($sql); $stmt->execute(['id' => $productId]); $product = $stmt->fetch(PDO::FETCH_ASSOC); return $product ?: null; } } // 使用示例 try { // 假设你已经配置好了PDO连接 $pdo = new PDO("mysql:host=localhost;dbname=test", "username", "password"); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $productService = new ProductService($pdo); // 1. 获取产品信息 $productId = 1; // 假设产品ID为1 $product = $productService->getProduct($productId); if (!$product) { echo "Product not foundn"; exit; } $version = $product['version']; // 获取当前版本号 echo "Initial product version: " . $version . "n"; // 2. 第一次更新产品信息 $response = $productService->updateProduct($productId, 'Product A Updated', 120.00, $version); print_r($response); // 3. 模拟重复请求,使用相同的版本号 echo "nSimulating a duplicate request...n"; $response2 = $productService->updateProduct($productId, 'Product A Updated Again', 130.00, $version); print_r($response2); //4. 再次获取产品信息,查看版本号是否更新 $product = $productService->getProduct($productId); echo "New product version: " . $product['version'] . "n"; } catch (PDOException $e) { echo "Database connection failed: " . $e->getMessage(); } ?>数据库表结构:
CREATE TABLE products ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, price DECIMAL(10, 2) NOT NULL, version INT NOT NULL DEFAULT 0 -- 版本号 ); INSERT INTO products (name, price) VALUES ('Product A', 100.00); //插入测试数据代码解释:
updateProduct(): 更新产品信息,同时比较版本号是否一致。- SQL语句中使用
WHERE id = :id AND version = :version来判断版本号是否一致。 - 如果更新成功,
rowCount()会大于0;否则小于等于0,表示版本号不一致。 getProduct(): 获取产品信息,包括版本号。
优点:
- 并发性能较好,适用于读多写少的场景。
- 不需要额外的存储空间。
缺点:
- 需要额外的版本号字段。
- 更新失败时,需要客户端重新获取最新数据和版本号,然后重试。
- 只适用于更新操作。
- 悲观锁
悲观锁是一种并发控制机制,它假设在操作期间一定会发生冲突。在操作数据之前,先对数据加锁,防止其他线程修改数据。
-
流程:
- 客户端发起PUT请求,携带要更新的数据。
- 服务器端接收到请求后,先对数据库中对应的数据加锁(例如使用
SELECT ... FOR UPDATE)。 - 更新数据。
- 释放锁。
-
PHP代码示例:
<?php class ProductService { private $pdo; public function __construct(PDO $pdo) { $this->pdo = $pdo; } /** * 更新产品信息 (使用悲观锁) * @param int $productId 产品ID * @param string $name 产品名称 * @param float $price 产品价格 * @return array */ public function updateProduct(int $productId, string $name, float $price): array { try { // 开启事务 $this->pdo->beginTransaction(); // 1. 获取产品信息并加锁 $sql = "SELECT id, name, price, version FROM products WHERE id = :id FOR UPDATE"; $stmt = $this->pdo->prepare($sql); $stmt->execute(['id' => $productId]); $product = $stmt->fetch(PDO::FETCH_ASSOC); if (!$product) { // 回滚事务 $this->pdo->rollBack(); return ['status' => 'error', 'message' => 'Product not found']; } // 2. 更新产品信息 $sql = "UPDATE products SET name = :name, price = :price WHERE id = :id"; $stmt = $this->pdo->prepare($sql); $stmt->execute([ 'id' => $productId, 'name' => $name, 'price' => $price, ]); // 提交事务 $this->pdo->commit(); return ['status' => 'success', 'message' => 'Product updated successfully']; } catch (PDOException $e) { // 回滚事务 if ($this->pdo->inTransaction()) { $this->pdo->rollBack(); } return ['status' => 'error', 'message' => 'Failed to update product: ' . $e->getMessage()]; } } } // 使用示例 try { // 假设你已经配置好了PDO连接 $pdo = new PDO("mysql:host=localhost;dbname=test", "username", "password"); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $productService = new ProductService($pdo); // 1. 第一次更新产品信息 $productId = 1; // 假设产品ID为1 $response = $productService->updateProduct($productId, 'Product A Updated', 120.00); print_r($response); // 2. 模拟重复请求 echo "nSimulating a duplicate request...n"; $response2 = $productService->updateProduct($productId, 'Product A Updated Again', 130.00); print_r($response2); } catch (PDOException $e) { echo "Database connection failed: " . $e->getMessage(); } ?>数据库表结构:
CREATE TABLE products ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, price DECIMAL(10, 2) NOT NULL, version INT NOT NULL DEFAULT 0 -- 版本号 ); INSERT INTO products (name, price) VALUES ('Product A', 100.00); //插入测试数据代码解释:
updateProduct(): 更新产品信息,使用悲观锁。beginTransaction(): 开启事务。SELECT ... FOR UPDATE: 对读取的数据加锁,防止其他事务修改。commit(): 提交事务。rollBack(): 回滚事务。
优点:
- 可以保证数据的一致性。
- 实现简单。
缺点:
- 并发性能较差,适用于写多读少的场景。
- 可能导致死锁。
- 锁的粒度较大,可能会影响其他操作。
- 状态机
对于有状态变化的操作,可以使用状态机来实现幂等性。例如,订单状态有:待支付、已支付、已发货、已完成等。在更新订单状态时,只有当订单状态满足条件时,才能更新成功。
-
流程:
- 客户端发起PUT请求,携带要更新的状态。
- 服务器端接收到请求后,先读取数据库中当前状态。
- 判断当前状态是否允许更新到目标状态。
- 如果允许,则更新状态。
- 如果不允许,则拒绝请求。
-
PHP代码示例:
<?php class OrderService { private $pdo; // 定义订单状态常量 const STATUS_PENDING = 1; // 待支付 const STATUS_PAID = 2; // 已支付 const STATUS_SHIPPED = 3; // 已发货 const STATUS_COMPLETED = 4; // 已完成 const STATUS_CANCELLED = 5; // 已取消 public function __construct(PDO $pdo) { $this->pdo = $pdo; } /** * 更新订单状态 * @param int $orderId 订单ID * @param int $newStatus 新的状态 * @return array */ public function updateOrderStatus(int $orderId, int $newStatus): array { try { // 1. 获取当前订单状态 $currentStatus = $this->getCurrentOrderStatus($orderId); if ($currentStatus === null) { return ['status' => 'error', 'message' => 'Order not found']; } // 2. 验证状态转换是否允许 if (!$this->isStatusTransitionAllowed($currentStatus, $newStatus)) { return ['status' => 'error', 'message' => 'Invalid status transition']; } // 3. 更新订单状态 $sql = "UPDATE orders SET status = :status WHERE id = :id"; $stmt = $this->pdo->prepare($sql); $stmt->execute([ 'id' => $orderId, 'status' => $newStatus, ]); return ['status' => 'success', 'message' => 'Order status updated successfully']; } catch (PDOException $e) { return ['status' => 'error', 'message' => 'Failed to update order status: ' . $e->getMessage()]; } } /** * 获取当前订单状态 * @param int $orderId 订单ID * @return int|null */ private function getCurrentOrderStatus(int $orderId): ?int { $sql = "SELECT status FROM orders WHERE id = :id"; $stmt = $this->pdo->prepare($sql); $stmt->execute(['id' => $orderId]); $order = $stmt->fetch(PDO::FETCH_ASSOC); return $order ? (int)$order['status'] : null; } /** * 验证状态转换是否允许 * @param int $currentStatus 当前状态 * @param int $newStatus 新的状态 * @return bool */ private function isStatusTransitionAllowed(int $currentStatus, int $newStatus): bool { // 定义状态转换规则 $allowedTransitions = [ self::STATUS_PENDING => [self::STATUS_PAID, self::STATUS_CANCELLED], // 待支付可以变更为已支付或已取消 self::STATUS_PAID => [self::STATUS_SHIPPED, self::STATUS_CANCELLED], // 已支付可以变更为已发货或已取消 self::STATUS_SHIPPED => [self::STATUS_COMPLETED], // 已发货可以变更为已完成 self::STATUS_COMPLETED => [], // 已完成不能再变更 self::STATUS_CANCELLED => [], // 已取消不能再变更 ]; return isset($allowedTransitions[$currentStatus]) && in_array($newStatus, $allowedTransitions[$currentStatus]); } } // 使用示例 try { // 假设你已经配置好了PDO连接 $pdo = new PDO("mysql:host=localhost;dbname=test", "username", "password"); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $orderService = new OrderService($pdo); // 1. 第一次更新订单状态 $orderId = 1; // 假设订单ID为1 $response = $orderService->updateOrderStatus($orderId, OrderService::STATUS_PAID); print_r($response); // 2. 模拟重复请求 echo "nSimulating a duplicate request...n"; $response2 = $orderService->updateOrderStatus($orderId, OrderService::STATUS_PAID); print_r($response2); // 3. 尝试非法状态转换 echo "nAttempting an invalid status transition...n"; $response3 = $orderService->updateOrderStatus($orderId, OrderService::STATUS_PENDING); // 从已支付变更为待支付 print_r($response3); } catch (PDOException $e) { echo "Database connection failed: " . $e->getMessage(); } ?>数据库表结构:
CREATE TABLE orders ( id INT AUTO_INCREMENT PRIMARY KEY, status INT NOT NULL DEFAULT 1 -- 订单状态,默认为待支付 ); INSERT INTO orders (status) VALUES (1); //插入测试数据,状态为待支付代码解释:
updateOrderStatus(): 更新订单状态。getCurrentOrderStatus(): 获取当前订单状态。isStatusTransitionAllowed(): 验证状态转换是否允许。$allowedTransitions: 定义状态转换规则。
优点:
- 适用于有状态变化的操作。
- 可以保证状态的正确性。
缺点:
- 需要定义状态和状态转换规则。
- 实现较为复杂。
- 记录请求日志
记录每次请求的详细信息,例如请求参数、请求时间、客户端IP等。在处理请求之前,先检查是否已经存在相同的请求。如果存在,则直接返回上次的结果;否则执行业务逻辑,并记录请求日志。
-
流程:
- 客户端发起POST/PUT请求。
- 服务器端接收到请求后,根据请求参数生成唯一标识符。
- 查询请求日志,判断是否存在相同的请求。
- 如果存在,则直接返回上次的结果。
- 如果不存在,则执行业务逻辑,记录请求日志,并返回结果.
-
PHP代码示例:
<?php class ApiService { private $pdo; public function __construct(PDO $pdo) { $this->pdo = $pdo; } /** * 处理API请求 * @param string $endpoint API接口地址 * @param array $params 请求参数 * @return array */ public function processApiRequest(string $endpoint, array $params): array { // 1. 生成请求ID $requestId = $this->generateRequestId($endpoint, $params); // 2. 检查请求日志 $log = $this->getRequestLog($requestId); if ($log) { // 3. 如果存在相同的请求,则返回上次的结果 return json_decode($log['response'], true); } // 4. 执行业务逻辑 $result = $this->executeBusinessLogic($endpoint, $params); // 5. 记录请求日志 $this->logRequest($requestId, $endpoint, $params, $result); return $result; } /** * 生成请求ID * @param string $endpoint API接口地址 * @param array $params 请求参数 * @return string */ private function generateRequestId(string $endpoint, array $params): string { // 可以使用md5或sha1算法,将endpoint和params组合成唯一ID $data = $endpoint . json_encode($params); return md5($data); } /** * 获取请求日志 * @param string $requestId 请求ID * @return array|null */ private function getRequestLog(string $requestId): ?array { $sql = "SELECT id, endpoint, params, response, created_at FROM request_logs WHERE request_id = :request_id"; $stmt = $this->pdo->prepare($sql); $stmt->execute(['request_id' => $requestId]); $log = $stmt->fetch(PDO::FETCH_ASSOC); return $log ?: null; } /** * 记录请求日志 * @param string $requestId 请求ID * @param string $endpoint API接口地址 * @param array $params 请求参数 * @param array $result 执行结果 */ private function logRequest(string $requestId, string $endpoint, array $params, array $result): void { $sql = "INSERT INTO request_logs (request_id, endpoint, params, response, created_at) VALUES (:request_id, :endpoint, :params, :response, NOW())"; $stmt = $this->pdo->prepare($sql); $stmt->execute([ 'request_id' => $requestId, 'endpoint' => $endpoint, 'params' => json_encode($params), 'response' => json_encode($result), ]); } /** * 执行业务逻辑 (示例) * @param string $endpoint API接口地址 * @param array $params 请求参数 * @return array */ private function executeBusinessLogic(string $endpoint, array $params): array { // 模拟业务逻辑 // 在实际应用中,你需要根据endpoint和params执行相应的业务逻辑 return ['status' => 'success', 'message' => 'Request processed', 'data' => $params]; } } // 使用示例 try { // 假设你已经配置好了PDO连接 $pdo = new PDO("mysql:host=localhost;dbname=test", "username", "password"); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $apiService = new ApiService($pdo); // 1. 第一次请求 $endpoint = '/api/products'; $params = ['name' => 'Product A', 'price' => 100]; $response = $apiService->processApiRequest($endpoint, $params); print_r($response); // 2. 模拟重复请求 echo "nSimulating a duplicate request...n"; $response2 = $apiService->processApiRequest($endpoint, $params); print_r($response2); } catch (PDOException $e) { echo "Database connection failed: " . $e->getMessage(); } ?>数据库表结构:
CREATE TABLE request_logs ( id INT AUTO_INCREMENT PRIMARY KEY, request_id VARCHAR(255) UNIQUE NOT NULL, -- 请求ID,唯一索引 endpoint VARCHAR(255) NOT NULL, -- API接口地址 params TEXT NOT NULL, -- 请求参数 (JSON格式) response TEXT NOT NULL, -- 响应结果 (JSON格式) created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- 请求时间 );代码解释:
processApiRequest(): 处理API请求。generateRequestId(): 生成请求ID,使用md5算法将API接口地址和请求参数组合成唯一ID。getRequestLog(): 查询请求日志,判断是否存在相同的请求。logRequest(): 记录请求日志。executeBusinessLogic(): 执行业务逻辑。
优点:
- 适用于各种场景。
- 可以记录请求的详细信息,方便排查问题。
缺点:
- 需要额外的存储空间来存储请求日志。
- 性能较低,需要查询数据库。
- 请求ID的生成方式需要根据具体业务场景进行选择。
策略对比
| 策略 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Token机制 | 简单易实现,适用于各种场景,可以设置Token的过期时间,防止Token无限期占用资源。 | 需要额外的存储空间来存储Token,客户端需要先请求Token,增加了一次网络请求。 | 适用于各种场景,包括创建、更新等操作。尤其适用于需要较高安全性的场景,例如支付接口。 |
| 唯一性约束 | 实现简单,不需要额外的存储空间,性能较好,直接利用数据库的索引。 | 只适用于创建操作,不适用于更新操作,需要数据库支持唯一性约束,错误判断逻辑可能依赖于具体的数据库实现。 | 适用于创建操作,例如创建订单、创建用户等,并且数据库中有唯一性约束的字段。 |