Skip to content

074. 通用服务、接口层与测试调试

学习目标

这一节把通用模块从底层构建器推进到服务函数和接口层。

学完后,你应该能理解:

  • 通用模块和已有业务接口的关系;
  • 通用接口如何通过动态路由选择模块;
  • 服务函数如何执行 SQL 和拦截器;
  • 新增、批量新增、更新、删除、查询的大体流程;
  • 如何用测试用例发现 SQL 构建器问题。

通用模块是独立模块

通用模块不会替换前面已经写好的所有业务接口。

它是一个独立模块。

已有用户接口仍然可以继续使用 SQLModel 和原来的 Service 写法。

通用模块主要服务于那些结构相似、表格化、配置化的后台功能。

所以它和普通业务接口可以并存。

通用接口路径

通用接口使用动态路由。

路径中间会带一个模块标识。

例如:

txt
/generic/user/list
/generic/project/list

这里的 userproject 就是模块标识。

后端根据模块标识读取模块配置。

再根据动作类型调用对应服务函数。

从接口到服务函数

一个通用请求大致经过几层:

  1. 中间件认证 Token;
  2. 接口读取模块标识和请求参数;
  3. 服务函数读取模块配置;
  4. 前置拦截器执行;
  5. SQL 构建器生成 SQL 和参数;
  6. 数据库连接执行 SQL;
  7. 后置拦截器执行;
  8. 返回结果给前端。

这个流程和中间件不是一回事。

中间件包在接口外面。

拦截器是服务函数内部的扩展点。

为什么实现顺序是倒着的

通用模块的实现顺序不是从接口开始。

它先定义类型,再写 SQL 构建器,再写拦截器,再写服务函数,最后写接口。

看起来像是倒着做。

但这是按依赖关系来的。

上层接口依赖下层服务。

服务依赖 SQL 构建器和拦截器。

所以先把底层能力写好,后面接口才容易接上。

服务函数参数

通用服务函数会接收一批上下文。

常见参数包括:

  • 数据库 Session;
  • 请求参数;
  • 模块配置;
  • 当前用户;
  • 调试信息数组;
  • 是否自动提交事务。

Session 用来拿数据库连接。

真正执行 SQL 的是连接对象。

当前用户来自认证中间件挂到请求对象上的用户信息。

当前用户的用途

新增和更新时,经常要写入标准字段。

例如:

  • 创建时间;
  • 创建人;
  • 更新时间;
  • 更新人。

创建人和更新人通常来自当前登录用户。

但有些公开接口可能没有 Token。

所以当前用户参数也可能为空。

服务函数要能处理这种情况。

调试信息

服务函数可以接收 debugData

它是一个数组。

执行过程中会把 SQL 和参数放进去。

结构类似:

py
{
    "sql": "... %s ...",
    "values": [...]
}

这样调用方可以知道一次接口到底执行了哪些 SQL。

调试通用模块时非常重要。

自动提交事务

新增、更新、删除默认可以自动提交。

但有些业务需要一次执行多件事。

例如:

  • 新增订单;
  • 扣库存;
  • 写日志。

这时就应该关闭自动提交,把多个动作放在同一个事务里。

全部成功再提交。

任意一步失败就回滚。

新增服务流程

单条新增的大体流程是:

  1. 准备调试数组;
  2. 获取数据库连接;
  3. 处理请求行数据;
  4. 转换字段值;
  5. 执行新增前拦截器;
  6. 构建 INSERT SQL;
  7. 参数化执行 SQL;
  8. 必要时提交事务;
  9. 查询刚新增的数据;
  10. 执行新增后拦截器;
  11. 返回新增结果。

新建后再查一遍,是为了把数据库最终结果返回给前端。

例如默认值、转换后的字段、标准字段都能一起带回。

批量新增

批量新增和单条新增类似。

区别是请求行数据变成数组。

拦截器也会使用批量新增对应的动作。

新增完成后,可以用本次新增出来的 ID 列表,再查一遍刚新增的数据。

这样返回结果仍然是完整对象列表。

更新和删除

更新流程和新增类似。

它会执行更新前和更新后拦截器。

再构建 UPDATE SQL。

删除可以直接执行 DELETE SQL。

使用 SQL 删除时,不一定要先查再删。

如果业务只是删除某些记录,直接按 ID 删除更简单。

但如果删除前需要判断状态、权限或关联关系,就要先查再决定。

分页查询服务

分页查询服务会:

  • 处理默认请求参数;
  • 构建查询 SQL;
  • 执行查询;
  • 转换字段值;
  • 执行后置拦截器;
  • 按需查总数;
  • 返回列表结果。

如果需要总数,会再执行一次只查总数的查询。

所以带总数的分页查询通常更重。

单条查询

单条查询本质上可以复用分页查询。

它会构建一个等值筛选条件。

例如按 ID 查询:

txt
id = xxx

再调用列表查询,只取一条结果。

这样能复用字段转换、拦截器和 SQL 构建逻辑。

测试用例的重要性

通用模块底层代码很多。

只靠手动测试很容易漏。

所以要先跑 SQL 构建器测试。

因为上层服务和接口都依赖它。

如果构建器不稳定,上层测试失败也很难定位。

从测试失败定位问题

测试可以明确指出期望 SQL 和实际 SQL 的差异。

例如新增 SQL 构建时,如果空值字段应该被跳过,但代码误把空值字段也拼进 INSERT,就会触发断言失败。

这时对比左右两边 SQL,就能看到多出来的字段。

再回到构建器里检查字段过滤逻辑。

这种反馈比手工看代码可靠。

调试测试

PyCharm 可以单独运行一个测试文件或某个测试用例。

也可以只重新运行失败的测试。

如果要看某个函数内部流程,可以打断点后用 Debug 运行测试。

通用模块代码层级深。

后面调试时要优先缩小到单个测试用例。

否则一次跑全部测试会很慢,也不容易看清流程。

这一节的重点

这一节把通用模块连接到了接口层。

你需要记住:

  • 通用模块和普通业务接口可以并存;
  • 动态路由里的模块标识决定读取哪份配置;
  • 服务函数负责执行拦截器、SQL 构建、SQL 执行和结果转换;
  • 调试信息数组能记录 SQL 和参数;
  • 自动提交事务要按业务场景决定;
  • 单条查询可以复用列表查询;
  • SQL 构建器要先用测试兜住,再往上测服务和接口。

AI Agent 课程学习文档。