Apache OpenWhisk 管理工具 wskadmin 完整实战指南:用户、命名空间限额、数据库与系统日志运维
后端云原生【免费下载链接】openwhiskApache OpenWhisk is an open source serverless cloud platform项目地址https://gitcode.com/gh_mirrors/ope/openwhisk点击查看免费下载本篇技术指南以 Apache OpenWhisk 仓库中 tools/admin/README.md 为骨架系统讲解管理工具wskadmin以及 Scala 重写版wskadmin-next的安装、配置与全部核心命令。wskadmin是面向 OpenWhisk 部署的运维利器它可以创建与删除 subject用户、管理其命名空间与授权密钥、封禁/解封用户按命名空间设置 action 与 trigger 限额还能直接转储资产库whisks、激活库activations或主体库subjects并查询其视图以及按组件或事务 ID 检索系统日志。读完本文你将掌握本地与生产 OpenWhisk 部署中最常用的全部管理命令、底层存储结构与源码实现细节能够独立完成用户开通、权限回收、限流配置与故障排查。1. wskadmin 是什么一图看懂三大职责在 Apache OpenWhisk 中wskadmin是运维人员而非普通用户使用的命令行工具与面向应用的wskCLI 分工明确。它围绕一个部署提供三类管理能力用户subject与命名空间管理创建/删除用户为用户在指定命名空间签发uuid:key形式的授权凭据封禁/解封用户以及反查某个密钥归属哪个用户。按命名空间设置限额limits覆盖调用频率、并发、运行时长、内存、日志大小、运行类型白名单、激活记录存储开关等用于多租户场景下的配额控制。数据库与日志巡检直接读取 subjects / whisks / activations 三类数据库及 CouchDB 视图按组件或事务 ID 检索 controller、scheduler、invoker 的日志定位本地部署问题。从仓库源码看仓库中并存两套实现Python 版wskadmin入口脚本为 tools/admin/wskadmin直接操作 CouchDB HTTP API配合 tools/admin/wskprop.py 读取属性、tools/admin/wskutil.py 封装 HTTP 请求。Scala 版wskadmin-next位于 tools/admin/src/main/scala/org/apache/openwhisk/core/通过 ArtifactStore SPI 访问数据库因此是**数据库无关DB agnostic**的实现其行为与 Python 版对齐但配置走 HOCON 风格。1.1 版本选择与构建Python 版wskadmin开箱即用直接执行仓库中脚本即可依赖系统 Python 与whisk.properties属性文件。Scala 版wskadmin-next需要构建$ ./gradlew :tools:admin:build构建产物为tools/admin/build/libs/openwhisk-admin-tools-1.0.0-SNAPSHOT-cli.jar并被安装为可执行脚本bin/wskadmin-next。构建说明详见 tools/admin/README-NEXT.md。2. 配置wskadmin 如何找到数据库2.1 Python 版whisk.properties 与环境变量wskadmin启动时wskadmin会通过 wskprop.py 沿仓库根目录向上查找whisk.properties并校验以下必需属性同时支持同名环境变量覆盖环境变量优先级更高见 getPropertyValue属性键作用DB_PROTOCOL数据库协议http/httpsDB_HOST数据库主机DB_PORT数据库端口DB_USERNAME/DB_PASSWORD数据库管理员凭据DB_WHISK_AUTHS主体subjects数据库名如whisk_local_subjectsDB_WHISK_ACTIONS资产whisks数据库名如whisk_local_whisksDB_WHISK_ACTIVATIONS激活activations数据库名如whisk_local_activationsWHISK_LOGS_DIR系统日志目录若属性缺失工具会逐条提示property xxx not found in environment or property file并以退出码 2 结束。HTTP 层由 wskutil.py 提供默认使用 Basic Auth 校验可通过环境变量DB_VERIFY_CERTfalse关闭 HTTPS 证书校验并支持https_proxy代理。2.2 Scala 版whisk.conf 与 -c 覆盖wskadmin-next默认读取部署时生成的$OPENWHISK_HOME/whisk.conf由 Ansibleproperties部署生成模板见 ansible/templates/whisk.conf.j2。如需连接自定义环境可编写覆盖配置文件并通过-c传入例如默认 CouchDB 单机场景的application-cli.confinclude classpath(application.conf) whisk { couchdb { protocol http host 172.17.0.1 port 5984 username whisk_admin password some_passw0rd provider CouchDB databases { WhiskAuth whisk_local_subjects WhiskEntity whisk_local_whisks WhiskActivation whisk_local_activations } } }随后执行$ ./wskadmin-next -c application-cli.conf user get guest从 Main.scala 可以看到-c指定的文件会以config.file系统属性注入未指定时默认回退到../whisk.conf。由于 Spring Boot 启动脚本会改变工作目录其fileConverter还利用OLDPWD环境变量做路径回退。wskadmin-next的 Scala 实现通过ArtifactStoreProviderSPI 创建数据存储见 UserCommand.createDataStore因此不绑定具体数据库实现这也解释了其 DB agnostic 的设计定位。3. 管理用户subjectscreate / delete / get / whois / list / block / unblockwskadmin user -hPython 版或wskadmin-next user -hScala 版会打印完整的用户管理帮助。核心命令映射如下Python 版分支逻辑见 userCmd子命令功能create创建用户并打印uuid:key授权密钥可指定命名空间、预置密钥、轮换密钥、强制更新delete删除整个用户或仅移除某命名空间访问权get获取用户默认命名空间或指定命名空间的密钥-a列出全部命名空间与密钥whois根据uuid:key反查 subject 与命名空间list列出拥有某命名空间访问权的所有身份及其密钥block/unblock封禁 / 解封一个或多个用户3.1 典型操作示例Python 版 wskadmin# 创建新用户默认命名空间即用户名 $ wskadmin user create userA prints key # 为用户添加指定命名空间 $ wskadmin user create userA -ns space1 prints new key specific to userA and space1 # 第二个用户加入同一命名空间 $ wskadmin user create userB -ns space1 prints new key specific to userB and space1 # 列出共享某命名空间的所有用户 $ wskadmin user list space1 -a key for userA userA key for userB userB # 移除用户对某命名空间的访问 $ wskadmin user delete userB -ns space1 Namespace deleted # 获取 userA 默认命名空间的密钥 $ wskadmin user get userA prints key specific to userA default namespace # 封禁 / 解封用户 $ wskadmin user block userA userA blocked successfully $ wskadmin user unblock userA userA unblocked successfully # 彻底删除用户 $ wskadmin user delete userB Subject deleted3.2 密钥格式与校验规则源码级细节从 wskadmin 与 UserCommand.scala 可确认以下硬性约束subject 名至少 5 个字符否则报Subject name must be at least 5 characters见 CommandMessages.scala授权密钥格式为uuid:key其中 uuid 必须是合法 UUID-u/--auth指定时校验key 至少64 个字符未指定时由工具随机生成Python 版使用uuid.uuid4()与 64 位随机字母数字blocked 用户不可再被修改对已封禁用户执行create会报The subject you want to edit is blocked同一命名空间重复签发密钥会报Namespace already exists此时可用-r/--revoke撤销旧密钥并生成新密钥保留原 uuid或用-f/--force强制更新为新uuid:key-g/--genonly可只生成uuid:key而不写入数据库适合预生成密钥再导入-s/--silent可抑制密钥回显。Python 版内部把 subject 文档直接存入 subjects 数据库文档_id即 subject 名namespaces数组保存{name, uuid, key}三元组见 createUserCmd封禁实现为在文档上置blocked: true/false字段blockUserCmd。Scala 版的行为完全对齐ExtendedAuth扩展了WhiskAuth以读写blocked布尔字段UserCommand.scalawhois与list则通过Identity.get/Identity.list查询。3.3 常用运维组合给租户开通账号wskadmin user create tenant1 -ns teamA然后wskadmin user list teamA -a核对密钥泄露处置wskadmin user create userA -ns space1 -r换 key 保留 uuid或直接wskadmin user block userA安全审计wskadmin user whois uuid:key反查来源wskadmin user get userA -a全量导出命名空间密钥。4. 按命名空间管理限额limitswskadmin limitsPython 版与wskadmin-next limitsScala 版用于为命名空间设置 action 与 trigger 的节流throttle及资源上限实现多租户差异化配额。4.1 基础用法# 查询某命名空间是否有自定义限额无则沿用系统默认 $ wskadmin limits get space1 No limits found, default system limits apply # 设置每分钟调用次数上限 $ wskadmin limits set space1 --invocationsPerMinute 1 Limits successfully set for space1 # 设置允许的运行时类型白名单 $ wskadmin limits set space1 --allowedKinds nodejs:6 python Limits successfully set for space1 # 关闭激活记录入库不写入 activation store $ wskadmin limits set space1 --storeActivations false Limits successfully set for space1 # 删除命名空间自定义限额恢复系统默认 $ wskadmin limits delete space1 Limits deleted重要限额是挂在命名空间上的与具体用户无关即使共享该命名空间的所有用户都被删除限额文档依然存在必须手动wskadmin limits delete清理。4.2 支持的限额参数全表Python 版 setLimitsCmd 与 Scala 版 LimitsCommand.scala 声明的参数完全一致Scala 版为保持向后兼容参数名显式保留驼峰命名参数含义类型--invocationsPerMinute每分钟允许的调用次数int≥0--firesPerMinute每分钟允许的触发器trigger触发次数int≥0--concurrentInvocations命名空间内允许的并发调用数int≥0--allowedKinds允许的运行时类型列表如nodejs:6 python字符串列表--storeActivations是否将激活记录存入 activation storetrue/false--minActionMemory/--maxActionMemoryaction 内存下限 / 上限int--minActionLogs/--maxActionLogs激活日志大小下限 / 上限int--minActionTimeout/--maxActionTimeoutaction 运行时长下限 / 上限int--minActionConcurrency/--maxActionConcurrencyaction 并发下限 / 上限int--maxParameterSize参数最大字节数字符串支持单位--maxPayloadSize载荷最大字节数字符串支持单位--truncationSize激活记录截断阈值字符串支持单位未显式设置的字段在写入时沿用文档中已有值Python 版逐字段合并见 setLimitsCmdlimits get只打印已显式设置的字段没有自定义限额时输出No limits found, default system limits apply。4.3 底层存储结构限额并非独立表而是以namespace/limits为文档 ID 存放在 subjects 数据库中Python 版docId namespace /limits写入/读取/删除该文档setLimitsCmdScala 版LimitsCommand.limitIdOf(name) s${name.name}/limitsLimitEntity继承WhiskAuth但toJson输出的是UserLimits序列化结果——源码注释明确说明没有写限额的 API因此借道 WhiskAuth 但把 auth JSON 替换为限额 JSONLimitsCommand.scala。因此用wskadmin db get subjects也能直接看到这些xxx/limits文档。5. 数据库巡检wskadmin db get本地调试时经常需要直接查看数据库内容。wskadmin db get -h会打印全部可用选项用法如下# 读取整个 whisks资产数据库默认只读主索引不含文档体 $ wskadmin db get whisks # 包含完整文档内容 $ wskadmin db get whisks --docs # 只查询指定视图设计文档/视图名 $ wskadmin db get whisks --view whisks.v2/actions三个逻辑库名到实际数据库名的映射见 getDbCmd命令参数实际数据库来自属性配置subjectsDB_WHISK_AUTHS如whisk_local_subjectswhisksDB_WHISK_ACTIONS如whisk_local_whisksactivationsDB_WHISK_ACTIVATIONS如whisk_local_activations也支持直接传任意数据库名。--view必须符合design/view格式例如查询 actions 视图时写whisks.v2/actions底层请求会追加?reducefalseinclude_docstrue|false并格式化输出 JSONgetDbCmd。仓库 ansible/files/ 目录下提供了这些视图的完整设计文档定义如whisks_design_document_for_entities_db_v2.1.0.json、auth_design_document_for_subjects_db_v2.0.0.json等可作为理解视图字段结构的参考。6. 系统日志检索wskadmin syslog get针对本地部署的日志排查wskadmin syslog get提供按组件、按事务、按正则三种检索方式# 查看当前可用的 controller 与 invoker 日志 $ wskadmin syslog get # 按正则过滤 $ wskadmin syslog get --grep ERROR # 按事务 ID 隔离单个请求的完整调用链 $ wskadmin syslog get --tid transaction-id # 只看指定组件controller 第 0 个实例 $ wskadmin syslog get controller0参数说明见 parseArgscomponents可变参数取值为controllerN、schedulerN、invokerNN 为实例编号默认是controller0 scheduler0 invoker0-t/--tid按事务 ID 过滤等价于对日志执行grep [#tid_tid]-g/--grep按任意正则表达式过滤不带过滤参数时直接cat对应组件的日志文件。底层实现getLogsCmd从WHISK_LOGS_DIR/component/component_logs.log读取各组件日志再按行内 ISO8601 时间戳排序合并输出便于跨组件还原一次调用的完整时间线。每个 OpenWhisk 请求都会携带形如#tid_xxx的 TransactionId参见 TransactionId因此--tid是定位某个请求到底经历了哪些组件、在哪里出错最直接的手段。7. 两个实现的异同小结与适用建议维度wskadminPythonwskadmin-nextScala入口tools/admin/wskadminbin/wskadmin-next由./gradlew :tools:admin:build生成数据库访问直连 CouchDB HTTP APIwskutil.pyArtifactStore SPI数据库无关见 Main.scala配置来源whisk.properties 环境变量whisk.conf或-c application.conf覆盖用户命令create/delete/get/whois/list/block/unblock同左create 增加-f/-r选项limits 参数16 项完整支持核心 5 项invocationsPerMinute、concurrentInvocations、firesPerMinute、allowedKinds、storeActivationsdb / syslog 巡检✅ 支持未提供README-NEXT 仅覆盖 user 与 limits使用建议本地 CouchDB 部署用 Python 版即可获得最全功能含db、syslog若部署使用非 CouchDB 存储如通过 SPI 接入其他 ArtifactStore则应选用数据库无关的wskadmin-next。8. 小结wskadmin把 OpenWhisk 部署中最常遇到的三类运维任务收敛到一个命令行入口用户与命名空间凭据管理create/delete/get/whois/list/block/unblock、命名空间级配额控制limits 的 16 项参数与namespace/limits文档、数据库与日志巡检db get 与 syslog get。结合 tools/admin/README.md、tools/admin/README-NEXT.md 及两套实现的源码你可以进一步深入其底层存储结构与校验规则将其直接应用于本地开发环境的排障或生产多租户部署的日常治理。赞分享后端云原生【免费下载链接】openwhiskApache OpenWhisk is an open source serverless cloud platform项目地址https://gitcode.com/gh_mirrors/ope/openwhisk点击查看免费下载相关推荐Apache OpenWhisk 运维管理实战指南使用 wskadmin 管理用户、命名空间限额与排查数据库/日志Apache OpenWhisk 运维管理实战指南使用 wskadmin 管理用户、命名空间限额与排查数据库/日志 wskadmin 是 Apache Ope云原生后端微服务Apache OpenWhisk 运维管理实战wskadmin-next 工具的用户与命名空间限额管理指南Apache OpenWhisk 运维管理实战wskadmin next 工具的用户与命名空间限额管理指南 Apache OpenWhisk 作为开源无服务器后端云原生Apache OpenWhisk wskadmin-next 管理工具实战指南从用户治理到命名空间限额Apache OpenWhisk wskadmin next 管理工具实战指南从用户治理到命名空间限额 本文以 tools/admin/README NEXT云原生后端微服务上一篇在 Tekton Pipeline 中使用 Azure KMSKey Vault托管签名密钥sigstore 集成与 Trusted Resources 实战下一篇Visual Syslog Server构建企业级日志管理平台的5大核心优势创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

