欢迎来到Bokey的空间🌼

加载中...

💓 0🔥 189

🚀关于通用接口系统设计分享

关于我实现通用接口的设计理念,希望对大家有启发

🕘 2025-05-18

🚀通用接口系统设计分享

设计背景

在日常后端开发中,你会发现一个很现实的问题:

80% 的接口,本质上都是 CRUD(增删改查)

但传统开发方式却是这样的:

每个模块写一套 Controller / Service / Router
重复处理分页、鉴权、字段过滤、事务
每个接口风格还不统一

久而久之,就会出现这些问题:
❌ 代码大量重复
❌ 修改成本高(改一个逻辑要改 N 个地方)
❌ 权限控制分散
❌ 接口风格混乱
❌ 开发效率低


💡所以我当时在想一个问题:

既然 CRUD 是“高度模式化”的,那为什么还要一遍遍手写?

于是有了这套系统的核心目标:
🚀 快速开发:减少重复代码
🔧 高度可配置:支持不同业务场景
🔐 权限解耦:统一中间件控制
♻️ 高复用性:通用 CRUD + 关联查询
⚙️ 可扩展性:允许局部自定义

因此我开发了自己的这套通用接口

这套通用后端好用的点在于:

  • 把“接口实现” → “接口声明”
  • 把“业务逻辑” → “参数加工”

这样基本的接口建表后复制一下配置就能快速完成接口,同时也不会缺失安全性,鉴权或者部分特殊处理都可以在配置中实现,可以非常快速的完成接口开发。
最重要的是,通用接口仍然可以在后期无痛转化成处理专项场景的接口,极大的满足了快速开发、可拓展性强的开发需求

🔍 与传统开发方式对比

维度 传统方式 通用接口方案
开发方式 手写 Controller 配置驱动
权限控制 分散在业务代码 中间件统一治理
查询结构 不统一 标准化 where/order/include
事务管理 手动控制 路由声明式开启
可维护性
可治理性
可读性

核心设计思想

这套系统本质上只做了一件事:

把“接口实现”变成“接口配置”

一句话总结:

👉 配置即接口,积木式开发


✨ 设计原则拆解

  • 减少重复劳动
    CRUD 是标准化行为 → 不应该重复写代码
  • 配置即接口
    接口不是写出来的,而是“声明出来的”
  • 可治理
    对高风险点统一控制(字段权限、查询范、事务一致性)
  • 可扩展

允许“局部自定义”,但不破坏整体结构

总体架构:配置驱动的通用接口链路

通用接口的核心是一条稳定的处理链路:

  • Route Config(配置层):用统一结构描述每个公开接口:HTTP 方法、路径、前置中间件、处理器、后置中间件,以及固定参数(如主模型、关联模型等)。
  • Dynamic Router(路由装配层):启动时读取所有配置,自动把它们挂载为 Express 路由。
  • Base Controller(通用控制器层):提供通用的 get / create / update / delete / relevance 等处理器。
  • Base Service(通用服务层):封装 ORM 访问与通用数据操作语义(分页、include、事务、关联同步等)。
  • Base Middlewares(治理层):围绕“请求输入/权限/事务”提供可复用的中间件工厂,按路由配置插拔。

这套设计主要突出:

  • 配置即接口:一个接口不是“写出来的”,而是“配置出来的”(声明式)。
  • Controller、Middleware 积木化:把能力拆成可复用积木,通过拼接完成接口编写,而不是复制粘贴。

🧩 配置即接口:核心结构设计

在我的通用接口中,主要的“接口配置结构”如下(说明式代码):

TypeScript 复制代码
export interface BaseRouteConfig {
  method: "get" | "post" | "put" | "delete"; // HTTP方法
  externalPath: string; // 路由别名
  preMiddlewares: RequestHandler[]; // 前置中间件数组
  handler: RequestHandler; // 使用的控制器
  fixedParams?: {
    MainModel: string;
    RelevanceModels?: string[] | string;
    targetAlias?: string;
  }; // 内部通用路由需要的params参数数组
  postMiddlewares: RequestHandler[]; // 后置中间件数组
}

