后端【免费下载链接】express-validatorAn express.js middleware for validator.js.项目地址https://gitcode.com/gh_mirrors/ex/express-validator点击查看免费下载express-validator 的核心设计是声明式的——把校验链当作中间件直接挂进 Express 路由即可自动执行。但现实业务中我们常常需要把验证的执行时机和流程控制握在自己手里。本文以 v6.5.0 官方指南《Running validations imperatively》为骨架系统讲解如何通过校验链与净化链上统一的run(req)方法手动触发验证、如何用validationResult(req)收集错误并结合仓库源码剖析其底层实现帮助你写出可复用的自定义校验中间件、按条件触发的动态校验等实战方案。从声明式到命令式为什么需要run(req)express-validator 默认推崇声明式用法也就是把校验链直接作为 Express 中间件传入路由处理器框架会在请求到达时自动运行这些验证const { body } require(express-validator); app.post(/api/create-user, [ body(email).isEmail(), body(password).isLength({ min: 6 }), ], (req, res) { // 请求到这里时校验已经自动完成 });这种模式简洁高效大多数 API 在直接传给路由处理器时表现最佳。但有些场景需要开发者自己掌握校验的运行时机希望复用同一套校验逻辑于多个路由并统一封装错误响应格式校验规则依赖运行时条件例如只有提交了密码才校验确认密码需要控制校验的并发/串行顺序或提前短路后续校验。为此express-validator 提供了一条命令式入口校验链和净化链上都有run(req)方法。官方文档指出该方法同时存在于验证链与净化链上。调用它等于把运行验证的控制权从框架手里交还给你的中间件或路由处理器。从仓库源码看run(req)并非校验链独有的能力而是通过一个名为ContextRunner的接口抽象出来的统一行为。当前版本文档中这样定义它interface ContextRunner { run(req: Request, options?: { dryRun: boolean }): PromiseResult; }ContextRunner是所有会执行某种校验/净化逻辑的中间件共同实现的接口返回一个专属于该验证链/中间件的Result对象详见 docs/api/misc.md。ValidationChain、checkExact()、checkSchema()、oneOf()返回的中间件都实现了该接口因此它们的run(req)语义完全一致。run(req)的底层执行机制理解了接口定义后再看它的实现能帮助你准确预判调用run(req)后的行为。核心实现位于 src/chain/context-runner-impl.ts 的ContextRunnerImpl.run()构建上下文Context如果持有的是ContextBuilder先调用.build()生成校验上下文校验上下文里记录了目标字段、请求位置body/query/params/headers/cookies、字段实例以及一串待执行的校验/净化条目context items。短路检查如果当前请求上已有的上下文中存在bail且已有错误即请求级.bail()生效直接返回空结果不再执行本链对应ValidationHalt机制。字段选择通过selectFields()按字段路径与位置从req中取出待校验的值挂到上下文上。逐条目执行遍历上下文栈中的每个校验/净化条目对每个字段实例并行执行。若某个条目抛出ValidationHalt例如.bail()该字段实例后续条目被跳过。结果持久化除非传入了dryRun: true否则会将这条上下文的执行结果追加到请求对象上并同步净化产生的字段新值。关键点在于第 5 步源码中run()结束时会执行internalReq[contextsKey] (internalReq[contextsKey] || []).concat(context);其中contextsKey是express-validator#contexts见 src/base.ts。这正是校验结果被持久化到req的实现证据它决定了两个重要行为之后调用validationResult(req)会包含这次手动运行的验证结果链中的净化器如body(message).trim()会直接更新req.body.message与声明式中间件行为一致。如果传入options.dryRun: true则只执行校验并返回结果不写入req也不影响validationResult(req)的返回内容。官方示例展示了 dryRun 的精确语义const usernameResult await check(username).notEmpty().run(req, { dryRun: true }); const passwordResult await check(password).notEmpty().run(req, { dryRun: false }); const result validationResult(req); // result 包含 passwordResult 的错误但不包含 usernameResult 的错误示例一标准化的验证错误响应原文档给出的第一个典型场景是把一组校验链封装成可复用的自定义中间件validate统一输出400错误响应。这也是命令式校验最常见的价值所在——用一份代码统一所有路由的校验入口和错误格式// 可被多个路由复用的校验中间件 const { validationResult } require(express-validator); const validate validations { return async (req, res, next) { await Promise.all(validations.map(validation validation.run(req))); const errors validationResult(req); if (errors.isEmpty()) { return next(); } res.status(400).json({ errors: errors.array() }); }; }; app.post(/api/create-user, validate([ body(email).isEmail(), body(password).isLength({ min: 6 }) ]), async (req, res, next) { // 走到这里说明请求没有任何校验错误 const user await User.create({ ... }); });逐行拆解这段代码的要点validations.map(validation validation.run(req))对传入的每个校验链调用run(req)。这里用Promise.all让所有校验链并行执行互不阻塞适合互相独立的字段校验。validationResult(req)从请求中提取全部已验证字段的错误包装成Result对象见下文。因为run(req)已把上下文持久化到req这里才能拿到完整错误集。errors.isEmpty()判断请求是否有效为空则调用next()放行否则返回400和统一的 JSON 错误结构。res.status(400).json({ errors: errors.array() }).array()返回错误对象数组便于前端直接展示。进阶串行执行与失败短路原文档的并行版本适合大多数场景但当校验链之间存在强依赖、或某个字段校验失败后不希望继续执行后续链例如避免对格式错误的输入再发数据库查询时可以改为串行执行并在首个失败处中断。更新的官方指南 docs/guides/manually-running.md 提供了等价思路const validate validations { return async (req, res, next) { for (const validation of validations) { const result await validation.run(req); if (!result.isEmpty()) { return res.status(400).json({ errors: result.array() }); } } next(); }; };串行版直接利用run(req)的返回值Result而非重新调用validationResult(req)逐条判断是否失败一旦失败立即返回。值得注意的是该版本的错误响应直接来自当前这条链的Result而非全量validationResult(req)——两条策略各有取舍并行版一次性返回所有字段的错误用户体验更好串行版尽早短路、开销更小更利于控制数据库/外部 API 等重操作的触发次数。示例二带条件的验证第二个官方示例展示了命令式校验的另一大优势——在路由处理器内部按请求内容动态决定是否追加校验app.post(/update-settings, [ body(email).isEmail(), body(password).optional().isLength({ min: 6 }) ], async (req, res, next) { // 如果提交了密码则必须同时提供确认密码 if (req.body.password) { await body(passwordConfirmation) .equals(req.body.password).withMessage(passwords do not match) .run(req); } // 检查校验错误然后更新用户设置 const errors validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ errors: errors.array() }); } // ...更新设置 });这里body(passwordConfirmation)这条链并非静态注册的中间件而是在if分支里临时构建并立即执行。由于run(req)会把执行结果写入请求随后validationResult(req)能一并收集这条动态链的错误。withMessage(passwords do not match)则把默认错误信息替换为业务友好的提示。官方建议优先使用.if()不过官方指南在 docs/guides/manually-running.md 末尾附了一条重要提醒这只是一个演示手动运行校验能力的示例。如果只是想做条件校验更推荐用声明式的.if()修饰符让校验规则保持静态、可读、集中body(newPassword) // 仅当提供了旧密码时才校验 .if((value, { req }) req.body.oldPassword) // 也可以传入另一个校验链作为条件 .if(body(oldPassword).notEmpty()) .isLength({ min: 6 });两种方式都能实现条件触发取舍原则是规则本身固定不变时优先用.if()声明式、易审查规则依赖复杂运行时逻辑、或需要在请求处理中段动态追加校验时才用手动run(req)。关于.if()的完整语义可参考 docs/api/validation-chain.md。结果对象validationResult与ResultAPI命令式校验的最后一步几乎总是检查结果因此必须掌握validationResult与Result的完整 API详见 docs/api/validation-result.md。validationResult(req)validationResult(req: Request): ResultValidationError它从请求中提取全部已验证字段的错误并包装成Result对象。实现上见 src/validation-result.ts它读取req[contextsKey]上累积的所有校验上下文把每个上下文的errors扁平拼接起来——这解释了为什么手动run(req)与声明式中间件的错误会汇总到同一个结果里。Result的常用方法方法签名说明.isEmpty()isEmpty(): boolean是否没有任何错误即请求是否有效.array()array(options?: { onlyFirstError?: boolean }): T[]返回错误数组onlyFirstError: true时每个字段只保留首个错误.mapped()mapped(): Recordstring, T返回字段路径 → 错误的映射对象每字段仅首个错误.formatWith()formatWithT(formatter): ResultT用自定义格式化函数重包结果返回新的Result.throw()throw(): void有错误时抛出一个带Result方法的错误对象便于转发给 Express 错误处理中间件一个实用的组合用validationResult.withDefaults预设全局错误格式让所有命令式校验的输出风格一致const myValidationResult validationResult.withDefaults({ formatter: error error.msg, }); // 之后统一使用 const errors myValidationResult(req).array(); // [Invalid value, ...]Result的错误对象带有type判别字段field/alternative/alternative_grouped/unknown_fieldstype: field时可通过error.path、error.location、error.value、error.msg拿到字段级细节便于定制响应结构。何时该用命令式校验场景清单综合原文档与仓库现状以下场景优先考虑run(req)手动运行多路由复用校验并统一错误格式封装validate(validations)中间件一处定义、处处复用示例一。请求中段的动态条件校验校验规则依赖请求其他字段的值或外部状态示例二。精细控制执行顺序与短路串行遍历校验链、首个失败即停止或配合Result返回值逐条决策。隔离仅执行、不持久化的校验通过dryRun: true先试跑校验、观察结果而不污染req例如在真实保存前做一次预检。反之如果只是静态规则校验直接挂中间件或使用.if()条件链即可代码更简洁、更易被静态审查。注意事项与常见误区run(req)返回的是Result不是布尔值判断成败请用result.isEmpty()不要依赖await本身是否抛错——校验失败不会抛出异常只有执行异常如自定义校验器内部抛错才会中断。结果持久化是默认行为只要没传dryRun手动运行的链就会写入req并被后续validationResult(req)收集。若在多个中间件中重复运行同一批链错误会累积注意去重或使用dryRun。净化也会生效run(req)不仅执行校验链上的净化器如trim()、toLowerCase()同样会更新请求字段行为与声明式中间件完全一致。bail 与短路语义请求级.bail({ level: request })会影响后续所有链并行Promise.all下各链仍会执行但结果中会体现中断设计错误响应时需留意。TypeScript 类型若要封装自己的校验器可用import { ValidationChain } from express-validator标注参数类型更通用的做法是标注为ContextRunner这样checkExact()、checkSchema()、oneOf()的产物也能传入。小结命令式校验run(req)是 express-validator 从声明式中间件走向可编程校验流程的关键接口它统一实现了ContextRunner底层通过把执行上下文持久化到req来与validationResult(req)无缝协作。掌握它你就能写出可复用的标准化校验中间件、按条件动态追加的校验逻辑以及精细控制执行顺序与开销的自定义校验流程——同时记得官方建议能用.if()表达的条件优先保持声明式。赞分享后端【免费下载链接】express-validatorAn express.js middleware for validator.js.项目地址https://gitcode.com/gh_mirrors/ex/express-validator点击查看免费下载相关推荐Haystack × IBM Db2用 IBMDb2DocumentStore 与 IBMDb2EmbeddingRetriever 构建向量检索 RAG 流水线Haystack × IBM Db2用 IBMDb2DocumentStore 与 IBMDb2EmbeddingRetriever 构建向量检索 RAG 流后端express-validator 命令式校验实战用 run(req) 精准掌控验证链的执行时机express validator 命令式校验实战用 run req 精准掌控验证链的执行时机 导读 express validator 的设计哲学是声明式后端express-validator 命令式验证指南用 run(req) 手动掌控验证流程express validator 命令式验证指南用 run req 手动掌控验证流程 express validator 默认推荐声明式的中间件写法把验证后端上一篇【热门开源项目下载】MusicFree 小白级图文教程下一篇【Sa-Token】开源下载和安装教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考