Rust使用Actix-web和SeaORM开发WebAPI通过Swagger UI查看接口文档

news/2024/11/15 6:22:51/文章来源:https://www.cnblogs.com/vinciyan/p/18412896

本文将介绍Rust语言使用Actix-web和SeaORM库,数据库使用PostgreSQL,开发增删改查项目,同时可以通过Swagger UI查看接口文档和查看标准Rust文档

开始项目

首先创建新项目,名称为rusty_crab_api

cargo new rusty_crab_api

Cargo.toml

[dependencies]
sea-orm = { version = "1.0.0-rc.5", features = [ "sqlx-postgres", "runtime-tokio-native-tls", "macros" ] }
tokio = { version = "1.35.1", features = ["full"] }
chrono = "0.4.33"
actix-web = "4.4.0"
serde = { version = "1.0", features = ["derive"] }
utoipa = { version = "4", features = ["actix_extras"] }
utoipa-swagger-ui = { version = "4", features = ["actix-web"] }
serde_json = "1.0"

使用SeaORM作为ORM工具,它提供了sea-orm-cli​工具,方便生成entity

PostgreSQL创建数据库

CREATE TABLE "user" (id SERIAL PRIMARY KEY,username VARCHAR(32) NOT NULL,birthday TIMESTAMP,sex VARCHAR(10),address VARCHAR(256)
);COMMENT ON COLUMN "user".username IS '用户名称';
COMMENT ON COLUMN "user".birthday IS '生日';
COMMENT ON COLUMN "user".sex IS '性别';
COMMENT ON COLUMN "user".address IS '地址';

安装sea-orm-cli

cargo install sea-orm-cli

生成entity

sea-orm-cli generate entity -u postgres://[用户名]:[密码]@[IP]:[PORT]/[数据库] -o src/entity

自动帮我们生成src./entity/user.rs文件

#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(table_name = "user")]
pub struct Model {#[sea_orm(primary_key)]pub id: i32,pub username: String,pub birthday: Option<DateTime>,pub sex: Option<String>,pub address: Option<String>,
}#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}impl ActiveModelBehavior for ActiveModel {}

接下来编写接口函数,新建src/handlers/user.rs,编写用户表的增删改查代码,同时在代码文件中编写说明文档,提供给Rust标准文档和Swagger UI使用

