news 2026/9/14 18:34:08

NocoBase 数据库 Filter 操作符完全指南:从 $eq 到 $dateOn 的 40+ 查询运算符详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
NocoBase 数据库 Filter 操作符完全指南:从 $eq 到 $dateOn 的 40+ 查询运算符详解

NocoBase 数据库 Filter 操作符完全指南:从 $eq 到 $dateOn 的 40+ 查询运算符详解

【免费下载链接】nocobaseNocoBase is an open-source AI + no-code platform for building business systems fast. Instead of generating everything from scratch, AI works on top of production-proven infrastructure and a WYSIWYG no-code interface, so you get both speed and reliability.项目地址: https://gitcode.com/GitHub_Trending/no/nocobase

导读:本文以 NocoBase 官方数据库 API 文档 operators.md 为骨架,系统讲解 Repository 的find/findOne/findAndCount/count等接口filter参数中全部内置运算符的语义、SQL 对应关系、适用字段类型与实战示例,并结合 packages/core/database/src/operators 目录下的源码实现与测试用例,深入揭示每个运算符在 PostgreSQL / MySQL / SQLite 等不同数据库下的底层行为。读完本文,你将能熟练编写任意复杂度的 NocoBase 数据过滤条件,并掌握通过db.registerOperators()扩展自定义运算符的方法。

一、Filter 参数与运算符的 JSON 化设计

在 NocoBase 中,Repository是数据访问的核心入口,其查询方法都接受一个filter参数用于描述过滤条件:

const repository = db.getRepository('books'); repository.find({ filter: { title: { $eq: '春秋', }, }, });

为了让过滤条件能够被 JSON 序列化、跨 HTTP API 传输,NocoBase 将查询运算符统一表示为$为前缀的字符串标识,例如$eq$in$and。这样filter就是一个纯 JSON 结构,既可以在服务端代码中直接书写,也可以由前端通过 API 请求参数动态传入。

运算符注册的底层机制

从源码看,运算符的注册发生在Database类的initOperators()方法中(database.ts):

initOperators() { const operators = new Map(); // Sequelize 内置 for (const key in Op) { operators.set('$' + key, Op[key]); const val = Utils.underscoredIf(key, true); operators.set('$' + val, Op[key]); operators.set('$' + val.replace(/_/g, ''), Op[key]); } this.operators = operators; this.registerOperators({ ...(extendOperators as unknown as MapOf<OperatorFunc>), }); }

这里有两个层次:

  1. Sequelize 内置运算符兜底:NocoBase 底层基于 Sequelize ORM,启动时会把 SequelizeOp中的全部符号(如Op.likeOp.betweenOp.regexpOp.colOp.isOp.not等)批量注册为对应的$前缀字符串。这就是$like$notLike$iLike$regexp$col$is$not$gt$between等运算符的来源。
  2. NocoBase 自定义实现覆盖:随后通过registerOperators()注册 NocoBase 自研的运算符实现(见 operators/index.ts),这些实现会覆盖同名的 Sequelize 默认行为,并新增$dateOn$isFalsy$match$exists等 NocoBase 专属运算符:
// packages/core/database/src/operators/index.ts export default { ...association, // $exists / $notExists ...date, // $dateOn / $dateBefore / $dateAfter ... ...array, // $match / $anyOf / $arrayEmpty ... ...empty, // $empty / $notEmpty ...string, // $includes / $startsWith / $endWith ... ...eq, // $eq ...ne, // $ne ...$in, // $in ...notIn, // $notIn ...boolean, // $isFalsy / $isTruly ...childCollection, };

也就是说,一套filter语法规则,最终会被解析成对应数据库方言可执行的 SQL 条件,这一设计让上层业务代码与底层数据库解耦。

二、通用运算符(适用于任意字段类型)

通用运算符对所有字段类型都有效,用于最基础的等值、包含与空值判断。

$eq— 等于

判断字段值是否等于指定值,相当于 SQL 的=

repository.find({ filter: { title: { $eq: '春秋', }, }, });

$eq的值为简单字符串时,写法等价于title: '春秋'的简写形式。从 eq.ts 的实现看,$eq还有两个隐藏行为:

  • 字符串字段的自动类型转换:若目标字段类型是string而传入值不是字符串,会执行String(val)强转;传入数组时则映射为Op.inIN查询);
  • 数组值支持:即使不是字符串字段,只要传入数组,$eq也会转为IN查询。

