后端【免费下载链接】mikro-ormTypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, MariaDB, MS SQL Server, PostgreSQL and SQLite/libSQL databases.项目地址https://gitcode.com/gh_mirrors/mi/mikro-orm点击查看免费下载MikroORM 作为一套以 Data Mapper、Unit of Work 与 Identity Map 为核心模式的 TypeScript ORM目前官方支持 MongoDB、MySQL、MariaDB、MS SQL Server、PostgreSQL 以及 SQLite/libSQL 等数据库当需要接入这些数据库之外的新数据源时官方提供了一条标准的扩展路径——实现自己的自定义驱动Custom Driver。本文以 version-5.9/custom-driver.md 为骨架结合当前仓库中packages/core与packages/sql的源码实现系统讲解驱动架构的四大组成类Platform、SchemaHelper、Connection、Driver各自承担的职责、必须实现的方法契约以及如何在MikroORM.init()中注册启用。读完本文你将能够独立为任意 SQL 或非 SQL 数据源编写一个可运行的 MikroORM 驱动。驱动架构总览四大组件各司其职官方文档明确指出要接入一个当前未受支持的数据库需要设计 4 个类它们分别承担描述能力生成建表 SQL通信与持久化编排四种职责组件职责核心基类mikro-orm/corePlatform描述数据库支持的特性与方言差异事务、RETURNING、命名策略、标识符引用等PlatformSchemaHelper描述如何构建 Schema建表/删表语句、自增语句、类型映射等SchemaHelperConnection负责与数据库建立连接、执行查询、管理事务ConnectionDriver使用 Connection 将实体变更持久化到数据库实现 CRUD 原生方法DatabaseDriver / IDatabaseDriver从当前仓库源码看mikro-orm/core为这四类提供了基础抽象而mikro-orm/sql包则在它们之上派生了一组 SQL 专用的基类AbstractSqlPlatform、AbstractSqlConnection、AbstractSqlDriver见 packages/sql/src 目录下的同名文件SQL 驱动的绝大部分工作查询构建、joined 加载策略、事务、savepoint都已被这些基类接管。因此实际开发中有两条路线SQL 数据库继承mikro-orm/sql的抽象类只需补充数据库特有的 dialect 与方言细节非 SQL 数据库文档库、KV 存储等直接继承mikro-orm/core的Platform、Connection、DatabaseDriver自行实现全部 CRUD。下文先按官方文档的顺序逐一剖析这四大组件再给出两种路线的完整落地示例。Platform描述数据库能力与方言差异Platform是整个驱动体系的能力清单它向 ORM 核心宣告当前数据库支持哪些特性、使用哪种 SQL 方言。官方文档给出的自定义平台骨架如下方法带;表示必须声明这些能力的实现import { Platform } from mikro-orm/core; export class MyCustomPlatform extends Platform { // 这里可以覆写默认设置 usesPivotTable(): boolean; supportsTransactions(): boolean; supportsSavePoints(): boolean; getNamingStrategy(): { new (): NamingStrategy; }; getIdentifierQuoteCharacter(): string; getParameterPlaceholder(index?: number): string; usesReturningStatement(): boolean; normalizePrimaryKeyT number | string(data: IPrimaryKey): T; denormalizePrimaryKey(data: IPrimaryKey): IPrimaryKey; getSerializedPrimaryKeyField(field: string): string; }需要说明的是在文档对应的 5.9 时代这些能力大多需要逐一声明而从当前仓库源码packages/core/src/platforms/Platform.ts看Platform基类已经为这些能力提供了合理的默认实现自定义平台只需覆写与目标数据库存在差异的部分。基类中的代表性默认实现包括usesPivotTable()是否使用 pivot 表表达 M:N 关系SQL 驱动为trueMongoDB 等文档驱动为falsesupportsTransactions()默认受配置项disableTransactions控制见 Platform.ts 中supportsTransactions的默认返回!this.config.get(disableTransactions)getNamingStrategy()默认返回UnderscoreNamingStrategyusesReturningStatement()是否支持INSERT ... RETURNINGPostgreSQL 为trueMySQL 等为falsenormalizePrimaryKey()/denormalizePrimaryKey()主键在原生驱动包装形式与标量值之间的互转如 MongoDB 的ObjectId与字符串之间的转换usesImplicitTransactions()、usesOutputStatement()、usesBatchInserts()、usesBatchUpdates()、supportsNativeEnums()、supportsSchemas()、getDefaultSchemaName()等一批特性开关也全部在基类中带有默认值可按需覆写。除此之外Platform还承载了大量 SQL 方言生成逻辑例如getCurrentTimestampSQL()、getDateTimeTypeDeclarationSQL()、getDefaultVarcharLength()默认 255、quoteIdentifier()默认使用反引号引用标识符、quoteValue()、getJsonDeclarationSQL()、getArrayDeclarationSQL()等。这意味着自定义平台不仅要回答支持什么还要回答SQL 怎么写。值得注意的是Platform的setConfig()方法在初始化时会注入Configuration并解析forceUtcTimezone、timezone等配置因此自定义平台内部可以直接通过this.config.get(...)读取用户配置见 Platform.ts。SchemaHelper驱动 Schema 生成与迁移Platform的一部分职责由SchemaHelper承担它提供如何构建 Schema的信息是 Schema 生成器SchemaGenerator与迁移工具生成 DDL 的依据。官方文档给出的自定义 SchemaHelper 骨架import { SchemaHelper } from mikro-orm/core; export class MyCustomSchemaHelper extends SchemaHelper { // 这里可以覆写默认设置 getIdentifierQuoteCharacter(): string; getSchemaBeginning(): string; getSchemaEnd(): string; getSchemaTableEnd(): string; getAutoIncrementStatement(meta: EntityMetadata): string; getPrimaryKeySubtype(meta: EntityMetadata): string; getTypeDefinition(prop: EntityProperty, types?: Recordstring, string, lengths?: Recordstring, number): string; getUnsignedSuffix(prop: EntityProperty): string; supportsSchemaConstraints(): boolean; supportsSchemaMultiAlter(): boolean; supportsSequences(): boolean; quoteIdentifier(field: string): string; dropTable(meta: EntityMetadata): string; indexForeignKeys(): boolean; }从当前仓库源码看SchemaHelper的实现在 packages/sql/src/schema/SchemaHelper.ts 中它是一个面向 SQL 平台的抽象类构造函数接收AbstractSqlPlatform实例见constructor(protected readonly platform: AbstractSqlPlatform)。它的核心方法包括getSchemaBeginning()/getSchemaEnd()在迁移脚本首尾插入的 SQL例如禁用/恢复外键检查由disableForeignKeysSQL()与enableForeignKeysSQL()支撑dropTableIfExists()生成drop table if exists ...语句并根据platform.usesCascadeStatement()决定是否追加cascade见 SchemaHelper.tsgetPrimaryKeys()结合索引定义推断主键列inferLengthFromColumnType()从列类型字符串推断长度supportsSchemaConstraints()、supportsSequences()、supportsMigrationSchema()等能力开关。实际开发中SQL 驱动通常复用基类SchemaHelper仅当目标数据库的 DDL 方言差异明显时才创建子类并覆写对应方法。仓库中各官方驱动的PostgreSqlSchemaHelper、MySqlSchemaHelper等即是这一模式的参考实现对于完全非 SQL 的数据源Platform.getSchemaHelper()默认返回undefined见 Platform.ts 中getSchemaHelper的实现表示不支持 Schema 生成。Connection负责建连、查询与事务第三个组件是连接包装器负责真正与数据库对话。官方文档给出的最小实现骨架import { Connection } from mikro-orm/core; export class MyCustomConnection extends Connection { // 实现抽象方法 connect(): Promisevoid; isConnected(): Promiseboolean; close(force?: boolean): Promisevoid; getDefaultClientUrl(): string; execute(query: string, params?: any[], method?: all | get | run): PromiseQueryResult | any | any[]; }对照当前仓库源码packages/core/src/connections/Connection.tsConnection抽象类实际要求实现的核心抽象方法为connect()、isConnected()、checkConnection()返回{ ok: true }或{ ok: false; reason: string; error?: Error }与execute()。同时基类已经内置了大量通用能力构造函数从Configuration自动提取dbName、clientUrl、host、port、user、password、multipleStatements、pool、schema、driverOptions等连接参数见 Connection.tsgetConnectionOptions()负责解析clientUrl支持schema查询参数并回填 host/port/user/password/dbNametransactional()/begin()/commit()/rollback()提供事务骨架基类默认抛出不支持错误需要自定义驱动覆写executeQuery()/logQuery()内置了查询日志与slowQueryThreshold慢查询日志能力executeDump()用于执行整段 SQL dumpQueryResult类型统一了原生查询的返回形状{ affectedRows, insertId, row?, rows?, insertedIds? }见 Connection.ts。execute()的method参数约定为all返回多行数组、get返回单行对象、run执行写操作并返回QueryResult自定义驱动需按此契约实现。Driver把实体变更编排为数据库操作最后一个组件是Driver它负责使用 Connection 把变更持久化到数据库是 ORM 核心与 Connection 之间的编排层。官方文档明确给出两条实现路径如果你在构建 SQL 驱动继承AbstractSqlDriver会很方便否则继承DatabaseDriver抽象类。想要完全掌控一切可以直接实现IDatabaseDriver接口。官方文档给出的DatabaseDriver子类骨架import { DatabaseDriver } from mikro-orm/core; export class MyCustomDriver extends DatabaseDriver { // 初始化 connection 和 platform protected readonly connection new MyCustomConnection(this.config); protected readonly platform new MyCustomPlatform; // 并实现抽象方法 findT extends AnyEntity(entityName: string, where: FilterQueryT, populate?: string[], orderBy?: Recordstring, QueryOrder, limit?: number, offset?: number): PromiseT[]; findOneT extends AnyEntity(entityName: string, where: FilterQueryT | string, populate: string[]): PromiseT | null; nativeInsertT extends AnyEntityTypeT(entityName: string, data: EntityDataT): PromiseQueryResult; nativeUpdateT extends AnyEntity(entityName: string, where: FilterQueryT | IPrimaryKey, data: EntityDataT): PromiseQueryResult; nativeDeleteT extends AnyEntity(entityName: string, where: FilterQueryT | IPrimaryKey): PromiseQueryResult; countT extends AnyEntity(entityName: string, where: FilterQueryT): Promisenumber; }对照当前仓库源码DatabaseDriver 的抽象方法在文档基础上略有演进增加了nativeInsertMany()批量插入、stream()等同时IDatabaseDriver接口packages/core/src/drivers/IDatabaseDriver.ts定义了完整的驱动契约包括createEntityManager()、connect()/close()/reconnect()、getConnection(type?: ConnectionType)支持读写分离的 read replica、find/findOne/findVirtual/stream、nativeInsert/nativeInsertMany/nativeUpdate/nativeUpdateMany/nativeDelete/nativeClone/count等。DatabaseDriver基类还通过EntityManagerType符号与EntityManager建立关联并默认实现createEntityManager()见 DatabaseDriver.ts。非 SQL 驱动的官方参考实现是 MongoDB 驱动MongoDriver extends DatabaseDriverMongoConnection它内部组合MongoConnection与MongoPlatform并在构造函数中通过super(config, [mongodb])声明原生依赖包名见 packages/mongodb/src/MongoDriver.ts。完整实战为 SQL 数据库编写自定义驱动虽然 5.9 文档主要演示了继承 core 基类的路线但当前仓库源码显示mikro-orm/sql包为 SQL 驱动提供了更省力的继承体系绝大多数新驱动都走这条路。SQL 层基于 Kysely 查询构建器因此自定义连接的核心工作是提供一个 Kysely dialect。第一步实现 Connection提供 Kysely dialect继承AbstractSqlConnection实现抽象的createKyselyDialect()方法见 packages/sql/src/AbstractSqlConnection.ts 中abstract createKyselyDialect(overrides: Dictionary): MaybePromiseDialect的声明import { AbstractSqlConnection } from mikro-orm/sql; import type { Dialect, Dictionary } from kysely; export class MyConnection extends AbstractSqlConnection { createKyselyDialect(overrides: Dictionary): Dialect { // overrides 中携带来自 MikroORM 配置的 driverOptions // 返回目标数据库的 Kysely dialect 实例 return new MyKyselyDialect({ ... }); } }AbstractSqlConnection的connect()逻辑会检查是否通过driverOptions直接提供了 dialect 实例此时复用否则调用createKyselyDialect()创建并缓存 Kysely 客户端见 AbstractSqlConnection.ts。连接生命周期、事务、savepoint、查询执行与流式读取都由基类基于 Kysely 完成。第二步实现 Platform继承AbstractSqlPlatform按需覆写能力方法。AbstractSqlPlatform本身扩展了 core 的Platform并提供 SQL 默认值其getSchemaHelper()默认返回平台持有的SchemaHelper实例见 packages/sql/src/AbstractSqlPlatform.ts并会注册SqlSchemaGenerator扩展以启用 Schema 生成能力。import { AbstractSqlPlatform } from mikro-orm/sql; export class MyPlatform extends AbstractSqlPlatform { supportsTransactions(): boolean { return true; } usesReturningStatement(): boolean { return false; } getDefaultSchemaName(): string | undefined { return undefined; } // 需要 Schema 生成时可提供自定义 SchemaHelper 实例 }常见的可覆写方法及用途包括supportsTransactions()是否支持事务、usesReturningStatement()是否支持INSERT ... RETURNING、supportsSchemas()是否支持命名 schema如 PostgreSQL基类默认false见 AbstractSqlPlatform.ts、getDefaultSchemaName()默认 schema 名、quoteIdentifier()/quoteValue()标识符与字面量引用方式、getCurrentTimestampSQL()、getSearchJsonPropertySQL()JSON 属性访问语法、escape()等。第三步实现 Driver继承AbstractSqlDriverMyConnection在构造函数中传入平台实例、连接类与原生依赖包名列表import { type Configuration, EntityManagerType } from mikro-orm/core; import { AbstractSqlDriver } from mikro-orm/sql; import { MyConnection } from ./MyConnection.js; import { MyPlatform } from ./MyPlatform.js; export class MyDriver extends AbstractSqlDriverMyConnection { constructor(config: Configuration) { super(config, new MyPlatform(), MyConnection, [kysely, my-native-driver]); } }对照 AbstractSqlDriver.ts 的构造函数super()的四个参数依次为config、platform实例、connection连接类与connector原生依赖包名数组基类会用连接类创建主连接与只读副本this.replicas this.createReplicas(...)见 AbstractSqlDriver.ts。connector数组中的包名会在启动时被校验缺失时给出友好的错误提示。AbstractSqlDriver已实现find、findOne、nativeInsert、nativeUpdate、nativeDelete、count、批量操作、查询构建与 joined 加载策略等全部重活。第四步注册驱动通过配置项driver将驱动类传给MikroORM该配置项在 packages/core/src/utils/Configuration.ts 中定义为driver?: { new (config: Configuration): Driver }const orm await MikroORM.init({ driver: MyDriver, dbName: my-database, entities: [Author, Book], });完整实战为非 SQL 数据库编写自定义驱动对于文档库、KV 存储等非 SQL 数据源没有现成的查询构建器可用必须直接继承mikro-orm/core的基类并实现更多方法。Connection继承Connection并实现抽象方法同时按需覆写事务方法import { Connection } from mikro-orm/core; export class MyConnection extends Connection { async connect(): Promisevoid { // 建立与数据库的连接 } async isConnected(): Promiseboolean { // 返回是否已连接 } async checkConnection(): Promise{ ok: true } | { ok: false; reason: string; error?: Error } { // 校验连接存活 return { ok: true }; } async close(force?: boolean): Promisevoid { // 关闭连接 } async execute(query: string, params?: any[], method?: all | get | run): Promiseany { // 执行查询并按 method 约定返回形状 } // 可以添加该数据库特有的查询方法 }Platform继承Platform覆写能力开关与主键转换方法import { Platform } from mikro-orm/core; export class MyPlatform extends Platform { usesPivotTable(): boolean { return false; } supportsTransactions(): boolean { return false; } getNamingStrategy(): { new (): NamingStrategy } { return MyNamingStrategy; } normalizePrimaryKeyT number | string(data: IPrimaryKey): T { /* ... */ } denormalizePrimaryKey(data: IPrimaryKey): IPrimaryKey { /* ... */ } }对于文档型数据库主键通常不是自增整数normalizePrimaryKey/denormalizePrimaryKey负责在原生驱动包装类型与标量值之间互转usesPivotTable()返回false则意味着 M:N 关系不使用中间表存储。Driver继承DatabaseDriverMyConnection组合连接与平台并实现 CRUD 抽象方法import { DatabaseDriver, type Configuration, EntityManagerType } from mikro-orm/core; import { MyConnection } from ./MyConnection.js; import { MyPlatform } from ./MyPlatform.js; export class MyDriver extends DatabaseDriverMyConnection { protected override readonly connection new MyConnection(this.config); protected override readonly platform new MyPlatform(); constructor(config: Configuration) { super(config, [my-native-driver]); } // 实现抽象 CRUD 方法 async findT extends object(entityName: string, where: FilterQueryT): PromiseEntityDataT[] { /* ... */ } async findOneT extends object(entityName: string, where: FilterQueryT): PromiseEntityDataT | null { /* ... */ } async nativeInsertT extends object(entityName: string, data: EntityDictionaryT): PromiseQueryResultT { /* ... */ } async nativeInsertManyT extends object(entityName: string, data: EntityDictionaryT[]): PromiseQueryResultT { /* ... */ } async nativeUpdateT extends object(entityName: string, where: FilterQueryT, data: EntityDictionaryT): PromiseQueryResultT { /* ... */ } async nativeDeleteT extends object(entityName: string, where: FilterQueryT): PromiseQueryResultT { /* ... */ } async countT extends object(entityName: string, where: FilterQueryT): Promisenumber { /* ... */ } }DatabaseDriver构造函数的第二个参数同样是原生依赖包名数组基类通过this.config、this.metadata、this.comparator等字段获得元数据与变更追踪能力EntityManager会经由createEntityManager()与驱动绑定。参考实现仓库中的现成范例编写自定义驱动前建议先研究仓库中已有的驱动包它们就是最直接的官方示例PostgreSQLpackages/postgresql功能最完整的 SQL 驱动涵盖 schema 支持、JSON 操作符、数组类型等MySQL / MariaDBpackages/mysql、packages/mariadb带各自平台特性的 SQL 驱动SQLitepackages/sqlite、packages/sql/src/dialects/sqlite最小化 SQL 驱动是起步学习的最佳样本——其中 SqliteDriver 直接继承AbstractSqlDriver并通过super(config, new SqlitePlatform(), BaseSqliteConnection, [kysely])组装三要素MongoDBpackages/mongodb非 SQL 驱动典范直接继承DatabaseDriver见 MongoDriver.ts配套有MongoPlatform、MongoConnection、MongoEntityManager与MongoExceptionConverter等完整组件libSQL / PGlite / sql-jspackages/libsql、packages/pglite、packages/sql-js基于 Kysely dialect 快速适配 SQLite 系数据库的范例。所有驱动包统一位于仓库 packages 目录下。另外针对 SQLite 系数据库当前文档还专门提供了自定义 SQLite dialect的用法说明见 usage-with-sqliteSQL 通用驱动SqliteDriver在未配置 dialect 时会抛出明确错误提示通过driverOptions传入 Kysely dialect见 BaseSqliteConnection.ts这是理解驱动与 dialect 解耦设计的最佳示例。实现要点总结职责边界清晰Platform回答能做什么、SQL 怎么写SchemaHelper回答Schema 怎么建Connection回答怎么连、怎么执行Driver回答怎么把实体变更落库优先复用基类SQL 驱动优先继承mikro-orm/sql的AbstractSqlPlatform/AbstractSqlConnection/AbstractSqlDriver绝大多数逻辑已被基类实现只需补充 dialect 与方言差异非 SQL 驱动才需要完整实现Platform/Connection/DatabaseDriver的抽象方法契约以源码为准抽象方法与接口定义以 IDatabaseDriver.ts、DatabaseDriver.ts、Connection.ts 为准execute()的method参数all | get | run与QueryResult返回形状是连接层的关键契约依赖包校验super(config, [...])中声明原生依赖包名缺失时会得到清晰的启动错误注册即用实现完成后通过MikroORM.init({ driver: MyDriver, ... })一行接入即可获得 EntityManager、Repository、查询构建、事务与 Identity Map 等完整 ORM 能力。赞分享后端【免费下载链接】mikro-ormTypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, MariaDB, MS SQL Server, PostgreSQL and SQLite/libSQL databases.项目地址https://gitcode.com/gh_mirrors/mi/mikro-orm点击查看免费下载相关推荐MQTT JMeter插件物联网性能测试的终极解决方案MQTT JMeter插件物联网性能测试的终极解决方案 在物联网应用日益普及的今天如何确保MQTT服务器的稳定性和性能成为开发者和运维人员面临的重要挑战。MAgenda v6 自定义数据库驱动Custom Backend Driver完整指南从 AgendaBackend 接口到 SQLite 落地实现Agenda v6 自定义数据库驱动Custom Backend Driver完整指南从 AgendaBackend 接口到 SQLite 落地实现 Ag数据库驱动开发指南Python版DataStax Driver数据库驱动开发指南Python版DataStax Driver 项目介绍 本教程基于 DataStax Python Driver https://githu后端上一篇终极指南如何用 LangChain 快速构建个人数据智能助手下一篇Microsoft Fluent Emoji现代友好的开源表情符号库全解析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
企业数字化 ERP 产品动态
相关推荐
Claude代码模板:本地化CLI工作流与MCP协议实践 1. 这不是“Claude代码模板”,而是一套被误读的本地化开发工作流最近在多个技术社区和内部协作群聊里,频繁看到“claude-code-templates”这个短语被当作一个现成工具、安装包甚至开源项目来讨论——有人问“怎么npm install claude-code-templates”&am… · 2026/9/25 3:23:51
网心云OES Plus刷Armbian教程:拆机短接与系统优化全流程 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 3:23:45
初等矩阵逆矩阵怎么记?交换、倍乘、倍加三种规则一眼看出 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 3:23:45
VisiData 列系统深度指南:Column 计算引擎、类型系统与聚合器实战 数据分析CLI数据可视化 【免费下载链接】visidata A terminal spreadsheet multitool for discovering and arranging data 项目地址: https://gitcode.com/gh_mirrors/vi/visidata 点击查看 免费下载 导读:本文围绕 VisiData 的 Column 体系展开&#… · 2026/9/25 6:04:43
Plannotator External Annotations API:把外部工具的标注实时推送到活动评审会话 【免费下载链接】plannotator Annotate and review coding agent plans and code diffs visually, share with your team, send feedback to agents with one click. 项目地址: https://gitcode.com/gh_mirrors/pl/plannotator 点击查看 免费下载 Plannotator 的 E… · 2026/9/25 6:04:43
Kubebuilder 项目路线图全景解读:2024–2026 战略规划与源码落地 开发者工具代码生成CLI云原生后端 【免费下载链接】kubebuilder Kubebuilder - SDK for building Kubernetes APIs using CRDs 项目地址: https://gitcode.com/gh_mirrors/ku/kubebuilder 点击查看 免费下载 本指南以仓库 roadmap/ 目录中的官方路线图文档为核心&a… · 2026/9/25 6:04:43
高通WCNSS_qcom_cfg.ini射频扫描与漫游参数调优实战 1. 从一次Wi-Fi断流排查说起前阵子帮一个做工业平板的朋友排查问题,设备用的是Qualcomm平台,Android 13,出厂后偶尔出现Wi-Fi扫描不到AP、连接后频繁掉线、热点开启失败的情况。抓了logcat和dmesg,发现WCNSS(Wireless … · 2026/9/25 6:04:25
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37