/// 表示创建新用户的请求体结构
#[derive(Debug, Deserialize, ToSchema)]
#[schema(example = json!({"username": "johndoe","birthday": "2023-09-09T15:53:00","sex": "male","address": "123 Main St, Anytown, USA"
}))]
pub struct CreateUser {/// 用户名  pub username: String,/// 生日(可选)#[schema(value_type = String)]pub birthday: Option<DateTime>,/// 性别(可选)pub sex: Option<String>,/// 地址(可选)pub address: Option<String>,
}
/// 创建新用户
///
/// # 请求体
///
/// 需要一个JSON对象,包含以下字段:
/// - `username`: 字符串,用户名(必填)
/// - `birthday`: ISO 8601格式的日期时间字符串,用户生日(可选)
/// - `sex`: 字符串,用户性别(可选)
/// - `address`: 字符串,用户地址(可选)
///
/// # 响应
///
/// - 成功:返回状态码200和新创建的用户JSON对象
/// - 失败:返回状态码500
///
/// # 示例
///
/// ‍‍```
/// POST /users
/// Content-Type: application/json
///
/// {
///     "username": "johndoe",
///     "birthday": "1990-01-01T00:00:00",
///     "sex": "M",
///     "address": "123 Main St, Anytown, USA"
/// }
/// ‍‍```
#[utoipa::path(post,path = "/api/users",request_body = CreateUser,responses((status = 200, description = "User created successfully", body = Model),(status = 500, description = "Internal server error"))
)]
pub async fn create_user(db: web::Data<sea_orm::DatabaseConnection>,user_data: web::Json<CreateUser>,
) -> impl Responder {let user = UserActiveModel {username: Set(user_data.username.clone()),birthday: Set(user_data.birthday),sex: Set(user_data.sex.clone()),address: Set(user_data.address.clone()),..Default::default()};let result = user.insert(db.get_ref()).await;match result {Ok(user) => HttpResponse::Ok().json(user),Err(_) => HttpResponse::InternalServerError().finish(),}
}
/// 获取指定ID的用户信息
///
/// # 路径参数
///
/// - `id`: 整数,用户ID
///
/// # 响应
///
/// - 成功:返回状态码200和用户JSON对象
/// - 未找到:返回状态码404
/// - 失败:返回状态码500
///
/// # 示例
///
/// ‍‍```
/// GET /users/1
/// ‍‍```
#[utoipa::path(get,path = "/api/users/{id}",responses((status = 200, description = "User found", body = Model),(status = 404, description = "User not found"),(status = 500, description = "Internal server error")),params(("id" = i32, Path, description = "User ID"))
)]
pub async fn get_user(db: web::Data<sea_orm::DatabaseConnection>,id: web::Path<i32>,
) -> impl Responder {let user = user::Entity::find_by_id(*id).one(db.get_ref()).await;println!("{id}");match user {Ok(Some(user)) => HttpResponse::Ok().json(user),Ok(None) => HttpResponse::NotFound().finish(),Err(_) => HttpResponse::InternalServerError().finish(),}
}
/// 更新指定ID的用户信息
///
/// # 路径参数
///
/// - `id`: 整数,用户ID
///
/// # 请求体
///
/// 需要一个JSON对象,包含以下字段(所有字段都是可选的):
/// - `username`: 字符串,新的用户名
/// - `birthday`: ISO 8601格式的日期时间字符串,新的用户生日
/// - `sex`: 字符串,新的用户性别
/// - `address`: 字符串,新的用户地址
///
/// # 响应
///
/// - 成功:返回状态码200和更新后的用户JSON对象
/// - 未找到:返回状态码404
/// - 失败:返回状态码500
///
/// # 示例
///
/// ‍‍```
/// PUT /users/1
/// Content-Type: application/json
///
/// {
///     "username": "johndoe_updated",
///     "address": "456 Elm St, Newtown, USA"
/// }
/// ‍‍```
#[utoipa::path(put,path = "/api/users/{id}",request_body = CreateUser,responses((status = 200, description = "User updated successfully", body = Model),(status = 404, description = "User not found"),(status = 500, description = "Internal server error")),params(("id" = i32, Path, description = "User ID"))
)]
pub async fn update_user(db: web::Data<sea_orm::DatabaseConnection>,id: web::Path<i32>,user_data: web::Json<CreateUser>,
) -> impl Responder {let user = user::Entity::find_by_id(*id).one(db.get_ref()).await;match user {Ok(Some(user)) => {let mut user: UserActiveModel = user.into();user.username = Set(user_data.username.clone());user.birthday = Set(user_data.birthday);user.sex = Set(user_data.sex.clone());user.address = Set(user_data.address.clone());let result = user.update(db.get_ref()).await;match result {Ok(updated_user) => HttpResponse::Ok().json(updated_user),Err(_) => HttpResponse::InternalServerError().finish(),}}Ok(None) => HttpResponse::NotFound().finish(),Err(_) => HttpResponse::InternalServerError().finish(),}
}
/// 删除指定ID的用户
///
/// # 路径参数
///
/// - `id`: 整数,用户ID
///
/// # 响应
///
/// - 成功:返回状态码204(无内容)
/// - 失败:返回状态码500
///
/// # 示例
///
/// ‍‍```
/// DELETE /users/1
/// ‍‍```
#[utoipa::path(delete,path = "/api/users/{id}",responses((status = 204, description = "User deleted successfully"),(status = 500, description = "Internal server error")),params(("id" = i32, Path, description = "User ID"))
)]
pub async fn delete_user(db: web::Data<sea_orm::DatabaseConnection>,id: web::Path<i32>,
) -> impl Responder {let result = user::Entity::delete_by_id(*id).exec(db.get_ref()).await;match result {Ok(_) => HttpResponse::NoContent().finish(),Err(_) => HttpResponse::InternalServerError().finish(),}
}

为了使用Swagger UI查看接口文档,还需要创建src/api_doc.rs文件

#[derive(OpenApi)]
#[openapi(paths(handlers::user::create_user,handlers::user::get_user,handlers::user::update_user,handlers::user::delete_user),components(schemas(Model,CreateUser)),tags((name = "users", description = "User management API"))
)]
pub struct ApiDoc;

在src/main.rs文件定义路由和配置Swagger UI

#[actix_web::main]
async fn main() -> std::io::Result<()> {let db: DatabaseConnection = db::establish_connection().await;let db_data = web::Data::new(db);HttpServer::new(move || {App::new().app_data(db_data.clone()).service(web::scope("/api").service(web::scope("/users").route("", web::post().to(create_user)).route("/{id}", web::get().to(get_user)).route("/{id}", web::put().to(update_user)).route("/{id}", web::delete().to(delete_user)).route("/test", web::get().to(|| async { "Hello, World!" })))).service(SwaggerUi::new("/swagger-ui/{_:.*}").url("/api-docs/openapi.json", ApiDoc::openapi()))}).bind("127.0.0.1:8080")?.run().await
}

到这里,项目完成开发,启动项目

cargo run

查看Swagger UI接口文档

浏览器打开http://localhost:8080/swagger-ui/

可以看到我们在Rust代码文件中的注释说明,这对于接口使用人员和代码维护人员都非常友好,当然对于接口的简单测试,在这个页面也是非常方便去进行

查看Rust标准文档

cargo doc --open

最后

项目的完整代码可以查看我的仓库​:https://github.com/VinciYan/rusty_crab_api.git

