函数式编程范式下如何动态调用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
相关产品推荐
相关产品推荐