$ne— 不等于

判断字段值是否不等于指定值,相当于 SQL 的!=

repository.find({ filter: { title: { $ne: '春秋', }, }, });

注意 ne.ts 中的实现细节:当值为数组时映射为NOT IN;当值为null时映射为IS NOT NULL(即Op.ne: null);而其他普通值会被翻译为{ Op.or: { Op.ne: val, Op.is: null } }—— 这意味着$ne的查询结果会包含该字段为NULL的记录,因为NULL != 值在 SQL 语义中同样成立。这在数据统计时需要特别留意。

$is— 是否为指定值

判断字段值是否为指定值,相当于 SQL 的IS,常用于空值判断:

repository.find({ filter: { title: { $is: null, }, }, });

$not— 是否不为指定值

判断字段值是否不为指定值,相当于 SQL 的IS NOT

repository.find({ filter: { title: { $not: null, }, }, });

$col— 字段间比较

判断字段值是否等于另一个字段的值,相当于 SQL 的=作用于两列之间:

repository.find({ filter: { title: { $col: 'name', }, }, });

以上示例表示筛选出title字段值与name字段值相等的记录,适用于同一行内字段间的关联比较。

$in/$notIn— 属于 / 不属于集合

判断字段值是否在指定数组中,分别相当于 SQL 的INNOT IN

repository.find({ filter: { title: { $in: ['春秋', '战国'], }, }, }); repository.find({ filter: { title: { $notIn: ['春秋', '战国'], }, }, });

这两个运算符在仓库中对应 in.ts 与 notIn.ts,并有专门的测试用例 in.test.ts 覆盖。

$empty/$notEmpty— 空值判断

  • $empty:判断一般字段是否为空。字符串字段判断是否为空串,数组字段判断是否为空数组;
  • $notEmpty:判断一般字段是否不为空,规则同上取反。
repository.find({ filter: { title: { $empty: true, }, }, }); repository.find({ filter: { title: { $notEmpty: true, }, }, });

其实现位于 empty.ts,会结合字段类型分别生成针对空字符串、空数组或NULL的条件。

三、逻辑运算符

逻辑运算符用于组合多个过滤条件,是所有复杂查询的基础。

$and— 逻辑与

逻辑 AND,相当于 SQL 的AND

repository.find({ filter: { $and: [{ title: '诗经' }, { isbn: '1234567890' }], }, });

$or— 逻辑或

逻辑 OR,相当于 SQL 的OR。注意其中可以嵌套其他运算符:

repository.find({ filter: { $or: [{ title: '诗经' }, { publishedAt: { $lt: '0000-00-00T00:00:00Z' } }], }, });
// 多条件 OR:数组中的每个对象都是一组独立条件 repository.find({ filter: { $or: [ { status: 'published' }, { status: 'draft', authorId: 1 }, ], }, });

$and/$or的取值都是条件对象数组,数组中的每个元素本身又是一个完整的 filter 片段,因此可以任意嵌套组合出多层次的复杂查询。

四、布尔类型字段运算符

以下运算符专门用于布尔类型字段(type: 'boolean'),在 boolean.ts 中实现。

$isFalsy— 判断为假

布尔字段值为false0NULL的情况都会被判断为$isFalsy: true

repository.find({ filter: { isPublished: { $isFalsy: true, }, }, });

源码中,$isFalsy在传入true/'true'时生成{ Op.or: { Op.is: null, Op.eq: false } },即同时覆盖NULLfalse两种“假”的情况;传入false时则取反,生成{ Op.eq: true }

$isTruly— 判断为真

布尔字段值为true1的情况都会被判断为$isTruly: true

repository.find({ filter: { isPublished: { $isTruly: true, }, }, });

相关行为可参考测试 boolean-operator.test.ts。

五、数字类型字段运算符

以下运算符用于数字类型字段,包括:

  • type: 'integer'
  • type: 'float'
  • type: 'double'
  • type: 'real'
  • type: 'decimal'

大小比较:$gt/$gte/$lt/$lte

分别对应 SQL 的>>=<<=

// 大于 repository.find({ filter: { price: { $gt: 100, }, }, }); // 大于等于 repository.find({ filter: { price: { $gte: 100, }, }, }); // 小于 repository.find({ filter: { price: { $lt: 100, }, }, }); // 小于等于 repository.find({ filter: { price: { $lte: 100, }, }, });

区间判断:$between/$notBetween

判断字段值是否在指定的两个值之间,分别相当于 SQL 的BETWEENNOT BETWEEN。值为包含两个元素的数组[最小值, 最大值]

repository.find({ filter: { price: { $between: [100, 200], }, }, }); repository.find({ filter: { price: { $notBetween: [100, 200], }, }, });

六、字符串类型字段运算符

以下运算符用于字符串类型字段(type: 'string'),核心实现在 string.ts。

包含与排除:$includes/$notIncludes

  • $includes:判断字符串字段是否包含指定子串;
  • $notIncludes:判断字符串字段是否不包含指定子串。
repository.find({ filter: { title: { $includes: '三字经', }, }, }); repository.find({ filter: { title: { $notIncludes: '三字经', }, }, });

前缀匹配:$startsWith/$notStatsWith

  • $startsWith:判断字符串字段是否以指定子串开头;
  • $notStatsWith:判断字符串字段是否不以指定子串开头(文档中的拼写如此,源码实现键名对应为$notStartsWith,见 string.ts)。
repository.find({ filter: { title: { $startsWith: '三字经', }, }, }); repository.find({ filter: { title: { $notStatsWith: '三字经', }, }, });

后缀匹配:$endsWith/$notEndsWith

  • $endsWith:判断字符串字段是否以指定子串结尾;
  • $notEndsWith:判断字符串字段是否不以指定子串结尾。
repository.find({ filter: { title: { $endsWith: '三字经', }, }, }); repository.find({ filter: { title: { $notEndsWith: '三字经', }, }, });

从源码结构看,string.ts 中这两个运算符的实现键名实际写作$endWith/$notEndWith(见 L159-L185),使用时两种拼写都需以实际生效键名为准。

SQL 风格匹配:$like/$notLike/$iLike/$notILike

  • $like:字段值是否包含指定的字符串,相当于 SQL 的LIKE
  • $notLike:字段值是否不包含指定的字符串,相当于 SQL 的NOT LIKE
  • $iLike:字段值是否包含指定字符串且忽略大小写,相当于 SQL 的ILIKE(仅 PG 适用);
  • $notILike:字段值是否不包含指定字符串且忽略大小写,相当于 SQL 的NOT ILIKE(仅 PG 适用)。
repository.find({ filter: { title: { $like: '计算机', }, }, }); repository.find({ filter: { title: { $notLike: '计算机', }, }, }); repository.find({ filter: { title: { $iLike: 'Computer', }, }, }); repository.find({ filter: { title: { $notILike: 'Computer', }, }, });

