Loco 是一个 Rust API 和 Web 框架,用于构建全栈产品。

Loco 这个名字源自 locomotive,是对 Rails 的致敬,而且 locolocomotive 更容易输入 :-)。此外,在一些语言中它表示“疯狂”,但这并不是最初的意图(或者,在 Rust 上构建 Rails 是疯狂的吗?只有时间才能证明!)。

你需要对 Rust 有中等程度的了解。你需要知道如何构建、测试和运行 Rust 项目,已经使用了一些流行的库,例如 clapregextokioaxum 或其他 Web 框架,没有什么太花哨的。Loco 中没有疯狂的生命周期扭曲或复杂/过于神奇的宏,你不需要知道它们是如何工作的来使用 Loco。

Loco 深受 Rails 的启发。如果你了解 Rails 和 Rust,你就会有宾至如归的感觉。如果你只了解 Rails 并且不熟悉 Rust,你会发现 Loco 令人耳目一新。我们不假设你了解 Rails。

我们认为 Rails 非常棒,因此本指南也深受 Rails 指南 的启发

Loco 是什么?

Loco 是一个适用于 Rust 的 Web 或 API 框架。它也是一个面向开发者的生产力套件:它包含了你在构建爱好或下一个创业公司时所需的一切。它也深受 Rails 的启发。

  • 你拥有 MVC 模型的一个变体,它消除了选项悖论。你专注于构建你的应用程序,而不是为使用什么抽象而做出学术决策。
  • 胖模型,瘦控制器。模型应该包含你大部分的逻辑和业务实现,控制器应该只是一个轻量级的路由器,它理解 HTTP 并移动参数。
  • 命令行驱动以保持你的动力和流程。生成内容,而不是从头开始复制、粘贴或编码。
  • 每个任务都“准备好基础设施”,只需插入你的代码并将其连接起来:控制器、模型、视图、任务、后台作业、邮件发送器等等。
  • 约定优于配置:决策已经为你完成——文件夹结构很重要,配置形状和值很重要,并且应用程序的连接方式对于应用程序的操作方式以及你如何发挥最大效率很重要。

创建一个新的 Loco 应用程序

你可以按照本指南进行循序渐进的“自下而上”学习,或者你可以跳过并使用 Loco 快速入门 进行更快的“自上而下”介绍。

安装

$ cargo install loco-cli

创建一个新的 Loco 应用程序

现在你可以创建你的新应用程序(对于内置身份验证,选择“Saas App”)。

$ loco new
 ❯ App name? · myapp
? ❯ What would you like to build? ›
  lightweight-service (minimal, only controllers and views)
  Rest API (with DB and user auth)
 Saas app (with DB and user auth)
🚂 Loco app generated successfully in:
myapp

你现在可以切换到 myapp

$ cd myapp

