Kysely 插件系统完全指南:从内置插件到自定义 KyselyPlugin 的实战解析
后端数据库【免费下载链接】kyselyA type-safe TypeScript SQL query builder项目地址https://gitcode.com/gh_mirrors/ky/kysely点击查看免费下载Kysely 是一个类型安全的 TypeScript SQL 查询构建器其插件系统允许你在查询执行前改写查询OperationNode 语法树并在执行后改写结果集从而在保持查询所见即所得的同时实现标识符命名转换、重复 JOIN 去重、空 IN 列表处理与 NULL 安全比较等横切能力。本文将围绕 site/docs/plugins.md 讲解插件系统的核心机制、四个官方内置插件的配置与底层实现并带你基于 src/plugin/kysely-plugin.ts 的接口契约编写自己的插件。读完本文你将能在实际项目中正确选择、配置甚至定制 Kysely 插件彻底解决命名风格不统一、动态查询 JOIN 重复、in ()空列表报错和 NULL 比较语义错误四类典型问题。一、插件系统概述KyselyPlugin 接口与执行时机插件本质上是实现了KyselyPlugin接口的类。该接口在 src/plugin/kysely-plugin.ts 中定义只包含两个方法transformQuery(args: PluginTransformQueryArgs): RootOperationNode在每条查询执行前被调用。你可以通过args.node拿到整棵OperationNode语法树并返回修改后的树。通常配合OperationNodeTransformer子类使用例如SnakeCaseTransformer、SafeNullComparisonTransformer都继承了 src/operation-node/operation-node-transformer.ts。transformResult(args: PluginTransformResultArgs): PromiseQueryResultUnknownRow在每条查询执行后被调用。通过args.result访问查询结果并返回修改后的结果典型用途是 CamelCasePlugin 把返回行的 snake_case 键改写为 camelCase。如果需要在transformQuery与transformResult之间传递与查询相关的数据源码注释明确建议使用WeakMap并以args.queryId作为键。因为transformQuery并不总是被transformResult配对调用使用强引用 Map 会导致孤立条目泄漏内存import type { KyselyPlugin, QueryResult, RootOperationNode, UnknownRow } from kysely const data new WeakMapany, MyData() const plugin { transformQuery(args) { data.set(args.queryId, /* ... */) return args.node }, async transformResult(args) { const something data.get(args.queryId) // ... return args.result }, } satisfies KyselyPluginPluginTransformResultArgs还继承了AbortableOperationOptions意味着结果转换阶段同样支持中止语义见 src/util/abort.ts。另外仓库中提供了一个不执行任何操作的NoopPluginsrc/plugin/noop-plugin.ts它直接原样返回节点与结果可作为编写自定义插件时的最小参考骨架。二、注册插件全局配置与查询级withPlugin插件的注册方式在文档中给出了最基础的形式——通过Kysely实例构造参数中的plugins数组const db new KyselyDatabase({ dialect: new PostgresDialect({ database: kysely_test, host: localhost, }), plugins: [new CamelCasePlugin()], })除全局注册外site/docs/recipes/0008-deduplicate-joins.md 还展示了查询级注册方式通过查询构建器的.withPlugin()方法在个别查询上临时启用插件适合只在特定业务路径需要去重的场景await db .withPlugin(new DeduplicateJoinsPlugin()) .selectFrom(person) .selectAll(person) // ... .executeTakeFirst()两种方式可组合使用全局插件作用于所有查询查询级插件只影响当前链式调用。这在实践中很有用——比如CamelCasePlugin全局启用而DeduplicateJoinsPlugin只在构建动态查询的函数里按需启用。三、CamelCasePlugin数据库 snake_case 与 JS camelCase 的双向桥接CamelCasePlugin 是使用率最高的内置插件它把数据库中的 snake_case 标识符与 JavaScript 侧的 camelCase 相互转换。其核心实现位于 src/plugin/camel-case/camel-case-plugin.tstransformQuery使用SnakeCaseTransformersrc/plugin/camel-case/camel-case-transformer.ts遍历语法树对每个IdentifierNode调用 snake_case 映射把查询中的 camelCase 表名、列名改写为数据库的 snake_casetransformResult遍历返回行把每一行的键通过mapRow递归转换为 camelCase——注意嵌套对象与数组元素也会被递归处理除非配置了maintainNestedObjectKeys。配置选项插件构造参数类型为CamelCasePluginOptions各选项的默认值与行为如下选项默认值说明upperCasefalse为true时 camelCase 转换为全大写 SNAKE_CASE例如fooBar FOO_BAR反向FOO_BAR fooBarunderscoreBeforeDigitsfalse为true时在数字前插入下划线例如foo12Bar foo_12_bar反向foo_12_bar foo12Bar多位数字只在第一个数字前插入underscoreBetweenUppercaseLettersfalse为true时在连续大写字母之间加下划线例如fooBAR foo_b_a_r默认为foo_barmaintainNestedObjectKeysfalse为true时嵌套对象的键不再转换为 camelCase仅转换顶层键这些选项在 src/plugin/camel-case/camel-case.ts 的createSnakeCaseMapper与createCamelCaseMapper中实现且两个映射函数都通过memoize做结果缓存避免重复字符串反复计算。createCamelCaseMapper在upperCase开启时会先判断字符串是否为全大写 snake_case如FOO_BAR是才转小写再做驼峰化从而保证已符合要求的字符串不被意外改动。使用示例文档中的示例SQLite 方言演示了数据库里的一切都以 camelCase 来写这一核心用法import * as Sqlite from better-sqlite3 import { CamelCasePlugin, Kysely, SqliteDialect } from kysely interface CamelCasedDatabase { userMetadata: { firstName: string lastName: string } } const db new KyselyCamelCasedDatabase({ dialect: new SqliteDialect({ database: new Sqlite(:memory:), }), plugins: [new CamelCasePlugin()], }) const person await db .selectFrom(userMetadata) .where(firstName, , Arnold) .select([firstName, lastName]) .executeTakeFirst() if (person) { console.log(person.firstName) }生成的 SQLSQLite为select first_name, last_name from user_metadata where first_name ?需要强调的是使用该插件时TypeScript 代码里的表名、列名、schema 等一切标识符都要写成 camelCaseKysely 会表现得像数据库本来就是 camelCase 定义的一样。高级定制覆写映射方法如果内置的转换规则仍不满足需求例如需要保留特定缩写文档与源码都支持通过继承覆写两个受保护方法class MyCamelCasePlugin extends CamelCasePlugin { protected override snakeCase(str: string): string { // 自定义 camelCase - snake_case 逻辑 return str } protected override camelCase(str: string): string { // 自定义 snake_case - camelCase 逻辑 return str } }四、DeduplicateJoinsPlugin消除动态查询中的重复 JOIN在动态构建查询时同一个 JOIN 可能被条件性地重复添加。例如 site/docs/recipes/0008-deduplicate-joins.md 中的getPerson函数当withPetName与withPetSpecies同时为true时pet表会被innerJoin两次导致数据库报错async function getPerson( id: number, withPetName: boolean, withPetSpecies: boolean, ) { return await db .selectFrom(person) .selectAll(person) .$if(withPetName, (qb) qb .innerJoin(pet, pet.owner_id, person.id) .select(pet.name as pet_name), ) .$if(withPetSpecies, (qb) qb .innerJoin(pet, pet.owner_id, person.id) .select(pet.species as pet_species), ) .where(person.id, , id) .executeTakeFirst() }安装DeduplicateJoinsPluginsrc/plugin/deduplicate-joins/deduplicate-joins-plugin.ts后其内部DeduplicateJoinsTransformer会扫描语法树并移除重复的 JOIN 节点。该插件既可按上文全局注册也可用.withPlugin(new DeduplicateJoinsPlugin())按需启用因此不会给绝大多数不需要去重的用户带来额外开销。关于为什么去重是插件而非默认行为文档给出了清晰的解释判断两个 JOIN 是否完全等价非常困难——简单 JOIN 很容易判断但包含嵌套子查询等复杂 JOIN 时会出现难以识别的边界情况。因此 Kysely 将其设计为可选项避免去重逻辑误伤不需要它的场景。五、HandleEmptyInListsPlugin从容应对in ()与not in ()in ()和not in ()在很多数据库中都是非法 SQL直接执行会触发运行时错误。HandleEmptyInListsPluginsrc/plugin/handle-empty-in-lists/handle-empty-in-lists-plugin.ts允许你通过strategy选项选择空列表的处理策略。EmptyInListsStrategy是一个接收EmptyInListNode、返回新的BinaryOperationNode的函数类型定义见 src/plugin/handle-empty-in-lists/handle-empty-in-lists.ts。需要特别说明设计哲学源码注释强调其他 ORM 的做法都是在底层偷偷改写查询这与 Kysely所见即所得WYSIWYG的理念不符。因此 Kysely 官方推荐在使用in/not in之前手动检查数组是否为空该插件采用 opt-in 方式由开发者显式选择是否以及如何处理并建议充分测试后再投入使用。策略一replaceWithNoncontingentExpression恒真/恒假替换将in替换为恒假表达式1 0将not in替换为恒真表达式1 1这与 Knex、Prisma、Laravel、SQLAlchemy 的处理方式一致。实现位于 src/plugin/handle-empty-in-lists/handle-empty-in-lists.ts 的replaceWithNoncontingentExpression其中1、等节点被模块级缓存复用||惰性初始化保证重复查询时零额外分配import Sqlite from better-sqlite3 import { HandleEmptyInListsPlugin, Kysely, replaceWithNoncontingentExpression, SqliteDialect, } from kysely const db new KyselyDatabase({ dialect: new SqliteDialect({ database: new Sqlite(:memory:) }), plugins: [ new HandleEmptyInListsPlugin({ strategy: replaceWithNoncontingentExpression, }), ], }) const results await db .selectFrom(person) .where(id, in, []) .where(first_name, not in, []) .selectAll() .execute()生成的 SQLSQLiteselect * from person where 1 0 and 1 1策略二pushValueIntoList向列表推入哨兵值对于in向空列表推入null得到in (null)。in (null)在逻辑上等价于 null结果为null大多数 SQL 数据库中的假值这与 TypeORM、Sequelize 的做法一致。源码明确建议如果打算在select、returning、output子句中使用in不要选用此策略因为其返回类型与比较运算默认的SqlBool类型不同。对于not in将左操作数强制转换为char并推入一个唯一字面值得到cast({{lhs}} as char) not in ({{VALUE}})。强制转换是为了避免非字符串列出现数据库错误。import Sqlite from better-sqlite3 import { HandleEmptyInListsPlugin, Kysely, pushValueIntoList, SqliteDialect, } from kysely const db new KyselyDatabase({ dialect: new SqliteDialect({ database: new Sqlite(:memory:) }), plugins: [ new HandleEmptyInListsPlugin({ // 为 not in 选择一个数据中绝对不可能出现的唯一值 strategy: pushValueIntoList(__kysely_no_values_were_provided__), }), ], }) const results await db .selectFrom(person) .where(id, in, []) .where(first_name, not in, []) .selectAll() .execute()生成的 SQLSQLiteselect * from person where id in (null) and cast(first_name as char) not in (__kysely_no_values_were_provided__)从 src/plugin/handle-empty-in-lists/handle-empty-in-lists.ts 的实现可以看到pushValueIntoList返回的闭包通过freeze复用in (null)的ValueListNode与cast ... as char所需的CastNode/DataTypeNode同样做了节点级缓存优化。自定义策略抛错或告警strategy可以是任意函数因此你也可以直接抛错避免向数据库发送注定失败的请求import Sqlite from better-sqlite3 import { HandleEmptyInListsPlugin, Kysely, SqliteDialect } from kysely const db new KyselyDatabase({ dialect: new SqliteDialect({ database: new Sqlite(:memory:) }), plugins: [ new HandleEmptyInListsPlugin({ strategy: () { throw new Error(Empty in/not-in is not allowed) }, }), ], }) const results await db .selectFrom(person) .where(id, in, []) .selectAll() .execute() // 抛出 Empty in/not-in is not allowed 错误同理你也可以在策略函数内打印警告日志后返回原节点实现只提醒不干预的效果。六、SafeNullComparisonPlugin让 NULL 比较符合直觉SQL 中 NULL、! NULL、 NULL永远返回NULL未知而不是开发者期望的真/假正确的写法是IS NULL/IS NOT NULL。SafeNullComparisonPluginsrc/plugin/safe-null-comparison/safe-null-comparison-plugin.ts专门解决这一问题当右侧操作数是null时自动执行如下算子改写原始算子改写后IS!IS NOTIS NOT其内部SafeNullComparisonTransformersrc/plugin/safe-null-comparison/safe-null-comparison-transformer.ts覆写transformBinaryOperation先递归转换子节点然后检查右操作数是否为null值的ValueNode、且算子属于/!/三者之一命中则重建为IS/IS NOT二元运算节点。典型收益当你在代码里持有可空变量如string | null时无需再手写条件化 WHERE 分支直接写query.where(name, , name)插件会根据运行时值自动选择正确算子const db new KyselyDatabase({ dialect, plugins: [new SafeNullComparisonPlugin()], }) // 当 name 为 null 时自动生成 where name is null // 当 name 为 foo 时生成 where name ? await db.selectFrom(person).where(name, , name).selectAll().execute()七、编写自定义插件从接口到完整实现基于 src/plugin/kysely-plugin.ts 的接口契约自定义插件的完整流程如下实现KyselyPlugin必须提供transformQuery同步返回RootOperationNode与transformResult异步返回PromiseQueryResultUnknownRow。用OperationNodeTransformer做查询改写从 src/operation-node/operation-node-transformer.ts 继承覆写对应的transform*方法如transformIdentifier、transformBinaryOperation即可精准修改语法树特定节点。所有内置插件的 transformer 都遵循这一模式。用WeakMapQueryId, T传递查询级数据在transformQuery写入、在transformResult读取避免内存泄漏。结果改写在transformResult中按需修改args.result.rows后返回新对象。仓库中还提供了几个便于参考的插件变体ImmediateValuePluginsrc/plugin/immediate-value/immediate-value-plugin.ts、ParseJSONResultsPluginsrc/plugin/parse-json-results/parse-json-results-plugin.ts以及WithSchemaPluginsrc/plugin/with-schema/with-schema-plugin.ts它们均从 src/index.ts 对外导出展示了查询改写与结果改写两类插件的不同侧重点可作为进一步学习的模板。八、结语Kysely 的插件系统以其简洁的两阶段钩子查询前改写 结果后改写实现了强大的可扩展性而官方内置的四个插件则分别覆盖了标识符命名转换、动态查询 JOIN 去重、空 IN 列表处理与 NULL 安全比较四个高频痛点。选择插件时的总原则是能用官方插件解决的优先使用能手动检查空数组的就不要引入额外策略需要特殊行为时再基于KyselyPlugin接口编写自定义插件。本文涉及的核心源码文件均可直接在仓库中查阅src/plugin/kysely-plugin.ts、src/plugin/camel-case/camel-case-plugin.ts、src/plugin/deduplicate-joins/deduplicate-joins-plugin.ts、src/plugin/handle-empty-in-lists/handle-empty-in-lists-plugin.ts 与 src/plugin/safe-null-comparison/safe-null-comparison-plugin.ts。赞分享后端数据库【免费下载链接】kyselyA type-safe TypeScript SQL query builder项目地址https://gitcode.com/gh_mirrors/ky/kysely点击查看免费下载相关推荐Kubo 插件系统完全指南加载机制、内置插件与自定义开发实战Kubo 插件系统完全指南加载机制、内置插件与自定义开发实战 KuboIPFS 的 Go 实现自 0.4.11 起引入了一套实验性插件系统允许在不重新编网络存储后端rrweb 插件机制完全指南从自定义 Record/Replay 插件到内置插件生态rrweb 插件机制完全指南从自定义 Record/Replay 插件到内置插件生态 导读 rrweb 提供了一套独立的 插件 APIPlugin API前端可观测性开发工具Bloatynosy插件系统完全指南从安装到自定义配置Bloatynosy是Windows系统优化和清理的终极工具其强大的插件系统让用户能够轻松扩展功能实现个性化系统配置。本文将为你详细介绍如何充分利用Bloa桌面应用上一篇InfoSpider日志系统设计结构化日志的实现与分析下一篇Wan2.2-S2V-14B技术深度解析音频驱动电影级视频生成的架构创新与实践应用创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

