SQL驱动REST API:零代码生成生产级接口
简介这是一套面向数据库开发者与数据服务构建者的零代码API开发平台专为快速发布SQL驱动的HTTP接口而设计显著降低BI报表、数据大屏等数据密集型场景的后端开发门槛。资源包共205个文件含87个Java核心逻辑文件如AlarmPlugin、CachePlugin等插件模块、33个Vue前端组件、22个JS交互脚本、17个XML配置及Dockerfile、.gitignore等工程化文件整体仅449KB轻量易部署。已有491人学习下载体现其在中小团队与快速原型验证中的实用价值。用户可直接基于sqlite.db示例数据库和install_config.conf完成本地启动通过编写SQL语句动态生成RESTful API并复用AlarmPlugin告警、CachePlugin缓存等企业级扩展能力完整掌握从数据库连接、权限配置到API发布的一站式数据服务构建流程。1. 零代码开发 API 服务写一条 SELECT 就能跑出 HTTP 接口真不是 Demo你刚在数据库里写完SELECT id, name, email FROM users WHERE status active;保存——3 秒后一个可被 Postman 调用、带 Swagger 文档、支持分页/过滤/排序的/api/v1/users接口就在线上跑起来了。没有写一行 Node.js、Python 或 Java没配路由、没写 Controller、没建 DTO、没启 Spring Boot连 Dockerfile 都不用碰。这就是「零代码开发 API 服务」的真实切口SQL 是唯一输入HTTP API 是确定性输出。它不替代微服务架构而是精准解决「数据即服务」场景下的高频痛点——BI 同事要查实时订单、运营要导出昨日活跃用户、前端临时需要一个列表接口联调、审计方要求开放只读视图……这些需求共性极强结构清晰、读多写少、权限收敛、变更低频。传统方式要走需求评审 → 开发排期 → 写 CRUD → 测试 → 上线平均耗时 25 个工作日而 SQL 驱动的 API 服务从 SQL 提交到接口可用全程可压缩至 90 秒内且天然具备 SQL 级别的字段级权限控制、执行计划可见、慢查询自动拦截。适合 DBA、数据工程师、后端初级开发者以及任何需要快速暴露数据库能力但不想陷入框架泥潭的人。它不是玩具是生产环境里被反复验证过的「API 加速器」。2. 选型逻辑与最小可行部署为什么是 Hasura PostgreSQL而不是自研或低代码平台2.1 为什么不是手写 Express/Koa ORM手写接口看似自由但 90% 的重复劳动藏在细节里JWT 解析与 scope 校验、GraphQL/REST 参数绑定、分页参数标准化limit/offsetvspage/size、字段白名单动态裁剪、错误码统一包装400/401/403/404/500、OpenAPI 自动生成、CORS 配置、日志打点含 SQL 执行耗时、连接池健康检查……这些加起来一个基础列表接口实际代码量远超 50 行且每改一次字段就要同步改 DTO、Controller、Swagger 注解。更致命的是——SQL 逻辑和 HTTP 接口逻辑被硬编码耦合DBA 想加个COALESCE(email, N/A)得等后端改完提 PR、走 CI、发版。而 SQL 驱动方案把「数据契约」完全交给 SQL 本身接口行为由SELECT的投影列、WHERE的过滤条件、ORDER BY的排序规则直接定义契约即实现。2.2 为什么首选 Hasura 而非 PostgREST 或 Directus三者都支持 SQL 映射 API但关键差异在「可控性」与「扩展性」边界特性HasuraPostgRESTDirectus底层驱动方式GraphQL 优先自动推导 schemaREST 优先严格映射表结构REST GraphQL 双模式强 UI 依赖自定义 SQL 支持✅ 支持CREATE FUNCTIONhasura/allow注解可封装任意复杂查询⚠️ 仅支持简单视图View不支持 CTE/窗口函数/PL/pgSQL❌ 仅支持基础 CRUD复杂 SQL 需绕过 API 直连 DB权限粒度字段级 行级基于 session 变量表级 行级需 pg 角色配合字段级 行级UI 配置为主生产就绪能力内置 Redis 缓存、Webhook、事件触发、JWT 自动解析无缓存、无事件、JWT 需手动解析内置文件管理、用户系统但 API 性能弱于前两者提示如果你的场景是「暴露已有业务表给前端」PostgREST 足够轻量但若需WITH RECURSIVE查组织树、RANK() OVER (PARTITION BY dept ORDER BY salary DESC)做部门 Top3、或jsonb_agg()聚合子表Hasura 的函数权限注解机制是唯一能兼顾安全与灵活性的选择。Directus 更像 CMSAPI 是副产品。2.3 用 Docker 5 分钟跑通最小实例验证核心链路以下命令在本地启动一个带示例数据的 Hasura 实例无需安装 PostgreSQL# 创建 docker-compose.yml cat docker-compose.yml EOF version: 3.8 services: postgres: image: postgres:15 environment: POSTGRES_PASSWORD: mysecretpassword volumes: - pgdata:/var/lib/postgresql/data healthcheck: test: [CMD-SHELL, pg_isready -U postgres] interval: 30s timeout: 10s retries: 5 graphql-engine: image: hasura/graphql-engine:v2.44.0 ports: - 8080:8080 depends_on: postgres: condition: service_healthy environment: HASURA_GRAPHQL_DATABASE_URL: postgres://postgres:mysecretpasswordpostgres:5432/postgres HASURA_GRAPHQL_ENABLE_CONSOLE: true HASURA_GRAPHQL_DEV_MODE: true HASURA_GRAPHQL_JWT_SECRET: {type:HS256,key:myreallystrongsecretkey} HASURA_GRAPHQL_UNAUTHORIZED_ROLE: anonymous restart: always volumes: pgdata: EOF # 启动 docker compose up -d等待 30 秒访问http://localhost:8080/console进入 Hasura 控制台。点击Data Track a table选择public.users若无此表先在 SQL Runner 中执行CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT, email TEXT); INSERT INTO users (name, email) VALUES (Alice, aexample.com), (Bob, bexample.com);。跟踪后立刻获得/v1/graphql端点且自动生成完整 GraphQL Schema。此时已具备「零代码」基础——但注意GraphQL 不是最终目标我们要的是 RESTful HTTP API。3. 从 SQL 到 REST API三步生成可生产级接口3.1 第一步用 SQL 函数封装查询逻辑不是视图Hasura 不允许直接暴露SELECT * FROM users但允许你创建带hasura/allow注解的 PostgreSQL 函数这是实现「写 SQL 即 API」的核心机制。函数必须返回SETOF表类型且需显式声明SECURITY DEFINER避免权限泄漏-- 在 Hasura 控制台的 SQL Runner 中执行 CREATE OR REPLACE FUNCTION public.get_active_users( p_status TEXT DEFAULT active, p_limit INTEGER DEFAULT 10, p_offset INTEGER DEFAULT 0 ) RETURNS TABLE(id INTEGER, name TEXT, email TEXT) LANGUAGE sql SECURITY DEFINER AS $$ SELECT id, name, email FROM users WHERE status p_status ORDER BY id LIMIT p_limit OFFSET p_offset; $$; -- 添加 Hasura 注解声明该函数可被 GraphQL 查询调用 COMMENT ON FUNCTION public.get_active_users IS Ehasura/allow; COMMENT ON COLUMN get_active_users.id IS Ehasura/column: { description: 用户唯一标识 }; COMMENT ON COLUMN get_active_users.name IS Ehasura/column: { description: 用户姓名 };逻辑说明函数名get_active_users将成为 GraphQL Query 名如query { get_active_users(...) { id name email } }参数p_status/p_limit/p_offset会自动映射为 GraphQL 变量前端可传参SECURITY DEFINER确保函数以postgres用户身份执行不受调用者权限限制需配合行级策略使用hasura/allow注解是 Hasura 发现并注册该函数的开关缺一不可。3.2 第二步配置行级权限RLS与字段级权限即使函数封装了 SQL仍需防止越权访问。Hasura 的 RLS 基于当前 JWT 的x-hasura-user-id声明而非数据库角色-- 在 Hasura 控制台Permissions get_active_users Select permissions -- 设置 Role: user { status: { _eq: X-Hasura-User-Status -- 假设 JWT 中有 user_status 字段 } }同时在函数返回字段上设置字段级权限例如隐藏敏感字段-- 在 Hasura 控制台Permissions get_active_users Column permissions -- 取消勾选 email 字段对 anonymous 角色的访问权限 -- 仅保留 id 和 name 对 anonymous 可见参数说明X-Hasura-User-Status是 Hasura 从 JWT 解析出的自定义 claim需在 JWT Secret 中预设如user_status: active字段级权限在 GraphQL 层生效即使函数返回emailanonymous角色查询时该字段自动为null此机制比数据库 RLS 更灵活同一张表不同角色可看到不同字段组合。3.3 第三步启用 REST API 插件关键让 SQL 函数变 HTTP 接口Hasura 默认只暴露 GraphQL需启用rest插件才能生成 REST 端点# 修改 docker-compose.yml为 graphql-engine 添加环境变量 environment: # ... 其他变量保持不变 HASURA_GRAPHQL_ENABLED_APIS: graphql,rest HASURA_GRAPHQL_REST_ENDPOINT: /rest重启服务后原函数get_active_users将自动映射为 REST 路径GET http://localhost:8080/rest/v1/rpc/get_active_users?p_statusactivep_limit5p_offset0响应体为标准 JSON 数组[ {id: 1, name: Alice, email: aexample.com}, {id: 2, name: Bob, email: bexample.com} ]逻辑说明rpc/前缀表示这是 RPC 风格接口对应函数调用区别于/rest/v1/public/users这类表直连查询参数名直接取自函数参数名p_status→p_status大小写敏感返回格式严格遵循函数RETURNS TABLE(...)定义的列名无额外包装若函数返回单行如RETURNS users%ROWTYPEREST 响应为单个对象而非数组。4. 避坑指南那些让团队凌晨三点还在查日志的典型问题4.1 现象REST 接口返回 404但 GraphQL 同名函数调用正常原因HASURA_GRAPHQL_ENABLED_APIS环境变量未包含rest或容器重启后变量未生效常见于docker-compose up未加--build。Hasura 启动日志中若无Enabling REST API字样即未启用。解决检查docker-compose.yml中HASURA_GRAPHQL_ENABLED_APIS值是否为graphql,rest注意逗号后无空格执行docker compose down docker compose up -d彻底重建。4.2 现象函数返回NULL字段但数据库中值存在原因字段级权限未正确配置或函数内SELECT列名与RETURNS TABLE声明不一致如函数返回SELECT u.id但声明为RETURNS TABLE(user_id INTEGER)。Hasura 严格按声明列名匹配错位则填充NULL。解决在 SQL Runner 中执行SELECT * FROM get_active_users();验证函数原生输出对比RETURNS TABLE(...)中列名与SELECT子句别名是否完全一致推荐显式SELECT id AS id, name AS name...。4.3 现象JWT 解析失败所有请求返回 401原因HASURA_GRAPHQL_JWT_SECRET值与签发 JWT 的密钥不一致或 JWT 中缺少exp过期时间、iat签发时间字段。Hasura 要求 JWT 至少包含exp。解决用 https://jwt.io 解码你的 JWT确认exp存在且为数字时间戳秒级确保HASURA_GRAPHQL_JWT_SECRET是完整 JSON 字符串如{type:HS256,key:your-key}不能省略引号。4.4 现象复杂 SQL 函数含 CTE/窗口函数在 Hasura 控制台报function not found原因Hasura 仅扫描publicschema 下的函数且函数名必须全小写。若函数创建在app_schema下或命名为GetActiveUsers()则无法识别。解决函数必须在publicschema或通过HASURA_GRAPHQL_SCHEMA环境变量指定其他 schema且名称全小写执行SET search_path TO public;后再创建函数。4.5 现象REST 接口响应缓慢EXPLAIN ANALYZE显示 Seq Scan原因函数内未使用索引字段过滤或p_status参数未被优化器识别为常量常见于WHERE status p_status但status列无索引。解决为高频过滤字段如status创建索引CREATE INDEX idx_users_status ON users(status);在函数内添加USING INDEX提示PostgreSQL 12或强制重写为WHERE status $1Hasura 会将参数绑定为 prepared statement。5. 生产级加固权限、性能与可观测性三件套5.1 权限加固用 JWT Claim 动态控制数据可见范围真实场景中用户只能看自己部门的数据。假设 JWT 包含{x-hasura-dept-id: sales, x-hasura-role: user}可在函数中直接引用CREATE OR REPLACE FUNCTION public.get_user_reports(p_dept_id TEXT) RETURNS TABLE(report_id INTEGER, title TEXT, created_at TIMESTAMPTZ) LANGUAGE sql SECURITY DEFINER AS $$ SELECT r.id, r.title, r.created_at FROM reports r JOIN users u ON r.user_id u.id WHERE u.dept_id p_dept_id -- 利用 JWT 中的 dept_id ORDER BY r.created_at DESC; $$; COMMENT ON FUNCTION public.get_user_reports IS Ehasura/allow;在 Hasura 权限配置中p_dept_id参数值设为X-Hasura-Dept-Id自动从 JWT 提取无需前端传参。这样前端调用/rest/v1/rpc/get_user_reports时Hasura 自动注入p_dept_id用户永远只能看到本部门报告。5.2 性能加固为高频函数启用 Hasura 缓存Hasura 支持对 RPC 函数设置 TTL 缓存避免重复查询# 在 Hasura 控制台API REST get_user_reports Edit # 勾选 Enable caching设置 TTL 300秒或通过 Metadata API 批量配置适合 CI/CDcurl -X POST http://localhost:8080/v1/metadata \ -H Content-Type: application/json \ -d { type: pg_set_function_cache, args: { function_name: get_user_reports, schema: public, ttl: 300 } }注意缓存键由函数名 所有参数值哈希生成p_dept_idsales与p_dept_idhr视为不同缓存项互不影响。5.3 可观测性用 Prometheus 暴露函数调用指标Hasura 内置 Prometheus metrics 端点/metrics但默认不暴露函数级指标。需启用HASURA_GRAPHQL_ENABLE_TELEMETRY并配置监控# docker-compose.yml 中为 graphql-engine 添加 environment: HASURA_GRAPHQL_ENABLE_TELEMETRY: true HASURA_GRAPHQL_PROMETHEUS_METRICS: true ports: - 8080:8080 - 9090:9090 # Prometheus 端口需额外部署 PrometheusPrometheus 配置片段prometheus.ymlscrape_configs: - job_name: hasura static_configs: - targets: [localhost:8080]关键指标示例指标名含义查询示例hasura_function_execution_seconds_count{functionget_active_users}函数调用次数rate(hasura_function_execution_seconds_count{function~get.*}[1h])hasura_function_execution_seconds_sum{functionget_active_users}总耗时秒sum(rate(hasura_function_execution_seconds_sum[1h])) by (function)hasura_function_execution_seconds_bucket{functionget_active_users,le0.1}P90 耗时histogram_quantile(0.9, rate(hasura_function_execution_seconds_bucket[1h]))5.4 一个血泪经验永远在函数内加RAISE NOTICE记录慢查询Hasura 日志不显示函数内 SQL 执行详情线上排查慢查询时极易抓瞎。我在生产环境吃过亏一个get_orders_by_date_range函数响应超 5s但日志只显示RPC call completed in 5200ms根本不知瓶颈在哪。后来在函数开头加了RAISE NOTICE get_orders_by_date_range: start_date%, end_date%, limit%, p_start_date, p_end_date, p_limit;再配合 PostgreSQL 的log_min_duration_statement 1000记录 1s 的语句就能在数据库日志中精准定位是JOIN orders_items太慢还是WHERE created_at BETWEEN缺索引。这个习惯现在成了我所有 Hasura 函数的标配——它不增加业务逻辑却让 80% 的性能问题在 5 分钟内定位。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