确保你也已经本地安装或运行(通过 Docker 或其他方式),如果你选择了具有 DB 依赖项的 starter:

  • Postgres(你的数据库将被命名为 myapp_development
  • Redis

要配置数据库,请使用 loco:loco 运行一个本地 postgres 数据库,并将其命名为 myapp_development

此 docker 命令启动 postgresql 数据库服务器。

docker run -d -p 5432:5432 -e POSTGRES_USER=loco -e POSTGRES_DB=myapp_development -e POSTGRES_PASSWORD="loco" postgres:15.3-alpine

此 docker 命令启动 redis 服务器:

docker run -p 6379:6379 -d redis redis-server

使用 doctor 命令检查所需的资源:

$ cargo loco doctor
    Finished dev [unoptimized + debuginfo] target(s) in 0.32s
     Running `target/debug/myapp-cli doctor`
✅ SeaORM CLI is installed
✅ DB connection: success
✅ Redis connection: success

Loco 默认创建的文件和文件夹及其作用:

文件/文件夹作用
src/包含控制器、模型、视图、任务等
app.rs主要组件注册点。在此处连接重要部分。
lib.rs组件的各种 rust 特定导出。
bin/具有 main.rs 文件, 你不需要关心它
controllers/包含控制器,所有控制器都通过 mod.rs 导出
models/包含模型,models/_entities 包含自动生成的 SeaORM 模型,models/*.rs 包含您的模型扩展逻辑,它们通过 mod.rs 导出
views/包含基于 JSON 的视图。可以通过 API 以 JSON 形式输出和输出的结构。
workers/包含后台工作进程。
mailers/发送电子邮件的邮件逻辑和模板。
fixtures/包含数据和自动固定加载逻辑。
tasks/包含您的日常业务导向任务,例如发送电子邮件、生成业务报告、数据库维护等。
tests/您的应用范围测试:模型、请求等。
config/基于阶段的配置文件夹:开发、测试、生产
channels/包含所有 channels 路由。

您好,Loco!

让我们快速获得一些响应。为此,我们需要启动服务器。

启动服务器

$ cargo loco start

                                                                                ▀     ▄  ▄ ▄▀
                                     ▀▄▄
                             ▀    ▀  ▀▄▀█▄
                                          ▀█▄
▄▄▄▄▄▄▄  ▄▄▄▄▄▄▄▄▄   ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄ ▀▀█
 ██████  █████   ███ █████   ███ █████   ███ ▀█
 ██████  █████   ███ █████   ▀▀▀ █████   ███ ▄█▄
 ██████  █████   ███ █████       █████   ███ ████▄
 ██████  █████   ███ █████   ▄▄▄ █████   ███ █████
 ██████  █████   ███  ████   ███ █████   ███ ████▀
   ▀▀▀██▄ ▀▀▀▀▀▀▀▀▀▀  ▀▀▀▀▀▀▀▀▀▀  ▀▀▀▀▀▀▀▀▀▀ ██▀
       ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
                https://loco.rs

listening on port 3000

现在,让我们看看它是否工作:

$ curl localhost:3000/api/_ping
{"ok":true}

内置的 _ping 路由将告诉您的负载均衡器一切都已启动。

让我们看看所有必需的服务是否已启动:

$ curl localhost:3000/api/_health
{"ok":true}

内置的 _health 路由将告诉您您已正确配置您的应用:app 已成功建立与 Postgres 和 Redis 实例的连接。

对 Loco 说“你好”

让我们为我们的服务添加一个快速的 hello 响应。

$ cargo loco generate controller guide
added: "src/controllers/guide.rs"
injected: "src/controllers/mod.rs"
injected: "src/app.rs"
added: "tests/requests/guide.rs"
injected: "tests/requests/mod.rs"

这是生成的控制器主体:

#![allow(clippy::unused_async)]
use loco_rs::prelude::*;

pub async fn echo(req_body: String) -> String {
    req_body
}

pub async fn hello(State(_ctx): State<AppContext>) -> Result<String> {
    // do something with context (database, etc)
    format::text("hello")
}

pub fn routes() -> Routes {
    Routes::new()
        .prefix("guide")
        .add("/", get(hello))
        .add("/echo", post(echo))
}

启动服务器:

$ cargo loco start

现在,让我们测试一下:

$ curl localhost:3000/api/guide
hello

Loco 具有强大的生成器,这将使您在构建应用时提高 10 倍的生产力并推动您的势头。

如果您想暂时娱乐一下,让我们“以困难的方式学习”并手动添加一个新控制器。

添加一个名为 home.rs 的文件,并在 mod.rspub mod home;

src/
  controllers/
    auth.rs
    home.rs      <--- add this file
    users.rs
    mod.rs       <--- 'pub mod home;' the module here

接下来,设置一个 hello 路由,这是 home.rs 的内容:

// src/controllers/home.rs
use loco_rs::prelude::*;

// _ctx contains your database connection, as well as other app resource that you'll need
async fn hello(State(_ctx): State<AppContext>) -> Result<String> {
    format::text("ola, mundo")
}

pub fn routes() -> Routes {
    Routes::new().prefix("home").add("/hello", get(hello))
}

最后,在 app.rs 中注册此新控制器路由:

src/
  controllers/
  models/
  ..
  app.rs   <---- look here

routes() 中添加以下内容:

// in src/app.rs
#[async_trait]
impl Hooks for App {
    ..
    fn routes() -> AppRoutes {
        AppRoutes::with_default_routes()
            .add_route(controllers::guide::routes())
            .add_route(controllers::notes::routes())
            .add_route(controllers::auth::routes())
            .add_route(controllers::user::routes())
            .add_route(controllers::home::routes()) // <--- add this
    }

就是这样。关闭服务器并重新启动:

$ cargo loco start

并访问 /home/hello

$ curl localhost:3000/api/home/hello
ola, mundo

您可以使用以下命令查看所有路由:

$ cargo loco routes
  ..
  ..
[POST] /auth/login
[POST] /auth/register
[POST] /auth/reset
[POST] /auth/verify
[GET] /home/hello      <---- 这是我们的新路由
[GET] /notes
[POST] /notes
  ..
  ..
$

MVC

传统的 MVC(模型-视图-控制器)源自桌面 UI 编程范式。但它很快也应用于 Web 服务,MVC 的鼎盛时期大约在 2010 年代初期,此后出现了许多不同的范式和架构。

MVC 仍然是一种非常强大的原则和架构,可以用来简化项目,这也是 Loco 遵循的原则。

尽管 Web 服务和 API 没有 视图 的概念,因为它们不生成 HTML 或 UI 响应,但我们认为 稳定安全 的服务和 API 确实有视图的概念 -- 这就是序列化的数据,包括它的形状、兼容性和版本。

// 一个典型的 Loco 应用程序包含 MVC 的所有部分

src/
  controllers/
    users.rs
    mod.rs
  models/
    _entities/
      users.rs
      mod.rs
    users.rs
    mod.rs
  views/
    users.rs
    mod.rs

这是一个重要的 认知 原则。该原则认为,只有将这些视为一个独立管理的 事物,才能创建安全、兼容的 API 响应 -- 就是 Loco MVC 中的“V”。

Loco 中的模型具有与 Rails 中相同的语义:胖模型,瘦控制器。这意味着每次你想构建一些东西时 -- 你都会接触到一个模型

生成模型

Loco 中的模型表示数据。通常,这些数据存储在你的数据库中。你的应用程序的大多数(如果不是全部)业务流程将编码在模型(作为 Active Record)或几个模型的编排中。

让我们创建一个名为 Article 的新模型:

$ cargo loco generate model article title:string content:text

added: "migration/src/m20231202_173012_articles.rs"
injected: "migration/src/lib.rs"
injected: "migration/src/lib.rs"
added: "tests/models/articles.rs"
injected: "tests/models/mod.rs"

数据库迁移

数据库模式是通过迁移生成的。迁移是你数据库结构的一次性更改:它可以包含完整的表添加、修改或索引创建。

// 这是从以下命令中生成到 `migrations/` 中的:
//
// $ cargo loco generate model article title:string content:text
//
// 它是由 Loco 的迁移器框架自动应用的。
// 你也可以使用以下命令手动应用它:
//
// $ cargo loco db migrate
//
#[async_trait::async_trait]
impl MigrationTrait for Migration {
    async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
        manager
            .create_table(
                table_auto(Articles::Table)
                    .col(pk_auto(Articles::Id))
                    .col(string_null(Articles::Title))
                    .col(text(Articles::Content))
                    .to_owned(),
            )
            .await
    }

    async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
        manager
            .drop_table(Table::drop().table(Articles::Table).to_owned())
            .await
    }
}

你可以 通过将迁移按顺序应用到一个新的数据库模式中 来重新创建一个完整的数据库——这由 Loco 的迁移器(它源自 SeaORM)自动完成。

在生成一个新模型时,Loco 将:

  • 生成一个新的“up”数据库迁移
  • 应用迁移
  • 从数据库结构中反映实体并生成你的 _entities 代码

你将在 models/_entities/ 中找到你的新模型作为一个实体,它与你的数据库结构同步:

src/models/
├── _entities
│   ├── articles.rs  <-- 从数据库模式同步,不要编辑
│   ├── mod.rs
│   ├── notes.rs
│   ├── prelude.rs
│   └── users.rs
├── articles.rs   <-- 为你生成,你的逻辑在这里。
├── mod.rs
├── notes.rs
└── users.rs

使用 playground 与数据库交互

你的 examples/ 文件夹包含:

  • playground.rs - 一个尝试和试验你的模型和应用程序逻辑的地方。

让我们使用 playground.rs 使用你的模型获取数据:

// 位于 examples/playground.rs 中
// 使用此文件尝试一些东西
use eyre::Context;
use loco_rs::{cli::playground, prelude::*};
// 要引用 articles::ActiveModel,你的导入应如下所示:
use myapp::{app::App, models::_entities::articles};

#[tokio::main]
async fn main() -> eyre::Result<()> {
    let ctx = playground::<App>().await.context("playground")?; // <- remove '_'

    // 添加此内容:
    let res = articles::Entity::find().all(&ctx.db).await.unwrap();
    println!("{:?}", res);

    Ok(())
}

返回文章列表

在示例中,我们使用以下内容返回列表:

let res = articles::Entity::find().all(&ctx.db).await.unwrap();

要了解如何运行更多查询,请访问 SeaORM 文档

要执行你的 playground,请运行:

$ cargo playground
[]

现在,让我们插入一个项目:

async fn main() -> eyre::Result<()> {
    let ctx = playground::<App>().await.context("playground")?;

    // 添加此内容:
    let active_model: articles::ActiveModel = articles::ActiveModel {
        title: Set(Some("how to build apps in 3 steps".to_string())),
        content: Set(Some("use Loco: https://loco.rs".to_string())),
        ..Default::default()
    };
    active_model.insert(&ctx.db).await.unwrap();

    let res = articles::Entity::find().all(&ctx.db).await.unwrap();
    println!("{:?}", res);

    Ok(())
}

并再次运行 playground:

$ cargo playground
[Model { created_at: ..., updated_at: ..., id: 1, title: Some("how to build apps in 3 steps"), content: Some("use Loco: https://loco.rs") }]

我们现在准备将其插入到 articles 控制器中。首先,生成一个新的控制器:

$ cargo loco generate controller articles
added: "src/controllers/articles.rs"
injected: "src/controllers/mod.rs"
injected: "src/app.rs"
added: "tests/requests/articles.rs"
injected: "tests/requests/mod.rs"

编辑 src/controllers/articles.rs

#![allow(clippy::unused_async)]
use loco_rs::prelude::*;

use crate::models::_entities::articles;

pub async fn list(State(ctx): State<AppContext>) -> Result<Json<Vec<articles::Model>>> {
    let res = articles::Entity::find().all(&ctx.db).await?;
    format::json(res)
}

pub fn routes() -> Routes {
    Routes::new().prefix("articles").add("/", get(list))
}

现在,启动应用程序:

$ cargo loco start

并发出请求:

$ curl localhost:3000/api/articles
[{"created_at":"...","updated_at":"...","id":1,"title":"how to build apps in 3 steps","content":"use Loco: https://loco.rs"}]

构建一个 CRUD API

接下来,我们将了解如何获取、删除和编辑单个文章。使用 axum 中的 Path 提取器按 ID 获取文章。

用以下内容替换 articles.rs 文件中的内容:

// this is src/controllers/articles.rs

#![allow(clippy::unused_async)]
use loco_rs::prelude::*;
use serde::{Deserialize, Serialize};

use crate::models::_entities::articles::{ActiveModel, Entity, Model};

// 定义一个用于接收参数的结构体
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Params {
    pub title: Option<String>,
    pub content: Option<String>,
}

impl Params {
    fn update(&self, item: &mut ActiveModel) {
        // 更新模型中的字段
        item.title = Set(self.title.clone());
        item.content = Set(self.content.clone());
    }
}

async fn load_item(ctx: &AppContext, id: i32) -> Result<Model> {
    // 根据 ID 从数据库中查找并加载文章模型
    let item = Entity::find_by_id(id).one(&ctx.db).await?;
    item.ok_or_else(|| Error::NotFound)
}

// 定义一个处理文章列表的异步函数
pub async fn list(State(ctx): State<AppContext>) -> Result<Json<Vec<Model>>> {
    // 从数据库中查找所有文章并返回 JSON 格式的结果
    format::json(Entity::find().all(&ctx.db).await?)
}

// 定义一个处理添加新文章的异步函数
pub async fn add(State(ctx): State<AppContext>, Json(params): Json<Params>) -> Result<Json<Model>> {
    // 创建一个新的文章模型
    let mut item = ActiveModel {
        ..Default::default()
    };
    // 使用参数更新模型
    params.update(&mut item);
    // 将模型插入数据库并返回 JSON 格式的结果
    let item = item.insert(&ctx.db).await?;
    format::json(item)
}

// 定义一个处理更新文章的异步函数
pub async fn update(
    Path(id): Path<i32>,
    State(ctx): State<AppContext>,
    Json(params): Json<Params>,
) -> Result<Json<Model>> {
    // 根据 ID 加载文章模型
    let item = load_item(&ctx, id).await?;
    // 将模型转换为可编辑的模型
    let mut item = item.into_active_model();
    // 使用参数更新模型
    params.update(&mut item);
    // 更新数据库中的模型并返回 JSON 格式的结果
    let item = item.update(&ctx.db).await?;
    format::json(item)
}

// 定义一个处理删除文章的异步函数
pub async fn remove(Path(id): Path<i32>, State(ctx): State<AppContext>) -> Result<()> {
    // 根据 ID 加载文章模型
    load_item(&ctx, id).await?.delete(&ctx.db).await?;
    // 删除数据库中的模型并返回空响应
    format::empty()
}

// 定义一个处理获取单个文章的异步函数
pub async fn get_one(Path(id): Path<i32>, State(ctx): State<AppContext>) -> Result<Json<Model>> {
    // 根据 ID 加载文章模型并返回 JSON 格式的结果
    format::json(load_item(&ctx, id).await?)
}

// 定义文章控制器的路由
pub fn routes() -> Routes {
    Routes::new()
        .prefix("articles")
        .add("/", get(list))
        .add("/", post(add))
        .add("/:id", get(get_one))
        .add("/:id", delete(remove))
        .add("/:id", post(update))
}

需要注意以下几点:

  • Params 是一个强类型的数据结构,用于接收请求参数,类似于 Rails 中的 _strongparams_,但更加安全。
  • Path(id): Path<i32> 从 URL 中提取 :id 参数。
  • 提取器的顺序很重要,需要遵循 axum 文档中指定的顺序(参数、状态、请求体)。
  • 最佳实践是创建一个 load_item 帮助器函数,并在所有处理单个文章的路由中使用它。
  • 虽然 use loco_rs::prelude::* 引入了构建控制器所需的所有内容,但还需要导入 crate::models::_entities::articles::{ActiveModel, Entity, Model} 和用于参数序列化的 Serialize, Deserialize

现在,你可以启动应用程序来测试这些功能:

$ cargo loco start

添加一篇文章:

$ curl -X POST -H "Content-Type: application/json" -d '{
  "title": "Your Title",
  "content": "Your Content xxx"
}' localhost:3000/api/articles
{"created_at":"...","updated_at":"...","id":2,"title":"Your Title","content":"Your Content xxx"}

获取文章列表:

$ curl localhost:3000/api/articles
[{"created_at":"...","updated_at":"...","id":1,"title":"how to build apps in 3 steps","content":"use Loco: https://loco.rs"},{"created_at":"...","updated_at":"...","id":2,"title":"Your Title","content":"Your Content xxx"}

添加第二个模型

让我们添加另一个模型,这次是:Comment。我们要创建一个关系——一个评论属于一篇文章,每篇文章都可以有多个评论。

我们不会手动编写模型和控制器,而是创建一个comment scaffold,它将生成一个完全可用的评论 CRUD API 。我们还将使用特殊的 references 类型:

$ cargo loco generate scaffold comment content:text article:references

如果你查看新迁移,你将在文章表中发现一个新的数据库关系:

      ..
      ..
  .col(integer(Comments::ArticleId))
  .foreign_key(
      ForeignKey::create()
          .name("fk-comments-articles")
          .from(Comments::Table, Comments::ArticleId)
          .to(Articles::Table, Articles::Id)
          .on_delete(ForeignKeyAction::Cascade)
          .on_update(ForeignKeyAction::Cascade),
  )
      ..
      ..

现在,让我们以以下方式修改我们的 API:

  1. 评论可以通过浅层路由添加:POST comments/
  2. 评论只能在嵌套路由中获取(强制存在一篇文章):GET posts/1/comments
  3. 评论不能被更新、单独获取或删除

src/controllers/comments.rs 中,删除不需要的路由和函数:

pub fn routes() -> Routes {
    Routes::new()
        .prefix("comments")
        .add("/", post(add))
        // .add("/", get(list))
        // .add("/:id", get(get_one))
        // .add("/:id", delete(remove))
        // .add("/:id", post(update))
}

接着调整 src/controllers/comments.rs 中的参数和更新函数:

pub struct Params {
    pub content: Option<String>,
    pub article_id: i32, // <- add this
}

impl Params {
    fn update(&self, item: &mut ActiveModel) {
        item.content = Set(self.content.clone());
        item.article_id = Set(self.article_id.clone()); // <- add this
    }
}

现在我们需要在 src/controllers/articles.rs 中获取一个关系。添加以下路由:

pub fn routes() -> Routes {
  // ..
  // ..
  .add("/:id/comments", get(comments))
}

并实现关系获取:

// 要引用 comments::Entity,导入应该如下所示:
use crate::models::_entities::{
    articles::{ActiveModel, Entity, Model},
    comments,
};

pub async fn comments(
    Path(id): Path<i32>,
    State(ctx): State<AppContext>,
) -> Result<Json<Vec<comments::Model>>> {
    let item = load_item(&ctx, id).await?;
    let comments = item.find_related(comments::Entity).all(&ctx.db).await?;
    format::json(comments)
}

这被称为“延迟加载”,我们首先获取项目,然后获取其关联关系。不用担心 - 还有另一种方法可以急切地加载评论和文章。

现在让我们向文章 1 添加一个评论:

$ curl -X POST -H "Content-Type: application/json" -d '{
  "content": "this rocks",
  "article_id": 1
}' localhost:3000/api/comments
{"created_at":"...","updated_at":"...","id":4,"content":"this rocks","article_id":1}

并获取关系:

$ curl localhost:3000/api/articles/1/comments
[{"created_at":"...","updated_at":"...","id":4,"content":"this rocks","article_id":1}]

到这里我们的 Loco 指南就结束了。如果你坚持到了这里,干得好!

任务:导出数据报告

现实世界的应用程序需要处理各种各样的情况。假设您的某些用户或客户需要某种报告。

您可以:

  • 连接到您的生产数据库,发出临时 SQL 查询。或使用某种 DB 工具。这是不安全的,不保密,容易出错,并且无法自动化
  • 将您的数据导出到类似 Redshift 或 Google 的地方,并在那里发出查询。这是浪费资源,不安全,无法正确测试,并且很慢
  • 构建一个管理员。这是耗时且浪费的
  • 或者在 Rust 中构建一个临时任务,它编写速度快、类型安全、受编译器保护、速度快、环境感知、可测试且安全。

cargo loco task 的用武之地就在这里。

首先,运行 cargo loco task

$ cargo loco task
user_report		[output a user report]

您将看到为您生成的示例任务。这是任务的主体:

// find it in `src/tasks/user_report.rs`
impl Task for UserReport {
    fn task(&self) -> TaskInfo {
      // description that appears on the CLI
        TaskInfo {
            name: "user_report".to_string(),
            detail: "output a user report".to_string(),
        }
    }

    // variables through the CLI:
    // `$ cargo loco task name:foobar count:2`
    // will appear as {"name":"foobar", "count":2} in `vars`
    async fn run(&self, app_context: &AppContext, vars: &BTreeMap<String, String>) -> Result<()> {
        let users = users::Entity::find().all(&app_context.db).await?;
        println!("args: {vars:?}");
        println!("!!! user_report: listing users !!!");
        println!("------------------------");
        for user in &users {
            println!("user: {}", user.email);
        }
        println!("done: {} users", users.len());
        Ok(())
    }
}

您可以根据需要修改此任务。使用 app_context 访问模型或任何其他环境资源,并使用 vars 获取通过 CLI 提供的变量。

使用以下命令运行此任务:

$ cargo loco task user_report var1:val1 var2:val2 ...

请记住:这是环境相关的,因此您只需编写一次任务,然后根据需要在开发或生产环境中执行。任务被编译到主应用程序二进制文件中。

身份验证:对您的请求进行身份验证

如果您选择了 SaaS App starter,那么您应该有一个完全配置的身份验证模块内置到应用程序中。

让我们看看在添加评论时如何要求身份验证。

返回 src/controllers/comments.rs 并查看 add 函数:

pub async fn add(State(ctx): State<AppContext>, Json(params): Json<Params>) -> Result<Json<Model>> {
    let mut item = ActiveModel {
        ..Default::default()
    };
    params.update(&mut item);
    let item = item.insert(&ctx.db).await?;
    format::json(item)
}

要要求身份验证,我们需要以这种方式修改函数签名:

async fn add(
    auth: auth::JWT,
    State(ctx): State<AppContext>,
    Json(params): Json<Params>,
) -> Result<Json<CurrentResponse>> {
  // we only want to make sure it exists
  let _current_user = crate::models::users::Model::find_by_pid(&ctx.db, &auth.claims.pid).await?;

  // next, update
  // homework/bonus: make a comment _actually_ belong to user (user_id)
  let mut item = ActiveModel {
      ..Default::default()
  };
  params.update(&mut item);
  let item = item.insert(&ctx.db).await?;
  format::json(item)
}