尧图精选

EverShop 数据层基石:@evershop/postgres-query-builder 查询构建器全面指南

🕒 发布时间:2026/10/2 17:08:38 📁 来源:尧图网络
电商后端前端【免费下载链接】evershop️ Typescript E-commerce Platform项目地址https://gitcode.com/GitHub_Trending/ev/evershop点击查看免费下载本指南围绕 EverShop 开源电商平台TypeScript 实现所依赖的独立数据库工具包evershop/postgres-query-builder展开完整讲解它的安装方式、链式查询 API、参数绑定与 SQL 注入防护机制、事务处理以及它在 EverShop 各业务模块中的真实落地用法。读完本文你将掌握如何用一套简洁的异步 API 完成 PostgreSQL 的增删改查、关联查询与事务控制并理解其全部用户数据经参数绑定转义的安全设计。包概述与安装evershop/postgres-query-builder是 EverShop 平台的数据访问层核心工具定位为一个用于 NodeJS 的 PostgreSQL 查询构建器见 packages/postgres-query-builder/README.md。它完整实现了 async/await 异步编程模型所有查询方法均返回 Promise可直接配合node-postgrespg包使用。在 package.json 中可以看到其工程形态包名evershop/postgres-query-builder当前版本2.1.0模块类型type: moduleESM主入口为编译后的dist/index.js运行时要求node 18.0.0依赖仅两个pg ^8.10.0与uniqid ^5.3.0后者用于生成绑定参数占位键许可证MIT安装方式npm install evershop/postgres-query-builder由于它依赖pg实际使用前你还需要准备好一个pg.Pool连接池实例例如const { Pool } require(pg); const pool new Pool({ host: localhost, port: 5432, user: evershop, password: your_password, database: evershop });基础查询SELECT 与简单 WHERE最简单的一次查询只涉及两个环节用select()指定字段用from()指定表然后execute(pool)执行const { select } require(evershop/postgres-query-builder); const products await select(*) .from(product) .where(product_id, , 1) .execute(pool);execute()返回的是查询结果的行数组rows可以直接遍历使用。where()接受三个参数字段名、操作符、值。支持标准 SQL 操作符、、、LIKE、IN等。追加更多条件时可以使用.and()const { select } require(evershop/postgres-query-builder); const products await select(*) .from(product) .where(product_id, , 1) .and(sku, LIKE, sku) .execute(pool);如果你需要OR条件可以直接在查询对象上继续链式调用orWhere()const { select } require(evershop/postgres-query-builder); const query select(*).from(product); query.where(product_id, , 1).and(sku, LIKE, sku); query.orWhere(price, , 100); const products await query.execute(pool);从源码实现看src/index.tsQuery类内部维护了一个_where的Where节点where/andWhere/orWhere都会以AND/OR作为链接符link挂载到条件树上。值得注意的细节是当_where树为空时andWhere/orWhere会退化为where避免生成无谓的AND/OR前缀条件渲染时第一个叶子会去掉自身的链接符最终输出形如WHERE (...)的规范 SQL。表关联JOIN 查询多表关联是电商查询的高频场景例如商品表关联价格表。构建器提供leftJoin、rightJoin、innerJoin三种方式通过.on()指定连接条件const { select } require(evershop/postgres-query-builder); const query select(*).from(product); query.leftJoin(price).on(product.product_id, , price.product_id); query.where(product_id, , 1).and(sku, LIKE, sku); query.andWhere(price, , 100); const products await query.execute(pool);源码中的Join类src/index.ts 的Join定义会为每个连接保存{ type, table, alias, on }元组其中on是一个以ON为链接符的独立条件节点渲染时拼接为LEFT JOIN price AS price ON ...。on()方法返回该条件节点因此你也可以在.on()之后继续链式追加AND/OR条件。若在未声明任何 join 的情况下调用.on()会抛出Invalid call错误。另外从SelectQuery的 API 可以看到 join 还支持别名leftJoin(product, p)并且 EverShop 内部专门为 COUNT 类查询提供了pruneUnreferencedLeftJoins()方法当某个 LEFT JOIN 在 SELECT、WHERE、GROUP BY、HAVING、ORDER BY 以及其它 JOIN 的 ON 子句中都没有被引用时会将其从 SQL 中剔除并清理其绑定参数避免 LEFT JOIN 带来的行数膨胀拖慢计数查询该优化注释中记录了对 30 万商品目录的实测效果。写操作INSERT、UPDATE、DELETE 与 UPSERTINSERTinsert(table).given(data)传入一个对象构建器只会把表中真实存在的列写入 SQL多余字段自动忽略const { insert } require(evershop/postgres-query-builder); const query insert(user) .given({ name: David, email: emailemail.com, phone: 123456, status: 1, notExistedColumn: This will not be a part of the query }); await query.execute(pool);UPDATEupdate(table).given(data).where(...)同样只更新存在的列并通过WHERE限定行const { update } require(evershop/postgres-query-builder); const query update(user) .given({ name: David, email: emailemail.com, phone: 123456, status: 1, notExistedColumn: This will not be a part of query }) .where(user_id, , 1); await query.execute(pool);写操作的底层机制INSERT/UPDATE 的实现有一个共同点它们在生成 SQL 之前会先通过information_schema.columns查询目标表的列元数据列名、数据类型、是否可空、是否自增等然后只挑选.given()中确实存在于表结构中的字段参与生成 SQL。这样notExistedColumn这类不存在的键会被静默过滤从根上杜绝了拼错列名导致运行时错误。另外一个实用行为是构建器会检测identity_generation为BY DEFAULT/ALWAYS的标识列作为主键执行 INSERT 后返回的单行对象会被附加insertId属性即主键值执行 UPDATE 后则附加updatedId。两条语句末尾都带有RETURNING *因此你能直接拿到写入后的完整行数据。DELETEdel()工厂函数生成删除语句const { del } require(evershop/postgres-query-builder); await del(user) .where(user_id, , 1) .execute(pool);从DeleteQuery的实现看SQL 由DELETE FROM table WHERE 片段拼接而成同样支持.and()/.or()链式条件。UPSERTinsertOnUpdate除了 README 展示的基础写操作源码中还提供了 README 未展开的insertOnUpdate()工厂函数用于实现 PostgreSQL 的INSERT ... ON CONFLICT (...) DO UPDATE SET ...语义。它要求第二个参数为冲突列数组且不能为空const { insertOnUpdate } require(evershop/postgres-query-builder); const query insertOnUpdate(product, [sku]) .given({ sku: SKU-001, name: Updated name, price: 100 }); await query.execute(pool);生成的 SQL 形如INSERT INTO product (...) VALUES (...) ON CONFLICT (sku) DO UPDATE SET name :..., price :... RETURNING *。该 API 在 EverShop 中广泛用于幂等写入场景例如 URL 重写记录的落库见 recordRedirect.ts。事务处理多步写操作需要保证原子性时可以使用包导出的连接管理与事务控制函数。流程是先从连接池取出独立连接显式BEGIN成功后COMMIT异常时ROLLBACKconst { Pool } require(pg); const { insert, getConnection, startTransaction, commit, rollback } require(evershop/postgres-query-builder); const pool new Pool(connectionSetting); // Create a connection from the pool const connection await getConnection(pool); // Start a transaction await startTransaction(connection); try { await insert(user) .given({ name: David, email: emailemail.com, phone: 123456, status: 1, notExistedColumn: This will not be a part of the query }) .execute(connection); await commit(connection); } catch (e) { await rollback(connection); }对应的实现位于 src/index.ts 的 Connection management functions 部分getConnection(pool)等价于pool.connect()从连接池取出一个独占连接startTransaction(connection)执行BEGIN并在连接对象上打上INTRANSACTION true标记commit(connection)执行COMMIT然后release(connection)归还连接rollback(connection)执行ROLLBACK并释放连接内部的release()会额外检查INTRANSACTION标记——事务未结束的连接不会被提前归还连接池这正是事务内多次execute(connection)能共享同一连接的原因。execute()的第二个参数releaseConnection默认true控制执行后是否自动释放连接在事务内应保持其默认行为不变事务本身会管理连接的归还。参数绑定与 SQL 注入防护README 的安全章节明确承诺所有用户提供的数据都会被转义All user provided data will be escaped。这一承诺通过**参数绑定parameterized query**机制实现而不是简单的字符串拼接。从 src/index.ts 的Query.execute()与SelectQuery.execute()实现可以看到完整链路每个值占位符都以uniqid()生成的随机键命名形如:xxxx值被收集进内部的_binding字典数组、对象等复合值由 toString.js 先做JSON.stringify序列化真正执行前构建器把:key逐一替换为pg驱动需要的$1, $2, ...位置参数并将值按序放入values数组最终通过connection.query({ text, values })交给node-postgres执行由数据库驱动完成转义与安全处理。因此用户输入永远不会以字面量形式拼进 SQL 文本这是该构建器防注入的核心保障。需要原样插入 SQL 表达式如函数、运算符或原始片段时包提供了sql()与value()两个辅助函数返回带有isSQL标记的SQLValue对象sql(NOW())会被当作合法 SQL 片段原样进入语句value(...)则强制按普通值处理。字段名解析规则见 fieldResolve.js普通字段会被包裹成字段表.字段形式会被解析为表.字段从而规避注入与命名冲突。进阶查询能力排序、分页、分组与单行加载SelectQuery还提供 README 未逐一展开但源码中完整实现的进阶能力const { select, sql } require(evershop/postgres-query-builder); const products await select(product_id, sku) .from(product) .where(status, , 1) .orderBy(created_at, DESC) // 排序默认 ASC .limit(0, 20) // limit(offset, limit) 分页 .groupBy(sku) // 分组 .having(COUNT(product_id), , 10) // 分组后过滤 .execute(pool);各能力要点orderBy(field, direction)第二参数默认ASC也可用orderDirection()单独设置方向limit(offset, limit)注意参数顺序是偏移量在前、条数在后渲染为LIMIT n OFFSET mgroupBy(...fields)接受可变参数字段经fieldResolve规范化having(field, operator, value)作用于分组结果load(connection)等价于limit(0, 1)后取第一行返回单行对象或null适合按主键取一条记录的场景见SelectQuery.load()实现clone()深度克隆整棵查询树含 WHERE、JOIN、ORDER BY 等便于在不影响原查询的前提下派生变体EverShop 的计数查询即基于此思路removeOrderBy()/removeGroupBy()/removeLimit()动态移除查询子句。SelectQuery.execute()还内置了两类容错逻辑当 PostgreSQL 返回42703未定义的列错误时自动移除ORDER BY后重试一次——兼容某些旧表缺少排序列的场景当返回22P02无效文本表示通常发生在对空结果集执行COUNT类聚合、类型转换异常时若 SELECT 列表含COUNT(...)则返回[{ count: 0 }]而非抛错保证分页接口总能拿到安全的计数结果。在 EverShop 项目中的实际应用该构建器并非孤立工具而是 EverShop 整个数据层的底座。EverShop 在其核心包内提供了类型安全封装层 packages/evershop/src/lib/postgres/query.ts在原生 API 之上叠加了 TypeScript 类型约束定义TableName联合类型覆盖product、order、customer、cart、url_rewrite、changeset等 50 张已知表并保留string回退以兼容自定义表通过RowOfT、ColumnOfT、AllPrefixedColumns映射类型实现表 → 行类型 → 列名的自动关联让.given()、.where()、.select()的字段参数获得自动补全与编译期校验提供TypedQueryChain、TypedInsertQuery、TypedUpdateQuery、TypedDeleteQuery、TypedInsertOnUpdateQuery、TypedJoin等链式接口并把select/insert/update/del/insertOnUpdate以及sql/value/getConnection/startTransaction/commit/rollback等全部重新导出。实际业务代码中的典型用法如 getCurrentUser.tsimport { select } from ../../../../lib/postgres/query.js; const currentAdminUser await select() .from(admin_user) .where(uuid, , adminUserUuid) .load(pool);数据库连接池则在 packages/evershop/src/lib/postgres/connection.ts 中统一构建通过DB_HOST/DB_PORT/DB_USER/DB_PASSWORD/DB_NAME等环境变量读取连接配置支持DB_SSLMODEdisable/require/prefer/verify-ca/verify-full/no-verify以及DB_SSLROOTCERT/DB_SSLCERT/DB_SSLKEY配置 SSL 证书链并在onConnect钩子中按店铺时区设置SET TIMEZONE。理解这套封装有助于你在自己的 NodeJS 项目中直接复用evershop/postgres-query-builder的完整能力。赞分享电商后端前端【免费下载链接】evershop️ Typescript E-commerce Platform项目地址https://gitcode.com/GitHub_Trending/ev/evershop点击查看免费下载相关推荐Vue Query Builder完全指南3分钟构建复杂数据查询界面Vue Query Builder完全指南3分钟构建复杂数据查询界面 Vue Query Builder是一个强大的Vue.js UI组件库专门用于构建包含前端UI组件使用 mcp-use 构建生产级 MCP 服务器从脚手架到工具、资源与 Widget 的完整实践指南使用 mcp use 构建生产级 MCP 服务器从脚手架到工具、资源与 Widget 的完整实践指南 导读 本文是 CopilotKit 仓库中 open m人工智能AI AgentAgent 框架前端后端Vue Query Builder实战指南轻松构建智能数据查询界面Vue Query Builder实战指南轻松构建智能数据查询界面 在当今数据驱动的时代如何让用户能够直观地构建复杂的数据查询条件成为了许多应用面临的挑战。前端UI组件上一篇Steam游戏库智能分类革命Depressurizer让你的游戏世界井然有序下一篇litellm 自定义提供商从0到1一个类接入任意 LLM创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →