如何使用Rust框架 Actix构建web后端

如何使用Rust框架 Actix构建web后端

rust的生态里,后端框架使用量最大的两个框架是RocketActix,本文使用的是Actix框架。

本文部分展示代码为,曾经公司王者荣耀比赛写的后端项目逻辑。展示的代码隐藏了部分实现,这里大家有兴趣的话可以直接在我的github repo 中寻找源码,并欢迎大家给我提供意见(点赞一键三连🐶)。也欢迎大家对于本文积极提出意见。

环境

  • macOS环境下安装rust简单,一行命令curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh rust的相关工具就下载好了。这里如果需要可以更新下载源为国内源。
  • IDE使用JetBrains的RustRover,目前EAP版本下免费。

启动

创建项目之后,项目的依赖都在Cargo.toml中配置,数据库依赖方面使用sqlx,features使用mysql。:

toml 复制代码
[dependencies]
actix-web = "4.4"
dotenvy = "0.15"
env_logger = "0.10"
sqlx = { version = "0.7.1", features = ["mysql", "runtime-tokio", "chrono"] }
······

关于架构方面,使用多层架构:

css 复制代码
.
├── Cargo.lock
├── Cargo.toml
└── src
    ├── controller
    ├── main.rs
    ├── model
    ├── repository
    └── service

入口文件

main文件书写如下,分别是函数签名和内容:

rust 复制代码
#[actix_web::main]
async fn main() -> io::Result<()> {
    init_environment();
    let server_addr = env::var(SERVER_ADDR).expect(SERVER_ADDR_NOT_SET_MSG);
    let database_url = env::var(DATABASE_URL).expect(DATABASE_URL_NOT_SET_MSG);
    let pool = init_database(database_url).await;

    log::info!("{}{}", STARTING_SERVER_LOG, server_addr);

    HttpServer::new(move || { create_app(pool.clone()) })
        .bind(server_addr)?
        .run()
        .await
}

先用一个属性宏将main函数转换为一个异步函数,允许函数使用异步操作。函数签名中,函数名为main,返回类型为io::Result<()>:: 在rust里是关联函数调用(类似于静态方法),与之对应的是实例函数(类似于成员方法)。

继续看函数内容,获得环境里的值进行初始化。然后HttpServer::new初始化一个新的HTTP服务器实例;move || { create_app(pool.clone()) }这是一个rust的闭包写法,获取了作用域外的变量的所有权(所有权是rust 核心机制,这里就不展开了)。然后绑定指定的地址、启动服务器、以及声明启动服务器的异步。

定义controller

我们定义一下controller的代码,这里的思路和其他后端框架实现差不多。

用属性宏指定请求路径,获得接收的参数PostParam,然后传递这个param给对应的service处理,响应成功。

rust 复制代码
#[post("/")]
async fn pick_heroes(
    web::Json(param): web::Json<PostParam>,
    app_state: web::Data<AppState>,
) -> actix_web::Result<impl Responder> {
    let response_data = app_state.service.pick.pick_heroes(param).await?;
    Ok(web::Json(response_data))
}

定义model

这是我们前端需要用到的MyResult结构体,包含多个字端,包括整数、字符串、时间结构体、数组Vec包裹的日志结构体;属性宏赋予了调试和序列化的特性。

rust 复制代码
#[derive(Debug, Serialize)]
pub struct MyResult {
    pub team_id: i32,
    pub data: String,
    pub time: NaiveDateTime,
    pub logs: Vec<Log>,
}

定义service

下面是service的核心代码片段,定义PickServicetrait特征和PickServiceImpl结构体,然后先给PickServiceImpl结构体加一个返回自身的关联函数。然后实现PickService这个定义好的trait。

#[async_trait]宏在这里帮助我们在trait里支持异步函数;: Sync + Send意为有两个约束,SyncSend是两个并发特性(trait),能确保类型在多线程环境中的安全使用;Arc是一个用于线程安全的指针,可以用来在多线程环境中分享数据;<dyn HeroRepository>表示实现了这个trait。

rust 复制代码
#[async_trait]
pub trait PickService: Sync + Send {
    async fn pick_heroes(&self, param: PostParam) -> Result<MyResult, actix_web::Error>;
}

pub struct PickServiceImpl {
    pub hero_repository: Arc<dyn HeroRepository>,
    pub team_repository: Arc<dyn TeamRepository>,
    pub log_repository: Arc<dyn LogRepository>,
}

impl PickServiceImpl {
    pub fn new(
        hero_repository: Arc<dyn HeroRepository>,
        team_repository: Arc<dyn TeamRepository>,
        log_repository: Arc<dyn LogRepository>,
    ) -> Self {
        PickServiceImpl { hero_repository, team_repository, log_repository }
    }
}

下面代码实现了方法pick_heroes,可以将team_repository查询到的team数据,放入MyResult结构体之中,之后又将teamresult的可变引用放入check_team_is_picked方法,进一步处理,处理之后的rusult则成功响应返回。方法返回Result<MyResult, sqlx::Error>类型,即成功时返回MyResult对象,失败则返回sqlx::Error

rust 复制代码
#[async_trait]
impl PickService for PickServiceImpl {
    async fn pick_heroes(&self, param: PostParam) -> Result<MyResult, actix_web::Error> {
        let mut team = self.team_repository.get_by_encrypt_code(param.encrypt_code).await
            .map_err(actix_web::error::ErrorInternalServerError)?;

        let mut result = MyResult {
            team_id: team.id,
            data: team.pick_content.clone(),
            time: current_time(),
            logs: self.log_repository.get_by_team_id(team.id).await
                .expect(GET_LOGS_FAILED_ERROR),
        };

        self.check_team_is_picked(&mut team, &mut result).await;
        Ok(result)
    }
}

定义repository

这是team相关的repo的一个函数,在sqlx这个包的使用习惯下,直接把sql写在代码里,返回我们想要查询的Team数据,如果报错,则返回sqlx::Error类型。

rust 复制代码
#[async_trait]
impl TeamRepository for TeamRepositoryImpl {
    async fn get_by_encrypt_code(&self, encrypt_code: String) -> Result<Team, sqlx::Error> {
        sqlx::query_as::<_, Team>("SELECT * FROM `team` WHERE `encrypt_code` = ?")
            .bind(encrypt_code)
            .fetch_one(&*self.pool)
            .await
    }
}

End

代码风格和项目依赖架构就到此为止了,以上举了一些代码的例子,展示Actix框架中的一些风格和功能,以及rust语言在开发项目中的特性。在学习rust的路上,老实说我遇到不小的挑战,很多语法和特性习惯跟之前掌握的语言语法差距不小,以及老生常谈地跟rust编译器斗智斗勇、debug的漫长经历。

作为StackOverflow调查中连续多年的最受欢迎语言,整体来说算是国外雷声大,国内雨点小。不过随着一些国内的大厂(如字节)入场,和一些市场和生态的兴起,或许Rust语言未来在国内兴盛也未可知(希望别像"风暴要火")。

相关推荐
电子科技圈7 分钟前
先进封装、芯粒架构和3D集成——先进异构集成亟需兼具标准化与定制化能力的互联及总线IP解决方案
tcp/ip·设计模式·架构·软件构建·代码规范·设计规范
止语Lab1 小时前
好的 DX 不等于少写代码——三种语言的摩擦力设计课
后端
吃饱了得干活1 小时前
别再手动解析 LLM 输出了!LangChain 四种结构化输出方案对比
后端·python·langchain
程序员天天困1 小时前
Arthas trace 命令怎么用?一行定位最慢那行代码
jvm·后端
Huiturn1 小时前
GPT 5.6 连续编码 10 小时,纯 Python 啃下 Word 二进制格式——doc2docx 实现拆解
后端
AI小白Lin1 小时前
33 个 AI 专家全票通过?那问题才刚开始
人工智能·架构
用户298698530141 小时前
Python 实现 Excel 与 Markdown 互转的实用指南
后端·python·excel
用户77283104908401 小时前
krono-job:零侵入、单二进制交付的分布式任务调度平台(开源)
后端
Conan在掘金1 小时前
鸿蒙报错速查:Function lacks ending return statement,返回路径漏 return 就炸,根因 + 真解法
后端
人间凡尔赛2 小时前
AI-Native 云原生架构:2026 年从容器编排到智能体编排的范式革命
后端·云原生·架构