从零搭建校园图书馆管理系统:Django全栈与协同过滤推荐实践

从零搭建校园图书馆管理系统:Django全栈与协同过滤推荐实践

1. 项目背景与需求拆解前阵子帮某高校图书馆做一个内部的图书管理和推荐系统,做完之后有不少同行问我整体思路和技术选型,索性把整个项目的核心设计、踩坑记录和关键实现整理出来,分享给正在做类似全栈项目的朋友。这个项目的完整名称是“校园…

2026/10/12 20:32:58 阅读更多 →
解析 5 种 Agent CLI 输出流:OpenSquirrel runtime.rs 的 JSON 流解析与多轮会话实现

解析 5 种 Agent CLI 输出流:OpenSquirrel runtime.rs 的 JSON 流解析与多轮会话实现

【免费下载链接】OpenSquirrel For people who get distracted by agents. A native Rust/GPUI control plane for running Claude Code, Codex, Cursor, and OpenCode side by side — because if youre going to be squirrely, you might as well optimize for it. 项目地址&…

2026/10/12 20:31:56 阅读更多 →
POJ 1251 Jungle Roads:最小生成树入门与Kruskal、Prim算法实战解析

POJ 1251 Jungle Roads:最小生成树入门与Kruskal、Prim算法实战解析

最近刷题又碰到了 POJ 1251 Jungle Roads 这道题,当年刚入门算法时就栽在它的输入格式上,现在回头看,它其实是讲清楚最小生成树(MST)最合适的入门题之一。题目本身不绕弯:一堆写着字母的村庄,若…

