经过一番不懈努力,我们终于完成了一个提供 RESTFul API 的 Todo 服务。虽然功能简单,但它涉及到了 Axum 开发的多个方面。

运行效果

我们先来看看这个小项目的运行效果,一是检验我们的成果,二是对功能进行简单测试。

TodoList 部分

创建 TodoList

$ curl -X POST -H 'content-type:application/json;charset=utf-8' -d '{"title":"axum中文网"}' 127.0.0.1:9527/todo
{"code":0,"msg":"OK","data":{"id":3}}

curl 参数说明:

  • -H 'content-type:application/json;charset=utf-8':指定我们的请求是 JSON 格式的

  • -d '{"title":"axum中文网"}':指定我们要传递的数据

返回信息符合我们的预期:

{ "code": 0, "msg": "OK", "data": { "id": 3 } }

新创建的 TodoList 的 ID 为 3,API 为我们返回了这个 ID。

获取所有 TodoList

$ curl 127.0.0.1:9527/todo
{"code":0,"msg":"OK","data":[{"id":3,"title":"axum中文网"},{"id":2,"title":"清单2"},{"id":1,"title":"清单1"}]}

可以看到,除了刚刚通过调用 API 创建的 ID 为 3 的记录之外,我们通过导入 SQL 文件创建的 2 条记录也在,符合预期。

获取单个 TodoList

修改 TodoList

$ curl -X PUT -H 'content-type:application/json;charset=utf-8' -d '{"title":"axum中文网,axum.rs", "id":3}' 127.0.0.1:9527/todo/3
{"code":0,"msg":"OK","data":true}

修改后,我们再次获取这个 TodoList 看看:

$ curl 127.0.0.1:9527/todo/3
{"code":0,"msg":"OK","data":{"id":3,"title":"axum中文网,axum.rs"}}

可以看到,ID 为 3 的 TodoList 的title字段的数据已经成功修改了。

删除 TodoList

$ curl -X DELETE 127.0.0.1:9527/todo/3
{"code":0,"msg":"OK","data":true

删除后,我们再尝试获取这个 TodoList:

$ curl 127.0.0.1:9527/todo/3
{"code":2,"msg":"不存在的记录","data":null}

TodoItem 部分

获取指定 TodoList 下的所有 TodoItem

$ curl 127.0.0.1:9527/todo/1/items
{"code":0,"msg":"OK","data":[{"id":1,"title":"清单项目 1","checked":false,"list_id":1},{"id":2,"title":"清单项目 2","checked":false,"list_id":1}]}

获取某个 TodoItem 的详情

$ curl 127.0.0.1:9527/todo/1/items/1
{"code":0,"msg":"OK","data":{"id":1,"title":"清单项目 1","checked":false,"list_id":1}}

将某个 TodoItem 标识为已完成

即,将其checked修改为true

curl -X PUT 127.0.0.1:9527/todo/1/items/1
{"code":0,"msg":"OK","data":true}

再获取它的详情看看:

$ curl 127.0.0.1:9527/todo/1/items/1
{"code":0,"msg":"OK","data":{"id":1,"title":"清单项目 1","checked":true,"list_id":1}}

删除某个 TodoItem

$ curl -X DELETE 127.0.0.1:9527/todo/1/items/1
{"code":0,"msg":"OK","data":true}

再尝试获取它:

curl 127.0.0.1:9527/todo/1/items/1
{"code":2,"msg":"不存在的记录","data":null}

创建一个新的 TodoItem

curl -X POST -d '{"title":"学习axum中文网课程", "list_id":1}' -H 'content-type:application/json;charset=utf-8' 127.0.0.1:9527/todo/1/items
{"code":0,"msg":"OK","data":{"id":4}}

总结

本项目使用了很多 axum 知识,如果你对某些知识点不清楚,可以在我们的《漫游 axum》专题里查找。你可能才开始使用 axum 开发,对本专题中很多代码不理解,没关系,你先照着代码自行练习,多敲几遍再配合我们的《漫游 axum》专题,相信会慢慢理解的。

我们的这个 Todo 服务大概涉及到以下知识点:

  • 配置文件:通过dotenvconfig两个 crate,将我们的配置文本和结构体进行映射

  • 自定义错误和自定义Result:我们自定义了自己的错误AppError,无论是数据库操作还是 handler,最终都是将错误转换成了AppError。为了简化操作,我们还定义了自己的Result。注意,每个项目只能定义一次自己的Result。所以,我们在handler模块又定义了一个HandlerResult

  • 自定义响应:我们的响应是一个 JSON,它通过code来标识是否成功,通过msg来传递可能出现的错误信息,通过data来返回期望的数据。为了统一,我们将其封装成了Response。同时,AppError也在实现IntoResponse的时候使用了它。

  • 数据库连接:我们使用了deadpool-postgres这个连接池,它包装了tokio-postgres。为了方便将查询结果填充到结构体,我们还使用了tokio-pg-mappertokio-pg-mapper-derive

  • 日志:虽然我们将所有可能出现的错误都转换成了AppError,但并没有对这些错误进行记录。为了记录这些错误,我们使用了tracingtracing-subscriber

  • 重构:我们对代码进行了重构,这是很重要的。既能提高开发效率,又能易于扩展。尤其注意 handler 模块的log_error是如何封装闭包的以及db里的get_stmt等函数是如何既能接收Client又能接收事务的。

请特别关注我们是如何对代码进行重构的。