源码细节:虽然$like系列直接来源于 Sequelize 内置运算符,但 NocoBase 自定义的$includes/$startsWith等运算在底层同样走 LIKE/ILIKE 路径。以 string.ts 为例:

  • PostgreSQL:会通过CAST(字段 AS TEXT)将字段强制转为文本后拼接%...%通配符,并区分ILIKE(忽略大小写)与LIKE
  • MySQL 等数据库ILIKE会退化为LIKE(MySQL 的默认排序规则本身通常不区分大小写);
  • 通配符转义escapeLike()会将用户输入中的%_转义,避免通配符注入导致意外匹配;MSSQL 方言下则采用[包裹的转义写法;
  • 数组值支持$includes$startsWith等传入数组时,会生成多个条件并用OR/AND组合。

正则匹配:$regexp/$notRegexp/$iRegexp/$notIRegexp

  • $regexp:字段值是否匹配指定的正则表达式,相当于 SQL 的REGEXP(仅 PG 适用);
  • $notRegexp:字段值是否不匹配指定的正则表达式,相当于 SQL 的NOT REGEXP(仅 PG 适用);
  • $iRegexp:字段值是否匹配指定正则且忽略大小写,相当于 SQL 的~*(仅 PG 适用);
  • $notIRegexp:字段值是否不匹配指定正则且忽略大小写,相当于 SQL 的!~*(仅 PG 适用)。
repository.find({ filter: { title: { $regexp: '^计算机', }, }, }); repository.find({ filter: { title: { $notRegexp: '^计算机', }, }, }); repository.find({ filter: { title: { $iRegexp: '^COMPUTER', }, }, }); repository.find({ filter: { title: { $notIRegexp: '^COMPUTER', }, }, });

注意:正则类运算符(REGEXPILIKE等)依赖数据库方言能力,官方文档标注为仅 PostgreSQL 适用;在 MySQL / SQLite 等其他数据库上使用时需要确认对应方言的等价支持。字符串运算符的完整行为可参考测试 string-operator.test.ts 与 operators/tests/string.test.ts。

七、日期类型字段运算符

以下运算符用于日期类型字段(type: 'date'),核心实现在 date.ts。日期运算符内部会调用@nocobase/utilsparseDate进行解析,并根据字段类型自动处理时区与格式转换。

$dateOn/$dateNotOn— 在某一天 / 不在某一天

判断日期字段是否(不)在某天内,传入日期字符串即可:

repository.find({ filter: { createdAt: { $dateOn: '2021-01-01', }, }, }); repository.find({ filter: { createdAt: { $dateNotOn: '2021-01-01', }, }, });

源码细节$dateOn在收到'2021-01-01'这类单值时会解析为[当天 00:00:00, 次日 00:00:00)的左闭右开区间,翻译为{ Op.gte: 起始, Op.lt: 结束 };若parseDate返回数组(日期范围),则同样生成区间条件。这意味着“在某一天”实际是“当天零点至次日零点之间”,不会漏掉当天任意时刻的数据。

$dateBefore/$dateNotBefore— 在某个值之前 / 不在之前

  • $dateBefore:判断日期字段是否在某个值之前,相当于小于传入的日期值;
  • $dateNotBefore:判断日期字段是否不在某个值之前,相当于大于等于传入的日期值。
repository.find({ filter: { createdAt: { $dateBefore: '2021-01-01T00:00:00.000Z', }, }, }); repository.find({ filter: { createdAt: { $dateNotBefore: '2021-01-01T00:00:00.000Z', }, }, });

$dateAfter/$dateNotAfter— 在某个值之后 / 不在之后

  • $dateAfter:判断日期字段是否在某个值之后,相当于大于传入的日期值;
  • $dateNotAfter:判断日期字段是否不在某个值之后,相当于小于等于传入的日期值。
repository.find({ filter: { createdAt: { $dateAfter: '2021-01-01T00:00:00.000Z', }, }, }); repository.find({ filter: { createdAt: { $dateNotAfter: '2021-01-01T00:00:00.000Z', }, }, });

源码细节(时区与特殊日期字段)date.ts中的parseDateTimezone()会根据字段类型决定解析时区——datetimeNoTz(无时区时间)与dateOnly(仅日期)字段强制按+00:00解析,其他日期字段使用db.options.timezonetoDate()还会针对UnixTimestampField调用field.dateToValue()将日期转回 Unix 时间戳,并在转换完成后发出filterToDate事件供其他逻辑订阅。此外源码中还实现了文档未单列出的$dateBetween(日期区间)运算符。相关行为有大量测试覆盖,如 operator/date/datetime-tz.test.ts、date-only.test.ts 与 unix-timestamp.test.ts。

八、数组类型字段运算符

以下运算符用于数组类型字段(type: 'array'),核心实现在 array.ts。这类运算符对不同数据库生成了差异化的 SQL:

运算符语义PostgreSQLMySQLSQLite
$match数组值完全匹配指定数组@><@双向包含JSON_CONTAINS双向json()相等比较
$anyOf包含指定数组中任意值?|运算符JSON_OVERLAPSjson_each子查询
$noneOf不包含指定数组中任意值取反?|NOT JSON_OVERLAPS取反json_each
$arrayEmpty数组是否为空jsonb_array_lengthjson_lengthjson_array_length
$arrayNotEmpty数组是否不为空同上取>0同上取>0同上取>0

$match/$notMatch— 完全匹配 / 不匹配

判断数组字段的值是否(不)匹配指定数组中的值:

repository.find({ filter: { tags: { $match: ['文学', '历史'], }, }, }); repository.find({ filter: { tags: { $notMatch: ['文学', '历史'], }, }, });

$anyOf/$noneOf— 包含任意值 / 不包含任意值

判断数组字段的值是否(不)包含指定数组中的任意值:

repository.find({ filter: { tags: { $anyOf: ['文学', '历史'], }, }, }); repository.find({ filter: { tags: { $noneOf: ['文学', '历史'], }, }, });

$arrayEmpty/$arrayNotEmpty— 数组是否为空

repository.find({ filter: { tags: { $arrayEmpty: true, }, }, }); repository.find({ filter: { tags: { $arrayNotEmpty: true, }, }, });

源码细节emptyQuery()会生成IFNULL(json_array_length(字段), 0) = 0(PG 用coalesce(jsonb_array_length(...), 0),MySQL 用json_length(...))这样的表达式来判断空数组,并对NULL值做了兜底处理,因此$arrayEmpty也能覆盖字段为NULL的情况。

九、关系字段类型运算符

以下运算符用于判断关系是否存在,字段类型包括:

  • type: 'hasOne'
  • type: 'hasMany'
  • type: 'belongsTo'
  • type: 'belongsToMany'

实现在 association.ts:

$exists— 有关系数据

repository.find({ filter: { author: { $exists: true, }, }, });

源码中直接映射为{ Op.not: null },即关系外键不为空。

$notExists— 无关系数据

repository.find({ filter: { author: { $notExists: true, }, }, });

源码中映射为{ Op.is: null },即关系外键为空。例如筛选出没有关联作者(author为空)的图书记录。

十、扩展自定义运算符:db.registerOperators()

NocoBase 的运算符体系是开放可扩展的。Database类提供了registerOperators()方法,用于注册自定义运算符:

// packages/core/database/src/database.ts#L764-L768 registerOperators(operators: MapOf<OperatorFunc>) { for (const [key, operator] of Object.entries(operators)) { this.operators.set(key, operator); } }

每个运算符本质上是一个函数,接收(value, ctx)两个参数,返回 Sequelize 可识别的查询条件对象(Op符号组合、Sequelize.literal原生 SQL 片段等)。ctx中携带了字段路径、模型、数据库实例等上下文信息(如 string.ts 中的ctx.db.sequelize.getDialect()ctx.fullNamectx.fieldName)。

扩展示例——注册一个判断字符串长度为指定值的运算符:

import { Op } from 'sequelize'; db.registerOperators({ $lengthOf(value) { // 返回 Sequelize 查询条件(此处为示意,实际需结合具体数据库方言实现) return { [Op.like]: `${'_'.repeat(value)}`, }; }, }); // 使用 repository.find({ filter: { title: { $lengthOf: 4, }, }, });
// 自定义运算符还可以利用 ctx 访问数据库方言,实现跨库逻辑 db.registerOperators({ $customOperator(value, ctx) { const dialect = ctx.db.sequelize.getDialect(); // ...根据 dialect 返回不同实现 }, });

扩展点的完整实现可查阅 database.ts 以及 operators/index.ts 中内置运算符的聚合方式,二者共同构成了“内置兜底 + 自定义覆盖”的可插拔运算符机制。

十一、与其他查询 API 的组合使用

filter参数同样适用于findOnefindAndCountcount等 Repository 方法:

// findOne:查询第一条匹配记录 const book = await repository.findOne({ filter: { title: { $eq: '春秋', }, }, }); // findAndCount:同时返回数据与总数,常用于分页列表 const [rows, total] = await repository.findAndCount({ filter: { $and: [ { price: { $between: [50, 200] } }, { publishedAt: { $dateOn: '2021-01-01' } }, ], }, offset: 0, limit: 20, }); // count:仅统计数量 const count = await repository.count({ filter: { isPublished: { $isTruly: true, }, }, });

这些查询方法的行为在 repository.test.ts、filter.test.ts 与 filter-match.ts 中有大量测试与实现可对照验证。

十二、小结:运算符速查表

分类运算符相当于 SQL适用字段
通用$eq/$ne=/!=任意
通用$is/$notIS/IS NOT任意(常配null
通用$col列与列比较任意
通用$in/$notInIN/NOT IN任意
通用$empty/$notEmpty空值/空串/空数组判断任意
逻辑$and/$orAND/OR条件组合
布尔$isFalsy/$isTruly真假判断boolean
数字$gt/$gte/$lt/$lte>/>=/</<=数字
数字$between/$notBetweenBETWEEN/NOT BETWEEN数字
字符串$includes/$notIncludes包含/不包含子串string
字符串$startsWith/$notStatsWith前缀/非前缀string
字符串$endsWith/$notEndsWith后缀/非后缀string
字符串$like/$notLikeLIKE/NOT LIKEstring
字符串$iLike/$notILikeILIKE/NOT ILIKE(仅 PG)string
字符串$regexp/$notRegexp/$iRegexp/$notIRegexpREGEXP/~*等(仅 PG)string
日期$dateOn/$dateNotOn当天/非当天date
日期$dateBefore/$dateNotBefore</>=date
日期$dateAfter/$dateNotAfter>/<=date
数组$match/$notMatch/$anyOf/$noneOf数组包含匹配array
数组$arrayEmpty/$arrayNotEmpty空/非空数组array
关系$exists/$notExists外键非空/为空关系字段

掌握这五大类 40+ 运算符及其底层实现逻辑,你就能在 NocoBase 中构建从简单等值查询到跨字段、跨关系、跨数据库方言的任意过滤条件,并在需要时通过registerOperators()无缝扩展自己的查询能力。

【免费下载链接】nocobaseNocoBase is an open-source AI + no-code platform for building business systems fast. Instead of generating everything from scratch, AI works on top of production-proven infrastructure and a WYSIWYG no-code interface, so you get both speed and reliability.项目地址: https://gitcode.com/GitHub_Trending/no/nocobase

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 18:31:02

Doris Remote Catalog 实战:性能测试、常见坑与选型指南

如果你用过Doris&#xff0c;大概率遇到过这种场景&#xff1a;业务方在群里喊&#xff0c;“能帮我看下为什么数仓里没有XX表的数据吗&#xff1f;”你点开Hive一看&#xff0c;原始数据明明在&#xff0c;可就为了一个临时查询&#xff0c;要么现场丢一个Spark任务&#xff0…

作者头像 李华
网站建设 2026/9/14 18:29:51

如何在编辑器中为 Authelia 的 YAML 配置启用 JSON Schema 校验

如何在编辑器中为 Authelia 的 YAML 配置启用 JSON Schema 校验 【免费下载链接】authelia The Single Sign-On Multi-Factor portal for web apps. OpenID Certified™ and Post-Quantum Cryptography Ready. 项目地址: https://gitcode.com/GitHub_Trending/au/authelia …

作者头像 李华
网站建设 2026/9/14 18:27:32

基于YOLOv8的鱼类疾病检测系统:从数据标注到模型部署全流程解析

简介&#xff1a;基于Python和YOLOv8的鱼类疾病检测系统&#xff0c;面向水产养殖从业者、计算机视觉学习者和算法工程师&#xff0c;借助深度学习实现鱼类疾病自动化识别&#xff0c;解决人工巡检效率低、漏检率高的问题。系统覆盖22种鱼类及其常见病症&#xff08;如出血、眼…

作者头像 李华
网站建设 2026/9/14 18:26:42

FPGA HDMI发射器IP核深度解析:从AXI-Stream到EDID动态适配

简介&#xff1a;本资源是一个面向FPGA开发者的HDMI输出IP核工程包&#xff0c;专为Xilinx全系列器件&#xff08;从Artix到Virtex&#xff09;设计&#xff0c;解决高清音视频信号在数字系统中可靠编码与物理层传输的关键问题&#xff0c;适用于多媒体终端、显示控制、嵌入式视…

作者头像 李华
网站建设 2026/9/14 18:25:33

Kvasir-SEG息肉检测数据集:YOLO格式解析与医疗目标检测实战

简介&#xff1a;本资源是面向医学图像AI初学者与目标检测实践者的YOLO格式息肉检测专用数据集&#xff0c;基于Kvasir-SEG公开数据构建&#xff0c;聚焦单类别&#xff08;息肉&#xff09;的端到端训练需求&#xff0c;适用于结肠镜辅助诊断模型开发、课程实验及竞赛基线训练…

作者头像 李华