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

函数式编程范式下如何动态调用API且符合SOLID原则?

现有方案的SOLID符合度判定

你调整后的第二种实现完全符合SOLID原则的核心要求:

  • 单一职责原则:每个serX.js仅负责对应单个API的调用逻辑,requestPrice.js仅负责服务类型的路由分发,两类逻辑完全解耦,修改任意一方都不会影响另一方。
  • 开闭原则:新增第13个API时,仅需要新增对应ser13.js文件,不需要修改requestPrice.js的分发逻辑,满足对扩展开放、对修改关闭的要求。
  • 里氏替换原则:所有API模块导出的函数都遵循相同的入参(接收请求data)、出参(返回调用结果)契约,任意替换API实现都不会影响分发层逻辑。
  • 接口隔离原则:每个API实现仅需要处理自身业务需要的入参字段,不需要依赖无关的请求参数。
  • 依赖倒置原则:分发层依赖的是「接收数据返回结果」的抽象函数契约,而非具体的API实现逻辑,上层逻辑和下层实现完全解耦。

现有方案的潜在风险

当前实现存在路径遍历漏洞:如果恶意用户传入的service值为../xxx/evil这类路径字符串,会导致require加载非预期的文件,存在安全隐患。

更优雅的优化方案

方案1:白名单校验(最简安全方案)

新增合法服务类型的白名单校验,从根源避免路径遍历风险,同时保留原有的开闭特性:

// requestPrice.js
// 合法服务白名单,也可以放在配置文件中统一管理
const ALLOWED_SERVICES = new Set([
  'ser1', 'ser2', 'ser3', 'ser4', 'ser5', 'ser6',
  'ser7', 'ser8', 'ser9', 'ser10', 'ser11', 'ser12'
])

function requestPrice(req) {
  const service = req.body?.service
  // 先校验服务合法性
  if (!ALLOWED_SERVICES.has(service)) {
    throw new Error('不支持的服务类型')
  }
  const api = require(`./${service}`)
  return api(req.body)
}

方案2:自动扫描注册(适合API数量多、迭代频繁的场景)

如果后续API数量持续增加,不想每次新增都手动维护白名单,可以在服务启动时自动扫描对应目录下的API文件生成白名单,完全做到新增API零修改分发层代码:

// requestPrice.js
const fs = require('fs')
const path = require('path')

// 服务启动时自动扫描生成白名单,仅执行一次
const ALLOWED_SERVICES = new Set(
  fs.readdirSync(__dirname)
    .filter(filename => /^ser\d+\.js$/.test(filename))
    .map(filename => path.basename(filename, '.js'))
)

async function requestPrice(req) {
  const service = req.body?.service
  if (!ALLOWED_SERVICES.has(service)) {
    throw new Error('不支持的服务类型')
  }
  // 用ESModule动态import可实现懒加载,无需启动时加载所有API模块
  const api = (await import(`./${service}.js`)).default
  return api(req.body)
}

方案3:可注入适配(测试友好优化)

如果需要更方便的做单元测试,可以把模块加载逻辑做成可注入参数,测试时直接传入mock函数即可,不需要读取真实文件:

async function requestPrice(req, apiLoader = (service) => import(`./${service}.js`)) {
  const service = req.body?.service
  if (!ALLOWED_SERVICES.has(service)) throw new Error('不支持的服务类型')
  const api = (await apiLoader(service)).default
  return api(req.body)
}

// 单元测试示例,无需依赖真实ser1.js文件
test('调用ser1返回正确结果', async () => {
  const mockApi = jest.fn(() => Promise.resolve({ price: 100 }))
  const mockLoader = () => Promise.resolve({ default: mockApi })
  const result = await requestPrice({ body: { service: 'ser1' } }, mockLoader)
  expect(result.price).toBe(100)
})

内容的提问来源于stack exchange,提问作者smarber

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 19:21:02