本文目录导读:

- 核心思路
- 方案一:基于MySQL定时任务 + 邮件/短信(适合中小型项目)
- 方案二:事件驱动 + Redis Pub/Sub(适合中等规模)
- 方案三:用户端的“到货通知”(用户订阅模式)
- 方案四:推送通知(WebSocket / SSE)(面向C端用户实时提醒)
- 进阶方案:实时库存 + 消息队列 + 多渠道
- 选型建议
在PHP项目中实现缺货提醒,通常需要结合库存监控、通知机制以及用户偏好设置,以下是几种常见的实现方案,从简单到复杂:
核心思路
- 监控库存:每次订单生成或库存变动时,检查当前库存量。
- 判断阈值:如果库存低于预设的“安全库存”或“缺货线”,则触发提醒。
- 发送通知:通过邮件、短信、站内信或WebSocket推送通知相关方(管理员、用户)。
基于MySQL定时任务 + 邮件/短信(适合中小型项目)
这是最传统且无需额外服务的方法,适合库存变动不频繁的场景。
数据库设计
在products表添加min_stock(最低库存预警线)字段。
在orders表(或库存日志表)记录每次扣减。
创建PHP脚本
<?php
// check_low_stock.php
require_once 'config.php'; // 数据库连接等配置
$threshold = 10; // 假设低于10件为缺货
// 查询库存低于阈值的商品
$sql = "SELECT id, name, stock, min_stock
FROM products
WHERE stock < ? AND stock > 0"; // 排除无库存的
$stmt = $pdo->prepare($sql);
$stmt->execute([$threshold]);
$lowStockItems = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (!empty($lowStockItems)) {
// 发送邮件给管理员
$to = "admin@example.com";
$subject = "库存预警提醒";
$message = "以下商品库存不足:\n\n";
foreach ($lowStockItems as $item) {
$message .= "商品ID: {$item['id']} - {$item['name']} (库存: {$item['stock']})\n";
}
mail($to, $subject, $message);
// 可选:记录到提醒日志表
}
设置Cron任务(Linux)或计划任务(Windows)
# 每5分钟执行一次检查 */5 * * * * /usr/bin/php /path/to/check_low_stock.php
缺点:
- 实时性差(取决于Cron执行间隔)。
- 高并发下可能漏发(因库存变化快)。
- 无法主动推送给用户。
事件驱动 + Redis Pub/Sub(适合中等规模)
利用Redis的发布/订阅或队列,在库存变更时立即触发提醒。
库存更新时发布事件
// 当扣减库存时(例如在订单创建成功后的代码中)
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
// 检查更新后的库存
$newStock = $productModel->getStock($productId);
$threshold = 10;
if ($newStock < $threshold && $newStock > 0) {
$event = json_encode([
'type' => 'low_stock',
'product_id' => $productId,
'current_stock' => $newStock,
'timestamp' => time()
]);
// 发布到频道
$redis->publish('stock_alert', $event);
}
后台Worker订阅并处理
// worker.php (独立进程运行)
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->subscribe(['stock_alert'], function ($redis, $channel, $message) {
$data = json_decode($message, true);
// 发送邮件/短信/钉钉/企业微信通知
sendAlertEmail($data['product_id'], $data['current_stock']);
// 记录日志
logAlert($data);
});
通过php worker.php在后台常驻运行,或使用Supervisor管理。
优点:实时性强,无需轮询数据库。
用户端的“到货通知”(用户订阅模式)
如果想让用户主动订阅缺货商品,并在到货时提醒他们:
用户订阅表
CREATE TABLE `stock_subscriptions` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`product_id` int(11) NOT NULL,
`email` varchar(255) NOT NULL,
`status` tinyint(4) DEFAULT '0', -- 0:等待中, 1:已通知
`created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `user_product` (`user_id`, `product_id`)
);
补货时触发
// 当补货操作完成(入库、退货、供应商送货)
public function restockProduct($productId, $quantity) {
// 更新库存...
$product->stock += $quantity;
$product->save();
// 检查是否有用户订阅了该商品
$subscriptions = StockSubscription::where('product_id', $productId)
->where('status', 0)
->get();
// 如果库存从0变为>0,且用户订阅存在
if ($product->stock > 0 && $subscriptions->count() > 0) {
foreach ($subscriptions as $sub) {
// 发送邮件给订阅用户
$this->sendBackInStockEmail($sub->email, $product);
// 标记为已通知
$sub->status = 1;
$sub->save();
}
}
}
优化:使用队列(如RabbitMQ、Beanstalkd)将邮件发送任务异步化,避免阻塞补货流程。
推送通知(WebSocket / SSE)(面向C端用户实时提醒)
如果需要在前端实时显示“库存紧张”或缺货,推荐使用Server-Sent Events (SSE)或WebSocket。
使用SSE示例(PHP Server-Sent Events)
// sse.php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('Connection: keep-alive');
$lastId = 0;
while (true) {
// 每次循环检查当前登录用户关注的商品库存
$userId = $_SESSION['user_id']; // 或从JWT获取
$lowStockProducts = $db->query("
SELECT p.id, p.name, p.stock
FROM products p
JOIN user_wishlist uw ON p.id = uw.product_id
WHERE uw.user_id = ? AND p.stock < p.min_stock
", [$userId])->fetchAll();
if (!empty($lowStockProducts)) {
echo "id: " . ++$lastId . "\n";
echo "event: low_stock\n";
echo "data: " . json_encode($lowStockProducts) . "\n\n";
ob_flush();
flush();
}
sleep(10); // 每10秒检查一次
}
前端通过EventSource接收:(但注意SSE对缺货场景的轮询效率较低,更适合推送新消息)
进阶方案:实时库存 + 消息队列 + 多渠道
| 组件 | 技术选型 | 用途 |
|---|---|---|
| 库存扣减 | Redis + Lua脚本(原子操作) | 保证高并发下库存准确 |
| 事件触发 | RabbitMQ/Redis List | 异步处理库存变更事件 |
| 通知分发 | PHP Worker (Supervisor) | 轮询队列,区分通知类型 |
| 通知渠道 | Mailgun/SendGrid (邮件), Twilio (短信), 钉钉/企微机器人 | 根据重要性选择渠道 |
| 用户偏好 | 数据库表 | 用户选择“邮件/短信/App推送” |
示例:RabbitMQ消费者
// consumer.php
$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');
$channel = $connection->channel();
$channel->queue_declare('stock_alert_queue', false, true, false, false);
$callback = function ($msg) {
$data = json_decode($msg->body, true);
if ($data['type'] === 'low_stock') {
// 根据用户设置的通知方式发送
if ($data['notification_type'] === 'email') {
sendEmail($data['user_email'], $data['message']);
} elseif ($data['notification_type'] === 'sms') {
sendSMS($data['phone'], $data['message']);
}
}
$msg->delivery_info['channel']->basic_ack($msg->delivery_info['delivery_tag']);
};
$channel->basic_consume('stock_alert_queue', '', false, false, false, false, $callback);
while ($channel->is_consuming()) {
$channel->wait();
}
选型建议
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 小型电商站,管理员关心缺货 | Cron + 邮件 | 简单,无需额外组件 |
| 用户可订阅到货通知 | 订阅表 + 队列 | 体验好,用户主动参与 |
| 实时库存页面显示“紧张” | WebSocket/SSE | 前端实时感知 |
| 高并发、大流量(秒杀后) | 方案二/进阶:Redis + RabbitMQ | 解耦,可靠,不拖垮数据库 |
| 需要多渠道通知(邮件、短信、App) | 进阶方案:消息队列 + 渠道适配器 | 灵活扩展 |
关键提醒:
- 对于到货通知,务必在补货成功、库存从0变为>0时触发,而非每次库存变化都触发,避免骚扰用户。
- 邮件通知最好加入退订链接,符合GDPR/反垃圾邮件法规。
- 高并发下,建议使用Redis + Lua扣减库存,避免数据库行锁和性能问题。