
后端微服务云原生【免费下载链接】midway A Node.js Serverless Framework for front-end/full-stack developers. Build the application for next decade. Works on AWS, Alibaba Cloud, Tencent Cloud and traditional VM/Container. Super easy integrate with React and Vue. 项目地址https://gitcode.com/gh_mirrors/mi/midway点击查看免费下载导读本文基于 Midway 开源仓库中的 CRUD 组件官方文档结合 packages/crud 下的源码与测试系统讲解midwayjs/crud的定位、核心抽象、四种数据库适配、类式/函数式路由生成、统一查询协议、DTO 校验、Swagger 集成与软删除等完整能力。读完本文你将掌握如何在 Midway 项目中用最少的代码收敛资源型接口的重复劳动同时理解组件内部三层架构CrudService 抽象、数据库适配层、HTTP 暴露层是如何协同工作的并能判断什么场景适合使用、什么场景应该绕开它。组件定位它不是自动生成接口的脚手架midwayjs/crud本质上是一套面向资源的标准 CRUD 能力而不仅仅是接口生成器。官方文档给出的能力清单包括统一的增删改查 service 抽象统一的分页 / 排序 / 过滤 / 搜索协议可选的 REST 路由快捷生成与现有 validation / swagger / web 路由体系的接入如果你经常在不同模块里重复编写findAndCount、save、update、delete以及手动解析page、limit、sort还为每个资源写一套几乎一样的 Controller那么这个组件就是用来把这些重复劳动收起来的工具。适用性信息描述是否支持可用于标准项目✅可用于 Serverless❌可用于一体化✅包含独立主框架❌包含独立日志❌它能做什么、不做什么一句话概括先提供一个可复用的 CRUD service再按需把它暴露成 HTTP 接口。因此它支持两种使用方式只把它当数据访问层能力来用——适合业务逻辑比较复杂、不想让组件替你生成接口的场景把它当接口快捷层来用——适合资源型接口很多、希望快速拿到统一 REST API 的场景。它解决的是资源型接口的重复代码问题但不会替代你的业务 service。复杂业务下单检查库存/优惠券/支付状态、创建用户时同步多个系统、删除资源前的权限与状态机校验依然应该写在你自己的 service 里CRUD 组件只是提供一个稳定的资源操作基座。核心概念三层架构在深入代码之前先理解三个层次。这些层次在 packages/crud/src/interface.ts 中都有对应的类型定义。1.CrudServiceT标准资源仓储接口CrudServiceT是组件最核心的抽象定义了统一的资源操作接口见 interface.ts#L103-L110list(query): PromiseCrudPageResultT findOne(id): PromiseT | null create(data): PromiseT update(id, data): PromiseT replace?(id, data): PromiseT // 可选默认回退到 update delete(id): Promisevoid可以把它理解成一个标准化的资源仓储服务接口。2. 数据库适配层四个官方适配基类Midway CRUD 目前提供 4 个官方适配基类分别对应不同数据访问组件TypeOrmCrudServiceT源码typeorm/service.tsMikroCrudServiceT源码mikro/service.tsSequelizeCrudServiceT源码sequelize/service.tsMongooseCrudServiceT源码mongoose/service.ts它们都继承同一个抽象基类BaseCrudServiceT源码service.ts实现了相同的 CRUD 核心接口但底层分别对接不同的数据访问组件。BaseCrudService中封装了分页元数据计算normalizePageMeta、实体存在性断言assertEntityFound、删除模式解析resolveDeleteMode等公共逻辑。3. HTTP 暴露层可选的壳如果希望快速生成路由可以使用两种方式类式Crud()装饰器函数式defineCrudRoutes()工厂这两种方式都只是把同一个CrudService暴露成 HTTP 接口不会生成第二套独立逻辑。从源码看类式入口在 decorator.ts通过saveModule与saveClassMetadata记录 CRUD 元数据函数式入口在 functional/index.ts。什么时候适合用、什么时候不适合适合大量资源型接口结构相似列表查询都需要统一分页、排序、过滤想减少重复的 Repository / Model 调用代码想让不同模块的 API 风格保持一致不适合主要是复杂工作流不是标准资源接口一个接口需要跨多个聚合、多个事务、多个外部系统你希望所有查询语义都完全自由不接受统一约束如果一个模块的核心不是资源管理而是复杂业务流程那更适合直接写普通 Controller Service而不是强行套 CRUD。安装依赖先安装 CRUD 组件本身$ npm i midwayjs/crud4 --save然后根据你使用的数据库组件安装对应依赖# TypeORM $ npm i midwayjs/typeorm4 typeorm^1.0.0 --save # MikroORM $ npm i midwayjs/mikro4 mikro-orm/core^6 --save # Sequelize $ npm i midwayjs/sequelize4 sequelize sequelize-typescript --save # Mongoose $ npm i midwayjs/mongoose4 mongoose --save或者在package.json中增加如下依赖后重新安装{ dependencies: { midwayjs/crud: ^4.0.0, midwayjs/typeorm: ^4.0.0, midwayjs/mikro: ^4.0.0, midwayjs/sequelize: ^4.0.0, midwayjs/mongoose: ^4.0.0, typeorm: ^1.0.0, mikro-orm/core: ^6.4.5, sequelize: ^6.37.5, sequelize-typescript: ^2.1.6, mongoose: ^8.9.5 } }从仓库中的 package.json 可以看到组件本身只把midwayjs/core作为运行时依赖四种 ORM 组件均作为 devDependencies 用于测试验证测试中使用了typeorm1.1.0、sequelize6.37.8、mongoose8.24.1等因此你在业务项目里按需安装对应 ORM 依赖即可。启用组件在src/configuration.ts中引入组件。下面以koa typeorm crud为例import { Configuration } from midwayjs/core; import * as koa from midwayjs/koa; import * as typeorm from midwayjs/typeorm; import * as crud from midwayjs/crud; Configuration({ imports: [ koa, typeorm, crud, ], }) export class MainConfiguration {}如果你使用其他数据库组件只需要把对应组件加到imports中即可。配置加载时发生了什么CrudConfiguration源码configuration.ts会在onConfigLoad阶段扫描所有带Crud()元数据的模块依次执行ensureControllerMetadata校验类上同时声明了Controller()否则抛出CrudConfigErrorensurePrototypeMethods为未手写实现的list/detail/create/update/delete等方法注入默认实现ensureRouteMetadata把默认路由元数据写入现有WEB_ROUTER_KEY元数据链与 Midway 原生路由体系打通ensureParamMetadata为各路由补齐参数注入元数据query、param、bodyensureSwaggerMetadata为各路由补齐 Swagger 元数据。这一点在 configuration.test.ts 中有测试覆盖Crud()修饰的类如果没有Controller()onConfigLoad会直接报错。入门先从 service-only 开始对于新手来说最容易理解的方式不是先生成路由而是先把它当作一个可复用的数据服务。这也是官方推荐的理解顺序因为这样你能先理解组件真正提供的核心是什么业务代码应该写在哪里路由层只是一个可选外壳如果一上来就只看Crud()很容易误以为这只是一个自动生成接口的装饰器。最小 TypeORM 示例先定义一个资源 serviceimport { Provide } from midwayjs/core; import { InjectEntityModel } from midwayjs/typeorm; import { TypeOrmCrudService } from midwayjs/crud/typeorm; import { Repository } from typeorm; import { UserEntity } from ../entity/user; Provide() export class UserCrudService extends TypeOrmCrudServiceUserEntity { InjectEntityModel(UserEntity) repo: RepositoryUserEntity; }然后在业务 service 里直接组合它import { Provide, Inject } from midwayjs/core; Provide() export class UserService { Inject() userCrudService: UserCrudService; async listUsers() { return this.userCrudService.list({ page: 1, limit: 20, sort: [], filters: [], }); } async createUser(input: CreateUserDTO) { return this.userCrudService.create(input); } }这时候你已经获得统一的 CRUD 能力但不会自动生成任何 HTTP 路由业务仍然由你自己的UserService负责组织这也是最适合复杂业务场景的用法。service-only.test.ts 中正好演示了这种组合UserDomainService通过构造注入UserCrudService完全不需要Crud()装饰器。其他数据库适配如果你的项目不是 TypeORM也可以使用其他官方适配。这四个适配基类的对外用法保持一致区别主要在于底层仓储注入方式和 ORM 行为。MikroORMimport { Provide } from midwayjs/core; import { InjectRepository } from midwayjs/mikro; import { MikroCrudService } from midwayjs/crud/mikro; Provide() export class UserCrudService extends MikroCrudServiceUserEntity { InjectRepository(UserEntity) repo; }Mikro 适配在底层通过findAndCount、persistAndFlush、assign、nativeDelete等仓储方法实现 CRUD见 mikro/service.ts。Sequelizeimport { Provide } from midwayjs/core; import { InjectRepository } from midwayjs/sequelize; import { SequelizeCrudService } from midwayjs/crud/sequelize; import { Repository } from sequelize-typescript; Provide() export class UserCrudService extends SequelizeCrudServiceUserModel { InjectRepository(UserModel) repo: RepositoryUserModel; }Sequelize 适配通过findAndCountAll、findByPk、create、update、destroy实现见 sequelize/service.ts。Mongooseimport { Inject, Provide } from midwayjs/core; import { MongooseDataSourceManager } from midwayjs/mongoose; import { MongooseCrudService } from midwayjs/crud/mongoose; Provide() export class UserCrudService extends MongooseCrudServiceUserDocument { Inject() mongooseDataSourceManager: MongooseDataSourceManager; async onReady() { this.repo this.mongooseDataSourceManager .getDataSource(default) .model(User); } }Mongoose 适配与 ORM 型适配有个显著差异它的默认主键字段是_id见 mongoose/service.ts#L141-L143其他适配默认都是id可通过CrudOptions.id覆盖。各适配器的底层差异适配基类列表查询更新策略删除策略主键默认值TypeOrmCrudServiceQueryBuilder getManyAndCountfindOnemergesavedelete/softDeleteidMikroCrudServicefindAndCountfindOneassignpersistAndFlushnativeDelete/ 赋值deletedAtidSequelizeCrudServicefindAndCountAllupdatefindByPkdestroyidMongooseCrudServicefindcountDocumentsfindOneAndUpdatedeleteOne/ 赋值deletedAt_id各适配器还提供了统一的错误映射例如 TypeORM 的23505唯一约束映射为 409CrudPersistenceError、EntityNotFoundError映射为 404CrudNotFoundError见 typeorm/utils.ts#L113-L140Mongoose 的11000唯一键错误同样映射为 409见 mongoose/utils.ts#L92-L110。这保证了不同数据库下错误语义的一致性。快速生成类式 REST 接口当你已经有一个CrudService之后如果希望快速生成标准资源型接口可以使用Crud()。最小类式示例import { Controller, Inject } from midwayjs/core; import { Crud } from midwayjs/crud; import { UserEntity } from ../entity/user; import { UserCrudService } from ../service/user.crud; Controller(/users) CrudUserEntity({ model: UserEntity, service: UserCrudService, }) export class UserController { Inject() crudService: UserCrudService; }默认会生成GET /usersGET /users/:idPOST /usersPATCH /users/:idDELETE /users/:id除了这五个默认路由源码中的路由定义表还预留了PUT /users/:idreplace、POST /users/bulkcreateMany、DELETE /users/bulkdeleteMany三类扩展路由见 routeBuilder.ts#L7-L16其中replace在类式模式下默认生成其余两个尚未在默认路由集合中启用。为什么还要Inject() crudService因为Crud()只是声明这个 Controller 是一个 CRUD 资源真正执行业务的是你绑定的crudService。也就是说Crud()负责生成默认路由crudService负责真正执行 CRUD 逻辑这是这个组件最重要的设计原则之一。从 routeBuilder.ts#L42-L108 可以看到运行时 handler 会从控制器实例上读取crudService常量CRUD_SERVICE_KEY见 constants.ts如果缺失会抛出CrudConfigError随后把解析好的 query/params/body 转发给 service 的对应方法。业务逻辑写在哪里不要把复杂业务逻辑塞到Crud()里。推荐做法是资源级默认行为写在UserCrudService复杂业务编排写在你自己的UserService/ Domain Service非标准动作写成普通路由方法例如Controller(/users) CrudUserEntity({ model: UserEntity, service: UserCrudService, }) export class UserController { Inject() crudService: UserCrudService; async create() { // 自定义事务、调用多个 service、做额外校验 } async resetPassword() { // 非标准资源动作 } }同名方法会优先使用你手写的实现因此你可以只覆写部分默认行为。这一点在源码中有明确保障ensurePrototypeMethods只对原型上不存在的方法注入默认实现见 configuration.ts#L68-L81configuration.test.ts 也验证了已存在的方法与路由元数据会被保留。裁剪默认路由如果你不想暴露所有默认路由可以通过routes.only或routes.exclude控制。CrudUserEntity({ model: UserEntity, service: UserCrudService, routes: { only: [list, detail, create], }, })这对于只能查、不能删或只开放后台管理的一部分动作的场景很有用。源码中getEnabledCrudRoutes的逻辑是优先取only列表未配置时默认启用list/detail/create/update/delete再统一剔除exclude中声明的路由见 routeBuilder.ts#L21-L28。函数式路由模式如果项目使用函数式 API而不是类式 Controller可以从midwayjs/crud/functional导入defineCrudRoutes()。最小示例import { defineApi } from midwayjs/core/functional; import { defineCrudRoutes } from midwayjs/crud/functional; import { UserEntity } from ../entity/user; import { UserCrudService } from ../service/user.crud; const crudRoutes defineCrudRoutesUserEntity({ model: UserEntity, service: UserCrudService, }); export default defineApi(/users, api ({ ...crudRoutes(api), }));和自定义动作一起使用函数式模式最常见的用法是把默认 CRUD 路由和自定义动作合并在同一个defineApi()里。export default defineApi(/users, api ({ ...crudRoutes(api), resetPassword: api .post(/:id/reset-password) .handle(async ({ input, ctx }) { return { ok: true }; }), }));这样可以让标准资源动作和业务动作共存在同一个资源路由下。functional.test.ts 验证了这种组合展开crudRoutes(api)得到list/detail/create/update/delete五个路由键再叠加自定义resetPassword动作。函数式模式的工作原理从 functional/routeBuilder.ts 源码看defineCrudRoutes会遍历buildCrudRoutes(options)生成的默认路由定义对每个路由通过apimethod.handle(...)构建出对应的函数式路由handler 内部从ctx.requestContext中getAsync(options.service)取出 service 实例再复用与类式完全相同的createCrudRouteHandler逻辑。因此函数式与类式共用同一套路由语义与查询协议只是暴露方式不同。查询协议列表接口使用统一的查询协议这是这个组件最重要的能力之一。支持的 query 参数参数说明是否可重复pagenumber页码从 1 开始否limitnumber每页条数会被maxLimit封顶否sortfield:ASC\|DESC排序字段必须在sortable白名单是filterfield\|\|operator\|\|value过滤字段必须在filterable白名单是searchkeyword搜索只作用于searchable白名单否joinrelation关联加载必须在join白名单是fieldsfield1,field2,...字段裁剪部分适配器支持否例如GET /users?page1limit20sortcreatedAt:DESCfilterstatus||eq||activesearchharryjoinprofile支持的过滤操作符首阶段支持 8 个操作符定义在 interface.ts#L11-L19校验在 queryParser.tseq等于ne不等于gt大于gte大于等于lt小于lte小于等于in在集合内值用逗号分隔like模糊匹配以 TypeORM 为例这些操作符会被翻译成Equal / Not / MoreThan / MoreThanOrEqual / LessThan / LessThanOrEqual / In / Like等 QueryBuilder 条件见 typeorm/utils.ts#L50-L77Mongoose 适配则翻译为$ne / $gt / $gte / $lt / $lte / $in / $regex等 MongoDB 操作符见 mongoose/utils.ts#L29-L48。查询协议的约束有意设计的安全边界这些约束是有意设计出来的用来保证资源接口的一致性sort字段必须在sortable白名单里filter字段必须在filterable白名单里search只会作用于searchable白名单join必须在join白名单里join首阶段只支持一层关系名不支持profile.company也就是说这个组件不会让客户端随意拼接任意字段查询而是让你在服务端声明资源允许暴露的查询能力。从 queryParser.ts 源码可以看到一整套严格的校验逻辑page/limit必须是正整数否则抛出Invalid page value等错误、sort必须是field:ASC|DESC格式、filter必须严格三段式且操作符在白名单内、嵌套 join 直接报Nested joins are not supported。同时limit会被maxLimit硬性封顶Math.min(parsedLimit, maxLimit)防止客户端请求超大分页。这些边界在 query.test.ts 中都有逐项测试覆盖。在Crud()中声明查询能力CrudUserEntity({ model: UserEntity, service: UserCrudService, query: { defaultLimit: 20, maxLimit: 100, sortable: [id, createdAt], filterable: [status], searchable: [name, email], join: [profile], }, })这样配置后查询行为就会按这个白名单执行。源码中的默认值是defaultLimit: 20、maxLimit: 100常量见 constants.ts#L14-L19如果你的资源没有配置query这两个默认值会自动生效。此外CrudOptions.query还支持defaultSort默认排序可用于给列表接口固定默认排序规则见 interface.ts#L83-L91。返回结构默认列表接口会返回统一的分页结构而不是直接返回数组类型定义见 interface.ts#L42-L54type CrudPageResultT { data: T[]; meta: { page: number; limit: number; total: number; pageCount: number; hasNext: boolean; hasPrev: boolean; }; };这能让前端和不同资源接口之间保持一致的分页消费方式。pageCount、hasNext、hasPrev的计算逻辑封装在BaseCrudService.normalizePageMeta中见 service.ts#L44-L59pageCount Math.ceil(total / limit) || 1hasNext page pageCounthasPrev page 1。其他默认返回规则detail返回单个资源对象找不到时抛 404CrudNotFoundErrorcreate返回创建后的资源对象update返回更新后的资源对象delete默认返回204 No Content从 routeBuilder.ts 的运行时 handler 可以看到detail在findOne返回空时会主动抛出CrudNotFoundError而在各适配器中update/delete对不存在的记录也会统一抛出 404 语义的错误如 Sequelize 适配在update影响行数为 0、destroy影响行数为 0 时抛出 404见 sequelize/service.ts#L60-L96。DTO、Validation 与 SwaggerCRUD 路由会尽量复用现有 Midway 组件而不是另起一套协议。DTO 绑定你可以在Crud()中声明四类 DTOdto.create创建请求体dto.update更新请求体dto.replace替换请求体dto.query列表查询参数例如CrudUserEntity({ model: UserEntity, service: UserCrudService, dto: { create: CreateUserDTO, update: UpdateUserDTO, query: UserQueryDTO, }, })从源码看DTO 绑定还影响路由的参数元数据声明了dto.query时list路由直接以该 DTO 作为 query 参数类型并生成一个 QUERY 注入点未声明时组件会为page/limit/sort/filter/search/join/fields七个 query 参数逐个生成 Swagger 参数元数据见 configuration.ts#L124-L153。Validation如果项目里启用了midwayjs/validation或兼容的 validation servicedto.create会用于创建请求体校验dto.update会用于更新请求体校验dto.replace会用于替换请求体校验dto.query会用于列表查询参数校验如果没有安装 validation 组件CRUD 本身不会强行报错而是只跳过这一步自动校验。这个可选降级行为体现在 validation.ts 中applyCrudValidation会先从ctx.requestContext尝试获取validationService获取不到就静默返回不做任何校验。这与 validation-swagger.test.ts 中validation service 不可用时 no-op的测试一致。Swagger自动生成的 CRUD 路由会复用现有 Web 元数据链所以 Swagger 组件可以识别到这些路由。也就是说默认路由会出现在 Swagger 文档里基础的 path / query / body / response 元数据会被自动补齐这意味着你不需要为每一个简单资源接口手动补一遍同样的 swagger 装饰器。从 swagger.ts 源码可以看到组件会为每个路由自动写入apiOperationsummary如List resources、Get resource detail、path 参数id、query 参数page/limit/sort/filter/search/join/fields、以及响应元数据create返回 201、delete返回 204、其余返回 200响应类型默认取model也可以通过serialize配置覆盖。serialize响应序列化CrudOptions还提供了serialize配置可为get/list/create/update四个动作分别指定响应类型见 interface.ts#L92-L97。例如不希望在 Swagger 中暴露实体内部字段时可以用专门的 GetDTO/CreateDTO 作为响应类型。响应类型的解析优先级是serialize.create || serialize.get || modelcreate、serialize.update || serialize.get || modelupdate/replace、serialize.get || modeldetail具体见 swagger.ts#L62-L78。软删除默认删除行为是硬删除常量CRUD_DEFAULT_DELETE_MODE hard见 constants.ts#L24。如果你希望某个资源使用软删除需要显式开启CrudUserEntity({ model: UserEntity, service: UserCrudService, delete: { mode: soft, }, })开启后DELETE /:id会走软删除list/detail默认排除已软删的数据如果底层实体或仓储不支持软删除会直接报错软删除在不同适配器中的实现TypeORM依赖实体上的DeleteDateColumn。delete调用repo.softDeletelist通过 QueryBuilder 追加deletedAt IS NULL条件detail在 where 中追加deletedAt: null。若实体没有isDeleteDate列会抛出CrudFeatureNotSupportedError见 typeorm/utils.ts#L82-L108。MikroORM软删除通过给实体assign一个deletedAt: new Date()并persistAndFlush实现查询时同样过滤deletedAt: null见 mikro/service.ts#L66-L109。Sequelize依赖模型的paranoid配置delete通过destroy完成Sequelize 在 paranoid 模型下自动转软删并会检查options.paranoid是否开启见 sequelize/utils.ts。Mongoose依赖 schema 上的deletedAt字段软删除通过findOneAndUpdate写入deletedAt并检查schema.paths.deletedAt是否存在见 mongoose/utils.ts#L78-L87。typeorm.test.ts 对软删除行为有完整验证仓库不支持软删除时delete/list/findOne都会抛CrudFeatureNotSupportedError支持时delete走softDeletelist追加deletedAt IS NULLdetail查询带deletedAt: null。为什么不是默认软删除因为软删除不是所有资源都适合会影响唯一键约束会影响查询逻辑会影响索引设计会影响后台数据管理所以这里采用的是显式开启的策略而不是默认偷偷帮你切换成软删。一个更完整的类式示例下面给一个稍完整一点的例子把前面的内容串起来import { Controller, Inject, Provide } from midwayjs/core; import { Crud } from midwayjs/crud; import { InjectEntityModel } from midwayjs/typeorm; import { TypeOrmCrudService } from midwayjs/crud/typeorm; import { Repository } from typeorm; Provide() export class UserCrudService extends TypeOrmCrudServiceUserEntity { InjectEntityModel(UserEntity) repo: RepositoryUserEntity; } Controller(/users) CrudUserEntity({ model: UserEntity, service: UserCrudService, dto: { create: CreateUserDTO, update: UpdateUserDTO, query: UserQueryDTO, }, query: { defaultLimit: 20, maxLimit: 100, sortable: [id, createdAt], filterable: [status], searchable: [name, email], join: [profile], }, delete: { mode: soft, }, }) export class UserController { Inject() crudService: UserCrudService; }这段代码的效果是生成一个/users资源接口自动获得标准 CRUD 路由自动使用统一查询协议自动按 DTO 做校验如果启用了 validation自动被 swagger 扫描删除逻辑使用软删除二级导出稳定入口组件提供这些稳定入口与 package.json 的exports字段一一对应入口内容midwayjs/crud核心类型 Crud()装饰器 错误类 查询解析工具midwayjs/crud/typeormTypeOrmCrudService适配midwayjs/crud/mikroMikroCrudService适配midwayjs/crud/sequelizeSequelizeCrudService适配midwayjs/crud/mongooseMongooseCrudService适配midwayjs/crud/functionaldefineCrudRoutes()函数式路由适配建议这样理解主入口核心类型 Crud()数据库二级入口各自的 CRUD service 适配functional函数式路由适配主入口还额外导出parseCrudId/parseCrudQuery两个查询解析工具以及BaseCrudService基类见 index.ts如果你需要在自己封装的 service 中复用查询协议解析逻辑可以直接引用。错误处理一览组件定义了一套稳定的错误体系见 error.ts全部继承自 Midway 统一错误基类并注册了crud错误码段错误类HTTP 状态触发场景CrudConfigError-Crud()缺少model/service、缺少Controller()、控制器缺少crudService绑定CrudQueryError400query 参数格式非法、字段不在白名单、操作符不支持CrudNotFoundError404资源不存在detail/update/delete 目标缺失CrudPersistenceError409唯一约束冲突、外键引用冲突等已知数据库错误CrudFeatureNotSupportedError-配置了软删除但实体/仓储不支持其中 400/404/409 会直接作为 HTTP 响应返回给客户端保证异常语义跨接口一致。最后的建议推荐的进阶学习路径如果你是第一次接触这个组件建议按这个顺序使用先学service-only把它当一个标准 CRUD service理解组件真正提供的核心是什么再用Crud()快速展开简单资源接口体会同名方法优先手写实现的覆写机制最后再用函数式模式或高级查询配置白名单、DTO、serialize、软删除按需组合。这样更容易理解它的边界也更不容易把复杂业务错误地塞进自动 CRUD 层。源码中 packages/crud/test 下的 17 个测试文件service-only.test.ts、query.test.ts、typeorm.test.ts、mikro.test.ts、sequelize.test.ts、mongoose.test.ts、functional.test.ts、validation-swagger.test.ts等覆盖了组件从查询解析、路由构建到各适配器的完整行为是理解组件边界与默认值的最佳参考资料。赞分享后端微服务云原生【免费下载链接】midway A Node.js Serverless Framework for front-end/full-stack developers. Build the application for next decade. Works on AWS, Alibaba Cloud, Tencent Cloud and traditional VM/Container. Super easy integrate with React and Vue. 项目地址https://gitcode.com/gh_mirrors/mi/midway点击查看免费下载相关推荐Midway 声明式 REST CRUD 组件midwayjs/crud 的 Service 基座、查询协议与路由适配层全解析Midway 声明式 REST CRUD 组件 midwayjs/crud 的 Service 基座、查询协议与路由适配层全解析 midwayjs/cru后端微服务云原生Midway 声明式 REST CRUD 组件全解析从 CrudService 基座到自动路由生成Midway 声明式 REST CRUD 组件全解析从 CrudService 基座到自动路由生成 导读 本文围绕 Midway 仓库中 midwayjs/后端微服务云原生Midway 函数式 CRUD 指南用 defineCrudRoutes() 在 defineApi() 中快速生成标准 REST 接口Midway 函数式 CRUD 指南用 defineCrudRoutes 在 defineApi 中快速生成标准 REST 接口 导读 本文围绕 Midway后端微服务云原生上一篇Hy3推理模式切换指南如何用reasoning_effort参数平衡速度与精度下一篇Capybara测试环境变量配置不同环境的测试创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考