|
|
@@ -0,0 +1,189 @@
|
|
|
+### 实现中的关键信息
|
|
|
+1. TODO 根据限购与特价进行价格计算的关键参数
|
|
|
+
|
|
|
+2. handleLimitBuy -- 限购处理
|
|
|
+
|
|
|
+3. 订单取消也要处理 -- 后期处理
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+如果填了限购数量,则显示清空时间栏
|
|
|
+![[Pasted image 20260326141152.png|500]]
|
|
|
+- 超出数量
|
|
|
+ - 超出后就不让买了
|
|
|
+ 1. 如果有设置特价(则显示超出数量栏),可以让按原价继续买;也可以不让买(死卡数量)
|
|
|
+ 2. ~~如果不卖,则可以不设置特价~~
|
|
|
+- 如果没库存,限购活动自动停止。库存还有,但设置了清空时间,则到了时间点自动清空
|
|
|
+ - 有特价的怎么处理?---- 1. 处理时,去除特价且去除限购 2. 仅去除限购
|
|
|
+
|
|
|
+**最终**
|
|
|
+
|
|
|
+### 要实现的地方
|
|
|
+- 批发花材管理
|
|
|
+- 零售花材管理
|
|
|
+### 影响处
|
|
|
+有二个地方:
|
|
|
+1. 后台开单
|
|
|
+2. 客户下单
|
|
|
+
|
|
|
+- 前端的:批发和零售也都加,现在是修改花材和从平台采购花材已有基础,但建新品种还没有也补。
|
|
|
+- 零售前端的:清已购 和 已购列表 没有也要补。
|
|
|
+
|
|
|
+### 相关接口
|
|
|
+- 零售端花材列表接口 -- product/ghs-item-v2?id=1571&showAll=0
|
|
|
+ - limitBuy 关键字
|
|
|
+- 零售向批发买花材 --
|
|
|
+
|
|
|
+- 批发更新花材接口 --
|
|
|
+
|
|
|
+### 缓存设置
|
|
|
+biz-ghs/order/classes/OrderItemClass.php -- replaceItem() 376行
|
|
|
+
|
|
|
+
|
|
|
+## AI 解决 -- 用 RabbitMQ 把“key 过期触发”这件事,变成延迟消息 → 到期自动投递 → 消费执行
|
|
|
+https://chatgpt.com/c/69c646d2-fe5c-83e8-89f4-fbd91c395997
|
|
|
+
|
|
|
+已按你的要求落地:用 RabbitMQ 在到达清空时间后,把 `OrderItemClass` 的 `limitBuy` 重置为 `0`。
|
|
|
+
|
|
|
+### 本次改动
|
|
|
+
|
|
|
+- 在 `ProductClass` 的 `createLimitBuyCache()` 中改为发布延迟消息(不再用不存在的 `sendMessage`):
|
|
|
+ - 文件:`biz-ghs/product/classes/ProductClass.php`
|
|
|
+ - 逻辑:使用 `stockProducer` 向 `limitBuyDelayExchange` 投递消息,带 `expiration`(毫秒);
|
|
|
+ - 消息到期后通过死信路由进入真实消费队列,触发清零。
|
|
|
+- 新增 RabbitMQ 延迟路由配置:
|
|
|
+ - 文件:`common/config/rabbitMQ.php`
|
|
|
+ - 新增:
|
|
|
+ - `limitBuyDelayExchange`、`limitBuyExchange`
|
|
|
+ - `limitBuyDelayQueue`(带 dead-letter 到 `limitBuyExchange`)
|
|
|
+ - `limitBuyQueue`
|
|
|
+ - 对应 bindings:`limitBuyDelayRoute`、`limitBuyRoute`
|
|
|
+ - 并把 `limitBuyQueue` 挂到现有 `stockConsumer`。
|
|
|
+- 在库存消费者中新增 `limit_buy_clear` 处理分支:
|
|
|
+ - 文件:`common/components/rabbitmq/stockConsumer.php`
|
|
|
+ - 收到到期消息后调用订单项清零方法。
|
|
|
+- 在订单项类新增清零方法:
|
|
|
+ - 文件:`biz-ghs/order/classes/OrderItemClass.php`
|
|
|
+ - 新方法:`clearLimitBuyByProductId($productId)`,执行 `updateByCondition(['productId' => $productId], ['limitBuy' => 0])`。
|
|
|
+
|
|
|
+### 额外修正
|
|
|
+
|
|
|
+- `createLimitBuyCache()` 参数统一为 `($productId, $seconds = 0)`,匹配当前调用方式,避免把秒数错当 `customId`。
|
|
|
+- 已做 `php -l` 语法检查,4 个改动文件均通过。
|
|
|
+
|
|
|
+### 你这边需要确认
|
|
|
+
|
|
|
+- 消费进程要包含 `stockConsumer`(你现在的配置已把 `limitBuyQueue` 绑定到它):
|
|
|
+ - `php yii rabbitmq/consume stockConsumer`
|
|
|
+- 首次使用新队列/交换机时,如未开启自动声明,可手动执行:
|
|
|
+ - `php yii rabbitmq/declare-all`
|
|
|
+
|
|
|
+如果你希望“只清空未完成订单”的 `limitBuy`,我可以再帮你把 `clearLimitBuyByProductId()` 的更新条件加上订单状态过滤。
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+### 疑问
|
|
|
+@common/config/rabbitMQ.php:45-49 @common/config/rabbitMQ.php:122-126 这个是不是多余的?
|
|
|
+
|
|
|
+不多余,按你现在这套“延迟队列 + 死信转发”实现,这两段是**成对必需**的:
|
|
|
+
|
|
|
+- `limitBuyExchange`(`45-49`)是死信目标交换机;
|
|
|
+- `limitBuyQueue -> limitBuyExchange(limitBuyRoute)`(`122-126`)是把交换机路由到实际消费队列的绑定。
|
|
|
+
|
|
|
+因为 `limitBuyDelayQueue` 里配置了:
|
|
|
+
|
|
|
+- `x-dead-letter-exchange = limitBuyExchange`
|
|
|
+- `x-dead-letter-routing-key = limitBuyRoute`
|
|
|
+
|
|
|
+消息过期后会投到这个交换机+路由键。
|
|
|
+如果删掉你标的这两段,过期消息通常会因为“找不到可路由目标”被丢弃,`limitBuy` 就不会自动清零。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+如果你想精简配置,也可以改成复用现有 `stockExchange/stockQueue`,那就能去掉这两段(以及 `limitBuyQueue` 的相关配置),但要同步改 `x-dead-letter-exchange/routing-key`。我可以直接帮你改成这种精简版。
|
|
|
+
|
|
|
+链路:`cancelLimitBuyProducer -> limitBuyDelayExchange -> limitBuyDelayQueue(DLX) -> limitBuyExchange -> limitBuyQueue -> cancelLimitBuyConsumer -> OrderItemClass::clearLimitBuyByProductId()`
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+### 风险修复
|
|
|
+
|
|
|
+有 2 个需要注意的问题(按严重度):
|
|
|
+
|
|
|
+- **高风险:重复创建限购缓存时,旧延迟消息无法失效,可能“提前清空”**
|
|
|
+ - 当前实现每次 `createLimitBuyCache()` 都会重新发一条延迟消息,但没有“版本号/到期时间校验”。如果同一 `productId` 在旧消息到期前又改了新的清空时间,旧消息仍会按原时间触发,导致 `OrderItemClass.limitBuy` 被提前置 0。
|
|
|
+
|
|
|
+```2662:2672:biz-ghs/product/classes/ProductClass.php
|
|
|
+// 用 RabbitMQ 延迟消息在到期时清空订单项限购字段
|
|
|
+$message = [
|
|
|
+ 'type' => 'limit_buy_clear',
|
|
|
+ 'productId' => $productId,
|
|
|
+];
|
|
|
+$producer = Yii::$app->rabbitmq->getProducer('cancelLimitBuyProducer');
|
|
|
+$producer->publish($message, 'limitBuyDelayExchange', 'limitBuyDelayRoute', [
|
|
|
+ 'delivery_mode' => 2,
|
|
|
+ 'content_type' => 'application/octet-stream',
|
|
|
+ 'expiration' => (string)($seconds * 1000),
|
|
|
+]);
|
|
|
+```
|
|
|
+
|
|
|
+- **中风险:新增了独立消费者后,需要确保进程真的在跑**
|
|
|
+ - 你现在把 `limitBuyQueue` 挂到了 `cancelLimitBuyConsumer`,实现上是对的;但如果线上只启动了旧的 `stockConsumer`,限购清空消息会积压不消费。
|
|
|
+
|
|
|
+```187:193:common/config/rabbitMQ.php
|
|
|
+[
|
|
|
+ //取消限购消费者
|
|
|
+ 'name' => 'cancelLimitBuyConsumer',
|
|
|
+ 'callbacks' => [
|
|
|
+ 'limitBuyQueue' => '\common\components\rabbitmq\cancelLimitBuyConsumer',
|
|
|
+ ]
|
|
|
+]
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+其余实现链路是通的:`stockProducer -> limitBuyDelayExchange -> limitBuyDelayQueue(DLX) -> limitBuyExchange -> limitBuyQueue -> cancelLimitBuyConsumer -> OrderItemClass::clearLimitBuyByProductId()`。
|
|
|
+
|
|
|
+### 建议修复方向(优先第1条)
|
|
|
+
|
|
|
+- 在消息里增加 `expireAt`(或版本号),消费者处理前比对当前产品的“有效清空时间”,不一致就直接 `ACK` 丢弃旧消息。
|
|
|
+- 或者改成“只在第一次创建时发消息,后续改时间走覆盖策略+校验”。
|
|
|
+
|
|
|
+如果你要,我可以直接按“`expireAt` 防抖校验”给你补一版最小改动代码。
|
|
|
+
|
|
|
+
|
|
|
+### 按方案一修复
|
|
|
+已执行,`expireAt` 防抖校验已加上,避免旧延迟消息提前清空限购。
|
|
|
+
|
|
|
+### 我做的改动
|
|
|
+
|
|
|
+- `ProductClass::createLimitBuyCache()`
|
|
|
+ - 文件:`biz-ghs/product/classes/ProductClass.php`
|
|
|
+ - 发送消息时新增 `clearAt`:
|
|
|
+ - `clearAt = time() + $seconds`
|
|
|
+ - 消息体新增 `clearAt`
|
|
|
+ - 新增 Redis 版本标记:`limit_buy_clear_at:{productId}`(`SET` 当前 `clearAt`)
|
|
|
+ - 当 `seconds <= 0` 时会清理该标记键。
|
|
|
+
|
|
|
+- `ProductClass` 新增两个方法
|
|
|
+ - `checkLimitBuyClearMessage($productId, $clearAt)`:校验消息是否是当前有效版本
|
|
|
+ - `clearLimitBuyClearMark($productId)`:清理版本标记
|
|
|
+ - 并在 `cancelLimitBuy($productId)` 中也清理标记,避免遗留。
|
|
|
+
|
|
|
+- `cancelLimitBuyConsumer` 消费逻辑
|
|
|
+ - 文件:`common/components/rabbitmq/cancelLimitBuyConsumer.php`
|
|
|
+ - 消费 `limit_buy_clear` 时新增:
|
|
|
+ - 校验 `clearAt` 参数
|
|
|
+ - 调用 `ProductClass::checkLimitBuyClearMessage(...)`
|
|
|
+ - 非当前版本消息直接忽略(ACK,不再重入队)
|
|
|
+ - 真正清空成功后,调用 `ProductClass::clearLimitBuyClearMark($productId)` 清理标记。
|
|
|
+
|
|
|
+### 校验结果
|
|
|
+
|
|
|
+- 已通过语法检查:
|
|
|
+ - `biz-ghs/product/classes/ProductClass.php`
|
|
|
+ - `common/components/rabbitmq/cancelLimitBuyConsumer.php`
|
|
|
+- 当前仅有一个历史 Intelephense 提示(`changePrice` 未定义),与本次改动无关。
|
|
|
+
|
|
|
+如果你愿意,我下一步可以再补一条“本地手工验证清单”(发消息 -> 等待过期 -> 验证旧消息被忽略 / 新消息生效)。
|