后端微服务云原生【免费下载链接】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 仓库中 functional-crud 规格文档 展开介绍函数式 CRUD 路由工厂defineCrudRoutes()的设计目标、使用方式与底层实现。它解决的核心问题是在 Midway 函数式路由风格defineApi()下如何在不回退到 class controller、不依赖Crud()装饰器的情况下直接生成标准 CRUD 接口。读完本文你将掌握defineCrudRoutes()的完整配置项、与自定义业务路由的合并方法、它与 class-based CRUD 共享的底层核心以及查询协议、DTO 校验与错误语义等实战细节。一、为什么需要函数式 CRUDMidway 提供了两种 Web 开发风格传统的基于装饰器与 class controller 的风格以及函数式路由风格。CRUD 组件midwayjs/crud最早以Crud()装饰器形态提供声明式资源注册但在函数式路由风格下用户面临一个缺口要么为每个资源手写 CRUD handler要么回退到 class controller。functional-crud 规格 正是为填补这一缺口而定义系统需要提供函数式 CRUD 路由工厂使用户在 Midway 函数式路由风格中无需回退到 class controller 即可暴露标准 CRUD 接口。该规格明确了三条核心要求函数式 CRUD 路由工厂调用defineCrudRoutes({ model, service, dto, query })即可获得可用于生成标准 CRUD 路由的函数式定义无需定义 class controller 或使用Crud()。共享 CRUD 核心函数式 CRUD 与 class-based CRUD 复用同一套 CRUD core而不是复制一套平行实现确保查询协议、DTO 绑定、错误语义与删除策略保持一致。最小公开 API 面defineCrudRoutes()输出的应是可被defineApi()消费或合并的 route map避免引入与defineApi()并列的另一套函数式注册协议。二、最小 API 面defineCrudRoutes()的入口与输出形态2.1 从固定二级路径导入规格明确要求函数式 CRUD 的入口必须从midwayjs/crud/functional导入而不是从midwayjs/crud主入口导出。这样做的目的是保持主入口 API 面的稳定把函数式能力作为独立、可选的二级入口提供给用户。import { defineCrudRoutes } from midwayjs/crud/functional;2.2 输出的是 route factory而不是注册对象规格中最关键的设计约束是defineCrudRoutes()返回一个接收api并产出 route map 的工厂函数而不是一个独立的注册对象。这一设计避免了让用户学习另一套与defineApi()并列的函数式注册协议。从源码看这一契约被精确定义在 interface.ts 中export type FunctionalCrudRouteFactoryT any ( api: FunctionalApiBuilder ) Recordstring, FunctionalRouteBuilder | FunctionalRouteDefinition { __entityType__?: T; };其中FunctionalApiBuilder是对defineApi()回调中api对象的最小抽象export interface FunctionalApiBuilder { get(path?: string): FunctionalRouteBuilder; post(path?: string): FunctionalRouteBuilder; patch(path?: string): FunctionalRouteBuilder; put(path?: string): FunctionalRouteBuilder; delete(path?: string): FunctionalRouteBuilder; }对应的入口实现在 functional/index.ts 中仅有十几行代码直接委托给 route builderexport function defineCrudRoutesT any( options: CrudOptions | FunctionalCrudOptions ): FunctionalCrudRouteFactoryT { return buildFunctionalCrudRoutesT(options); }这体现了规格中「系统不要求用户学习另一套与defineApi()并列的函数式注册协议」的要求工厂函数本身不注册任何东西只有被调用传入api后才产出路由 map。三、与defineApi()协同CRUD 与自定义业务路由并存3.1 基本用法函数式 CRUD 的典型用法是在defineApi(/prefix, api ({ ... }))中展开defineCrudRoutes()的结果并让 CRUD 默认路由与同一defineApi()中的自定义业务路由并存import { defineApi } from midwayjs/hooks; // 函数式路由入口 import { defineCrudRoutes } from midwayjs/crud/functional; import { User } from ./entity/user; import { UserService } from ./service/user; export default defineApi(/api/user, api { // 展开 CRUD 默认路由 const crudRoutes defineCrudRoutes({ model: User, service: UserService, }); return { ...crudRoutes(api), // 自定义业务路由与 CRUD 默认路由并存 resetPassword: api.post(/:id/reset-password).handle(async ({ params }) { // 自定义逻辑 return { ok: true }; }), }; });3.2 测试用例佐证仓库中的 functional.test.ts 直接验证了这一协同场景构造一个模拟apibuilder展开crudRoutes(api)后追加自定义的resetPassword路由断言最终路由集合为[list, detail, create, update, delete, resetPassword]且自定义路由的method为post、path为/:id/reset-password。这正是规格中「用户可在同一函数式路由对象中组合标准 CRUD 与非标准动作」的落地验证。3.3 组合原理route map 合并crudRoutes(api)返回的是以路由名为 key 的 route maplist、detail、create、update、delete因此在对象字面量中通过...crudRoutes(api)展开即可与手写路由合并。整个流程中用户没有接触任何独立的路由注册中心——函数式 CRUD 的结果直接依附现有 functional routing 生命周期满足规格中「系统不要求为函数式 CRUD 建立独立的路由注册中心」的要求。四、共享 CRUD Core函数式与 class-based 复用同一套核心规格强调函数式 CRUD 与 class-based CRUD 必须复用同一套 CRUD core而不是复制平行实现。源码验证了这一设计——functional/routeBuilder.ts 中的createFunctionalCrudRouteMap()直接复用了 class-based 路线上的两个关键函数buildCrudRoutes(options)生成默认路由表来自 routeBuilder.tscreateCrudRouteHandler(route.name, { [CRUD_SERVICE_KEY]: service }, options)创建运行时 handler转发到绑定的 CRUD service。for (const route of buildCrudRoutes(options)) { // ... routes[route.name] builder.handle(async ({ input, ctx }: any) { const service await requestContext.getAsync(options.service as any); return createCrudRouteHandler(route.name, { [CRUD_SERVICE_KEY]: service }, options)({ params: input?.params ?? ctx?.params ?? {}, query: input?.query ?? ctx?.query ?? {}, body: input?.body ?? ctx?.request?.body, ctx, }); }); }这意味着无论用户使用Crud()还是defineCrudRoutes()两种暴露方式最终都调用同一套CrudServiceT契约list、findOne、create、update、delete默认的查询协议、DTO 绑定、错误语义和删除策略保持一致——这正是规格中「共享同一 CRUD service 语义」的实现依据。4.1 service 的获取方式在函数式场景中options.service是一个 class 构造器例如UserService运行时通过请求上下文的 IoC 容器解析得到实例const requestContext ctx?.requestContext; if (!requestContext?.getAsync || !options.service) { throw new CrudConfigError( Functional CRUD routes require ctx.requestContext and options.service ); } const service await requestContext.getAsync(options.service as any);如果缺少ctx.requestContext或options.service系统会直接抛出CrudConfigError而不是静默失败——规格与测试functional.test.ts中handler({ ctx: {} })断言抛出CrudConfigError均验证了这一行为。五、完整配置项复用CrudOptions规格要求函数式 CRUD 尽量复用 class-based 的CrudOptions配置仅在确有必要时增加少量扩展字段。当前实现中FunctionalCrudOptions CrudOptions见 interface.ts即函数式场景没有引入额外配置字段。完整配置结构如下export interface CrudOptions { model: new (...args: any[]) any; // 实体模型 service?: new (...args: any[]) CrudServiceAdapterany; // CRUD service id?: string; // 主键字段名默认 id dto?: { create?: new (...args: any[]) any; // 创建请求体 DTO update?: new (...args: any[]) any; // 更新请求体 DTO replace?: new (...args: any[]) any; // 整体替换 DTO query?: new (...args: any[]) any; // 查询 DTO }; routes?: { only?: CrudRouteName[]; // 只保留指定路由 exclude?: CrudRouteName[]; // 排除指定路由 overrides?: PartialRecordCrudRouteName, CrudRouteOverride; }; query?: { maxLimit?: number; // 分页上限 defaultLimit?: number; // 默认分页大小 sortable?: string[]; // 可排序字段白名单 filterable?: string[]; // 可过滤字段白名单 searchable?: string[]; // 可搜索字段白名单 join?: string[]; // 可关联展开白名单 defaultSort?: CrudSort[]; // 默认排序 }; serialize?: { get?: new (...args: any[]) any; // 详情响应序列化 list?: new (...args: any[]) any; // 列表响应序列化 create?: new (...args: any[]) any; update?: new (...args: any[]) any; }; delete?: { mode?: hard | soft; // 删除策略 }; }一个贴合实际的示例defineCrudRoutes({ model: User, service: UserService, query: { defaultLimit: 10, maxLimit: 100, sortable: [createdAt, name], filterable: [status, role], searchable: [name, email], defaultSort: [{ field: createdAt, order: DESC }], }, delete: { mode: soft }, })六、默认路由矩阵与路由裁剪6.1 稳定的默认路由矩阵routeBuilder.ts 中定义了完整的默认路由表const DEFAULT_ROUTE_DEFINITIONS: RecordCrudRouteName, CrudRouteDefinition { list: { name: list, method: GET, path: / }, detail: { name: detail, method: GET, path: /:id }, create: { name: create, method: POST, path: / }, update: { name: update, method: PATCH, path: /:id }, replace: { name: replace, method: PUT, path: /:id }, delete: { name: delete, method: DELETE, path: /:id }, createMany: { name: createMany, method: POST, path: /bulk }, deleteMany: { name: deleteMany, method: DELETE, path: /bulk }, };其中getEnabledCrudRoutes()定义了默认启用集为list / detail / create / update / delete五个资源路由HTTP 方法与路径分别映射为GET /、GET /:id、POST /、PATCH /:id、DELETE /:id。首阶段仅支持单一路径参数:id单主键优先不要求支持复合主键路由模板。6.2 通过配置裁剪routes.only与routes.exclude用于限制可用路由指定only时只注册列表中的路由否则使用默认五路由集合并剔除exclude中列出的路由。被排除的默认路由不再暴露 HTTP 入口。// 只暴露只读接口 defineCrudRoutes({ model: User, service: UserService, routes: { only: [list, detail] }, });6.3 运行时 handler 的分发逻辑createCrudRouteHandler()是函数式与 class-based 共用的运行时核心按路由名分发到 service 方法list先做校验parseCrudQuery(payload.query, options)解析查询参数后调用service.list()detailparseCrudId()解析:id后调用service.findOne()实体不存在时抛出CrudNotFoundError404create/update/replace校验后分别调用对应 service 方法delete直接调用service.delete()。由于该 handler 同时被createCrudControllerMethod()class-based 路径与函数式 CRUD 复用两种风格在请求处理层的语义天然一致。七、统一查询协议分页、排序、过滤与关联7.1CrudQuery与稳定分页结构客户端对列表接口传入page、limit、sort、filter、search、join、fields等参数后系统解析为统一的CrudQueryexport interface CrudQuery { page: number; limit: number; sort: CrudSort[]; filters: CrudFilter[]; search?: string; joins?: string[]; fields?: string[]; }列表结果采用稳定的分页对象而非裸数组CrudPageResultT包含data与meta其中meta至少包含page、limit、total、pageCount、hasNext、hasPrev六个字段详见 interface.ts 中CrudPageMeta定义。limit行为受资源声明的defaultLimit与maxLimit约束。7.2 filter operator 白名单首阶段仅支持 8 种过滤操作符对未支持的 operator 返回 400 错误export type CrudFilterOperator | eq | ne | gt | gte | lt | lte | in | like;7.3 URL 参数格式约定sort、filter、join使用重复 query key表达多值fields使用逗号分隔字符串表达字段集合不要求深层嵌套对象 query 语法首阶段join仅支持一层关系名包含.的多层路径返回 400 错误search采用固定 OR 模糊匹配语义每个字段的基础匹配与likeoperator 一致若资源未声明searchable传入search直接返回 400 错误不会忽略参数继续执行。7.4 白名单与非法片段均返回 400未在白名单中的sort、filter或join字段以及格式非法的sort/filter片段系统均返回 400 错误且错误信息明确指出被拒绝的字段与原因不会静默忽略。这在 query.test.ts 等测试中均有覆盖。八、DTO 校验、序列化与 Swagger8.1 DTO 驱动的校验dto.create与dto.update分别驱动POST与PATCH路由的请求体验证校验失败行为与现有 validation 组件保持一致dto.query绑定列表查询。更新 DTO 可通过PartialDto()等派生工具复用创建 DTO 的元数据无需手写重复校验规则。默认路由的 DTO 绑定规则固定为create绑定dto.create、update绑定dto.update、list绑定dto.query。响应形态约定为list返回分页对象detail/create/update返回单资源对象delete返回空响应。校验逻辑实现在 validation.ts。8.2 响应序列化声明serialize配置后CRUD 路由响应按对应 DTO 或序列化模型输出未声明时保持与现有 handler 返回值一致的默认序列化行为。8.3 Swagger 可见性启用midwayjs/swagger时自动生成的 CRUD 路由会被纳入 Swagger 文档可区分列表、详情、创建、更新、删除等操作声明 query 规则和 DTO 后文档中包含对应的 query 参数、路径参数和请求体模型且文档模型与实际运行时约束保持一致相关实现见 swagger.ts测试覆盖见 validation-swagger.test.ts。九、可预测的错误语义自动生成的 CRUD 路由提供统一、可预测的错误语义场景响应详情、更新或删除访问不存在的资源主键404CrudNotFoundError非法分页、排序、过滤或 join 参数400错误载荷包含字段级原因底层 ORM/数据库抛出可识别的约束异常通过适配层映射为稳定的上层异常不直接暴露底层驱动细节函数式场景缺少ctx.requestContext或options.service启动/调用阶段直接抛出CrudConfigError错误类型定义见 error.ts。十、扩展点与当前边界10.1 服务层按方法粒度覆写用户可继承官方 CRUD service 基类如TypeOrmCrudServiceT、SequelizeCrudServiceT、MongooseCrudServiceT并覆写单个方法如create该资源只替换对应数据访问逻辑其他未覆写方法继续使用默认 CRUD 行为。业务层也可以在普通 Service 中组合 CRUD service 与其他领域服务构建非标准资源流程。10.2 删除策略TypeORM 适配器默认执行硬删除若需软删除通过delete.mode soft显式开启此时默认list与detail查询不再返回已软删数据。若底层 ORM 适配器或实体不具备软删除能力系统返回明确错误而非静默降级为硬删除。10.3 鉴权与中间件CRUD 路由上可继续挂载现有 Guard、Middleware 或其他 Web 装饰器自动生成的 CRUD 路由仍参与现有请求处理链不要求用户改用独立的鉴权模型。10.4 函数式特有的 fallback builderfunctional/routeBuilder.ts 中还实现了一个createFallbackBuilder()当传入的api对象缺少某个 HTTP method 方法如api.post不存在时自动构造一个最小 builder 兜底保证 route map 仍能产出带method、path、handler的路由定义。这一细节使defineCrudRoutes()对自定义或简化的api实现具备更好的兼容性测试见functional.test.ts中的 fallback 用例。10.5 当前边界首阶段默认路由仅支持单主键:id不要求支持复合主键路由模板首阶段join仅支持一层关系其他 ORM如 MikroORM、Leoric通过实现相同 CRUD service 契约接入现有 CRUD API 无需改变仓库中已存在mikro、mongoose、sequelize、typeorm四套适配器目录见 packages/crud/src。十一、小结defineCrudRoutes()是 Midway 函数式路由与 CRUD 组件之间的桥梁它从midwayjs/crud/functional导入返回可被defineApi()消费的 route factory最小化 API 面它在运行时复用buildCrudRoutes()与createCrudRouteHandler()与 class-basedCrud()共享同一套 CRUD core 语义它完整继承CrudOptions配置体系支持路由裁剪、查询白名单、DTO 校验、软删除与 Swagger 集成它通过...crudRoutes(api)展开方式与自定义业务路由自然并存让函数式风格下的资源接口开发保持与手写路由一致的体验。对于希望在函数式路由风格下快速搭建标准 REST 资源接口的 Midway 开发者defineCrudRoutes()提供了「零 class controller、零Crud()」的轻量方案且其行为与 class-based CRUD 完全对齐切换风格无需重新学习一套协议。赞分享后端微服务云原生【免费下载链接】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点击查看免费下载相关推荐paascloud-master代码生成器使用指南快速构建CRUD接口paascloud master代码生成器使用指南快速构建CRUD接口 paascloud master作为Spring Cloud微服务架构的实战项目提供后端微服务电商认证鉴权Midway CRUD 组件深度解析从 Service 优先核心到声明式 REST 资源生成Midway CRUD 组件深度解析从 Service 优先核心到声明式 REST 资源生成 midwayjs/crud 是 Midway 面向资源型接口提后端微服务云原生Midway Functional Web Routing API 设计指南defineApi 链式 DSL、纯函数式服务与 React/Vue 前后端一体化开发Midway Functional Web Routing API 设计指南defineApi 链式 DSL、纯函数式服务与 React/Vue 前后端一体化后端微服务云原生上一篇从0到1部署Laguna-M.1-nvfp4硬件要求、环境配置与常见问题解决下一篇终极指南如何快速安装和管理Pock Touch Bar小部件创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考