Claude Tag 是什么:Anthropic 把 Claude 塞进 Slack 当同事用了

Claude Tag 是什么:Anthropic 把 Claude 塞进 Slack 当同事用了

Claude Tag 是什么:Anthropic 把 Claude 塞进 Slack 当同事用了 资料来源:Claude Tag 产品页、Anthropic 官方发布稿 Introducing Claude Tag(2026-06-23)、Agent Identity 安全模型 一句话说明白 Claude Tag 是 Anthropic 推出的…

2026/10/9 5:05:13 阅读更多 →
React Native鸿蒙适配实战:ArkTS组件桥接与工程落地

React Native鸿蒙适配实战:ArkTS组件桥接与工程落地

搞RN的工程,尤其是跨端玩得比较深的朋友,这两年多少都听过“鸿蒙适配”这个坎。React Native本身只认Android和iOS,鸿蒙不在默认支持列表里,但市场需求又摆在那。于是就有了这么一条路:在RN工程里,通过桥接…

2026/10/9 5:05:13 阅读更多 →
游戏引擎底层架构剖析:主循环、内存、文件与日志系统

游戏引擎底层架构剖析:主循环、内存、文件与日志系统

聊游戏引擎架构,我得先说一个可能有点得罪人的观察:很多人对引擎的认知,基本停留在“引擎就是那个能跑3D画面的东西”这个层面。渲染确实是最直观的部分,但如果你真去啃过商业引擎的源码,或者自己动手攒过一套小引擎&a…

