Strapi V4自定义API更新商品集合报405/EMFILE错误咨询
问题根因
首先你误判了错误类型,日志里抛出的不是405 Method Not Allowed,是EMFILE系统错误,错误码-24,含义是进程打开的文件/网络套接字数超过操作系统上限,直接原因有三个:
- 你在遍历商品列表的for循环中没有做任何并发控制,同步发起所有商品的PUT更新请求,只要商品数量稍多,瞬间会创建成百上千个TCP连接,直接打满Heroku基础实例的文件描述符上限(基础dyno默认上限仅1024),新连接无法创建就会抛出该错误。
- 你的实现逻辑本身存在冗余:自定义接口本身就运行在Strapi服务进程内,反而通过公网HTTP请求调用自身的公开API,平白增加网络开销、鉴权成本,也更容易触发并发、限流类问题。
- 你日志里的Authorization头值是硬编码的
process.env.BEARER字符串,没有实际读取环境变量,就算并发问题解决,也会因为鉴权失败被接口拦截。
无HTTP请求的替代实现方案
Strapi V4内部全局挂载了Entity Service API,控制器代码可以直接调用该接口操作所有集合类型的内容,完全不需要走公网HTTP请求,性能更高也不会触发连接耗尽问题,改造后的代码如下:
"use strict"; const axios = require("axios"); const qs = require("qs"); async function getUpdates() { const baseLinkerData = qs.stringify({ method: "getInventoryProductsList", parameters: '{"inventory_id": 3807}', }); const config = { method: "post", url: "https://api.baselinker.com/connector.php", headers: { "X-BLToken": process.env.BL_TOKEN, "Content-Type": "application/x-www-form-urlencoded", }, data: baseLinkerData, }; const response = await axios(config); return response.data.products; } async function updateProducts(productList) { // 控制并发数,避免数据库连接被打满,可根据实例配置调整 const CONCURRENCY_LIMIT = 10; const productEntries = Object.entries(productList); for (let i = 0; i < productEntries.length; i += CONCURRENCY_LIMIT) { const batch = productEntries.slice(i, i + CONCURRENCY_LIMIT); await Promise.all( batch.map(async ([strapiProductId, sourceProduct]) => { const updateData = { ean: sourceProduct.ean || null, sku: sourceProduct.sku || null, name: sourceProduct.name, quantity: sourceProduct.stock ? parseInt(sourceProduct.stock.bl_5076) : 0, price_brutto: sourceProduct.prices ? parseFloat(sourceProduct.prices["3624"]) : 0, }; // 直接调用Strapi内部服务更新商品,api::product.product为products集合的默认UID // 如果你修改过内容类型命名,替换为对应UID即可 return strapi.entityService.upsert("api::product.product", strapiProductId, { data: updateData }); }) ); } } module.exports = { exampleAction: async (ctx) => { try { const productUpdates = await getUpdates(); await updateProducts(productUpdates); ctx.body = { code: 200, message: "商品同步完成", syncCount: Object.keys(productUpdates).length }; } catch (err) { ctx.throw(500, err); } }, };
改造说明
- 所有异步操作都加了
await,接口会等待全部同步完成后再返回结果,不会出现接口已经返回、后台任务还在跑导致的异常中断问题。 - 加了并发控制,分批次处理商品更新,不会瞬间打满数据库连接或者系统文件描述符。
- 完全移除了调用自身Strapi API的axios请求,所有更新操作直接在服务内部完成,没有额外网络开销,也不需要配置API鉴权密钥。
- 如果你的单批次同步商品量超过1000,建议把同步逻辑改成Strapi异步队列任务,接口收到请求后直接返回受理成功,后台异步执行同步,避免网关超时。
内容的提问来源于stack exchange,提问作者Paul Kamyszek
相关产品推荐
相关产品推荐

