经过一番不懈努力,我们终于完成了一个提供 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 参数说明:
-
-X POST
:指定使用POST
方式进行请求 -
-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
获取单个 TodoList
$ curl 127.0.0.1:9527/todo/3
{"code":0,"msg":"OK","data":{"id":3,"title":"axum中文网"}}
修改 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}
可以看到,ID 为 3 的 TodoList 已经不存在了。
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}
再尝试获取它:
创建一个新的 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 服务大概涉及到以下知识点:
-
配置文件:通过
dotenv
和config
两个 crate,将我们的配置文本和结构体进行映射 -
自定义错误和自定义
Result
:我们自定义了自己的错误AppError
,无论是数据库操作还是 handler,最终都是将错误转换成了AppError
。为了简化操作,我们还定义了自己的Result
。注意,每个项目只能定义一次自己的Result
。所以,我们在handler
模块又定义了一个HandlerResult
。 -
自定义响应:我们的响应是一个 JSON,它通过
code
来标识是否成功,通过msg
来传递可能出现的错误信息,通过data
来返回期望的数据。为了统一,我们将其封装成了Response
。同时,AppError
也在实现IntoResponse
的时候使用了它。 -
数据库连接:我们使用了
deadpool-postgres
这个连接池,它包装了tokio-postgres
。为了方便将查询结果填充到结构体,我们还使用了tokio-pg-mapper
和tokio-pg-mapper-derive
。 -
日志:虽然我们将所有可能出现的错误都转换成了
AppError
,但并没有对这些错误进行记录。为了记录这些错误,我们使用了tracing
和tracing-subscriber
。 -
重构:我们对代码进行了重构,这是很重要的。既能提高开发效率,又能易于扩展。尤其注意
handler
模块的log_error
是如何封装闭包的以及db
里的get_stmt
等函数是如何既能接收Client
又能接收事务的。
请特别关注我们是如何对代码进行重构的。