|
| 1 | +# Rust 后端开发提供接口文档的方案 |
| 2 | + |
| 3 | +Rust 生态中,提供 API 接口文档主要有以下几种方案。我来为你详细介绍最主流的方式。 |
| 4 | + |
| 5 | +--- |
| 6 | + |
| 7 | +## 方案对比 |
| 8 | + |
| 9 | +| 方案 | 适配框架 | 特点 | |
| 10 | +|------|---------|------| |
| 11 | +| **utoipa** | Actix-web, Axum, Rocket 等 | ⭐ 最流行,通过宏自动生成 OpenAPI 文档 | |
| 12 | +| **aide** | Axum | 专为 Axum 设计,与路由深度集成 | |
| 13 | +| **paperclip** | Actix-web | Actix 专用,较成熟 | |
| 14 | +| 手写 OpenAPI YAML/JSON | 任意 | 灵活但维护成本高 | |
| 15 | + |
| 16 | +让我为你查找 **utoipa**(最主流方案)的最新用法: |
| 17 | + |
| 18 | +非常好,下面是 Rust 后端开发中提供接口文档的 **完整指南**: |
| 19 | + |
| 20 | +--- |
| 21 | + |
| 22 | +## 🏆 推荐方案:utoipa(最主流) |
| 23 | + |
| 24 | +**utoipa** 是 Rust 生态中最流行的 OpenAPI 文档生成库,通过 **过程宏** 自动从代码生成 OpenAPI 3.x 文档,并搭配 Swagger UI 在线查看。 |
| 25 | + |
| 26 | +--- |
| 27 | + |
| 28 | +### 1. 添加依赖(`Cargo.toml`) |
| 29 | + |
| 30 | +以 **Axum** 框架为例: |
| 31 | + |
| 32 | +```toml |
| 33 | +[dependencies] |
| 34 | +axum = "0.8" |
| 35 | +tokio = { version = "1", features = ["full"] } |
| 36 | +serde = { version = "1", features = ["derive"] } |
| 37 | +serde_json = "1" |
| 38 | + |
| 39 | +# utoipa 核心 + Axum 集成 + Swagger UI |
| 40 | +utoipa = { version = "5", features = ["axum_extras"] } |
| 41 | +utoipa-axum = "0.2" |
| 42 | +utoipa-swagger-ui = { version = "9", features = ["axum"] } |
| 43 | +``` |
| 44 | + |
| 45 | +如果用 **Actix-web**,换成: |
| 46 | + |
| 47 | +```toml |
| 48 | +utoipa-swagger-ui = { version = "9", features = ["actix-web"] } |
| 49 | +``` |
| 50 | + |
| 51 | +--- |
| 52 | + |
| 53 | +### 2. 定义数据模型(Schema) |
| 54 | + |
| 55 | +使用 `#[derive(ToSchema)]` 宏让结构体自动生成 OpenAPI Schema: |
| 56 | + |
| 57 | +```rust |
| 58 | +use serde::{Deserialize, Serialize}; |
| 59 | +use utoipa::ToSchema; |
| 60 | + |
| 61 | +/// 宠物信息 |
| 62 | +#[derive(Serialize, Deserialize, ToSchema)] |
| 63 | +struct Pet { |
| 64 | + /// 宠物ID |
| 65 | + id: u64, |
| 66 | + /// 宠物名称 |
| 67 | + name: String, |
| 68 | + /// 宠物年龄 |
| 69 | + age: Option<i32>, |
| 70 | +} |
| 71 | + |
| 72 | +/// 创建宠物请求 |
| 73 | +#[derive(Deserialize, ToSchema)] |
| 74 | +struct CreatePetRequest { |
| 75 | + /// 宠物名称 |
| 76 | + name: String, |
| 77 | + /// 宠物年龄 |
| 78 | + age: Option<i32>, |
| 79 | +} |
| 80 | + |
| 81 | +/// 通用响应 |
| 82 | +#[derive(Serialize, ToSchema)] |
| 83 | +struct ApiResponse<T: ToSchema> { |
| 84 | + code: i32, |
| 85 | + message: String, |
| 86 | + data: Option<T>, |
| 87 | +} |
| 88 | +``` |
| 89 | + |
| 90 | +> 💡 **关键**:Rust 的 `///` 文档注释会自动成为 OpenAPI 的字段描述! |
| 91 | +
|
| 92 | +--- |
| 93 | + |
| 94 | +### 3. 定义 API 接口(Path) |
| 95 | + |
| 96 | +使用 `#[utoipa::path]` 宏标注每个接口的元信息: |
| 97 | + |
| 98 | +```rust |
| 99 | +use axum::{extract::Path, Json}; |
| 100 | +use utoipa::OpenApi; |
| 101 | + |
| 102 | +/// 根据 ID 获取宠物 |
| 103 | +/// |
| 104 | +/// 从数据库中根据宠物 ID 查询宠物信息 |
| 105 | +#[utoipa::path( |
| 106 | + get, |
| 107 | + path = "/pets/{id}", |
| 108 | + tag = "宠物管理", |
| 109 | + params( |
| 110 | + ("id" = u64, Path, description = "宠物 ID"), |
| 111 | + ), |
| 112 | + responses( |
| 113 | + (status = 200, description = "查询成功", body = Pet), |
| 114 | + (status = 404, description = "宠物不存在") |
| 115 | + ) |
| 116 | +)] |
| 117 | +async fn get_pet_by_id(Path(id): Path<u64>) -> Json<Pet> { |
| 118 | + Json(Pet { |
| 119 | + id, |
| 120 | + name: "小白".to_string(), |
| 121 | + age: Some(3), |
| 122 | + }) |
| 123 | +} |
| 124 | + |
| 125 | +/// 创建宠物 |
| 126 | +/// |
| 127 | +/// 创建一个新的宠物记录 |
| 128 | +#[utoipa::path( |
| 129 | + post, |
| 130 | + path = "/pets", |
| 131 | + tag = "宠物管理", |
| 132 | + request_body( |
| 133 | + content = CreatePetRequest, |
| 134 | + description = "宠物信息", |
| 135 | + content_type = "application/json" |
| 136 | + ), |
| 137 | + responses( |
| 138 | + (status = 201, description = "创建成功", body = Pet, |
| 139 | + example = json!({"id": 1, "name": "小白", "age": 3}) |
| 140 | + ), |
| 141 | + (status = 400, description = "参数错误") |
| 142 | + ), |
| 143 | + // 可选:接口安全认证 |
| 144 | + security( |
| 145 | + ("bearer_token" = []) |
| 146 | + ) |
| 147 | +)] |
| 148 | +async fn create_pet(Json(req): Json<CreatePetRequest>) -> Json<Pet> { |
| 149 | + Json(Pet { |
| 150 | + id: 1, |
| 151 | + name: req.name, |
| 152 | + age: req.age, |
| 153 | + }) |
| 154 | +} |
| 155 | + |
| 156 | +/// 获取所有宠物 |
| 157 | +#[utoipa::path( |
| 158 | + get, |
| 159 | + path = "/pets", |
| 160 | + tag = "宠物管理", |
| 161 | + params( |
| 162 | + ("page" = Option<u32>, Query, description = "页码"), |
| 163 | + ("size" = Option<u32>, Query, description = "每页数量"), |
| 164 | + ), |
| 165 | + responses( |
| 166 | + (status = 200, description = "查询成功", body = Vec<Pet>) |
| 167 | + ) |
| 168 | +)] |
| 169 | +async fn list_pets() -> Json<Vec<Pet>> { |
| 170 | + Json(vec![]) |
| 171 | +} |
| 172 | +``` |
| 173 | + |
| 174 | +--- |
| 175 | + |
| 176 | +### 4. 组装路由并挂载 Swagger UI |
| 177 | + |
| 178 | +#### 方式一:使用 `OpenApiRouter`(推荐,自动收集路由) |
| 179 | + |
| 180 | +```rust |
| 181 | +use utoipa::OpenApi; |
| 182 | +use utoipa_axum::{router::OpenApiRouter, routes}; |
| 183 | +use utoipa_swagger_ui::SwaggerUi; |
| 184 | + |
| 185 | +#[derive(OpenApi)] |
| 186 | +#[openapi( |
| 187 | + info( |
| 188 | + title = "宠物商店 API", |
| 189 | + version = "1.0.0", |
| 190 | + description = "这是一个示例宠物商店接口文档" |
| 191 | + ), |
| 192 | + // 可选:定义安全方案 |
| 193 | + // security( |
| 194 | + // ("bearer_token" = []) |
| 195 | + // ), |
| 196 | + // components( |
| 197 | + // security_schemes( |
| 198 | + // ("bearer_token" = SecurityScheme::Http( |
| 199 | + // HttpBuilder::new().scheme(HttpAuthScheme::Bearer).bearer_format("JWT").build() |
| 200 | + // )) |
| 201 | + // ) |
| 202 | + // ) |
| 203 | +)] |
| 204 | +struct ApiDoc; |
| 205 | + |
| 206 | +#[tokio::main] |
| 207 | +async fn main() { |
| 208 | + // ✅ OpenApiRouter 自动收集路由 + 生成文档 |
| 209 | + let (router, api) = OpenApiRouter::with_openapi(ApiDoc::openapi()) |
| 210 | + .routes(routes!(get_pet_by_id)) |
| 211 | + .routes(routes!(create_pet)) |
| 212 | + .routes(routes!(list_pets)) |
| 213 | + .split_for_parts(); |
| 214 | + |
| 215 | + // 挂载 Swagger UI |
| 216 | + let app = router.merge( |
| 217 | + SwaggerUi::new("/swagger-ui") |
| 218 | + .url("/api-docs/openapi.json", api) |
| 219 | + ); |
| 220 | + |
| 221 | + let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); |
| 222 | + println!("📖 Swagger UI: http://localhost:3000/swagger-ui/"); |
| 223 | + axum::serve(listener, app).await.unwrap(); |
| 224 | +} |
| 225 | +``` |
| 226 | + |
| 227 | +#### 方式二:手动注册(传统方式) |
| 228 | + |
| 229 | +```rust |
| 230 | +#[derive(OpenApi)] |
| 231 | +#[openapi( |
| 232 | + paths(get_pet_by_id, create_pet, list_pets), // 手动列出所有接口 |
| 233 | + components(schemas(Pet, CreatePetRequest)), // 手动列出所有 Schema |
| 234 | + info(title = "宠物商店 API", version = "1.0.0") |
| 235 | +)] |
| 236 | +struct ApiDoc; |
| 237 | +``` |
| 238 | + |
| 239 | +--- |
| 240 | + |
| 241 | +### 5. 效果 |
| 242 | + |
| 243 | +启动服务后访问: |
| 244 | + |
| 245 | +| 地址 | 说明 | |
| 246 | +|------|------| |
| 247 | +| `http://localhost:3000/swagger-ui/` | 📖 **Swagger UI** 交互式文档界面 | |
| 248 | +| `http://localhost:3000/api-docs/openapi.json` | 📄 OpenAPI JSON 规范文件 | |
| 249 | + |
| 250 | +Swagger UI 界面可以直接 **在线调试接口**,非常方便前后端联调。 |
| 251 | + |
| 252 | +--- |
| 253 | + |
| 254 | +### 6. 高级用法速查 |
| 255 | + |
| 256 | +#### 🔐 安全认证(JWT Bearer) |
| 257 | + |
| 258 | +```rust |
| 259 | +use utoipa::openapi::security::{HttpAuthScheme, HttpBuilder, SecurityScheme}; |
| 260 | + |
| 261 | +#[derive(OpenApi)] |
| 262 | +#[openapi( |
| 263 | + components(schemas(Pet)), |
| 264 | + modifiers(&SecurityAddon) |
| 265 | +)] |
| 266 | +struct ApiDoc; |
| 267 | + |
| 268 | +struct SecurityAddon; |
| 269 | + |
| 270 | +impl utoipa::Modify for SecurityAddon { |
| 271 | + fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) { |
| 272 | + let components = openapi.components.as_mut().unwrap(); |
| 273 | + components.add_security_scheme( |
| 274 | + "bearer_token", |
| 275 | + SecurityScheme::Http( |
| 276 | + HttpBuilder::new() |
| 277 | + .scheme(HttpAuthScheme::Bearer) |
| 278 | + .bearer_format("JWT") |
| 279 | + .build() |
| 280 | + ), |
| 281 | + ); |
| 282 | + } |
| 283 | +} |
| 284 | +``` |
| 285 | + |
| 286 | +#### 📦 枚举类型 |
| 287 | + |
| 288 | +```rust |
| 289 | +#[derive(Serialize, ToSchema)] |
| 290 | +enum PetStatus { |
| 291 | + Available, |
| 292 | + Pending, |
| 293 | + Sold, |
| 294 | +} |
| 295 | +``` |
| 296 | + |
| 297 | +#### 🏷️ 分组 Tag |
| 298 | + |
| 299 | +```rust |
| 300 | +#[utoipa::path( |
| 301 | + get, |
| 302 | + path = "/users", |
| 303 | + tag = "用户管理", // 接口分组 |
| 304 | + // ... |
| 305 | +)] |
| 306 | +``` |
| 307 | + |
| 308 | +--- |
| 309 | + |
| 310 | +### 7. 完整项目结构参考 |
| 311 | + |
| 312 | +``` |
| 313 | +my-api/ |
| 314 | +├── Cargo.toml |
| 315 | +└── src/ |
| 316 | + ├── main.rs # 启动入口 + Swagger 挂载 |
| 317 | + ├── routes/ |
| 318 | + │ ├── mod.rs |
| 319 | + │ └── pets.rs # 宠物相关接口 (#[utoipa::path]) |
| 320 | + ├── models/ |
| 321 | + │ ├── mod.rs |
| 322 | + │ └── pet.rs # 数据模型 (#[derive(ToSchema)]) |
| 323 | + └── doc.rs # OpenApi 定义 + SecurityAddon |
| 324 | +``` |
| 325 | + |
| 326 | +--- |
| 327 | + |
| 328 | +## 总结 |
| 329 | + |
| 330 | +| 步骤 | 做什么 | |
| 331 | +|------|--------| |
| 332 | +| ① | 给结构体加 `#[derive(ToSchema)]` | |
| 333 | +| ② | 给接口函数加 `#[utoipa::path(...)]` | |
| 334 | +| ③ | 用 `#[derive(OpenApi)]` 汇总注册 | |
| 335 | +| ④ | 挂载 `SwaggerUi` 到路由 | |
| 336 | + |
| 337 | +这套方案的核心优势是 **文档即代码**——接口文档直接从代码生成,避免了手写文档和代码不同步的问题。修改了接口代码,文档自动更新。 |
0 commit comments