Flet CupertinoNavigationBar 详解:用 Python 构建 iOS 风格底部导航栏

Flet CupertinoNavigationBar 详解:用 Python 构建 iOS 风格底部导航栏

Flet CupertinoNavigationBar 详解:用 Python 构建 iOS 风格底部导航栏 【免费下载链接】flet Build realtime web, mobile and desktop apps in Python only. No frontend experience required. 项目地址: https://gitcode.com/gh_mirrors/fl/flet 导读 Cu…

2026/9/22 11:17:52 阅读更多 →
seaborn v0.7.1 更新全解:误差条样式、clustermap 颜色标签与 heatmap 注解增强

seaborn v0.7.1 更新全解:误差条样式、clustermap 颜色标签与 heatmap 注解增强

seaborn v0.7.1 更新全解:误差条样式、clustermap 颜色标签与 heatmap 注解增强 【免费下载链接】seaborn Statistical data visualization in Python 项目地址: https://gitcode.com/gh_mirrors/se/seaborn 导读 seaborn 0.7.1(2016 年 6 月发布…

2026/9/22 11:17:52 阅读更多 →
JupyterLab 上下文帮助扩展:@jupyterlab/inspector-extension 的入口、命令与快捷键深度解析

JupyterLab 上下文帮助扩展:@jupyterlab/inspector-extension 的入口、命令与快捷键深度解析

