You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 03:45:37