Strapi中super.find()、Entity Service与Query Engine API的差异及结构统一方案
Strapi中super.find()、Entity Service与Query Engine API的差异解析
一、返回结构不同的原因
两者的定位和职责完全不同,导致了返回结构的差异:
super.find(ctx)是Strapi核心控制器提供的前端API层方法,内部会自动完成:请求上下文解析(分页、过滤、排序参数)、权限校验、结果格式化为Strapi REST标准结构(字段包裹在attributes中,添加meta分页信息)、错误处理,直接面向前端返回规范响应。strapi.entityService.findMany()是Strapi的底层服务层API,专注于数据操作本身,不处理请求上下文和响应格式化,直接返回数据库查询的原始实体数据,适合内部服务间的数据交互,而非直接返回给前端。
以下是你的代码示例和对应返回结果:
自定义控制器代码
const { createCoreController } = require("@strapi/strapi").factories; module.exports = createCoreController("api::product.product", ({ strapi }) => ({ async find(ctx){ const data = await super.find(ctx); // const data = await strapi.entityService.findMany("api::product.product"); return data; } }));
super.find(ctx) 返回的标准REST结构
{ "data": [ { "id": 8, "attributes": { "name": "Fiat Marea 20V ", "sku": "FT550", "description": "<p>Apenas uma descrição de teste Produto único topzeira </p><p> </p><p><strong>Altura</strong>: 12 </p><p><strong>Largura</strong>: 12 </p><p><strong>Produnfidade</strong>: 12</p>", "price": 19900, "status": true, "createdAt": "2022-12-02T22:23:47.483Z", "updatedAt": "2023-01-05T12:31:08.461Z", "quantity": 2, "price_discount": null, "publishedAt": "2023-01-05T12:14:25.342Z" } } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 1 } } }
strapi.entityService.findMany() 返回的原始数据结构
[ { "id": 8, "name": "Fiat Marea 20V ", "sku": "FT550", "description": "<p>Apenas uma descrição de teste Produto único topzeira </p><p> </p><p><strong>Altura</strong>: 12 </p><p><strong>Largura</strong>: 12 </p><p><strong>Produnfidade</strong>: 12</p>", "price": 19900, "status": true, "createdAt": "2022-12-02T22:23:47.483Z", "updatedAt": "2023-01-05T12:31:08.461Z", "quantity": 2, "price_discount": null, "publishedAt": "2023-01-05T12:14:25.342Z" } ]
二、让Entity Service返回与super.find()一致的结构
需要手动完成super.find()自动做的两件事:数据 sanitize(权限/字段过滤) 和 包装标准响应结构,示例代码如下:
const { createCoreController } = require("@strapi/strapi").factories; module.exports = createCoreController("api::product.product", ({ strapi }) => ({ async find(ctx){ // 1. 从请求上下文获取分页参数 const { page = 1, pageSize = 25 } = ctx.query; // 2. 用Entity Service查询,同时获取总条数 const [results, total] = await strapi.entityService.findMany("api::product.product", { pagination: { page: parseInt(page), pageSize: parseInt(pageSize) }, // 可添加其他参数:populate、filters等 }); // 3. 对结果进行sanitize处理(匹配权限规则、过滤字段) const sanitizedResults = await strapi.service('api::product.product').sanitizeOutput(results, ctx); // 4. 包装成标准REST响应结构 return { data: sanitizedResults, meta: { pagination: { page: parseInt(page), pageSize: parseInt(pageSize), pageCount: Math.ceil(total / parseInt(pageSize)), total } } }; } }));
如果使用strapi.query(旧版API),逻辑类似:先查询数据,再用对应服务的sanitizeOutput处理,最后包装结构。
三、Entity Service、super.find()与Query Engine API三者的核心差异
| 特性 | super.find() | Entity Service | Query Engine API |
|---|---|---|---|
| 定位 | 前端API层,处理请求与响应 | 服务层,内部数据操作统一接口 | 底层数据库查询层,直接操作数据库 |
| 自动处理请求上下文 | 是(自动解析分页、过滤参数) | 否(需手动传入参数) | 否(完全独立于请求上下文) |
| 响应格式化 | 是(标准REST结构) | 否(原始实体数据) | 否(原始数据库记录) |
| 权限校验 | 自动执行 | 需手动传入ctx或配置权限选项 | 完全绕过,无权限校验 |
| 关联数据处理 | 自动处理(基于请求中的populate参数) | 支持手动配置populate | 需手动编写关联查询逻辑 |
| 使用场景 | 自定义控制器中扩展默认API逻辑 | 自定义服务、跨内容类型数据操作 | 极端性能需求、复杂原生查询场景 |
内容的提问来源于stack exchange,提问作者Kaio Felipe
相关产品推荐
相关产品推荐