前端后端数据科学开发工具 【免费下载链接】jupyterlab JupyterLab computational environment. 项目地址: https://gitcode.com/gh_mirrors/ju/jupyterlab 点击查看 免费下载 JupyterLab 的"Contextual Help(上下文帮助)"面板能够…

2026/9/22 11:17:52 阅读更多 →

最新新闻

搞定公司在职证明模板源码解析,3步避开配置环境坑

搞定公司在职证明模板源码解析,3步避开配置环境坑

搞定公司在职证明模板源码解析,3步避开配置环境坑 配置环境就卡半天,明明照着文档敲代码,结果依赖装不上、字体渲染乱码,最后还得求HR要个原版文件。这种折磨谁懂?很多刚入行的开发同学,在写自动化脚本生成【公司在职证明模板】时,往往死磕在环境搭…

2026/9/22 12:39:32 阅读更多 →
云天青入门保姆级教程:告别语法堆砌,3步搭起第一个完整项目

云天青入门保姆级教程:告别语法堆砌,3步搭起第一个完整项目

云天青入门保姆级教程:告别语法堆砌,3步搭起第一个完整项目 刚把Python或者Java的基础语法敲完,是不是感觉脑子挺清楚,手也挺熟?但一让你独立写个东西,鼠标点着新建文件就发懵?这种“学会语法却不知怎么搭项目”的卡点,90%的新手都踩过…