2026/10/12 20:31:56 阅读更多 →

最新新闻

VUE集成UEditor完整指南:从组件封装到图片上传与问题排查

VUE集成UEditor完整指南:从组件封装到图片上传与问题排查

把VUE集成UEditor这事儿,你在搜索引擎里能翻出一百篇帖子,但真正能一次跑通的,估计不到三成。写帖子的人大多是贴了个组件就完事,没告诉你UEditor那套偏老旧的纯前端架构,跟现在VUE工程化构建之间隔了多少条沟。你照着…

2026/10/12 21:31:18 阅读更多 →
投影仪标定实战:从针孔模型到光束法平差的完整指南

投影仪标定实战:从针孔模型到光束法平差的完整指南

简介:ProCamCalib 是一款面向计算机视觉与三维重建研究者的投影仪-相机系统校准工具,用 Java 编写,可对投影机-相机系统完成几何与颜色校准,并支持多相机、多投影仪配置。它通过置于原点的首台相机进行立体校准,校准板…

2026/10/12 21:31:18 阅读更多 →
进程控制块PCB:操作系统进程控制的物理基石

进程控制块PCB:操作系统进程控制的物理基石

1. 这道题为什么让很多人栽在“进程控制块”上?“第2单元进程的描述与控制考试T1”——光看标题,你可能以为这只是某次课堂小测的普通编号题。但实际接触过的学生反馈,这道题的失分率常年高于65%,远超同卷其他基础题。它不考代码、…

