用Supabase重构知识交付体系:RLS权限与实时推送实践

先说个背景。我手里这套知识交付体系,已经跑了两年多,主要职责是把课程内容、配套文档、习题和系统通知分发给三类用户:学员、内容创作者、管理员。功能上不算复杂,但每次想调整一条访问规则,都要拉上前后端一起改代码;每次发布一篇新内容,客户端总要等半分钟才刷出来。那段时间我一直在想,问题到底出在架构上,还是出在我们对"知识交付"这件事的理解上。后来一位同事把 Supabase 拉到我们面前,我们花了两周做技术验证,随后用了大概两个月,把整套体系重写了一轮。这篇文章就是这次重构的完整记录:为什么选它、动了哪些刀、踩了哪些坑,以及重构之后我对"全栈知识交付"的理解发生了什么变化。

整个重构过程中我感受最深的一句话是:Supabase 表面上是一套后端服务,本质上它逼着你重新思考"数据归属权"。权限不再是接口里顺手写的 if 判断,而是数据自带的行为约束。这套思路放到知识交付场景里,恰好能把内容访问控制、发布推送、角色管理这些原本割裂的模块,全部统一到数据库层来解决。下面按我实际操作的顺序来拆。

1. 旧知识交付体系的三个卡点:权限散落、链路过长、联调内耗

1.1 权限逻辑散落在每一个接口里

老系统是典型的 Java Spring Boot + MySQL + Redis 组合,前端用 React。权限判断全部写在服务端接口里:管理员接口开头一个if(!isAdmin),创作者接口再来一个if(!isOwner),学员接口又判断enrollment.exists。一开始只有两三个角色,这样写还能忍受。等加入"试读用户""课程助理""内容审核员"这些角色之后,噩梦就来了:每个接口都要叠加好几层角色判断,而且不同接口的判断逻辑还不完全一致。最典型的一次,产品提了一个"试读用户只能看每门课的前两节"的需求,我统计了一下,要改 14 个接口,因为接口列表是写死的。

问题本质不是代码写得差,而是我们把"数据该不该被看到"这种业务规则,硬生生散落在了应用层各个接口的流程里。规则一多,必然不一致。

1.2 内容下发链路太长,发布像在走迷宫

知识交付的核心动作是"发布"。老系统的发布流程是:写 MySQL、更新 Redis 缓存、给消息队列丢一条通知、WebSocket 服务再向前端广播。每一步都可能出错,而且每一步出错的表现都不一样。有段时间经常出现运营反馈"后台显示已发布,学员那边却看不到"的问题,排查链路从接口日志翻到缓存过期时间,再翻到消息队列消费情况,一查就是小半天。

更深层的问题是,这套链路里数据库只是存储,真正的"业务逻辑"散在缓存层、队列层和推送服务里。每多一层,就多一种不一致的可能。

1.3 前后端联调成本随功能数量线性增长

老系统的接口文档靠手写,前端同学每次要查"某个字段到底返回什么格式",都得来问我。一个列表接口要支持排序、筛选、分页,前端要传五六个参数,后端要写对应的一大段逻辑。新功能从需求到上线,平均两个星期,其中一半时间花在接口联调和权限测试上。

现在回头看,这三个卡点指向同一个方向:我们缺一个"数据即接口"的中间层。正当我们考虑要不要自己写一套数据访问服务时,Supabase 出现了。

2. 第一刀先动数据层:用行级安全把权限写进每一行

2.1 行级安全(RLS)到底解决了什么

Supabase 和 Firebase 最大的区别,是它底层跑着完整的 PostgreSQL,而 Firebase 用的是封闭的 NoSQL 模型。这意味着你可以用数据库生态里已经沉淀了几十年的能力:外键、触发器、视图、物化视图,还有这个方案的核心——行级安全(Row Level Security,简称 RLS)。

RLS 的原理一句话就能说清:每次查询时,数据库根据当前用户的身份,自动过滤掉不该看到的行。Supabase 的 Auth 模块会为登录用户签发 JWT,PostgREST 把这个 JWT 解析成数据库会话变量,RLS 策略基于这些变量决定放行还是拦截。也就是说,前端可以直接通过 PostgREST 查数据库,但查到的每一行都经过策略过滤。

第一次听到这个设计时,我的第一反应是"这不就是把数据库裸奔给前端吗"。后来想通了:RLS 就是这个"裸奔"的守门员。规则写在数据库上,写接口的人、查接口的人,甚至未来新接的第三方系统,都无法绕过这些规则。

