电商库存补偿机制:MySQL事务与PHP实现方案
1. 问题背景与场景还原上周处理电商系统退货单时遇到个典型场景用户退回3件商品系统显示入库成功但库存数量未更新。这种幽灵库存问题在促销季高频出现直接影响后续销售和库存盘点。我们用的是PHPMySQL的经典架构事务处理看似完整却仍有漏洞。库存补偿机制本质上是个分布式事务问题。当退货单状态变为已入库时需要同时完成两个操作1入库记录写入t_return表 2对应商品在t_stock表的库存数量增加。这两个操作必须保持原子性但在高并发场景下可能出现部分成功的情况。2. 故障根因深度分析2.1 典型失败场景枚举通过日志分析发现主要存在三种故障模式数据库连接中断占比42%死锁导致事务回滚占比35%程序异常未捕获占比23%2.2 事务隔离级别的影响我们使用的REPEATABLE READ隔离级别在某些场景下会加剧问题。测试发现当同时存在库存查询和更新操作时容易产生以下序列-- 事务1 SELECT stock FROM t_stock WHERE sku_id1001; -- 读到100 -- 事务2 UPDATE t_stock SET stockstock3 WHERE sku_id1001; -- 事务1 UPDATE t_stock SET stockstock3 WHERE sku_id1001; -- 实际写入103而非1063. 补偿机制设计方案3.1 事务型消息表方案核心思路将库存变更操作转化为可重试的消息// 创建退货单时同步写入消息表 $db-beginTransaction(); try { $db-insert(t_return, $returnData); $db-insert(t_inventory_msg, [ msg_id uniqid(), sku_id $skuId, qty $returnQty, status 0 // 待处理 ]); $db-commit(); } catch (Exception $e) { $db-rollBack(); throw $e; }3.2 补偿任务处理器独立进程处理失败消息采用指数退避重试策略class InventoryCompensator { const MAX_RETRIES 5; public function processFailedMessages() { $messages $this-getPendingMessages(); foreach ($messages as $msg) { try { $this-applyInventoryChange($msg); $this-markMessageAsDone($msg[id]); } catch (Exception $e) { $this-handleRetry($msg, $e); } } } private function handleRetry($msg, $exception) { if ($msg[retry_count] self::MAX_RETRIES) { $this-alertAdmin($msg, $exception); return; } $nextRetry time() pow(2, $msg[retry_count]) * 60; $this-updateMessageRetry($msg[id], $nextRetry); } }4. 关键实现细节4.1 幂等性保障补偿操作必须实现幂等核心措施消息表增加唯一索引UNIQUE KEY uk_msg (biz_type,biz_id,sku_id)库存变更SQL改造UPDATE t_stock SET stock stock :delta WHERE sku_id :sku_id AND stock :delta 0 -- 防止超卖4.2 分布式锁应用使用Redis实现跨进程锁$lockKey inventory_compensate:{$skuId}; $lock $redis-set($lockKey, 1, [nx, ex 30]); if (!$lock) { throw new BusyException(操作频繁请稍后重试); } try { // 执行库存变更 } finally { $redis-del($lockKey); }5. 监控与告警体系5.1 监控指标设计关键监控项包括补偿消息积压量平均补偿延迟补偿成功率最终失败率5.2 日志规范示例$this-logger-info(库存补偿开始, [ msg_id $msgId, retry_count $retryCount, sku_id $skuId, qty $qty ]); $this-logger-error(库存补偿失败, [ msg_id $msgId, error $e-getMessage(), trace $e-getTraceAsString() ]);6. 实战避坑指南时间戳陷阱 避免使用服务器本地时间判断超时应该使用数据库事务时间SELECT created_at FROM t_inventory_msg WHERE msg_id ? FOR UPDATE批量处理优化 补偿任务不宜一次性处理过多消息建议分批$batchSize 100; do { $messages $db-select( SELECT * FROM t_inventory_msg WHERE status 0 AND next_retry_time NOW() ORDER BY created_at ASC LIMIT ?, [$batchSize] ); // 处理逻辑... } while (!empty($messages));补偿结果验证 增加事后校验任务比对入库记录与库存变更SELECT r.sku_id, r.qty AS return_qty, s.stock - COALESCE(h.stock_before, s.stock) AS actual_change FROM t_return r LEFT JOIN t_stock s ON r.sku_id s.sku_id LEFT JOIN t_inventory_history h ON r.return_id h.biz_id WHERE r.status COMPLETED AND r.created_at DATE_SUB(NOW(), INTERVAL 1 DAY)7. 性能优化方案7.1 数据库优化消息表索引设计主键自增id聚集索引联合索引(status, next_retry_time)唯一索引(biz_type, biz_id, sku_id)库存表热点更新优化-- 原始方式产生行锁竞争 UPDATE t_stock SET stock stock 1 WHERE sku_id 1001; -- 优化为减少锁冲突 INSERT INTO t_stock_delta (sku_id, delta) VALUES (1001, 1) ON DUPLICATE KEY UPDATE delta delta 1;7.2 缓存策略引入本地缓存减少数据库压力class InventoryCache { private static $localCache []; public static function getStock($skuId) { if (isset(self::$localCache[$skuId])) { return self::$localCache[$skuId]; } $stock $db-select(SELECT stock FROM t_stock WHERE sku_id ?, [$skuId]); self::$localCache[$skuId] $stock; return $stock; } public static function invalidate($skuId) { unset(self::$localCache[$skuId]); } }8. 灾备与恢复方案8.1 数据修复工具开发应急修复脚本处理长期未解决的补偿消息function forceCompensate($msgId) { $db-beginTransaction(); try { $msg $db-select(SELECT * FROM t_inventory_msg WHERE id ? FOR UPDATE, [$msgId]); // 记录当前库存快照 $db-insert(t_inventory_snapshot, [ sku_id $msg[sku_id], before_stock $db-selectValue(SELECT stock FROM t_stock WHERE sku_id ?, [$msg[sku_id]]), op_type FORCE_COMPENSATE ]); // 执行强制补偿 $db-update( UPDATE t_stock SET stock stock ? WHERE sku_id ?, [$msg[qty], $msg[sku_id]] ); // 标记消息为已处理 $db-update( UPDATE t_inventory_msg SET status 1 WHERE id ?, [$msgId] ); $db-commit(); } catch (Exception $e) { $db-rollBack(); throw $e; } }8.2 补偿流水记录建立完整的操作日志体系CREATE TABLE t_inventory_history ( id BIGINT PRIMARY KEY AUTO_INCREMENT, biz_id VARCHAR(32) NOT NULL COMMENT 业务ID, biz_type VARCHAR(20) NOT NULL COMMENT 业务类型, sku_id BIGINT NOT NULL, delta INT NOT NULL COMMENT 变更数量, stock_before INT NOT NULL, stock_after INT NOT NULL, operator VARCHAR(32) NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, INDEX idx_sku (sku_id), INDEX idx_biz (biz_type, biz_id) ) ENGINEInnoDB;9. 不同规模下的方案演进9.1 小型系统方案适合日均订单量1万的系统同步事务处理简单的定时补偿任务单数据库事务// 基础版补偿逻辑 function basicCompensate() { $failedReturns $db-select( SELECT * FROM t_return WHERE status COMPLETED AND NOT EXISTS ( SELECT 1 FROM t_inventory_log WHERE biz_id t_return.return_id ) ); foreach ($failedReturns as $return) { try { $db-update( UPDATE t_stock SET stock stock ? WHERE sku_id ?, [$return[qty], $return[sku_id]] ); $db-insert(t_inventory_log, [ biz_id $return[return_id], sku_id $return[sku_id], qty $return[qty] ]); } catch (Exception $e) { // 记录错误日志 } } }9.2 中大型系统方案适合日均订单量10万的系统需要引入消息队列解耦分库分表策略分布式事务框架多级缓存体系// 使用消息队列的补偿流程 function advancedCompensate() { $compensateTopic new Topic(inventory_compensate); // 生产者 $producer new Producer(); $producer-send($compensateTopic, [ return_id $returnId, sku_id $skuId, qty $qty ]); // 消费者 $consumer new Consumer(); $consumer-subscribe($compensateTopic, function ($message) { try { $this-processCompensateMessage($message); $message-ack(); } catch (Exception $e) { $message-nack(delay: pow(2, $message-retryCount()) * 60); } }); }10. 测试方案设计10.1 单元测试要点class InventoryServiceTest extends TestCase { public function testCompensateSuccess() { // 准备测试数据 $skuId $this-createTestSku(); $returnId $this-createTestReturn($skuId); // 模拟库存更新失败 $this-mockDatabaseFailure(); // 执行补偿 $result (new InventoryService())-compensateStock($returnId); // 验证结果 $this-assertTrue($result); $this-assertInventoryChanged($skuId, 3); } public function testCompensateIdempotent() { // 准备测试数据 $skuId $this-createTestSku(); $returnId $this-createTestReturn($skuId); // 第一次补偿 (new InventoryService())-compensateStock($returnId); // 第二次补偿 $result (new InventoryService())-compensateStock($returnId); // 验证幂等性 $this-assertTrue($result); $this-assertInventoryChanged($skuId, 3); // 不是6 } }10.2 压力测试方案使用JMeter模拟以下场景正常流程库存更新成功率异常流程数据库超时时的补偿效果并发冲突高并发退货时的数据一致性测试指标关注补偿延迟P99数据一致性率系统吞吐量影响11. 周边系统对接11.1 财务系统对账设计对账文件格式return_id,sku_id,expected_qty,actual_qty,time_diff R20230001,1001,3,3,0 R20230002,1002,1,0,300 -- 异常记录11.2 预警系统集成配置库存补偿异常告警规则单日补偿失败率5%单SKU补偿失败次数3次/小时补偿延迟30分钟的记录数突增12. 技术选型对比12.1 补偿方案对比方案类型实现复杂度数据一致性性能影响适用场景定时扫描低最终一致中等小型系统消息队列中最终一致低中大型系统TCC事务高强一致高金融系统SAGA模式高最终一致中分布式系统12.2 数据库选型建议MySQL适合大多数场景需优化事务配置[mysqld] innodb_lock_wait_timeout5 innodb_rollback_on_timeoutON transaction-isolationREAD-COMMITTEDPostgreSQL更适合复杂事务场景BEGIN; SAVEPOINT before_compensate; -- 补偿操作... RELEASE SAVEPOINT before_compensate; COMMIT;13. 性能优化进阶13.1 批量补偿处理public function batchCompensate(array $messageIds) { $chunks array_chunk($messageIds, 100); foreach ($chunks as $chunk) { $db-beginTransaction(); try { // 锁定所有消息记录 $messages $db-select( SELECT * FROM t_inventory_msg WHERE id IN (.implode(,, array_fill(0, count($chunk), ?)).) FOR UPDATE, $chunk ); // 按SKU分组处理 $skuGroups []; foreach ($messages as $msg) { $skuGroups[$msg[sku_id]][] $msg[qty]; } // 批量更新库存 foreach ($skuGroups as $skuId $qtys) { $total array_sum($qtys); $db-update( UPDATE t_stock SET stock stock ? WHERE sku_id ?, [$total, $skuId] ); } // 批量更新消息状态 $db-update( UPDATE t_inventory_msg SET status 1 WHERE id IN (.implode(,, array_fill(0, count($chunk), ?)).), $chunk ); $db-commit(); } catch (Exception $e) { $db-rollBack(); throw $e; } } }13.2 异步处理优化使用Swoole协程提升IO效率Co\run(function () { $channel new Chan(10); // 控制并发度 go(function () use ($channel) { $messages $this-getPendingMessages(); foreach ($messages as $msg) { $channel-push($msg); } $channel-close(); }); for ($i 0; $i 5; $i) { // 5个消费者 go(function () use ($channel) { while ($msg $channel-pop()) { $this-processMessage($msg); } }); } });14. 安全防护措施14.1 防重复攻击请求签名验证幂等令牌机制业务参数校验function verifyCompensateRequest($request) { // 1. 检查签名 $sign md5($request[biz_id].SECRET_KEY); if ($sign ! $request[sign]) { throw new InvalidRequestException(); } // 2. 检查幂等令牌 $tokenKey compensate_token:.$request[biz_id]; if (!$redis-set($tokenKey, 1, [nx, ex300])) { throw new DuplicateRequestException(); } // 3. 校验业务参数 if ($request[qty] 0 || $request[qty] 1000) { throw new InvalidParamException(); } }14.2 操作审计记录所有补偿操作的关键信息CREATE TABLE t_compensate_audit ( id BIGINT PRIMARY KEY AUTO_INCREMENT, operator VARCHAR(32) NOT NULL COMMENT 操作人, action VARCHAR(20) NOT NULL COMMENT 操作类型, target_id VARCHAR(64) NOT NULL COMMENT 目标ID, before_state TEXT COMMENT 操作前状态, after_state TEXT COMMENT 操作后状态, client_ip VARCHAR(45) NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, INDEX idx_target (target_id), INDEX idx_time (created_at) ) ENGINEInnoDB;15. 灰度发布策略15.1 流量染色方案在请求头注入灰度标记$request-headers-set(X-Gray-Release, inventory_v2);补偿处理器根据标记路由if ($request-headers-get(X-Gray-Release) inventory_v2) { $processor new V2Compensator(); } else { $processor new V1Compensator(); }15.2 数据对比验证开发数据比对工具确保新旧逻辑结果一致function compareCompensateResults($returnId) { $v1Result (new V1Compensator())-dryRun($returnId); $v2Result (new V2Compensator())-dryRun($returnId); $diff []; foreach ($v1Result as $skuId $qty) { if ($v2Result[$skuId] ! $qty) { $diff[$skuId] [ v1 $qty, v2 $v2Result[$skuId] ]; } } return $diff; }16. 遗留系统改造16.1 渐进式改造步骤先添加补偿消息表不修改主流程开发补偿处理器独立服务逐步迁移业务到新流程最终移除旧逻辑16.2 双写兼容方案function dualWriteCompensate($returnId) { // 旧逻辑 try { $this-legacyCompensate($returnId); } catch (Exception $e) { $this-logger-error(旧补偿逻辑失败, [return_id $returnId]); } // 新逻辑 try { $this-newCompensate($returnId); } catch (Exception $e) { $this-logger-error(新补偿逻辑失败, [return_id $returnId]); throw $e; } }17. 成本控制方案17.1 资源配额管理class ResourceManager { private static $quota [ compensate [ max_retries 5, daily_limit 10000, concurrency 50 ] ]; public static function checkQuota($operation) { $used $redis-incr(quota:{$operation}:.date(Ymd)); if ($used self::$quota[$operation][daily_limit]) { throw new QuotaExceededException(); } } }17.2 冷数据处理对超过30天的补偿消息归档处理-- 创建归档表 CREATE TABLE t_inventory_msg_archive LIKE t_inventory_msg; -- 每月归档 INSERT INTO t_inventory_msg_archive SELECT * FROM t_inventory_msg WHERE created_at DATE_SUB(NOW(), INTERVAL 30 DAY); DELETE FROM t_inventory_msg WHERE created_at DATE_SUB(NOW(), INTERVAL 30 DAY);18. 文档规范建议18.1 流程图标准使用PlantUML绘制补偿流程startuml start :创建退货单; fork :写入入库记录; fork again :生成补偿消息; end fork :返回成功; repeat :补偿处理器读取消息; :尝试库存变更; repeat while (变更失败?) is (是) -超过重试次数; :告警人工处理; enduml18.2 API文档示例POST /api/inventory/compensate 请求参数 { return_id: R20230001, items: [ { sku_id: 1001, qty: 1 } ], sign: md5(return_idsecret) } 成功响应 { code: 200, data: { compensated: true, retry_count: 0 } } 失败响应 { code: 500, error: INVENTORY_LOCKED, retry_after: 60 }19. 团队协作规范19.1 Code Review要点幂等性检查异常处理完整性事务边界合理性锁粒度控制日志记录完整性19.2 故障处理手册典型故障处理流程确认补偿消息状态SELECT * FROM t_inventory_msg WHERE biz_id R20230001;检查库存变更记录SELECT * FROM t_inventory_history WHERE biz_id R20230001;验证当前库存SELECT stock FROM t_stock WHERE sku_id 1001;执行手动补偿如有必要$compensator-forceCompensate(R20230001);20. 扩展思考方向20.1 与采购入库的协同考虑退货入库与采购入库的优先级处理function getInventoryAdjustmentPriority($type) { $priorities [ PURCHASE 100, // 采购入库优先 RETURN 90, COMPENSATE 80 ]; return $priorities[$type] ?? 50; }20.2 多仓库场景扩展支持仓库维度的补偿处理UPDATE t_stock SET stock stock :delta WHERE sku_id :sku_id AND warehouse_id :warehouse_id;20.3 与WMS系统集成设计库存补偿API规范补偿请求接口补偿结果查询接口补偿历史同步接口interface WmsInventoryService { public function requestCompensate(array $items); public function queryCompensateResult(string $requestId); public function syncCompensateHistory(DateTime $start, DateTime $end); }