2026/9/22 12:39:31 阅读更多 →
5分钟搞懂苹果手机外屏怎么换:这份速查手册让你不踩坑

5分钟搞懂苹果手机外屏怎么换:这份速查手册让你不踩坑

5分钟搞懂苹果手机外屏怎么换:这份速查手册让你不踩坑 刚入职建筑工地的兄弟,是不是也跟我当年一样,手里捧着《建筑工程施工质量验收统一标准》,看着满篇的“允许偏差”、“主控项目”头晕脑胀?知道要砌砖、要浇筑,但真到了现场,监理问你这面墙的垂直…

2026/9/22 12:39:30 阅读更多 →
IMX586与Hi3519AV100硬件适配:MIPI信号链调试全指南

IMX586与Hi3519AV100硬件适配:MIPI信号链调试全指南

1. 为什么IMX586在Hi3519AV100上“点不亮”?——从信号链断裂说起你拿到一块全新的IMX586模组,焊接到Hi3519AV100开发板上,烧写SDK、配置sensor_list、跑通mpp_sample_venc,结果preview窗口一片漆黑,log里只有“[VI] s…

2026/9/22 12:39:29 阅读更多 →
3个坑解决FSGS报错,最佳实践指南

3个坑解决FSGS报错,最佳实践指南

3个坑解决FSGS报错,最佳实践指南 刚接手新项目,运行代码直接崩了?满屏红色报错,StackTrace 长得像天书,看一眼头都大。别慌,这种时候最考验人的不是技术深度,而是排查思路。很多老手在处理 FSGS…