2.2 内容表上的核心策略:公开读、创作者写、学员限读

我们知识交付体系最核心的一张表是contents(课程内容),字段包括标题、正文、状态、创建人等。状态有四种:草稿、审核中、已发布、已归档。针对这张表,我们写了三组关键策略。

-- 所有人都能读已发布内容,这是知识交付的基础 create policy "published_content_public_read" on public.contents for select using (status = 'published'); -- 内容创作者只能操作自己的内容 create policy "creator_manage_own_content" on public.contents for all using (auth.uid() = owner_id) with check (auth.uid() = owner_id); -- 学员只能通过报名关系读取,且不能读草稿 create policy "enrolled_user_read_published" on public.contents for select using ( auth.uid() in ( select user_id from enrollments where content_id = contents.id ) and status = 'published' );

这三条策略覆盖了 90% 的访问场景。管理者可以通过 Supabase 控制台或者 service_role 密钥管理一切,但普通应用请求永远走 RLS。这里有个关键认知:规则一旦下沉到数据库,应用层的接口就变成了"翻译器",而不是"决策器"。

我特意提一个容易踩的坑:RLS 开启之后,连 PostgreSQL 自带的 service_role 也会被部分影响,只有超级权限才能绕过。开发时我们一度用 service_role 调试接口,发现策略完全没生效,还以为写错了,后来才搞清楚是角色差异。

2.3 试读、有效期和审核预览:三条复杂的实战策略

基础的读、写策略搞定之后,真正的考验来自业务规则。第一个是"试读用户只能看每门课前两节",第二个是"课程有有效期",第三个是"审核员可以预览但学员不行"。

试读的写法是用窗口函数判断内容在课程内的序号:

create policy "trial_user_read_first_two_sections" on public.contents for select using ( status = 'published' and row_number() over ( partition by course_id order by sort_order ) <= 2 );

有效期用expires_at字段配合now()判断,审核预览则靠额外的一张review_assignments表。这些策略组合起来之后,原来的 14 个接口权限判断,变成了数据库里十几条声明式策略。最直接的收益是,产品再提"加一种访问角色"时,我不再需要翻代码找所有接口,而是加一条策略。

提示:RLS 策略一定要在本地 Docker 版本的 Supabase 里反复验证,生产环境调试很多信息是被隐藏的。我们当时遇到策略写得过宽导致数据泄露风险的告警,就是因为本地没测全。

3. 推送链路的重塑:从轮询和自建WebSocket到数据库事件驱动

3.1 旧推送方案为什么费力不讨好

老系统的知识下发依赖两条路:列表页轮询 + 自建 WebSocket 推送。列表页每 30 秒请求一次最新内容列表,高峰期一栋楼里几百个用户同时开页面,接口压力全在列表查询上。自建 WebSocket 更麻烦,要维护连接状态、心跳、断线重连、消息可靠投递,还经常出现"服务重启丢消息"的问题。

Supabase 的 Realtime 模块把这件事简化了:它订阅 PostgreSQL 的 WAL(预写日志),表数据一旦变化,就把变更事件广播给订阅了相关 channel 的客户端。发布于我们而言,本质就是一次 UPDATE——把内容状态从"审核中"改成"已发布"。这一下,发布动作本身天然就是一个推送信号。

3.2 用触发器把"状态变更"变成"业务事件"

为了让推送更语义化,我们加了一张notify_events表,并且用触发器在内容发布时自动插入一条事件记录。

create function notify_on_published() returns trigger as $$ begin if new.status = 'published' and old.status <> 'published' then insert into notify_events (content_id, event_type, occurred_at) values (new.id, 'content_published', now()); end if; return new; end; $$ language plpgsql; create trigger trg_notify_on_published after update on public.contents for each row execute function notify_on_published();

客户端订阅notify_events这张表的变更,收到事件后只做一件事:按 content_id 去拉取最新内容。这里有个实践细节——广播的应该是信号,而不是内容本身。Realtime 消息体大小有限制,而且把整篇大文章塞进推送里,客户端解析成本也高。正确做法是推送"哪篇内容更新了",客户端再走 PostgREST 读取。

RLS 在这里还有一层妙用:订阅者只能收到自己有权限读的事件。因为 Realtime 的 channel 鉴权基于 RLS 的 select 策略,没权限的用户根本收不到对应事件,不需要我们在应用层再过滤一遍。

3.3 边缘函数接管的异步任务