2026/10/9 5:05:13 阅读更多 →

最新新闻

学Simulink——基于反电动势过零检测的直流无刷电机(BLDC)无感控制仿真

学Simulink——基于反电动势过零检测的直流无刷电机(BLDC)无感控制仿真

目录 手把手教你学Simulink——基于反电动势过零检测的直流无刷电机(BLDC)无感控制仿真 一、 引言:当“霍尔传感器”成为过去式——反电动势过零检测如何成就真正的“无感”BLDC? 二、 问题本质:反电动势过零的“物理机制”与“协同逻辑” 1. 核心物理机制 2. 协同逻辑…

2026/10/9 5:35:40 阅读更多 →
Agent执行错操作怎么办——从权限确认、沙箱隔离到幂等回滚的安全执行实战

Agent执行错操作怎么办——从权限确认、沙箱隔离到幂等回滚的安全执行实战

Agent 安全执行不是“多弹确认框”,而是把权限、隔离、验证与恢复做成一条系统闭环。 AI Agent | 智能体安全 | 权限控制 | Human-in-the-Loop | 沙箱隔离 | 最小权限 | 幂等性 | 回滚机制 | Guardrails | MCP安全 当 Agent 从“给建议”升级到“真正执行动作”,系统…

2026/10/9 5:35:40 阅读更多 →
多Agent成本失控实战——并发、上下文与Token预算如何系统治理 从“Agent越多越强”到“每一美元都能解释”