2026/9/22 12:39:28 阅读更多 →
STM32+ESP8266智能台灯实战:环境光检测与云平台控制完整方案

STM32+ESP8266智能台灯实战:环境光检测与云平台控制完整方案

半夜改代码的时候,台灯突然亮起来吓我一跳。我当时的设定是环境光低于某个阈值就自动开灯,结果忘了自己面前还开着显示器——屏幕一亮,传感器把整个书桌都照亮了。这种“智能”就显得特别傻。这个项目最初的动机就是这么朴素:做一…

2026/9/22 12:38:28 阅读更多 →

日新闻

3台商务办公笔记本实测:手写实现环境配置,告别卡半天

3台商务办公笔记本实测:手写实现环境配置,告别卡半天

3台商务办公笔记本实测:手写实现环境配置,告别卡半天 配置环境就卡半天?别怪机器慢,多半是你没选对工具链。在Java、Go或Python的项目现场, 手写实现…

2026/9/22 0:00:41 阅读更多 →
剑帝加点速查手册:3分钟搞懂核心逻辑

剑帝加点速查手册:3分钟搞懂核心逻辑

剑帝加点速查手册:3分钟搞懂核心逻辑 面试被问原理答不上来,是不是常态?别慌。很多开发者对着 GitHub 开源仓库里的代码发呆,看似简单实则暗藏玄机。今天这份【剑帝加点】速查手册,直接带你拆解核心实现,把面试必考的原理讲透。…

2026/9/22 0:00:41 阅读更多 →
手写实现图片压缩网站核心:搞定WebP转换与质量调优

手写实现图片压缩网站核心:搞定WebP转换与质量调优

手写实现图片压缩网站核心:搞定WebP转换与质量调优 复制来的代码跑不通不知道怎么调?别慌,这种“复制粘贴地狱”在开发圈太常见了。尤其是做 图片压缩网站…

2026/9/22 0:00:41 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/22 4:32:41 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/22 4:38:57 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/22 8:51:04 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/21 15:36:51 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/21 15:36:51 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/22 2:43:42 阅读更多 →