Skip to content

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

学习目标 ​

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

学完后,你应该能理解:

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

通用模块是独立模块 ​

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

它是一个独立模块。

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

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

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

通用接口路径 ​

通用接口使用动态路由。

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

例如:

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

这里的 user、project 就是模块标识。

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

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

从接口到服务函数 ​

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

  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 课程学习文档。