多Agent成本失控实战——并发、上下文与Token预算如何系统治理 从“Agent越多越强”到“每一美元都能解释”

多Agent成本失控实战——并发、上下文与Token预算如何系统治理从“Agent越多越强”到“每一美元都能解释”#多Agent #Agent #LLM #Token #成本优化 #上下文工程 #Prompt Caching #并发控制 #模型路由 #可观测性多Agent系统真正昂贵的地方,往往不是…

2026/10/9 5:35:40 阅读更多 →
context-mode:基于上下文感知的终端环境自动切换

context-mode:基于上下文感知的终端环境自动切换

1. 为什么需要 context-mode:从手动切换走向规则切换1.1 配置切换的痛点,可能只有折腾过的人才懂每天要在三四种工作场景里来回切换:白天在项目仓库里写业务代码,下午翻开几个开源项目的源码做研究,晚上可能还要写自己…

2026/10/9 5:35:40 阅读更多 →
Meson 0.53.0 新特性深度解析:fs 模块、动态链接器选择与构建配置摘要实战

Meson 0.53.0 新特性深度解析:fs 模块、动态链接器选择与构建配置摘要实战

构建工具 【免费下载链接】meson The Meson Build System 项目地址: https://gitcode.com/gh_mirrors/me/meson 点击查看 免费下载 本篇文章以 Meson 0.53.0 官方发布说明(Release-notes-for-0.53.0.md)为主体,逐项解析该版本引入…

