玩了一段时间 Vapor,想不明白谁家会用 swift 跑生产,可能也就 perfect 维护者背后的公司会用,至少在拆哪应该还是要快(la)准(man)狠(kpi)的。
rust 社区中关注了 rocket 一段时间,后面发现 actix-web 在各方面更有优势,加上要好好点 rust 技能树,所以就选定这个了。

这里会以重构我的闹钟后端作为蓝本,逐步做个记录。

TL;DR

  • 直接用 cargo 创建应用
  • 装载 actix-web
  • 写一个 api

创建应用

官方没有开箱即用的模板,所以还是从cargo new开始,这里定义了默认使用git作为版本控制

> cargo new --vcs git actix-demo

Cargo.toml中添加这些

[dependencies]
actix-web = "2.0"
actix-rt = "1.0"
# env
dotenv = "0.15.0"
log = "0.4.8"
env_logger = "0.7.1"

然后在main.rs创建启动函数

use actix_web::{App, HttpServer, get};

#[get("/")]
fn index() -> HttpResponse {
    HttpResponse::Ok().body("Hello World")
}

#[actix_rt::main]
async fn main() -> io::Result<()> {

    let app = || App::new().service(index);

    info!("serving on localhost:7001");

    HttpServer::new(app)
        .bind("localhost:7001")?
        .run()
        .await
}

一般来说这样就可以运行了

配置应用

按照习惯,工程结构还是愿意设置成这样:

  • controller
  • services
  • models
  • middleware

所以目录先确定下来:

+-- src
| +-- controller
| | +-- mod.rs
| +-- services
| | +-- mod.rs
| +-- models
| | +-- mod.rs
| +-- middleware
| | +-- mod.rs

为什么每个目录下都需要mod.rs?原因是人家rust对模块就是这么处理的啊!

接着,Cargo.toml需要加点东西,比如json的支持(可是你用的不是 GraphQL 吗!)

[dependencies]
# serde
serde = "1.0.104"
serde_derive = "1.0.104"
serde_json = "1.0.44"
json = "0.12.0"

接下来对代码作出一些改动,首先是一个通用的json结构体,创建 models/payload

use serde_derive::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Clone)]
pub struct Payload<T> {
    pub code: isize,
    pub message: String,
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub payload: Option<T>,
}

接着,从controller/index开始

#[derive(Deserialize)]
struct Info {
    message: String
}

#[get("/")]
pub async fn index(query: web::Query<Info>) -> Result<HttpResponse, Error> {

    // 在Query拿到与 Info 结构一样的参数
    // 如果你这么写,那么如果调用时如果不传 query 会报错,param 同理
    // rust 真严格..
    let result = crate::services::index::get_helloworld(query.message.clone());

    Ok(HttpResponse::Ok().json(result))
}

然后,services/index,暂时不用middleware

use crate::models::payload::Payload;

// 简单写一个服务
pub fn get_helloworld(msg: String) -> Payload<()> {
    Payload {
        code: 0,
        message: msg.to_string(),
        payload: None
    }
}

最后改一下main.rs

+mod controllers;
+mod models;
+mod services;

#[actix_rt::main]
async fn main() -> io::Result<()> {

    let app = || App::new()
-        .service(index);
+        .service(controllers::index::index);

    info!("serving on localhost:7001");

    HttpServer::new(app)
        .bind("localhost:7001")?
        .run()
        .await

测试一下,调用 localhost:7001?message=helloworld,就可以看到回复了!

{
    "code": 0,
    "message": "helloworld"
}

代码里我其实还用这些:

  • router
  • 写了一个配置的结构体用来封装 HOST, PORT 和数据库地址
  • 环境变量配置

文章就不写了,大概就那样。

后记

  • 建议用 CLion,VSCode 和 VIM 实在是太… IDE 也好,数据库分步调试什么的很方便
  • 没 key 怎么办,找一个长期维护的开源项目,嫖就完事儿了
  • 前端本来也想用rust(yew)的,可是在raspbian运行,不清楚结果。也不清楚其他选型,所以只能继续electron + react了,可能会去掉umi

下一期整数据库连接