后端【免费下载链接】express-validatorAn express.js middleware for validator.js.项目地址https://gitcode.com/gh_mirrors/ex/express-validator点击查看免费下载express-validator 是一个基于 Express.js 的校验中间件库它在请求进入路由处理之前按照你声明的规则读取并检查请求数据。本文以项目 7.0.0 版本文档 architecture.md 为主线深入剖析其“中间件 声明式校验”的核心架构并对照源码还原从check()到错误收集的完整内部执行链路帮助你理解每个校验器、净化器、bail()、oneOf()在底层究竟做了什么。核心思想一切皆是中间件在 Express.js 的术语体系中**中间件middleware**是接收 HTTP 请求的req与响应res对象的函数。中间件可以自行响应请求也可以将控制权移交给下一个注册的中间件。express-validator 的大部分实现就是一组这样的中间件函数——它们读取请求并根据你为每个字段声明的规则判断请求是否合法。这种设计的直接结果是一种“声明式”的编程风格Declarative Programming。你通常不需要告诉库什么时候执行校验——只要把校验链挂到路由上Express 会在请求经过该路由时自动运行当然如果你希望精确控制执行时机库也提供了手动运行imperative run的能力。以最常见的用法为例const { body } require(express-validator); app.post( /user, body(email).isEmail(), body(password).isLength({ min: 6 }), (req, res) { // 只有通过全部校验才会执行到这里 }, );这里body(email).isEmail()返回的正是一个 Express 中间件同时也是一个可继续链式调用的校验链对象Express 会按注册顺序依次执行它们。从校验链到上下文check()的组装过程声明式风格背后是 src/middlewares/check.ts 中的check()函数完成了关键组装。该函数接收字段名、校验位置Location与默认错误消息返回一个兼具“中间件”与“校验链”双重身份的ValidationChain// src/middlewares/check.ts节选 export function check( fields: string | string[] , locations: Location[] [], message?: FieldMessageFactory | ErrorMessage, ): ValidationChain { const builder new ContextBuilder() .setFields(Array.isArray(fields) ? fields : [fields]) .setLocations(locations) .setMessage(message); const runner new ContextRunnerImpl(builder); const middleware async (req, _res, next) { try { await runner.run(req); next(); } catch (e) { next(e); } }; return Object.assign( middleware, bindAll(runner), bindAll(new SanitizersImpl(builder, middleware)), bindAll(new ValidatorsImpl(builder, middleware)), bindAll(new ContextHandlerImpl(builder, middleware)), { builder }, ); }从中可以看到三层结构ContextBuilder上下文构建器记录本次校验的字段、位置、默认消息并作为后续积累校验规则ContextItem的容器见 src/context-builder.tsContextRunnerImpl执行器负责真正跑完所有规则四个 Impl 类通过Object.assign与中间件合并——ValidatorsImpl提供.isEmail()等校验方法SanitizersImpl提供.trim()等净化方法ContextHandlerImpl提供.bail()、.optional()、.custom()等方法runner提供.run()等手动执行能力。因此链上每调用一个方法都会把对应的ContextItem追加进 builder 的stack最终在运行时按顺序执行。字段位置Location库将请求数据的来源划分为五种位置定义于 src/base.tsLocation对应的请求属性快捷函数bodyreq.bodybody()cookiesreq.cookiescookie()headersreq.headersheader()paramsreq.paramsparam()queryreq.queryquery()其中check()的默认行为是同时覆盖全部五种位置而body()、param()等快捷函数只是buildCheckFunction在locations参数上做了收窄——这一点在 src/express-validator.ts 的ExpressValidator类里实现得相当直观readonly check this.buildCheckFunction([body, cookies, headers, params, query]); readonly body this.buildCheckFunction([body]); readonly param this.buildCheckFunction([params]); // ...执行阶段ContextRunnerImpl如何驱动整条校验链当中间件被 Express 调用时真正干活的函数是 src/chain/context-runner-impl.ts 中的run()。它按以下顺序推进构建Context若传入的是ContextBuilder先调用build()生成不可变的只读上下文对象Context含字段、位置、规则栈、optional/bail 标志、值可见性与默认消息见 src/context.ts检查请求级 bail如果请求上已存在带bail标志且已有错误的上下文则直接短路返回bail判定见 src/chain/context-runner-impl.ts字段选择selectFields根据context.fields与context.locations从请求中取出待校验字段实例存入dataMap见 src/field-selection.ts逐项执行规则栈对context.stack中的每个ContextItem并行Promise.all对每个字段实例运行。净化器改变instance.value后会写回请求对象_.set保证后续校验看到的是净化后的值记录上下文把本次context追加到req[contextsKey]上——contextsKey即express-validator#contexts见 src/base.ts所有已运行校验链的“错误账本”都挂在这个隐藏属性下返回ResultWithContextImpl既包含校验结果Result也保留对context的引用供需要读取上下文数据的场景如oneOf内部使用。值得一提的是run()支持dryRun: true选项——只执行规则、产生错误但不把字段数据写回请求、也不在请求上追加上下文。这一能力正是oneOf()在不污染请求状态的前提下“试跑”各候选链的基础。字段选择中的通配符处理字段选择由selectFields与expandPath完成src/field-selection.ts。除了普通的foo.bar点路径它还支持*通配符遍历对象的所有键例如body(products.*.price)**全局通配符globstar递归匹配任意深度的路径数字段自动转换为数组索引语法foo[0].bar、含特殊字符的键使用[key]形式路径重建逻辑见 src/field-selection.ts。这些通配符在req.body { bar: { foo: 1 }, baz: { foo: 2 } }配合*.foo这类字段时会把命中的每个路径都展开成一个独立的字段实例再通过uniqWith去重。校验与净化内置规则如何被挂载到链上内置校验器与净化器来自 validator.js。链上的.isEmail()、.trim()等方法由 src/chain/validators-impl.ts 与 src/chain/sanitizers-impl.ts 动态绑定每个内置方法被调用时都会把对应的ContextItemStandardValidation或Sanitization见 src/context-items/standard-validation.ts、src/context-items/sanitization.ts追加到 builder 的规则栈同时返回链本身以支持继续链式调用。如果你需要超越内置规则链上还提供.custom(fn)自定义校验器函数签名(value, meta) any返回 falsy 或抛错即视为校验失败meta携带req、location、path、pathValues见 src/base.ts.customSanitizer(fn)自定义净化器.optional({ values })通过undefined | null | falsy控制字段在何种取值下跳过校验src/context.ts.bail()字段级或请求级短路一旦前面规则失败便不再执行后续规则.if(condition)按条件执行后续规则。这些上下文级能力都在 src/context-handler-impl.ts 中实现统一以ContextItem形式进入执行栈。错误记录与可见性控制校验失败不会立刻中断流程而是被“内部记录”。每一条错误由Context.addError()生成src/context.ts错误类型定义于 src/base.ts错误类型含义field某个字段的值非法含location、path、value、msgunknown_fields请求中存在未声明校验的字段由checkExact()产生alternativeoneOf()中所有候选项均失败nestedErrors为各链错误alternative_grouped同上但nestedErrors按候选组分组默认错误消息是Invalid valuesrc/context.ts。字段值默认会附带在错误对象上但你可以通过hide()将其从错误中剔除或hide(value)用固定值替代redacted其实现由updateVisibility完成src/context.ts。所有上下文都追加在req[express-validator#contexts]上因此校验链即使分散在多个中间件中注册错误也能被统一汇总——这正是下一节validationResult()能“一处收口”的原因。错误提取validationResult与Result校验链只负责“记录”读取错误靠 src/validation-result.ts 的validationResult()const result validationResult(req); if (!result.isEmpty()) { return res.status(400).json({ errors: result.array() }); }其内部通过req[contextsKey]取出所有上下文再用_.flatMap(contexts, errors)合并全部错误封装为Result实例。Result提供array({ onlyFirstError })以数组形式返回错误mapped()以“字段名 → 错误”对象返回非field类型错误用_类型名作键formatWith(formatter)自定义错误格式化isEmpty()/throw()判空与抛错throw()会抛出一个绑定了同样方法的 Error适合与异步路由错误处理配合。复合校验中间件oneOf与checkExactoneOf至少一组候选链通过oneOf(chains, options)src/middlewares/one-of.ts创建的是一个“复合”中间件。它用一个带空操作dummyItem的代理上下文surrogate context以dryRun: true并行试跑所有候选链然后只要任一组无错误即视为请求通过若全部失败向代理上下文写入一条alternative或alternative_grouped取决于errorType选项错误nestedErrors中保存各组的字段错误明细默认消息为Invalid value(s)只有当某一整组全部通过时才把该组链的字段数据并入代理上下文以便matchedData()等后续读取src/middlewares/one-of.ts。errorType可选flat、least_errored或grouped控制错误如何组织oneOf还支持传入组数组即[[chainA, chainB], chainC]表示组内链必须全部通过。checkExact请求字段必须恰好匹配与声明式“只校验指定字段”不同checkExact()走的是“白名单”路线它校验完声明的字段后会反向扫描请求把未被任何校验链覆盖的字段标记为unknown_fields错误。其底层依赖selectUnknownFields与深度优先遍历findUnknownFieldssrc/field-selection.ts通配符*、**以及整棵子树被校验含[]叶子标记的路径都会被识别为“已覆盖”从而避免误报。手动运行把校验从中间件中解放出来声明式是默认风格但架构同样支持命令式控制。校验链本身实现了ContextRunner接口暴露run(req)方法因此你可以脱离 Express 手工执行const chain body(email).isEmail(); const result await chain.run(req); // req 可以是任意兼容对象这种模式便于在非 HTTP 场景如定时任务、内部服务调用复用同一套校验声明也是文档 guides/manually-running.md 介绍的核心用法。架构全景图把上述环节串起来一次请求校验的完整链路是app.use / 路由注册 └─ body(email).isEmail() 等校验链Middleware ValidationChain └─ runner.run(req) [ContextRunnerImpl] ├─ ContextBuilder.build() → Context字段/位置/规则栈/标志 ├─ selectFields() 从 req 提取字段实例支持 * 与 ** 通配符 ├─ 逐条执行 ContextItemStandardValidation / Sanitization / Bail / Custom… │ ├─ 净化器写回净化值到 req │ └─ 失败项由 Context.addError() 记录 └─ 追加 context 到 req[express-validator#contexts] └─ validationResult(req) → Resultarray / mapped / formatWith值得强调的架构要点校验与业务解耦校验链只做“读请求 记录错误”错误处理由你在路由内用validationResult()决定数据流单向净化sanitization修改请求值后后续校验立即感知因为每个字段实例是可变引用src/chain/context-runner-impl.ts上下文即账本req[express-validator#contexts]是跨中间件共享错误与已校验数据的唯一通道matchedData与validationResult都建立在这一约定之上src/base.ts可组合性普通校验链、oneOf、checkExact、checkSchema都是“中间件 ContextRunner”这一统一形状的不同拼装因此可以任意嵌套与组合。如果你希望进一步定制ExpressValidator类src/express-validator.ts允许传入自定义校验器/净化器映射并产出携带扩展方法的类型安全的校验链配合checkSchema还可将整组校验声明为 schema 对象。理解本文的中间件架构之后再去看 docs/architecture.md 对应的 5.x–6.x 历史版本或 官网文档 中的各功能页就能把每个 API 都对应回“构建 Context → 选择字段 → 执行规则栈 → 收集错误”这条主干上。赞分享后端【免费下载链接】express-validatorAn express.js middleware for validator.js.项目地址https://gitcode.com/gh_mirrors/ex/express-validator点击查看免费下载相关推荐express-validator 架构解析中间件、Context 上下文与声明式校验的运作机制express validator 架构解析中间件、Context 上下文与声明式校验的运作机制 本文基于 express validator 开源仓库Gi后端ccv SWT 场景文本检测实战Stroke Width Transform 的参数体系、实现原理与命令用法ccv SWT 场景文本检测实战Stroke Width Transform 的参数体系、实现原理与命令用法 导读 本文围绕 ccv 库中基于 Stroke后端express-validator v7 架构解析请求校验中间件从 check() 到 validationResult() 的完整工作原理express validator v7 架构解析请求校验中间件从 check 到 validationResult 的完整工作原理 本篇技术指南基于 exp后端上一篇dockerscan安全分析工具入门5分钟掌握容器漏洞检测核心功能下一篇Xray主题切换实现CSS变量与localStorage的状态持久化创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考