前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >用Actix写的一个类似于Facemash的小项目总结

用Actix写的一个类似于Facemash的小项目总结

作者头像
MikeLoveRust
发布2022-11-28 15:51:13
3860
发布2022-11-28 15:51:13
举报

前言

本后端项目用到的技术栈主要包括:

  • Actix Web框架;
  • Log 日志库;
  • Serde 序列化;
  • SnowFlake Id生成;
  • dotenv 获取环境配置;
  • MongoDB 存取;
  • lazy_static 全局静态初始化;
  • ELO 算法;
  • 使用 Pre-Commit 在 Git Commit 前进行校验;
  • 使用 Github Action 进行 CI;
  • 使用中间镜像对代码进行编译并创建部署镜像;
  • ……

阅读了本文,你应该也能够学会上面这些库的用法;

那么废话不多说,直接开始!

代码实现

代码目录结构

整个项目的目录结构如下(已去掉无关文件):

代码语言:javascript
复制
$ tree         
.
├── .env
├── .github
│   └── workflows
│       └── ci.yaml
├── .pre-commit-config.yaml
├── Cargo.toml
├── Dockerfile
├── Makefile
├── build-image.sh
└── src
    ├── algorithm
    │   ├── elo_rating.rs
    │   ├── k_factor.rs
    │   └── mod.rs
    ├── config
    │   └── mod.rs
    ├── controller
    │   ├── face_info_controller.rs
    │   ├── file_controller.rs
    │   └── mod.rs
    ├── dao
    │   ├── face_info_dao.rs
    │   ├── file_resource_dao.rs
    │   ├── mod.rs
    │   └── rating_log_dao.rs
    ├── entity
    │   ├── face_info.rs
    │   ├── file_resource.rs
    │   ├── mod.rs
    │   └── rating_log.rs
    ├── logger
    │   └── mod.rs
    ├── main.rs
    ├── resource
    │   ├── id_generator.rs
    │   ├── mod.rs
    │   └── mongo.rs
    ├── service
    │   ├── face_info_service.rs
    │   ├── file_resource_service.rs
    │   └── mod.rs
    └── utils
        ├── md5.rs
        └── mod.rs

下面来说明:

  • .github 目录:Github Actions 相关配置;
  • src 目录:项目源代码目录;
  • .pre-commit-config.yaml:Pre-Commit 配置;
  • .env:项目环境变量配置;
  • Cargo.toml:Cargo 项目配置;
  • Makefile:项目编译脚本;
  • Dockerfile:项目Docker镜像配置;
  • build-image.sh:打包镜像脚本;

对于 src 目录下的各个子目录,见名知意,基本上很好理解了!

服务入口

Cargo 项目约定程序的入口都是:src/main.rs 下;

我们从 main 函数来看做了些什么:

src/main.rs

代码语言:javascript
复制
#[macro_use]
extern crate log;

use actix_web::{middleware, App, HttpServer};
use dotenv::dotenv;
use mongodb::bson::doc;

use crate::controller::{face_info_controller, file_controller};
use crate::resource::mongo;

mod algorithm;
mod config;
mod controller;
mod dao;
mod entity;
mod logger;
mod resource;
mod service;
mod utils;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    dotenv().ok();
    logger::init();

    resource::check_resources().await;
    service::init_file_service().await;

    HttpServer::new(|| {
        App::new()
            .wrap(middleware::Logger::default())
            .service(face_info_controller::get_face_info_randomly)
            .service(face_info_controller::get_face_info_by_id)
            .service(face_info_controller::add_face_info)
            .service(face_info_controller::vote_face_info)
            .service(file_controller::create_file_resource_by_stream)
            .service(file_controller::create_file_resource)
            .service(file_controller::download_local_file)
    })
    .bind(("0.0.0.0", 8080))?
    .run()
    .await
}

在入口文件中,首先启用了一些库的宏(Macro),并声明了 Actix-Web 框架的 main 函数;