后续,我还会介绍如何使用Rust语言Web开发框架Salvo和SeaORM结合开发WebAPI

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/796739.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JOI Open 2016

T1 JOIRIS你在玩俄罗斯方块,游戏区域是一个宽度为 \(n\),高度足够大的矩形网格、初始时第 \(i\) 列有 \(a_i\) 个方块。 给定参数 \(k\),你可以做不超过 \(10^4\) 次操作,来将这个网格中的所有方块全部消除,一次操作形如:在网格的最顶端落下一个 \(1 \times k\) 或者 \(k…

章12——异常exception

异常 快捷键 ctrl + alt + t 选中 try-catch 如果进行异常处理,即使出现了异常,程序可以继续执行。异常介绍 开发过程中的语法错误和逻辑错误不是异常。 执行过程中所发生的异常事件可分为如下两大类:异常体系图小结:常见的运行时异常没有关联的类不能进行上下转型 异常处理…

APB总线总结

APB总结 一、简介 APB提供了一个低功耗的接口,并降低了接口的复杂性。APB接口用在低带宽和不需要高性能总线的外围设备上。APB是非流水线结构,所有的信号仅与时钟上升沿相关,这样就可以简化APB外围设备的设计流程,每个传输至少耗用两个周期。 二、信号列表信号名 来源 描述…

面试- Web安全

XSS攻击(跨站脚本攻击)XSS预防 < < > >XSRF(CSRF)攻击(跨站请求伪造)就像是你在不知情的情况下,被别人利用你的权限发起了某个你没打算进行的请求。重点是可以把你的用户信息给带过去,你不知不觉就帮我付款了。XSS 是恶意代码“潜伏”在页面上,欺骗你去执行…

k8s 中的 Service 简介【k8s 系列之二】

〇、前言 k8s 集群中的每一个 Pod 都有自己的 IP 地址,那么是不是有 IP 了,访问起来就简单了呢,其实不然。 因为在 k8s 中 Pod 不是持久性的,摧毁重建将获得新的 IP,客户端通过会变更 IP 来访问显然不合理。另外 Pod 还经常会通过多个副本来实现负载均衡,客户端如何高效的…

软工作业二:论文查重系统

这个作业属于哪个课程 <计科22级34班>这个作业要求在哪里 [<作业要求>](个人项目 - 作业 - 计科22级34班 - 班级博客 - 博客园 (cnblogs.com))这个作业的目标 通过实际编程任务,全面提升学生在编程、算法、项目管理、性能优化、代码测试和版本控制等方面的能力,为…

面试-JS基础知识-作用域和闭包

问题this的不同应用场景 手写bind函数 实际开发中闭包的应用场景,举例说明 创建10个<a>标签,点击的时候弹出来对应的序号作用域:某个变量的合法使用范围全局 函数 块级** 自由变量上面图的最里面的红框————a a1 a2都是自由变量,因为都没有被定义。会一层一层往上…

学习高校课程-软件设计模式-软件设计原则(lec2)

软件设计原则Feature of Good Design (1) 优秀设计的特点(一) Code reuse 代码复用 – Challenge: tight coupling between components, dependencies on concrete classes instead of interfaces, hardcoded operations – Solution: design patterns – 挑战:组件之间的紧…

ATTCK红队评估(红日靶场4)

靶场介绍本次靶场渗透反序列化漏洞、命令执行漏洞、Tomcat漏洞、MS系列漏洞、端口转发漏洞、以及域渗透等多种组合漏洞,希望大家多多利用。 环境搭建 机器密码 WEB主机 ubuntu:ubuntuWIN7主机 douser:Dotest123(DC)WIN2008主机 administrator:Test2008网络配置111网段是web的网…

Markdown随笔

冰冻三尺非一日之寒,持之以恒方位始终。 Markdown语法讲解标题一共六级标题分别为Ctrl+1~6: 一级 二级 三级 四级 五级 六级字体 粗体 粗斜体 斜体 删除线引用一个大于号>分割线图片超链接 点击进入百度百科列表 数字加上空格(有序) 点加上空格(无序)表格姓名 性别 年…

tarjan里的定义

强连通分量 - OI Wiki (oi-wiki.org)从以u为根的子树中的任意点出发。单次到达(从这个点指向某个点,有一条边) 的这些点中的dfn的最小值以v为根的子树,包含在以u为根的子树中,low[v]所用的子节点,一定也可以被low[u],这个点一定在以u为根的子树里,所以用low[v] 从u这个…

南沙csp-j/s一对一家教陈老师解题:1317:【例5.2】组合的输出

​【题目描述】排列与组合是常用的数学方法,其中组合就是从n个元素中抽出r个元素(不分顺序且r≤n),我们可以简单地将n个元素理解为自然数1,2,…,n,从中任取r个数。 现要求你用递归的方法输出所有组合。 例如n=5,r=3,所有组合为: 1 2 3 1 2 4 1 2 5 1 3 4 1 …