这种结构的本质是:把“接口的形态”用配置描述,把“接口的通用能力”下沉到框架层,把“差异化逻辑”留在中间件或参数工厂中。

为了更直观地体现“配置即接口”,下面是一段“路由配置如何长出来”的示意:

ts 复制代码
// 1) 声明一个接口 = 声明一条配置
const example: BaseRouteConfig = {
  method: "put",
  externalPath: "/resource/update",
  fixedParams: { MainModel: "Resource" },
  preMiddlewares: [
    // 鉴权中间件:声明该接口需要什么角色/登录态
    middlewares.auth.authJudgeFactory({ roleCheck: ["admin"]}),
    // 字段治理中间件:例如禁止客户端更新 id
     middlewares.base.limitRequestPayloadFactory({ deleteFields: ["id"]}),
    // 开启事务中间件:让后续写操作运行在同一个 transaction 中
     middlewares.base.setTransaction,
  ],
  // 控制器中间件:不写业务 CRUD,只复用通用 handler
  handler: baseController.update,
  postMiddlewares: [
    // 提交事务中间件
    middlewares.base.commitTransaction,
    // 返回数据中间件
    middlewares.network.response,
  ],
};

再进一步,“配置即接口”带来的一个工程体验是:新增接口时,更多是在“选择并排列积木”,而不是重复造轮子。

ts 复制代码
// 2) 同一套积木,拼接出不同接口形态
const listApi: BaseRouteConfig = {
  method: "get",
  externalPath: "/resource/list",
  fixedParams: { MainModel: "Resource", RelevanceModels: ["Owner"] },
  preMiddlewares: [
    middlewares.auth.authJudgeFactory({}),
    // 参数改写积木:把当前用户写入 where,实现“只查自己的”
    middlewares.base.changeOrCheckRequestPayloadFactory({ changeFields: { "where.ownerId": "req.user.id" } }),
  ],
  handler: baseController.getAll,
  postMiddlewares: [middlewares.network.response],
};

const createApi: BaseRouteConfig = {
  method: "post",
  externalPath: "/resource/create",
  fixedParams: { MainModel: "Resource" },
  preMiddlewares: [
     middlewares.auth.authJudgeFactory({}),
    middlewares.base.changeOrCheckRequestPayloadFactory({ changeFields: { ownerId: "req.user.id" } }),
     middlewares.base.setTransaction,
  ],
  handler: baseController.create,
  postMiddlewares: [ middlewares.base.setTransaction, middlewares.network.response,],
};

中间件式 Controller:把“拼装请求”与“调用通用服务”标准化

中间件式 Controller 的职责不是写业务逻辑,而是做“请求 → 查询/写入参数”的标准化拼装:

  • 读取统一查询结构:将 query/body 解析为结构化参数(例如 where、order、attributes、分页等)。
  • 构建 include(关联查询)
    • 支持普通关联 include
    • 支持递归关联 include(把多层关联以嵌套结构表达)
    • 对 include 的 attributes/required/through/where 等提供统一可控入口
  • 可插入的参数工厂:允许某些路由在进入通用 service 前,对参数做二次加工(例如默认排序、按当前用户过滤、补充 include 等)。
  • 事务感知:当路由启用了事务中间件,Controller 在创建/更新/删除/关联写入时会自动携带 transaction。

这样做的意义在于:将“数据接口的通用形态”固定下来,把变化点收敛为可治理的参数变换。

从“积木化”的视角看,Controller 更像一个稳定的“接口模板”,它不关心业务,只负责:

  • 把请求解析成统一的数据操作参数(例如 where/order/page/include)
  • 在必要时允许外部注入一个“参数加工器”(把变化点变成可控插件)
  • 将最终参数交给通用 Service 执行,并把结果挂到响应上下文中

说明式伪代码如下:

ts 复制代码
// 3) Controller 的职责:标准化拼装 + 可插拔参数加工
async function getAllController(req, res, next) {
  const query = parseQuery(req); // where/order/page/include...
  const factory = req.controllerParamFactory ?? (x => x);
  res.data = await baseService.getAll(await factory({
    MainModel: req.params.MainModel,
    ...query,
  }));
  next();
}

通用 Service:把 ORM 操作抽象成稳定语义

通用 Service 的设计思路是“提供稳定语义的能力,而非暴露 ORM 细节”:

  • 通用查询:支持分页与非分页两种模式,并对联表统计的常见问题(如 count 偏大)做统一处理策略。
  • 通用创建:支持单条与批量创建,保持调用方 API 一致性。
  • 通用更新:支持按条件更新,并支持在外部事务中运行。
  • 通用删除:提供软删除/硬删除(force)语义的统一入口。
  • 通用关联同步
    • 统一入口根据关联类型自动选择同步策略
    • 覆盖式语义更适合“后台配置/管理”场景(一次提交即以当前提交为准)
    • 支持在同步关系的同时附带更新某些目标字段(以满足“关系表带额外字段”的场景)

对外表现为:业务方只关心“要做什么”(查询/创建/更新/删除/关联),不需要关心 ORM 细节和边界处理。


治理型 Middlewares:把“安全与一致性”做成可插拔能力

通用接口最容易出现的问题是:字段越权、查询越权、批量写入缺少事务、以及参数不一致导致的数据脏写。为此引入了一组“治理型中间件工厂”:

  • 请求字段约束(limitRequestPayloadFactory)

    • 删除敏感字段(例如不允许客户端更新 id)
    • 替换/注入字段(例如强制写入某个固定值)
    • 过滤 attributes(避免敏感列被查询出来)
    • 可按角色生效(仅对某些角色启用更严格/更宽松的限制)
  • 请求参数改写/校验(changeOrCheckRequestPayloadFactory)

    • 把当前登录用户信息写入查询条件或请求体(实现“只能操作自己的数据”)
    • 校验某些字段只能是允许的值/符合正则/满足函数校验
  • 存在性校验(checkExistsFactory)

    • 在进入写操作之前做数据存在性验证,提前失败,提升可读性与错误一致性
  • 事务控制(setTransaction / commitTransaction)

    • 在路由层声明“该接口需要事务”
    • 统一把 transaction 放在请求上下文中,下游自动感知并复用

这些中间件的共同目标是:把“安全性、数据一致性、权限边界”从业务代码中抽离出来,成为路由级可复用的约束。


扩展点:如何在不破坏通用架构的前提下满足差异化需求

通用接口并不追求覆盖 100% 场景,而是提供明确的扩展方式:

  • 在配置层插入 preMiddlewares/postMiddlewares:用中间件实现权限、字段约束、额外校验、外部服务调用等。
  • 参数工厂(controllerParamFactory):在进入通用 service 前对查询参数做二次加工,适合“同一模型但不同视图/筛选/排序”的场景。
  • 少量专用接口与通用接口并存:当业务行为型接口(如支付回调)不适合 CRUD 时,独立实现专用 router/controller/service,不与通用接口耦合。

这体现了一种工程实践:用通用接口解决规模问题,用专用接口解决语义问题。


🎯适用场景与取舍

✅适用场景

  • 后台管理系统常见的数据管理页面(列表/详情/创建/编辑/删除)
  • 需要快速开放大量资源接口的中小型项目
  • 需要对接口进行统一治理(字段、权限、事务)以降低安全风险

⚖️ 取舍

  • 通用接口偏“数据导向”,不适合强业务语义的流程型接口
  • 需要约束团队使用方式(例如关联同步的覆盖式语义、字段限制的统一约定)
🚀关于通用接口系统设计分享
  • 发布于

    2025-05-18

  • 更新于

    2026-04-11

  • 类目

  • 作者

    Bokey

  • 版权协议

cc

Developed & Design by Bokey
已经发电运行了 0 天,我会继续努力
Copyright © 2024-2029 Bokey's Space
CC BY-NC-SA 4.0
粤ICP备2025398830号-1