在 main 函数中,做了一般后端服务都会做的事情:

  • 获取环境配置;
  • 初始化项目日志;
  • 初始化资源:数据库、Id生成器等;
  • 注册并启动服务;

下面我们分别来看

配置与日志

获取环境配置

我们可以通过 dotenv 库解析位于项目下、以及系统环境变量中的配置;

只需要下面一句话即可:

代码语言:javascript
复制
dotenv().ok();

配置文件如下:

.env

代码语言:javascript
复制
MONGODB_URI=mongodb://admin:123456@localhost:27017/?retryWrites=true&w=majority
LOG_LEVEL=INFO
SNOWFLAKE_MACHINE_ID=1
SNOWFLAKE_NODE_ID=1

主要是配置了 MongoDB 的连接地址、日志级别、SnowFlake 的配置;

上面的语句会将这些配置解析;

初始化Logger

main 函数中的这条语句初始化了 Logger:

代码语言:javascript
复制
logger::init();

这个是 logger 模块封装的一个函数:

logger/mod.rs

代码语言:javascript
复制
use std::env;

use crate::config::LOG_LEVEL;
use log::{Level, LevelFilter, Metadata, Record};

struct Logger;

pub fn init() {
    static LOGGER: Logger = Logger;
    log::set_logger(&LOGGER).unwrap();

    let log_level: String = env::var(LOG_LEVEL).unwrap_or_else(|_| String::from("INFO"));
    log::set_max_level(match log_level.as_str() {
        "ERROR" => LevelFilter::Error,
        "WARN" => LevelFilter::Warn,
        "INFO" => LevelFilter::Info,
        "DEBUG" => LevelFilter::Debug,
        "TRACE" => LevelFilter::Trace,
        _ => LevelFilter::Info,
    });
}

impl log::Log for Logger {
    fn enabled(&self, _metadata: &Metadata) -> bool {
        true
    }

    fn log(&self, record: &Record) {
        if !self.enabled(record.metadata()) {
            return;
        }

        let color = match record.level() {
            Level::Error => 31, // Red
            Level::Warn => 93,  // BrightYellow
            Level::Info => 34,  // Blue
            Level::Debug => 32, // Green
            Level::Trace => 90, // BrightBlack
        };

        println!(
            "\u{1B}[{}m[{:>5}]:{} - {}\u{1B}[0m",
            color,
            record.level(),
            record.target(),
            record.args(),
        );
    }

    fn flush(&self) {}
}

上面的代码首先定义了一个全局日志类型 Logger;

并在 init 函数中初始化了全局静态变量:LOGGER,并使用 log::set_logger 进行了设置;

同时,我们我们从环境变量中获取 LOG_LEVEL 日志级别配置(如果未设置,则默认为 INFO 级别),随后进行了设置;

我们为我们的 Logger 实现了log::Log Trait,这也是为什么我们能将该类型的变量设置为Logger的原因!

log::Log Trait 的实现中,我们简单定义了日志的输出格式以及输出颜色;

可以看到有了很多第三方库的支持,rust 还是非常好用的!

初始化资源

接下来我们调用:

代码语言:javascript
复制
resource::check_resources().await;
service::init_file_service().await;

来等待资源初始化完成;

下面初始化文件服务的逻辑非常简单,只是创建了一个临时文件:

代码语言:javascript
复制
pub async fn init_file_service() {
    init_local_directory().await;
}

pub async fn init_local_directory() {
    fs::create_dir_all(SAVE_DIR).unwrap()
}

我们重点来看 check_resources() 函数,在其中初始化并校验了 MongoDB 连接以及 SnowFlake Id生成器;

资源相关的初始化都是在 resource 模块中完成的;

resource 模块的入口 mod.rs 中定义了资源的校验函数:

resource/mod.rs

代码语言:javascript
复制
use crate::doc;

pub mod id_generator;
pub mod mongo;

pub async fn check_resources() {
    check_mongo().await;
    check_id_generator().await;
}