旧系统里还有一类需求天生适合边缘函数:生成结课证书、内容摘要、定时统计。以前这些要么写成 Spring Boot 的后台任务,要么丢进消息队列。现在用 Supabase Edge Functions(基于 Deno 运行时)处理,直接部署在边缘节点上。

以结课证书为例:学员完成课程后,前端调用 Edge Function,函数里查询完成记录,调用存储服务生成证书图片,再把证书地址写回数据库。整个过程用户感知是毫秒级,不需要我们再维护额外的任务队列服务。

需要提醒的是,Realtime 在生产环境要关注连接数和消息频率。免费层有并发连接限制,我们当时上线运营活动时,几百个用户同时刷新,一度触发了连接数上限。后面在客户端做了 channel 复用和退避重连策略才缓解。

4. 数据模型重构中的几个反直觉决策:视图当接口,状态当约束

4.1 视图即对外契约,表结构随便改

这是这次重构里我自己觉得最值的一刀。以前我们直接暴露contents表给应用层,后来需求一多,表字段不断增加:有了body_html又加了body_json,有了sort_order又加了priority。每次加字段,前端都会误用内部字段,后端还得去解释。

重构后我们立了一条规矩:应用层只能访问视图,不能直接碰基表。核心视图如下:

create view content_catalog_v as select id, title, summary, status, course_id, sort_order, published_at from contents where is_deleted = false;

这个视图就是给前端和 PostgREST 看的"对外接口"。底层表随便加字段、做拆分、改索引,只要视图的输出稳定,前端一行代码都不用动。数据库的关系模型跟面向服务的接口设计在这里合并了,表是内部实现,视图是公共契约。

4.2 用数据库约束替代应用层枚举

老系统里的角色和状态全靠 Java 枚举类管理,部署新枚举要重启服务、发新包。重构后我们把这些改成数据库层面的约束:

  • 角色用varchar加check约束,值集合集中管理;
  • 内容状态同样用check约束,保证脏数据进不来;
  • 权限规则用lookup表表达,RLS 策略 join 这张表判断用户具备哪些权限。

比如user_roles表的设计,解决了 JWT 角色信息更新不及时的问题。JWT 里存的角色在签发后就固定了,如果一个人刚被提升为课程助理,要等 token 刷新才生效。改为 RLS 策略里 joinuser_roles表,权限变更秒级生效,这是我们踩过坑之后才改的。

4.3 软删除和部分索引:RLS 策略的底层支撑

老系统的删除是物理删除,重构后改成了软删除——因为 RLS 策略里要判断is_deleted = false。这个改动让所有查询都多了一个过滤条件,为了性能,我们创建了部分索引:

create index idx_contents_active on contents (id, status) where is_deleted = false;

RLS 策略和索引的配合是个精细活。策略过滤的字段如果没有索引支撑,复杂查询会全表扫描。特别是 enrollments 表这种高频 join 对象,必须建立 (user_id, content_id) 的复合索引。我把重构前后的核心设计差异整理成一张表:

维度旧设计新设计
权限控制应用接口 if 判断数据库 RLS 策略
对外接口手写 REST 接口PostgREST + 视图
删除方式物理删除软删除 + 部分索引
角色来源JWT 固定声明user_roles 表实时 join
状态管理Java 枚举check 约束 + 触发器
推送机制自建 WebSocketRealtime + notify_events

这张表基本就是整个重构的缩影:一切都在往"数据库更厚、应用更薄"的方向走。

5. 灰度迁移实录:影子流量、双写、以及六个绕不开的坑

5.1 四阶段的平滑迁移路径

真正的重构不是推翻重来,而是让老系统像换血一样逐步过渡。我们分了四个阶段:

阶段一:影子读流量。新体系搭建完成后,把老系统的一部分只读查询复制一份到新体系跑,比对返回结果。这个阶段新体系不承接真实请求,纯粹验证数据一致性和策略正确性。

阶段二:读写双写。内容发布时同时写老库和新库,学员查询逐步切换到新体系。我们通过开关控制切流比例,先放 10% 的学员流量,观察一周,再逐步放大。

阶段三:写流量切换。内容创作者和管理员的写操作切到新体系,老系统变成只读状态,保留数据以备回滚。

阶段四:老系统下线。观察两周无异常后,关停老系统,迁移完成。

整个过程中,supabaseCLI 帮了大忙。本地开发用 Docker 起一套 Supabase,代码改动通过supabase db diff生成迁移 SQL,再supabase migration up应用到云端。数据库变更纳入版本管理之后,团队再没有出现过"本地和线上表结构不一致"的情况。

5.2 六个必须在生产环境之前解决的坑

迁移过程中我们踩了不少坑,这里挑六个最典型的列出来:

问题现象根因解决方案
Realtime 连接在 JWT 过期后仍保持token 有效期和 WebSocket 生命周期没对齐客户端定时校验 token,过期主动断开重连
PostgREST 列表接口默认只返回 1000 行默认 max-rows 限制所有列表查询显式指定分页参数
权限变更要等 token 刷新才生效JWT 角色声明固定改为 join user_roles 表动态判断
本地 Docker Supabase 和云端版本不一致CLI 版本漂移锁定统一版本号,升级走变更评审
更新通知表触发器反向触发内容表更新触发器递归调用在触发器函数里加 pg_trigger_depth() 保护
复杂 RLS 策略查询变慢策略 join 表缺索引对 enrollment 等高频表建复合索引

其中触发器递归那个问题最隐蔽。我们给notify_events表加了触发器之后,某次更新内容时发现写入事件居然触发了内容表自己的更新,导致死循环。查了半天才明白是触发器函数里间接更新了内容表,用pg_trigger_depth()做了一层保护才压住。

注意:任何涉及 RLS 策略的变更,都必须先在"无权限匿名用户""普通学员""创作者""管理员"四类身份下分别测试。我们团队后来把这种测试固化成了发布清单,每次改策略都要过一遍。

6. 重构之后,团队如何重新理解"全栈知识交付"

6.1 角色的变化:前端开始直面数据,后端转向策略治理

重构完成后最大的变化,是前端同学现在直接用supabase-js查询视图,后端同学的工作重心从"写 CRUD 接口"变成了"设计视图契约、编写 RLS 策略、维护数据迁移脚本"。团队里一开始有很多人抵触,觉得这样让前端碰数据库太危险。但我们的规矩很明确:视图是唯一入口,基表的写操作只能通过 Edge Function 封装,前端拿到的只是"恰好够用"的数据。

后端业务代码量减少大约 70%,这不是夸张。原来 14 个接口的权限判断变成了几十行 RLS 策略;原来要写的列表分页排序逻辑,PostgREST 原生支持;原来要维护的消息推送服务,现在一个触发器加一张事件表就完成了。

6.2 知识交付体系的价值迁移:从"应用功能"到"数据生态"

这次重构让我想明白了一件事:知识交付体系真正该交付的不是一堆接口,而是"任何人在任何时刻,都能按自己的身份获得恰好的知识"。这个目标靠应用层代码堆是堆不出来的,因为应用层代码总有漏改、漏判的地方。而当规则被写进数据的每一行、每一次变更、每一条推送事件时,知识的流动本身就变成了系统的默认行为。

当然,Supabase 也不是万能的。如果你的业务有大量复杂事务编排、海量离线报表、或者强一致性的跨服务分布式事务,那还需要谨慎评估。我们当时也保留了少量 Edge Function 来封装复杂的写操作,没有完全一刀切。

6.3 最后分享一个延续性的实用技巧

写在最后。重构完成后我们维护了一套"内容发布自检"的 Edge Function:运营人员在后台点发布之前,函数会自动执行三条检查——内容状态是否为已发布、RLS 策略是否允许目标学员读取、notify_events 是否正常插入。任何一条不通过就不允许发布。这个小工具上线之后,"发布成功但学员看不到"的问题从每周好几起降到了零。如果你也在做类似的知识交付体系,我建议你在重构完成后也补上这样一层自查机制,它比任何监控都更贴近业务本身。

相关新闻

C++ ranges并行算法避坑:数据竞争检测与修复指南

C++ ranges并行算法避坑:数据竞争检测与修复指南

我接手维护某数据清洗管线时&#xff0c;第一次体会到std::ranges并行执行与数据竞争检测的威力。代码本身很普通&#xff1a;读一批日志&#xff0c;filter掉非法行&#xff0c;transform成索引&#xff0c;最后用std::for_each加std::execution::par并行处理。功能跑起来没有…

2026/10/20 22:25:20 阅读更多 →
基于MATLAB的雷达与AIS航迹融合:时空对齐与滤波实现

基于MATLAB的雷达与AIS航迹融合:时空对齐与滤波实现

雷达和AIS&#xff0c;是水上交通监控里最常用也最容易“打架”的两类信息来源。雷达每个扫描周期都能给出目标的位置&#xff0c;但不知道这条船是谁&#xff1b;AIS每隔几秒到几分钟报一次船名、航速、航向&#xff0c;但目标一旦关机或者报文延迟&#xff0c;监控画面就会出…

2026/10/20 22:24:15 阅读更多 →
PyTorch迁移学习:冻结主干+微调分类器实战指南

PyTorch迁移学习:冻结主干+微调分类器实战指南

1. 项目概述&#xff1a;为什么“只训练分类器”是迁移学习最务实的起点在实际项目中&#xff0c;我见过太多人一上来就想着微调整个预训练模型——ResNet50全层解冻、ViT所有Transformer块都加梯度、甚至把ImageNet上训好的权重当“背景音乐”随便改。结果呢&#xff1f;显存爆…

2026/10/20 22:24:15 阅读更多 →

最新新闻

context-mode设计模式:如何让同一套核心逻辑适配多种运行上下文

context-mode设计模式:如何让同一套核心逻辑适配多种运行上下文

1. 从"context-mode"这个词说起&#xff1a;它到底在解决什么问题第一次看到"context-mode"这个标题&#xff0c;很多人会下意识地把它当成某个框架里的配置项&#xff0c;或者某个库的开关参数。但如果你真的在工程一线待过几年&#xff0c;就会意识到这个…

2026/10/20 23:14:43 阅读更多 →
Windows内核安全编程实战:进程/文件/注册表三重监控框架

Windows内核安全编程实战:进程/文件/注册表三重监控框架

简介&#xff1a;这是一份基于VC开发的360杀毒软件高仿项目源码&#xff0c;面向C初学者及Windows桌面应用进阶学习者&#xff0c;旨在通过完整可运行的商业级UI框架实践&#xff0c;掌握MFC/WinAPI界面开发、多线程扫描逻辑、皮肤化UI设计&#xff08;SkinUI库&#xff09;、界…

2026/10/20 23:14:43 阅读更多 →
ArcGIS符号库本质:CIM驱动的空间语义编码系统

ArcGIS符号库本质:CIM驱动的空间语义编码系统

简介&#xff1a;本资源为ArcGIS专业制图用户必备的符号库集成包&#xff0c;面向GIS工程师、地图制图师及地理信息相关专业学习者&#xff0c;解决日常项目中符号缺失、风格不统一、制图效率低等实际问题。压缩包共142个文件&#xff0c;涵盖74个.style符号样式文件&#xff0…

2026/10/20 23:14:43 阅读更多 →
Java线程中断与Happens-Before:并发编程可见性规则实战解析

Java线程中断与Happens-Before:并发编程可见性规则实战解析

1. 线程中断&#xff1a;协作式取消的正确打开方式先把结论摆在这里&#xff1a;Java里的线程中断不是“强制终止线程”&#xff0c;而是一个协作式的通知机制。也就是说&#xff0c;A线程想停掉B线程&#xff0c;不能直接动手把B拽下来&#xff0c;而是给B打个招呼——“你该停…

2026/10/20 23:14:43 阅读更多 →
Linux Thermal温控模块详解:从框架设计到设备树配置实战

Linux Thermal温控模块详解:从框架设计到设备树配置实战

1. 项目概述1.1 为什么要啃Linux Thermal模块做嵌入式或者跑服务器的朋友应该都有这种体会&#xff1a;芯片性能越来越强&#xff0c;功耗密度越来越大&#xff0c;散热问题已经从“硬件工程师的烦恼”变成了“系统工程师的噩梦”。我自己手头负责的一块主控板&#xff0c;四核…

2026/10/20 23:14:43 阅读更多 →
N1刷飞牛NAS避坑指南:从U盘启动到救砖恢复完整流程

N1刷飞牛NAS避坑指南:从U盘启动到救砖恢复完整流程

N1刷飞牛NAS这件事&#xff0c;我前前后后帮人折腾过不少次&#xff0c;也在论坛里看过太多翻车案例。很多人拿到教程的第一反应就是下载镜像、拿U盘怼上去开刷&#xff0c;结果要么U盘启动不起来&#xff0c;要么写到一半断电黑屏&#xff0c;最后抱着一个点不亮的盒子到处问怎…

2026/10/20 23:13:40 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

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

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

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

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

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

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/20 20:43:59 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 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/20 10:02:08 阅读更多 →
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/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练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/20 9:50:45 阅读更多 →