2026/10/9 5:35:40 阅读更多 →
HARA与风险评估方法

HARA与风险评估方法

EPS electronic power steering, 电子助力转向系统 发现了问题,下面就要制定措施 内容来源 : https://www.bilibili.com/video/BV1GdeQ6xEHi?spm_id_from333.788.videopod.sections&vd_source473185c2a7a9b79ef8fcea7dce5ca501

2026/10/9 5:34:40 阅读更多 →

日新闻

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API这个话题,隔三差五就会在群里被翻出来讨论一次。上周还有个同事线上处理一个订单超时问题,排查到最后发现是ZonedDateTime序列化后时区丢了,用户在下单当天晚上看到的时间整整差了8个小时。这类问题几乎每个做Java开发的人都遇到过…

2026/10/9 0:00:49 阅读更多 →
EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

前几个月我手头有好几台机器需要互相访问:办公室台式机、家里 NAS、还有一台云主机。如果只是偶尔传个文件倒还好,问题是工作场景经常要在几处环境之间来回切换,每次都先登录跳板机再层层代理,实在折腾。我先后试过端口映射、自建…

2026/10/9 0:00:49 阅读更多 →
AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent 这个词在过去一年里被反复提及,但真正动手搭过一套能跑起来的 Agent 系统的人都知道,从"知道它是什么"到"让它稳定干活"之间隔着一整套工程决策。我前后参与过几个 Agent 项目的落地,从最初用现成框架拼装&…

2026/10/9 0:01:50 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

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

2026/10/8 15:26:32 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

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

2026/10/8 15:26:40 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

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

2026/10/8 10:10:36 阅读更多 →

月新闻

我发现了一个新思路:用 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/8 21:13:17 阅读更多 →
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/8 15:26: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/7 13:34:55 阅读更多 →