async fn check_mongo() {
    mongo::MONGO_CLIENT
        .get()
        .await
        .database("admin")
        .run_command(doc! {"ping": 1}, None)
        .await
        .unwrap();
    info!("Mongo connected successfully.");
}

async fn check_id_generator() {
    info!("Id generate success: {}.", id_generator::get_id().await)
}

MongoDB 通过 Ping 校验了数据库连接,而 SnowFlake 通过创建了一个 Id 校验了正确性;

那么这些资源是在哪里初始化的呢?

主要是通过 lazy_static 在首次使用的时候初始化的!

lazy_static 的一个特性是:在首次使用这个变量的时候,才会进行静态初始化;

下面分别来看:

src/resource/mongo.rs

代码语言:javascript
复制
use std::env;

use async_once::AsyncOnce;
use lazy_static::lazy_static;
use mongodb::Client;

use crate::config::MONGODB_URI;

lazy_static! {
    pub static ref MONGO_CLIENT: AsyncOnce<Client> = AsyncOnce::new(async {
        let uri = env::var(MONGODB_URI).expect("You must set the MONGODB_URI environment var!");
        Client::with_uri_str(&uri).await.unwrap()
    });
}

上面的代码在 lazy_static! 宏中,异步初始化了 MongoDB 的连接:

首先,从环境变量中获取配置 MONGODB_URI,随后进行了初始化,并保存至变量:MONGO_CLIENT 中;

src/resource/id_generator.rs

代码语言:javascript
复制
use std::env;
use std::sync::Mutex;

use lazy_static::lazy_static;
use snowflake::SnowflakeIdBucket;

use crate::config;

lazy_static! {
    static ref ID_GENERATOR_BUCKET: Mutex<SnowflakeIdBucket> = Mutex::new({
        let machine_id: i32 = env::var(config::SNOWFLAKE_MACHINE_ID)
            .expect("You must set the SNOWFLAKE_MACHINE_ID environment var!")
            .parse::<i32>()
            .unwrap();
        let node_id: i32 = env::var(config::SNOWFLAKE_NODE_ID)
            .expect("You must set the SNOWFLAKE_NODE_ID environment var!")
            .parse::<i32>()
            .unwrap();

        SnowflakeIdBucket::new(machine_id, node_id)
    });
}

pub async fn get_id() -> String {
    ID_GENERATOR_BUCKET.lock().unwrap().get_id().to_string()
}

#[actix_rt::test]
async fn generate_id_test() {
    use dotenv::dotenv;

    dotenv().ok();
    println!("{}", get_id().await)
}

与上面的初始化类似,这里从环境变量中获取:SNOWFLAKE_MACHINE_IDSNOWFLAKE_NODE_ID,随后使用 SnowflakeIdBucket::new 进行了初始化;

同时,和 MongoDB 不同的是,这里需要使用 Mutex 进行封装,因为极有可能多个出现多个线程并发获取Id;

而 MongoDB 的 Client 已经是:Arc<ClientInner> 类型了!

我们也封装了 get_id 函数,直接供外部调用,而无需暴露 ID_GENERATOR_BUCKET 变量!

最下面是一个单测,用于测试我们的 Id 生成器;

至此,我们的资源初始化完成;

完整文章,请传送至:https://rustcc.cn/article?id=bafc76f0-1d85-40b1-a551-bb635e3fd37d

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-10-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Rust语言学习交流 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 代码实现
    • 代码目录结构
      • 服务入口
        • 配置与日志
          • 获取环境配置
          • 初始化Logger
          • 初始化资源
      相关产品与服务
      云数据库 MongoDB
      腾讯云数据库 MongoDB(TencentDB for MongoDB)是腾讯云基于全球广受欢迎的 MongoDB 打造的高性能 NoSQL 数据库,100%完全兼容 MongoDB 协议,支持跨文档事务,提供稳定丰富的监控管理,弹性可扩展、自动容灾,适用于文档型数据库场景,您无需自建灾备体系及控制管理系统。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档