相关新闻

新能源并网中同步电机与构网型变流器的频率稳定性研究

新能源并网中同步电机与构网型变流器的频率稳定性研究

1. 项目背景与核心问题电力系统中同步电机与构网型变流器的频率稳定性研究,是新能源并网领域的关键技术挑战。随着可再生能源渗透率不断提高,传统同步发电机占比下降,系统惯性降低导致频率调节能力减弱。构网型变流器(Grid-Formin…

2026/10/7 11:32:38 阅读更多 →
如何在Windows上彻底移除Microsoft Edge:EdgeRemover终极卸载指南

如何在Windows上彻底移除Microsoft Edge:EdgeRemover终极卸载指南

如何在Windows上彻底移除Microsoft Edge:EdgeRemover终极卸载指南 【免费下载链接】EdgeRemover A PowerShell script that correctly uninstalls or reinstalls Microsoft Edge on Windows 10 & 11. 项目地址: https://gitcode.com/gh_mirrors/ed/EdgeRemove…

2026/10/5 5:06:46 阅读更多 →
告别手动整理!MetaTube插件:让Jellyfin媒体库自动变智能的3个秘密

告别手动整理!MetaTube插件:让Jellyfin媒体库自动变智能的3个秘密

告别手动整理!MetaTube插件:让Jellyfin媒体库自动变智能的3个秘密 【免费下载链接】jellyfin-plugin-metatube MetaTube Plugin for Jellyfin/Emby 项目地址: https://gitcode.com/gh_mirrors/je/jellyfin-plugin-metatube 你是否曾经花费数小时手…

2026/10/4 0:36:16 阅读更多 →

最新新闻

MyBatis二级缓存源码深度解析:生命周期、装饰器链与五大致命坑

MyBatis二级缓存源码深度解析:生命周期、装饰器链与五大致命坑

1. 为什么你改了SQL却没刷新缓存?——从一个线上故障说起上周五下午三点,某电商后台订单查询接口响应时间突然从80ms飙升到1200ms,监控告警连响三轮。运维同学第一时间拉出慢SQL日志,发现执行计划里多了一堆SELECT * FROM order_i…

2026/10/13 11:47:23 阅读更多 →
AI Agent实战:家居高潜流失用户挽回,下单率提升104%

AI Agent实战:家居高潜流失用户挽回,下单率提升104%

做家居行业的用户增长,有个特别拧巴的场景:你手里明明握着一批高潜流失顾客,销售跟过了、优惠给过了、直播也邀请过,可他们就是不动。后来我们把AI Agent接进了这条挽回链路,才把问题真正跑通。这个项目里,…

2026/10/13 11:47:23 阅读更多 →
C++ set/multiset 底层红黑树:内存、迭代器与性能实战剖析

C++ set/multiset 底层红黑树:内存、迭代器与性能实战剖析

几个月前我在优化模拟项目X的排行榜模块时,遇到一个诡异现象:内存占用随着在线人数持续增长,插入同样数量数据的耗时也不断拉高。一开始我怀疑是业务代码里对象拷贝太狠,后来把热点路径上的std::vector std::sort换成了std::mult…

2026/10/13 11:47:23 阅读更多 →
Farming Code 架构解析:Language Server 与 Browser Use 如何支撑 Agent 持续耕作

Farming Code 架构解析:Language Server 与 Browser Use 如何支撑 Agent 持续耕作

1. 从 "Farming Code" 这个名字说起:它到底在解决什么问题第一次看到 "Farming Code" 这个标题,很多人会以为是某种游戏外挂或者自动化脚本合集。但结合 agent、Language Server、Browser Use、Computer Use 这几个关键词来看&#…

2026/10/13 11:47:23 阅读更多 →
CrashData:R语言清洗航空事故数据的完整实践指南

CrashData:R语言清洗航空事故数据的完整实践指南

简介:CrashData 是一份面向数据分析与航空安全研究者的 R 语言数据采集脚本资源,用于从 planecrashinfo.com 逐年抓取飞机失事的历史记录,解决公开事故数据分散、难以批量获取的问题。作者以略显取巧但可用的方式将网页内容整理为规整的 data…

2026/10/13 11:47:23 阅读更多 →
高德地图校园导航Android项目实战:从Key配置到路线规划全解析

高德地图校园导航Android项目实战:从Key配置到路线规划全解析

简介:这份基于高德地图的校园导航资源,是面向在校学生与初学者的完整项目包,适用于课程设计、毕业设计或项目初期演示。包内提供可直接运行的Android工程与APK安装包,涵盖Java源码、XML布局与配置、Gradle构建脚本及多个SO库&…

2026/10/13 11:46:22 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →