Rwf 框架全解: 如何无痛从 Python 迁移至 Rust 的新 Web 开发?

文摘   2024-10-24 12:44   江苏  

Rwf 是一个全面的框架,用于在 Rust 中构建 Web 应用程序。采用经典的 MVC(模型-视图-控制器)模式编写,Rwf 配备了构建快速且安全 Web 应用程序所需的一切标准功能。

unsetunset文档unsetunset

官方的文档 **在此提供[1]**。

unsetunset功能概览unsetunset

  • HTTP 服务器[2]
  • 用户友好的 ORM[3],轻松构建 PostgreSQL 查询
  • 动态模板[4]
  • 认证[5] & 内置用户会话
  • 中间件[6]
  • 后台任务[7]定时任务[8]
  • 数据库迁移
  • 内置 REST 框架[9] 支持 JSON 序列化
  • WebSockets 支持
  • 静态文件[10] 托管
  • Hotwired Turbo[11] 紧密集成,构建 后端驱动的 SPA[12]
  • 环境特定配置
  • 日志记录和指标
  • CLI[13]
  • 用于从 Django/Flask 应用 迁移[14] 的 WSGI 服务器

unsetunset快速开始unsetunset

要将 Rwf 添加到你的技术栈中,创建一个 Rust 二进制应用程序,并将 rwftokio 添加到你的依赖项中:

cargo add rwf
cargo add tokio@1 --features full

构建应用程序就像这样简单:

use rwf::prelude::*;
use rwf::http::Server;

#[derive(Default)]
struct IndexController;

#[async_trait]
impl Controller for IndexController {
    async fn handle(&self, request: &Request) -> Result<Response, Error> {
        Ok(Response::new().html("<h1>Hey Rwf!</h1>"))
    }
}

#[tokio::main]
async fn main() {
    Server::new(vec![
        route!("/" => IndexController),
    ])
    .launch("0.0.0.0:8000")
    .await
    .unwrap();
}

架构

Rwf 建立在古老的 模型-视图-控制器(MVC)[15] 架构之上。根据我的经验,这是唯一经受住时间考验并且能够扩展到数百万行代码的架构。

这份文档尚不完整。如果你愿意,欢迎随时添加内容。

unsetunset模块unsetunset

Rwf 被划分为不同的模块和源代码文件。以下是模块的文档,没有特定的顺序。

controller

MVC 中的 "C",控制器模块负责处理来自客户端的 HTTP 请求。有三个(3)个特性最值得关注:

rwf::controller::Controller

这个特性是所有希望由 HTTP 服务器服务的控制器所必需的。它只有一个必需的方法(handle),其他所有方法都是可选的,并且已经用默认值实现了。所有方法都可以被覆盖,使其非常可定制。

rwf::controller::RestController

这个特性实现了 REST 框架,特别是 Controller 特性的 handle 方法,并将传入的请求分割成 6 个 REST 动词。

rwf::controller::ModelController

与上述相同,但它还使用指定的 Model 类型实现了所有 6 个 REST 动词。它直接链接到 ORM,并读取、创建、更新和删除所需的记录。

rwf::controller::WebsocketController

实现 HTTP -> WebSocket 协议升级和通信。

controller::middleware

这个模块实现了控制器中间件。一旦你阅读了 mod.rs,这个模块就非常不言自明了。

http

HTTP 服务器和请求路由到控制器。

job

后台和定时任务。

model

ORM 和连接池。

view

动态模板 —— 基本上是我们自己的 Rails ERB 实现。

crypto

使用 AES-128 轻松加密/解密数据。我认为 应该足够了[16],但如果需要,我很高兴加入其他密码。

comms

通过 Websockets 和 Tokio broadcast 模块,HTTP 客户端之间的通信。有助于将 Rwf 的一个部分与另一个部分连接起来。仍在早期开发中,我认为我们可以在这里添加更多东西,例如 Django 风格的信号 / Rails 风格的回调。

功能路线图

Rwf 是全新的,但 Web 开发历史悠久。许多功能缺失或不完整。

unsetunsetORMunsetunset

  • [ ] 左连接(LEFT JOINs)
  • [ ] 右连接(RIGHT JOINs)
  • [ ] MySQL 支持
  • [ ] SQLite 支持
  • [ ] 分布式锁(使用 Postgres,而不是 Redis)
  • [ ] 更多测试

unsetunsetHTTP 服务器unsetunset

  • [ ] HTTP/2, HTTP/3
  • [ ] TLS
  • [ ] 模糊测试(不是四条腿的可爱动物,而是将垃圾输入路由器并试图使其崩溃的测试)
  • [ ] 事件流(EventStreams)
  • [ ] 集成测试

unsetunset动态模板unsetunset

  • [ ] 更好的错误消息,例如语法错误、未定义的变量、函数等。
  • [ ] 更多数据类型支持,例如 UUID、时间戳、任何我们忘记添加的 Rust 数据类型。
  • [ ] 更多测试
  • [x] context! 宏,从键值映射生成 Context
  • [x] render! 宏,加载并渲染模板为 Ok(Response::new().html())
  • [ ] 允许使用用户定义的函数(在启动时定义)扩展模板语法

unsetunset后台与定时任务unsetunset

  • [ ] 运行中/待处理/失败任务的统计信息(可以使用视图完成)
  • [ ] 更多测试
  • [ ] 支持更多 crontab 语法扩展

unsetunset认证与用户会话unsetunset

  • [ ] 添加默认用户模型,以便我们可以在没有任何样板代码的情况下支持账户(就像 Django 一样)
  • [ ] 内置 OAuth2(Google/GitHub 等)支持,用户只需要添加密钥/密文

unsetunset文档和指南unsetunset

  • [ ] 更多 README 风格的文档
  • [ ] 每个公共结构、函数、枚举等的代码文档(rust doc)

unsetunset从 Python 迁移unsetunset

  • [ ] 并行运行 Django/Flask 应用

unsetunset更多?unsetunset

请随时向此列表添加更多功能。

控制器基础 Rwf 提供了多个预构建的控制器,可以开箱即用,例如,用于处理 WebSocket 连接、REST 风格的交互或服务静态文件。对于其他所有情况,可以通过实现 Controller trait 来处理任何类型的 HTTP 请求。

什么是控制器? 控制器是 MVC 中的 "C":它处理用户与 Web 应用程序的交互,并代表他们执行操作。控制器负责处理用户输入,如表单,以及对应用程序的所有其他 HTTP 请求。

编写控制器 控制器是一个实现 Controller trait 的普通 Rust 结构体。例如,让我们编写一个返回当前 UTC 时间的控制器。

导入类型

use rwf::prelude::*;

prelude 模块包含了使用 Rwf 所需的大部分类型和特性。包含它会节省编写代码时的时间和努力,但不是必需的。

定义结构体

#[derive(Default)]
struct CurrentTime;

这个结构体没有字段,但你可以添加任何你想要跟踪的内部状态。自动推导出 Default trait,以提供一种方便的实例化方式。

实现 Controller trait

#[async_trait]
impl Controller for CurrentTime {
    /// 这个函数处理传入的 HTTP 请求。
    async fn handle(&self, request: &Request) -> Result<Response, Error> {
        let time = OffsetDateTime::now_utc();

        // 这创建了一个 HTTP "200 OK" 响应,
        // 带有 "Content-Type: text/plain" 头。
        let response = Response::new()
            .text(format!("The current time is: {:?}", time));

        Ok(response)
    }
}

Controller trait 是异步的。Rust 中对异步 trait 的支持还不完整,因此我们使用 async_trait 库来简化使用。trait 本身有几个方法,其中大多数都有合理的默认值。唯一需要手工编写的方法是 async fn handle()。

handle handle 方法接受一个 Request 并必须返回一个 Response。响应可以是任何有效的 HTTP 响应,包括 404 或甚至 500。

错误 如果在 async fn handle 函数中发生错误,Rwf 会自动返回 HTTP 500 并将错误显示给客户端。

连接控制器 一旦实现了控制器,将其添加到应用程序中需要将其映射到一个路由。路由是一个唯一的 URL,从应用程序的根开始。例如,/signup 是一个可以映射到 Signup 控制器的路由,并允许用户创建账户。

在服务器启动时将控制器添加到应用程序中。服务器可以从代码中的任何异步任务启动,但通常从 main 函数开始:

use rwf::prelude::*;
use rwf::http::{self, Server};

#[tokio::main]
async fn main() -> Result<(), http::Error> {
    Server::new(vec![
        // 将 `/time` 路由映射到 `CurrentTime` 控制器。
        route!("/time" => CurrentTime),
    ])
    .launch("0.0.0.0:8000")
    .await
}

注意

route! 宏是调用 CurrentTime::default().route("/time") 的简写。我们使用它因为它看起来很酷,但不是必需的。你可以以任何需要的方式实例化你的控制器结构体,并在将其添加到服务器时调用 Controller::route 方法。或者,你可以像我们在本例中所做的那样实现 Default trait 并使用宏。

使用 cURL 测试 一旦服务器运行,你可以使用 cURL(或任何常规浏览器,如 Firefox)测试你的端点:

cURL
输出
curl localhost:8000/time -w '\n'

分离 GET 和 POST 实现 Controller trait 的控制器不区分 HTTP 请求方法,并在一个函数中处理所有方法。大多数网站通过 GET 请求显示页面,并通过 POST 请求接受表单提交。为了避免在 handle 方法中编写样板代码,Rwf 有另一种类型的控制器 PageController,它将这两种方法分别拆分成自己的函数:async fn get 和 async fn post:

#[derive(Default, macros::PageController)]
struct Login;

impl PageController for Login {
    /// 处理 GET 请求。
    async fn get(&self, request: &Request) -> Result<Response, Error> {
        /* 通过渲染模板显示页面 */
    }

    /// 处理 POST 请求。
    async fn post(&self, request: &Request) -> Result<Response, Error> {
        let form = request.form_data();

        /* 处理表单提交并重定向 */
    }
}

ORM 基础 介绍 Rwf 带有自己的 ORM(对象关系映射)。Rwf ORM 非常灵活,支持从基本的按主键查询到多表连接和复杂的自定义查询。

什么是 ORM? ORM 是 MVC 设计中的 M:模型。它允许你轻松检索数据库表中存储的数据,并在应用程序中显示,而无需手动编写复杂的 SQL 查询。

它通过将自己附加到 Rust 结构体,并将表列中的数据映射到结构体字段(反之亦然),在此过程中自动将它们从数据库类型转换为 Rust 数据类型。

入门 使用 ORM 很简单,只需要为每个模型(或数据库表)定义一个结构体。例如,大多数 Web 应用程序都会有一个用户模型,它将数据存储在 "users" 表中:

数据库数据类型Rust 数据类型
idBIGINTi64
emailVARCHARString
created_atTIMESTAMPTZtime::OffsetDateTime

可以这样定义模型的 Rust 结构体:

use rwf::prelude::*;

#[derive(Clone, macros::Model)]
struct User {
    id: Option<i64>,
    email: String,
    created_at: OffsetDateTime,
}

使用以下查询可以创建数据库中的相同表:

CREATE TABLE users (
  id BIGSERIAL PRIMARY KEY,
  email VARCHAR NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

注意

id 列使用了可选的 Rust i64 整数。这是因为结构体将用于从表中插入和选择数据。插入时,id 列应为 None,并将由数据库自动分配。这确保了表中的所有行都有一个唯一的主键。

命名约定 结构体字段与数据库列同名,数据类型与它们各自的 Rust 类型相匹配。数据库中的表名对应于结构体的名称,小写并复数化。例如,User 模型将引用数据库中的 "users" 表。

包含模型数据的数据库表中的一行称为记录。macros::Model 宏自动实现了数据库到 Rust 以及反之的类型转换,并将列值映射到结构体字段。

查询数据 在 Rust 中定义了模型后,ORM 会自动实现 SQL 查询的编写。例如,要按主键获取记录,可以这样做:

let user = User::find(15)
    .fetch(&mut conn)
    .await?;

find 方法由 Model trait 为 User 结构体自动实现。它接受一个 Rust 整数,并产生以下查询:

SELECT * FROM "users" WHERE id = $1

fetch 方法组装查询,将其发送到数据库,并返回一行。该行被转换为 User 结构体的实例:

println!("user email: {}", user.email);

获取多行 可以使用 fetch_all 而不是 fetch 来查询多行,例如:

let users = User::all()
    .order("id")
    .limit(25)
    .fetch_all(&mut conn)
    .await?;

这将从 "users" 表中获取 25 条用户记录,按主键排序。结果将是一个 Vec,按照数据库返回的顺序:

for user in &users {
    println!("{}: {}", user.id, user.email);
}

ORM 可以用来编写简单和复杂的查询,而无需学习 SQL。Rwf 目前支持 PostgreSQL,但 SQLite 和 MySQL 等其他数据库也在路线图上。

模板概览 动态模板是 HTML 和一种编程语言的混合体,它指导 HTML 如何显示。例如,如果你的 Web 应用程序为用户有个人资料页面,你会希望每个用户都有专属于他们的页面。为了实现这一点,你只需要编写一个模板,并使用模板变量替换每个用户的独特部分,例如:

<div class="profile">
  <h2><%= username %></h2>
  <p><%= bio %></p>
</div>

变量 usernamebio 可以被替换为每个用户独特的值,例如:

use rwf::prelude::*;

let template = Template::from_str(r#"
<div class="profile">
  <h2><%= username %></h2>
  <p><%= bio %></p>
</div>
"#
)?;

let ctx = context!(
  "username" => "Alice",
  "bio" => "I like turtles"
);

let html = template.render(&ctx)?;

println!("{}", html);

输出将是:

<div class="profile">
  <h2>Alice</h2>
  <p>I like turtles</p>
</div>

模板帮助重用 HTML(以及 CSS、JavaScript),就像常规函数和结构体帮助重用代码一样。通过使用模板,你可以为不同的用户提供个性化的内容,而无需为每个用户编写单独的 HTML 页面。模板引擎会在运行时将模板变量替换为实际的数据,从而生成最终的 HTML 输出。这种方法不仅提高了开发效率,还使得维护和更新网站内容变得更加容易。

unsetunset启用配置unsetunset

要配置 Rwf,将名为 rwf.toml 的文件放置于你的应用工作目录中。在开发期间,这通常是你的 Cargo 项目的根目录。应用启动时,Rwf 会自动加载该文件中的配置设置。

unsetunset可用设置unsetunset

配置文件使用 TOML 语言编写。如果你不熟悉 TOML,它是一种简洁且富有表现力的语言,常用于 Rust 编程领域。

Rwf 的配置文件分为多个部分。[general] 部分控制着日志设置、加密使用的密钥等不同的选项。[database] 部分用于配置数据库连接设置,如数据库 URL、连接池大小等。

[general] 部分

  • log_queries:开启或关闭 ORM 执行的所有 SQL 查询的日志记录。默认为 false
  • secret_key:用于加密的密钥,使用 base64 编码。默认为随机生成。
  • cache_templates:开启或关闭动态模板的缓存。在调试模式下默认为 false,在发布模式下默认为 true

密钥

密钥是一个使用 base64 编码的随机生成数据字符串。一个有效的密钥包含 256 位的熵,必须使用安全的随机数生成器生成。

如果你的系统中安装了 Python,可以用几行代码为 Rwf 生成一个密钥:

import base64
import secrets

secret = base64.b64encode(secrets.token_bytes(int(256/8)))
print(secret)

[database] 部分

  • name:要连接的数据库名称。默认与环境变量 $USER 相同。若未设置,则默认为 postgres
  • user:连接数据库的用户名。默认为 $USER,若未设置,则默认为 postgres
  • url:完整的数据库连接字符串。格式为 postgresql://{user}/localhost:5432/{name},其中 {user}{name} 分别对应配置中的用户名和数据库名。

数据库 URL 遵循 The Twelve Factor App 标准,使用 URL 格式指定数据库连接。连接 PostgreSQL 时,驱动程序为 postgresql(或也可接受 postgres)。

driver://user:password@host:port/database_name

后台任务

任务概览 后台任务,也称为异步任务,是可以独立于主 HTTP 请求/响应生命周期运行的代码。在后台任务中执行代码允许你执行有用的工作,而无需让客户端等待任务完成。后台任务的例子包括发送电子邮件或与第三方 API 通信。

Rwf 拥有自己的后台任务队列和工作器,可以执行这些任务。

定义任务 后台任务是任何实现了 Job trait 的 Rust 结构体。任务需要实现的唯一 trait 方法是异步的 execute 方法,该方法接受用 JSON 编码的任务参数。

例如,如果我们想在用户注册我们的 Web 应用程序时发送欢迎电子邮件,我们可以将其作为后台任务来完成:

use rwf::prelude::*;
use rwf::job::{Error as JobError};
use serde::{Serialize, Deserialize};

#[derive(Default, Debug, Serialize, Deserialize)]
struct WelcomeEmail {
    email: String,
    user_name: String,
}

#[async_trait]
impl Job for WelcomeEmail {
    /// 此函数中的代码将在后台执行。
    async fn execute(&self, args: serde_json::Value) -> Result<(), JobError> {
        let args: WelcomeEmail = serde_json::from_value(args)?;

        // 用给定的电子邮件地址向用户发送电子邮件。

        Ok(())
    }
}

生成工作器 一旦我们有了后台任务,我们需要创建将在单独的线程(实际上是 Tokio 任务)中运行并执行这些任务的工作器。通常在 main 函数中生成工作器,但可以在代码的任何地方进行:

use rwf::job::Worker;
use tokio::time::{sleep, Duration};

#[tokio::main]
async fn main() -> Result<(), Error> {
    // 使用 4 个线程创建一个新的工作器。
    let worker = Worker::new(vec![
        WelcomeEmail::default().job()
    ])

    worker.start().await?;

    // 使主任务无限期休眠。
    sleep(Duration::MAX).await;
}

共享进程 可以在应用程序内部生成工作器,而无需创建单独的二进制应用程序。由于大多数任务将运行异步代码,Tokio 将有效地负载均衡前台(HTTP 请求/响应)和后台工作负载。

在 Web 应用程序内生成工作器时,使用上述代码而不包含 sleepWorker::start 方法几乎立即返回,因为它只是在单独的 Tokio 任务上生成一个工作器。

调度任务 随着后台任务的定义和工作器的运行,我们可以开始安排任务在后台运行。可以通过在代码的任何地方调用 queue_async 方法来安排任务运行:

let email = WelcomeEmail {
    email: "new-user@example.com".to_string(),
    user_name: "Alice".to_string(),
};

// 尽快在后台安排任务运行。
queue_async(&email).await?;

queue_async 方法在队列中创建任务记录并立即返回,而不执行实际工作。这使得这种方法非常快速,因此你可以在控制器内安排多个任务,而不会显著影响端点延迟。

加密

加密 Rwf 使用 AES-128 对用户会话和私有 Cookie 进行加密。同样的功能通过 rwf::crypto 模块提供,用于加密和解密任意数据。

加密数据 要使用 AES-128 和应用密钥加密数据,可以使用 encrypt 函数,例如:

use rwf::crypto::encrypt;
use serde_json::json;

let data = json!({
    "user""test",
    "password""hunter2"
});

// JSON 被转换为字节数组。
let data = serde_json::to_vec(&data).unwrap();

// 使用 AES 对数据进行加密。
let encrypted = encrypt(&data).unwrap();

任何类型的数据都可以加密,只要它能被序列化为字节数组。通常可以使用 serde 来实现序列化。

加密会产生一个 base64 编码的 UTF-8 字符串。你可以将这个字符串保存在数据库中,或者通过不安全的介质如电子邮件发送。

解密数据 要解密数据,可以对 encrypt 函数生成的字符串调用 decrypt 函数。解密算法会自动将 base64 编码的字符串转换为字节,并使用密钥进行解密,例如:

use rwf::crypto::decrypt;
use serde_json::from_slice;

let decrypted = decrypt(&encrypted).unwrap();
let json = from_slice::<Value>(&decrypted).unwrap();

assert_eq!(json["user"], "test");

在这里,Valueserde_json 库中用于表示 JSON 数据的结构体。这段代码假设你已经包含了必要的 serde_jsonrwf::crypto 模块。

日志

日志记录 Rwf 使用 log crate 进行日志记录。该 crate 使用标准的 INFO(信息)、WARN(警告)、ERROR(错误)和 DEBUG(调试)级别来输出不同重要性的信息。如果你有日志记录偏好,例如你想使用没有颜色的 JSON 结构化日志,你可以选择使用你喜欢的日志订阅器。或者,你可以使用 Rwf 内置的日志记录器,如下所示:

use rwf::prelude::*;

#[tokio::main]
async fn main() {
    // 确保在你的应用中只调用一次。
    Logger::init();

    /* ... */
}

记录查询 默认情况下,对数据库执行的查询不会被记录。如果你想看到执行了哪些操作(以及查询返回结果需要多长时间),可以在配置中切换 log_queries 设置。

记录请求 所有对 Rwf 的 HTTP 请求都以 INFO 级别记录。这在生产环境中很有用,可以帮助检测应用程序活动并调试任何问题(例如,负载均衡器配置错误)。

默认日志级别 默认情况下,Rwf 应用程序以 INFO 日志级别启动。由于 Rwf Logger 使用了 tracing-subscriber,你可以通过设置 RUST_LOG 环境变量来更改它,例如:

export RUST_LOG=debug

这将设置 Rust 应用程序的默认日志级别为 DEBUG,让你能够看到更详细的日志信息。

从 python 迁移

从 Python 迁移 Rwf 用 Rust 编写,因此如果你有现有的应用程序想要迁移到 Rust,你有几种选择。Rwf 内置了 WSGI 服务器,所以你可以在 Rwf 控制器旁边无修改地运行现有的 Django 或 Flask 应用程序。

使用 WSGI 注意

Rwf 的 WSGI 服务器仍然是实验性的,不如流行的 uWSGI 项目那样先进。

将 WSGI 应用程序添加到你的 Rwf 服务器非常简单。首先,确保 Python 项目在你的 PYTHONPATH 中,例如:

export PYTHONPATH=/path/to/python/project

Rwf 将直接将你的 Python 应用加载到其自己的内存中(使用 pyo3),所以它需要能够在导入你的应用模块时找到它。

Django Django 应用程序带有 WSGI 接口,Rwf 可以直接使用。通常,接口位于它自己的文件中,例如 project/wsgi.py。WsgiController 在初始化时接受 Python 模块作为参数,在这种情况下,是 project.wsgi。

一旦初始化,控制器可以被添加到服务器中,并安装在 /*(根,通配符)路径上。这确保所有请求都由 Django 处理:

use rwf::prelude::*;
use rwf::http::Server;
use rwf::controller::WsgiController;

#[tokio::main]
async fn main() {
    Server::new(vec![
        WsgiController::new("project.wsgi")
            .wildcard("/"),
    ])
    .launch("0.0.0.0:8000")
    .await
    .unwrap();
}

Python 依赖项 你的应用程序很可能有其他依赖项,例如 django 或 Flask 包等。为了确保它们在加载到 Rwf 时正常工作,要么在启动服务器之前创建并激活虚拟环境,要么全局安装这些包(例如,在 Docker 中部署时)。

将流量转移到 Rust 当你将端点重写为使用 Rwf 和 Rust 时,你可以一次移动一个路由的流量,而不会干扰你的用户。例如,如果你准备将路由 /users 移动到 Rust,将控制器添加到服务器中:

/// 你新的 "/users" 控制器
/// 用 Rust 编写。
use crate::controllers::Users;

#[tokio::main]
async fn main() {
    Server::new(vec![
        WsgiController::new("project.wsgi")
            .wildcard("/"),
        route!("/users" => Users),
    ])
    .launch("0.0.0.0:8000")
    .await
    .unwrap();
}

Rwf 路由算法将匹配请求到 /users 到 Users 控制器,而不是将其发送到 WSGI,因为 Users 控制器路径更具体,优先级高于通配符路由。

unsetunset总结unsetunset

Rwf 是一个 Rust Web 框架,支持文件和环境变量配置。它具备内置的 WSGI 服务器,允许直接在 Rwf 中运行 Django 或 Flask 应用,便于从 Python 迁移到 Rust。Rwf 使用 AES-128 加密技术保护用户会话和私有 Cookie,并提供加密和解密任意数据的功能。日志系统基于 log crate,支持不同的日志级别,可通过环境变量调整日志输出。对于希望逐步迁移到 Rust 的开发者,Rwf 提供了平滑过渡的方案,允许在同一个应用中同时运行 Python 和 Rust 代码,逐步将流量转移到 Rust 控制器。

参考资料
[1]

在此提供: https://levkk.github.io/rwf/

[2]

HTTP 服务器: examples/quick-start

[3]

ORM: examples/orm

[4]

动态模板: examples/dynamic-templates

[5]

认证: examples/auth

[6]

中间件: examples/middleware

[7]

后台任务: examples/background-jobs

[8]

定时任务: examples/scheduled-jobs

[9]

REST 框架: examples/rest

[10]

静态文件: examples/static-files

[11]

Hotwired Turbo: https://turbo.hotwired.dev/

[12]

后端驱动的 SPA: examples/turbo

[13]

CLI: rwf-cli

[14]

迁移: examples/django

[15]

模型-视图-控制器(MVC): https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller

[16]

应该足够了: https://security.stackexchange.com/questions/14068/why-most-people-use-256-bit-encryption-instead-of-128-bit


编程悟道
自制软件研发、软件商店,全栈,ARTS 、架构,模型,原生系统,后端(Node、React)以及跨平台技术(Flutter、RN).vue.js react.js next.js express koa hapi uniapp Astro
 最新文章