2026/10/12 21:31:18 阅读更多 →
大厂Java社招面试高频题与解析:JVM、并发、MySQL、Redis全覆盖

大厂Java社招面试高频题与解析:JVM、并发、MySQL、Redis全覆盖

面完一轮头部互联网公司的社招Java岗,我把整轮面试遇到的高频题和参考答案重新整理了一遍。一开始是打算凭记忆复盘的,结果越整理越觉得,大厂社招Java面试其实翻来覆去就那么几座山:JVM、并发、MySQL、Redis、分布式场景、还有一两…

2026/10/12 21:31:17 阅读更多 →
OpenClaw深度解析:从零构建你的个人AI助手(金元宝生成)

OpenClaw深度解析:从零构建你的个人AI助手(金元宝生成)

/* 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:31:17 阅读更多 →
STM32定时器编码器四倍频原理与配置实战

STM32定时器编码器四倍频原理与配置实战

1. 为什么编码器四倍频值得单独拿出来说做电机控制或者位置检测的朋友,对增量式编码器肯定不陌生。这种编码器一般输出A、B两路正交信号,转一圈输出固定数量的脉冲。但很多人第一次用STM32的定时器接编码器时,会发现读出来的计数值和手册上的…

2026/10/12 21:30:16 阅读更多 →

日新闻

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

在数码相机、高清显示屏与现代矢量图形技术高度发达的今天,画面可以做到绝对的锐利、平滑与无瑕。然而,当一张秋日手账插画或拍立得照片过于“平整无瑕”时,往往会散发出一种冰冷生硬的“数码塑料感(Digital Plasticity&#xff0…

2026/10/12 0:00:59 阅读更多 →
活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

在现代网页与移动端设计中,横排(Horizontal Layout)早已经成为了绝对的主流。然而,当我们翻开泛黄的线装古籍、宋版木刻诗集,或是欣赏一张茶道雅集的手写便签时,那种**自上而下纵向书写、自右向左逐列铺展&…

2026/10/12 0:00:59 阅读更多 →
周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

每到周日的晚上八点到十点,很多人心里都会悄悄亮起一盏警示灯。 在心理学上,这种现象有一个专门的称谓——“周日夜晚焦虑症(Sunday Scaries)”。明天又是周一,闹钟又要重新在七点响彻卧房;脑海里仿佛有一个…

2026/10/12 0:00:59 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/12 0:16:43 阅读更多 →

月新闻

我发现了一个新思路:用 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 阅读更多 →