Loco 指南

指南假设

这是一个“绕远路”的教程。它很长且深入,这是故意的,它向你展示了如何手动和使用生成器自动构建事物,以便你学习构建的技能以及事物是如何运作的。

为什么叫这个名字?

Loco 这个名字来源于 locomotive(火车头),以此向 Rails 致敬,并且 locolocomotive 更容易输入 :-) 。此外,在某些语言中,它的意思是“疯狂”,但这并非最初的意图(或者,在 Rust 上构建 Rails 是疯狂的吗?只有时间会证明!)。

我需要了解多少 Rust 知识?

你需要熟悉 Rust,达到初学者水平即可,但不需要超过中级初学者水平。你需要知道如何构建、测试和运行 Rust 项目,使用过一些流行的库,例如 clapregextokioaxum 或其他 Web 框架,不需要太花哨的东西。Loco 中没有疯狂的生命周期扭曲或复杂/过于神奇的宏,你不需要知道它们是如何工作的。

什么是 Loco?

Loco 的灵感强烈来源于 Rails。如果你了解 Rails Rust,你会感到宾至如归。如果你只了解 Rails 并且是 Rust 新手,你会发现 Loco 令人耳目一新。我们不假设你了解 Rails。

我们认为 Rails 非常棒,以至于本指南也强烈受到了 Rails 指南 的启发

Loco 是一个用于 Rust 的 Web 或 API 框架。它也是一个面向开发人员的生产力套件:它包含了构建业余项目或你的下一个初创公司所需的一切。它也受到了 Rails 的强烈启发。

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

创建新的 Loco 应用

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

安装

cargo install loco
cargo install sea-orm-cli # Only when DB is needed (仅在需要数据库时)

创建新的 Loco 应用

现在你可以创建你的新应用(选择 “SaaS app” 以获得内置身份验证)。

 loco new
 ❯ App name? · myapp
 ❯ What would you like to build? · Saas App with client side rendering (你想构建什么? · 具有客户端渲染的 Saas 应用)
 ❯ Select a DB Provider · Sqlite (选择数据库提供商 · Sqlite)
 ❯ Select your background worker type · Async (in-process tokio async tasks) (选择你的后台工作类型 · 异步(进程内 tokio 异步任务))

🚂 Loco app generated successfully in:
myapp/

- assets: You've selected `clientside` for your asset serving configuration. (资产:你已为你的资产服务配置选择了 `clientside`。)

Next step, build your frontend: (下一步,构建你的前端:)
  $ cd frontend/
  $ npm install && npm run build

以下是 Loco 默认为你创建的内容的概要:

文件/文件夹用途
src/包含控制器、模型、视图、任务等
app.rs主要组件注册点。在这里连接重要的部分。
lib.rs你的组件的各种 Rust 特定的导出。
bin/包含你的 main.rs 文件,你无需担心它
controllers/包含控制器,所有控制器都通过 mod.rs 导出
models/包含模型,models/_entities 包含自动生成的 SeaORM 模型,models/*.rs 包含你的模型扩展逻辑,这些逻辑通过 mod.rs 导出
views/包含基于 JSON 的视图。是可以 serde 并通过 API 作为 JSON 输出的结构体。
workers/包含你的后台工作程序。
mailers/邮件程序逻辑和模板,用于发送电子邮件。
fixtures/包含数据和自动 fixture 加载逻辑。
tasks/包含你日常面向业务的任务,例如发送电子邮件、生成业务报告、数据库维护等。
tests/你的应用范围测试:模型、请求等。
config/基于阶段的配置文件夹:development、test、production

Hello, Loco!

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

你现在可以切换到 myapp

$ cd myapp

启动服务器

cargo loco start

现在,让我们看看它是否活着:

$ curl localhost:5150/_ping
{"ok":true}

内置的 _ping 路由将告知你的负载均衡器一切正常。

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

$ curl localhost:5150/_health
{"ok":true}
内置的 _health 路由将告诉你,你已正确配置了你的应用程序:它可以成功建立与你的数据库和 Redis 实例的连接。

对 Loco 说 “Hello”

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

$ cargo loco generate controller guide --api
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::missing_errors_doc)]
#![allow(clippy::unnecessary_struct_initialization)]
#![allow(clippy::unused_async)]
use loco_rs::prelude::*;
use axum::debug_handler;

#[debug_handler]
pub async fn index(State(_ctx): State<AppContext>) -> Result<Response> {
    format::empty()
}

pub fn routes() -> Routes {
    Routes::new()
        .prefix("api/guides/")
        .add("/", get(index))
}

更改 index 处理程序主体:

// replace (替换)
    format::empty()
// with this (用这个替换)
    format::text("hello")

启动服务器:

cargo loco start

现在,让我们测试一下:

$ curl localhost:5150/api/guides
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 (在这里 'pub mod home;' 模块)

接下来,设置一个 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
// _ctx 包含你的数据库连接,以及你需要的其他应用程序资源
async fn hello(State(_ctx): State<AppContext>) -> Result<Response> {
    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
// 在 src/app.rs 中
#[async_trait]
impl Hooks for App {
    ..
    fn routes() -> AppRoutes {
        AppRoutes::with_default_routes()
            .add_route(controllers::guide::routes())
            .add_route(controllers::auth::routes())
            .add_route(controllers::home::routes()) // <--- add this (添加这个)
    }

就是这样。杀死服务器并再次启动它:

cargo loco start

并访问 /home/hello

$ curl localhost:5150/home/hello
ola, mundo

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

$ cargo loco routes
  ..
  ..
[POST] /api/auth/login
[POST] /api/auth/register
[POST] /api/auth/reset
[POST] /api/auth/verify
[GET] /home/hello      <---- this is our new route! (这是我们的新路由!)
  ..
  ..
$
SaaS Starter 将路由保留在 /api 下,因为它已为客户端做好准备,并且我们在脚手架中使用了 --api 选项。
当使用像 React Router 这样的客户端路由时,我们希望将后端路由与客户端路由分开:浏览器将使用 /home 而不是 /api/home,后者是后端路由,你可以从客户端调用 /api/home 而无需担心。然而,路由:/_health/_ping 是例外,它们保留在根目录。

MVC 与你

传统的 MVC(模型-视图-控制器)起源于桌面 UI 编程范式。 然而,它在 Web 服务中的适用性导致了它的快速采用。MVC 的黄金时代大约在 2010 年代初期,从那时起,许多其他范式和架构涌现出来。

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

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

// a typical loco app contains all parts of MVC
// 一个典型的 loco 应用包含 MVC 的所有部分

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

这是一个重要的 认知 原则。该原则声称,只有当你将 API 响应视为一个独立的、独立管理的东西时,你才能创建安全、兼容的 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"

数据库迁移

保持你的 schema 诚实是通过迁移完成的。迁移是对你的数据库结构的单一更改:它可以包含完整的表添加、修改或索引创建。

// this was generated into `migrations/` from the command:
// (这是通过以下命令生成到 `migrations/` 中的:)
//
// $ cargo loco generate model article title:string content:text
//
// it is automatically applied by Loco's migrator framework.
// (它由 Loco 的迁移器框架自动应用。)
// you can also apply it manually using the command:
// (你也可以使用以下命令手动应用它:)
//
// $ 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_tz(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  <-- sync'd from db schema, do not edit (从数据库 schema 同步,不要编辑)
│   ├── mod.rs
│   ├── prelude.rs
│   └── users.rs
├── articles.rs   <-- generated for you, your logic goes here. (为你生成,你的逻辑写在这里。)
├── mod.rs
└── users.rs

使用 playground 与数据库交互

你的 examples/ 文件夹包含:

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

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

// located in examples/playground.rs
// (位于 examples/playground.rs 中)
// use this file to experiment with stuff
// (使用此文件来试验内容)
use loco_rs::{cli::playground, prelude::*};
// to refer to articles::ActiveModel, your imports should look like this:
// (要引用 articles::ActiveModel,你的导入应该像这样:)
use myapp::{app::App, models::_entities::articles};

#[tokio::main]
async fn main() -> loco_rs::Result<()> {
    let ctx = playground::<App>().await?;

    // add this:
    // (添加这个:)
    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() -> loco_rs::Result<()> {
    let ctx = playground::<App>().await?;

    // add this:
    // (添加这个:)
    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 --api
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<Response> {
    let res = articles::Entity::find().all(&ctx.db).await?;
    format::json(res)
}

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

现在,启动应用程序:

cargo loco start

并发出请求:

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

构建 CRUD API

接下来,我们将了解如何获取单篇文章、删除和编辑单篇文章。按 ID 获取文章是使用来自 axumPath 提取器完成的。

articles.rs 的内容替换为以下内容:

// this is src/controllers/articles.rs
// 这是 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> {
    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<Response> {
    format::json(Entity::find().all(&ctx.db).await?)
}

pub async fn add(State(ctx): State<AppContext>, Json(params): Json<Params>) -> Result<Response> {
    let mut item: ActiveModel = Default::default();
    params.update(&mut item);
    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<Response> {
    let item = load_item(&ctx, id).await?;
    let mut item = item.into_active_model();
    params.update(&mut item);
    let item = item.update(&ctx.db).await?;
    format::json(item)
}

pub async fn remove(Path(id): Path<i32>, State(ctx): State<AppContext>) -> Result<Response> {
    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<Response> {
    format::json(load_item(&ctx, id).await?)
}

pub fn routes() -> Routes {
    Routes::new()
        .prefix("api/articles")
        .add("/", get(list))
        .add("/", post(add))
        .add("/{id}", get(get_one))
        .add("/{id}", delete(remove))
        .add("/{id}", patch(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 用于参数。
提取器的顺序很重要,因为更改它们的顺序可能会导致编译错误。向处理程序添加 #[debug_handler] 宏可以通过打印出更好的错误消息来提供帮助。有关提取器的更多信息,请参见 axum 文档

你现在可以测试它是否工作,启动应用程序:

cargo loco start

添加一篇新文章:

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

获取列表:

$ curl localhost:5150/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 --api
特殊的 references:<table> 也可用。用于当你想要为你的列使用不同的名称时。

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

      ..
      ..
  .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("api/comments")
        .add("/", post(add))
        // .add("/", get(list))
        // .add("/{id}", get(get_one))
        // .add("/{id}", delete(remove))
        // .add("/{id}", patch(update))
}

还要调整 src/controllers/comments.rs 中的 Params 和 update 函数,方法是更新标有 <- add this 的脚手架代码

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))
}

并实现关系获取:

// to refer to comments::Entity, your imports should look like this:
// (要引用 comments::Entity,你的导入应该像这样:)
use crate::models::_entities::{
    articles::{ActiveModel, Entity, Model},
    comments,
};

pub async fn comments(
    Path(id): Path<i32>,
    State(ctx): State<AppContext>,
) -> Result<Response> {
    let item = load_item(&ctx, id).await?;
    let comments = item.find_related(comments::Entity).all(&ctx.db).await?;
    format::json(comments)
}
这称为“懒加载”,我们首先获取项目,然后再获取其关联关系。别担心——还有一种方法可以急切地加载评论以及文章。

现在再次启动应用程序:

cargo loco start

向文章 1 添加评论:

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

并且,获取关系:

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

这结束了我们全面的 Loco 指南。如果你坚持到了这里,恭喜你!

任务:导出数据报告

真实世界的应用程序需要处理真实世界的情况。假设你的一些用户或客户需要某种报告。

你可以:

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

这就是 cargo loco task 的用武之地。

首先,运行 cargo loco task 以查看当前任务:

$ cargo loco task
seed_data		[Task for seeding data] (用于播种数据的任务)

生成一个新任务 user_report

$ cargo loco generate task user_report

added: "src/tasks/user_report.rs"
injected: "src/tasks/mod.rs"
injected: "src/app.rs"
added: "tests/tasks/user_report.rs"
injected: "tests/tasks/mod.rs"

src/tasks/user_report.rs 中,你将看到为你生成的任务。将其替换为以下内容:

// find it in `src/tasks/user_report.rs`
// 在 `src/tasks/user_report.rs` 中找到它

use loco_rs::prelude::*;
use loco_rs::task::Vars;

use crate::models::users;

pub struct UserReport;

#[async_trait]
impl Task for UserReport {
    fn task(&self) -> TaskInfo {
      // description that appears on the CLI
      // (出现在 CLI 上的描述)
        TaskInfo {
            name: "user_report".to_string(),
            detail: "output a user report".to_string(), // 输出用户报告
        }
    }

    // variables through the CLI:
    // (通过 CLI 的变量:)
    // `$ cargo loco task name:foobar count:2`
    // will appear as {"name":"foobar", "count":2} in `vars`
    // (将在 `vars` 中显示为 {"name":"foobar", "count":2})
    async fn run(&self, app_context: &AppContext, vars: &Vars) -> Result<()> {
        let users = users::Entity::find().all(&app_context.db).await?;
        println!("args: {vars:?}");
        println!("!!! user_report: listing users !!!"); // !!! user_report:列出用户 !!!
        println!("------------------------");
        for user in &users {
            println!("user: {}", user.email);
        }
        println!("done: {} users", users.len()); // 完成:{} 个用户
        Ok(())
    }
}

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

运行此任务使用:

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

args: Vars { cli: {"var1": "val1", "var2": "val2"} }
!!! user_report: listing users !!!
------------------------
done: 0 users

如果你之前没有添加用户,则报告将为空。

要添加用户,请查看 快速 Loco 之旅注册新用户 章节。

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

身份验证:验证你的请求

如果你选择了 SaaS App 启动器,你应该在应用程序中内置了一个完全配置的身份验证模块。 让我们看看如何在添加评论时要求身份验证。

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

pub async fn add(State(ctx): State<AppContext>, Json(params): Json<Params>) -> Result<Response> {
    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<Response> {
    // 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)
    // (作业/奖励:使评论 _实际_ 属于用户 (user_id))
    let mut item: ActiveModel = Default::default();
    params.update(&mut item);
    let item = item.insert(&ctx.db).await?;
    format::json(item)
}