前言

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

  • Actix Web框架;

  • Log 日志库;

  • Serde 序列化;

  • SnowFlake Id生成;

  • dotenv 获取环境配置;

  • MongoDB 存取;

  • lazy_static 全局静态初始化;

  • ELO 算法;

  • 使用 Pre-Commit 在 Git Commit 前进行校验;

  • 使用 Github Action 进行 CI;

  • 使用中间镜像对代码进行编译并创建部署镜像;

  • ……

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

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

代码实现

代码目录结构

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

$ 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

#[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 库解析位于项目下、以及系统环境变量中的配置;

只需要下面一句话即可:

dotenv().ok();

配置文件如下:

.env

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:

logger::init();

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

logger/mod.rs

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, // RedLevel::Warn => 93,  // BrightYellowLevel::Info => 34,  // BlueLevel::Debug => 32, // GreenLevel::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 还是非常好用的!

初始化资源

接下来我们调用:

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

来等待资源初始化完成;

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

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

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

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

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_ID 和 SNOWFLAKE_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

阅读。

用Actix写的一个类似于Facemash的小项目总结相关推荐

  1. 写了一个测试正则表达式的小工具

    这两天写了两个蜘蛛程序用来自动下载漫画,许多时候都是用他在网页中通过正则表达式获取关键字和信息.我用的正则表达式的工具是Expresso,这个工具无疑是目前最好的正则表达式的工具之一.但用着用着就觉得 ...

  2. [Electron]仿写一个课堂随机点名小项目

    自从前几个月下了抖音,无聊闲暇时就打会打开抖音,因为打开它有种莫名其妙打开了全世界的感觉... 无意中看到这个小视频:随机点名 于是仿写了一个课堂点名小项目,算是对Electron的一个简单的认识,有 ...

  3. 我给舅舅用Python写了一个订餐系统微信小程序!生意简直火爆!

    微信登录功能的实现 通过小程序的前端 配合python-flask的后端,实现登录接口的功能 在我们正式写代码之前 读一下微信小程序的官方文档. https://developers.weixin.q ...

  4. 徒弟做了一个Python的实战小项目——银行系统

    国际惯例:实践是检验真理的唯一标准. 众所周知,在编程过程中理论知识再充实也需要通过项目的炼金石.下面给大家看一下我徒弟做的一个小项目实战要求,是做一个银行系统,就是我们去银行办业务时候会有个自助的A ...

  5. 美团拍店,一个“顺道”赚钱的小项目,去饭店的路上,饭钱有了

    断更了,这几天每天都在路上,来来回回的,也没有闲下来的时候,前几天推荐大家一个相片搬运的项目,叫蚂蚁路客,有反馈说确实能赚点零花钱,但是实在是太少了,再加上天冷,这大冬天的出去一趟不够费事的,问我有没 ...

  6. 用【Python】写了一个水果忍者小游戏,玩过之后爱不释手

    前言 水果忍者到家都玩过吧,但是Python写的水果忍者你肯定没有玩过.今天就给你表演一个新的,用Python写一个水果忍者. 水果忍者的玩法很简单,尽可能的切开抛出的水果就行. 今天就用python ...

  7. python饮料购买_Python写的一个自动售饮料小程序!

    写这个程序的时候,我已学习Python将近有一百个小时,在CSDN上看到有人求助使用Python如何写一个自动售饮料的程序,我一想,试试写一个实用的售货程序.当然,只是实现基本功能,欢迎高手指点,新手 ...

  8. 聊聊程序员如何学习英语单词:写了一个记单词的小程序

    背景: 关于英文对程序员的重要性,就不多说了! 英语的学习,有很多,今天也不聊多,只聊英语单词! 关于单词的记忆,找过很多方法,下载过很多软件. 如图(其它不好用的都卸载了): 上图算是我以前用过软件 ...

  9. iOS:自己写的一个星级评价的小Demo

    重新整理了下自己星级评价的Demo,可以展示星级评价,可以动态修改星级. github的地址:https://github.com/hunterCold/HYBStarEvaluationView a ...

  10. 用Python写了一个水果忍者小游戏

    点击上方"菜学Python",选择"星标"公众号 超级无敌干货,第一时间送达!!! 水果忍者的玩法很简单,尽可能的切开抛出的水果就行. 今天小五就用python ...

最新文章

  1. 估值被砍700亿美元后,Waymo发重磅公开信:即将推出全自动驾驶打车服务
  2. java ctp行情_java-ctp
  3. IBM服务器win7系统忘记密码,图文详解Win7系统忘记开机密码的处理方法
  4. FU-A分包方式,以及从RTP包里面得到H.264数据和AAC数据的方法
  5. Android开发推荐资料大合集
  6. 2dpsk差分相干解调matlab,基于systemview和matlab的2DPSK
  7. 1-1Pytorch导学
  8. HTML DOM nodeName nodeValue
  9. tomcat启动报错解决org.jaxen.util.AncestorOrSelfAxisIterator
  10. 求超大文件上传方案( c# )
  11. 初探iOS项目使用MVP模式
  12. Jpress 企业简洁模板
  13. 周记---学会推迟满足感 享受长远的趣味
  14. 正则表达式,固话和手机号码验证,支持验证分机号
  15. android 自定义圆形头像,android自定义圆形头像
  16. 在win10上去除移动硬盘的bitlocker
  17. xgboost 自定义评价函数(metric)与目标函数
  18. php 井字棋,Unity3D 井字棋
  19. 解决jdbcTemplate处理sql带in的多个参数问题
  20. Antlr4:使用grun命令,触发NoClassDefFoundError

热门文章

  1. 2019年中总结之说走就走
  2. 各种梯度算法总结 + Total Variation
  3. python 模拟键盘按键错乱_python 采坑总结 调用键盘事件后导致键盘失灵的可能原因...
  4. IDEA 不检查语法错误问题
  5. 站在Stay老司机肩膀上分析Retrofit
  6. 华为+android+root权限获取root,如何获得华为手机的root权限?华为root权限获取教程...
  7. Typecho Fancybox 给文章图片添加灯箱效果
  8. 操作系统课堂笔记七-交换技术
  9. 2、通信中的交换技术
  10. 广和通高通物联网技术开放日成功举办