Rust生态技术栈

隗驰
2023-12-01

Rust开发生态【开发整理-20230106更新】

1.日志记录

1.1 simple_logger
[dependencies]
log = "0.4.14"
simple_logger = "2.1.0"
time = "0.3.7"

初始化

use log::LevelFilter;
use simple_logger::SimpleLogger;
use time::UtcOffset;

pub fn init_log() {
    SimpleLogger::new()
        .with_level(LevelFilter::Info)
        .with_colors(true)
        .with_utc_offset(UtcOffset::from_hms(8, 0, 0).unwrap())
        .init()
        .unwrap();
}
1.2 env_logger
[dependencies]
log = "0.4.0"
env_logger = "0.9.0"
// 简单形式:默认UTC 0 时区
use env_logger::Env;

env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
// use chrono::prelude::*;
// use env_logger::Env;
// use std::io::Write;

// 自定义时间+日志级别颜色
let env = Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info");
env_logger::Builder::from_env(env)
    .format(|buf, record| {
        let level = { buf.default_styled_level(record.level()) };
        writeln!(
          buf,
          "{} {} [{}:{}] {}",
          Local::now().format("%Y-%m-%d %H:%M:%S"),
          level,
          record.module_path().unwrap_or("<unnamed>"),
          record.line().unwrap_or(0),
          &record.args()
          )
    })
    .init();

2.输入/输出

特别注意:从控制台输入,会隐含读入\n换行符等,需要进行trim处理,否则使用match等方法匹配失败。

// 标准输出
println
// 标准错误
eprintln

// 输入
let mut input = String::new();
std::io::stdin()
.read_line(&mut input)
.expect("输入错误!");
// 输出
println!("你输入的内容是 : {}", input);

3.String类型的match

let user_choice = menu_handle();

// 通过String.as_str()方法可快速转换,此处为处理换行符,调用trim方法,trim方法隐含有转&str的能力
match user_choice.trim() {
    "1" => println!("用户选择1"),
    "2" => println!("用户选择2"),
    _ => println!("用户选择功能项无效"),
}

4.print!()输出无效问题

print!("输入文件名:");

解析:rust标准输出是行缓冲的,只有到了行尾才显示,想要输出需要手动刷新。

io::stdout().flush();

5.线程

use std::thread::sleep;

// 线程睡眠
sleep(Duration::from_secs(1));

6.Excel读取

calamine = "0.18.0"
// calamine = "0.18.0"
fn data_read(file_path: String) {
    //Excel读取
    let mut excel: Xlsx<_> = open_workbook(file_path).expect("打开Excel文件失败!");
    if let Some(Ok(r)) = excel.worksheet_range_at(0) {
        let (h, w) = r.get_size();
        log::info!("读取到Excel文件共{}行, {}列", h, w);
        let mut title_vec: Vec<String> = Vec::new();
        for i in 0..w {
            let title_name = r.get_value((0, i as u32)).unwrap().to_string();
            title_vec.push(title_name);
        }
        println!("titles:{:?}", title_vec);

        // 跳过首行标题行
        let rows = r.rows().skip(1);
        for row in rows {
            println!("row[5]={:?}", row[5]);
        }
    }
}

7.字符串转整型

let num = input.trim().parse::<i32>().unwrap();

8.终端输出彩色文本

colored = "2.0.0"
"this is blue".blue();
"this is red".red();
"this is red on blue".red().on_blue();
"this is also red on blue".on_blue().red();
"you can use truecolor values too!".truecolor(0, 255, 136);
"background truecolor also works :)".on_truecolor(135, 28, 167);
"bright colors are welcome as well".on_bright_blue().bright_red();
"you can also make bold comments".bold();
println!("{} {} {}", "or use".cyan(), "any".italic().yellow(), "string type".cyan());
"or change advice. This is red".yellow().blue().red();
"or clear things up. This is default color and style".red().bold().clear();
"purple and magenta are the same".purple().magenta();
"and so are normal and clear".normal().clear();
"you can specify color by string".color("blue").on_color("red");
String::from("this also works!").green().bold();
format!("{:30}", "format works as expected. This will be padded".blue());
format!("{:.3}", "and this will be green but truncated to 3 chars".green());

9.字符串截取

//注意:find查出是索引号,中文存储UTF8编码,一个中文占3个字节。
//总长度
let len = company.len_utf8();
let index = company_name.find("区").unwrap();
let end = min(company_name.len(), index + 3 * 3);
company_name[index + 3..end].to_string()

10.tokio线程

let handle = tokio::spawn(async move {
    sleep(Duration::from_secs(interval_time as u64));
});
handle.await.unwrap();

11.HTTP客户端

reqwest = { version = "0.11", features = "blocking"}
// reqwest可以使用阻塞方式
let body = reqwest::blocking::get(url).unwrap().text().unwrap();
// 网络文件下载
pub async fn download(url: &str) -> Vec<u8> {
    let response = reqwest::get(url).await.unwrap();
    let bytes = response.bytes().await.unwrap();
    let data: Result<Vec<_>, _> = bytes.bytes().collect();
    data.unwrap()
}
/// 保存文件到本地(二进制字节)
#[allow(dead_code)]
pub fn save_file_to_local(path: &Path, b: &[u8]) {
    let p = Path::new(path);
    if !p.exists() {
        let parent_path = p.parent().unwrap();
        fs::create_dir_all(parent_path).expect("创建文件夹失败");
    }
    let mut f = File::create(p).unwrap();
    let wr = f.write_all(b);
    match wr {
        Ok(_) => {
            info!(
                "保存文件到本地[{}]成功,文件大小:{}字节",
                path.to_str().unwrap(),
                b.len()
            );
        }
        Err(e) => {
            error!(
                "保存文件到本地[{}]失败,文件大小:{}字节,错误信息:{}",
                path.to_str().unwrap(),
                b.len(),
                e
            );
        }
    }
}

12.html转markdown

html2md = "0.2"
html2md::parse_html(&body);

13.命令行解析:clap(官方推荐)

clap = "3.0.0-beta.4"

14.HTTP服务器

# actix-web
# rocket
# warp
# axum
14.1 actix-web 自动重新加载开发服务器(开发热启动)
cargo install cargo-watch
cargo watch -x 'run --bin api-server'
14.1 actix-web sqlite数据库
# https://actix.rs/docs/databases/
# https://github.com/actix/examples/tree/master/databases/diesel
# https://diesel.rs/guides/getting-started.html
diesel = { version = "2.0.0", features = ["sqlite", "r2d2"] }

15.客户端框架

# Web技术栈:tauri
# 原生 GUI:druid、iced 和 sixtyfps。

16.Web前端框架

  • Yew:使用 WebAssembly 来创建多线程的前端 web 应用。

17.集合

//Vec HashMap都会扩容
//显式调用shrink_to_fit方法,可缩小容量

18.金融计算

# 目前较活跃的库:用纯Rust编写的十进制实现,适合财务计算
rust_decimal = "1.25.0"
# decimal类型默认serde格式化为str,需要格式化为float需指定feature
rust_decimal = { version = "1.25.0", features = [ "serde-float" ] }

19.数据库操作

// Rust 支持几乎所有主流的数据库,包括但不限于 MySQL、Postgres、Redis、RocksDB、Cassandra、MongoDB、ScyllaDB、CouchDB 等等
// 使用 ORM,可以用: diesel,或者 sea-orm。
// 如果你享受直接但安全的 SQL 查询,可以使用 sqlx。
// 连接池:r2d2
// sqlx框架
// sqlx = { version = "0.6", features = [ "runtime-tokio-native-tls", "mysql", "chrono" ] }
// MySQL连接池初始化
let pool = MySqlPool::connect("mysql://root:123456@127.0.0.1/book")
  .await
  .unwrap();

20.Web自动化测试

//Rust 有对标 puppeteer 的 headless_chrome
//以及对标 selenium 的 thirtyfour 和 fantoccini。
// chrome_driver下载地址:https://chromedriver.chromium.org/downloads
// 初始化chrome_driver连接客户端
async fn init_chrome_driver_client() -> Client {
    let cr = ClientBuilder::native()
        .connect("http://localhost:4444")
        .await;
    return match cr {
        Ok(c) => c,
        Err(e) => {
            error!(
                "连接Chrome Driver驱动服务失败,请确认启动Chrome Driver驱动,\
            启动命令参考:./chromedriver.exe --port=4444,错误信息:{}",
                e
            );
            panic!("连接Chrome Driver驱动服务失败,程序终止");
        }
    };
}

// fantoccini使用
let c = init_chrome_driver_client().await;
c.goto("SITE_BASE_URL").await.unwrap();
let element = c.find(Locator::Css(".nav")).await.unwrap();

fantoccini:适用于标准WebDriver协议

thirtyfour:适用于chromedriver(基于fantoccini的更上层的库)

# 封装好的chromedriver docker镜像 105版本及对应chromedriver 默认仅允许127.0.0.1连接
# 启动时可指定IP环境变量(为空时允许所有IP连接),PORT端口等
docker run -d --name chromedriver -p 4444:4444 -e IPS= seekerman/chromedriver:latest
/// rust示例

// thirtyfour = "0.31.0"
// tokio = { version ="1.17.0", features = ["full"] }
use thirtyfour::prelude::*;

#[tokio::main]
async fn main() -> WebDriverResult<()> {
    let mut caps = DesiredCapabilities::chrome();
    caps.add_chrome_arg("--disable-gpu").unwrap();
    caps.add_chrome_arg("--headless").unwrap();
    caps.add_chrome_arg("--no-sandbox").unwrap();
    let driver = WebDriver::new("http://localhost:4444", caps).await?;

    driver.goto("https://www.baidu.com").await?;
    let source = driver.source().await.unwrap();
    println!("source:{}", source);

    // Always explicitly close the browser.
    driver.quit().await?;

    Ok(())
}
# 使用selenium Server
docker run --rm -d -p 4444:4444 -p 5900:5900 --name selenium-server -v /dev/shm:/dev/shm selenium/standalone-chrome:4.1.0-20211123
// rust连接selenium server
use thirtyfour::prelude::*;
use tokio;

#[tokio::main]
async fn main() -> color_eyre::Result<()> {
    // The use of color_eyre gives much nicer error reports, including making
    // it much easier to locate where the error occurred.
    color_eyre::install()?;

    let caps = DesiredCapabilities::chrome();
    let driver = WebDriver::new("http://localhost:4444", caps).await?;

    // Navigate to https://wikipedia.org.
    driver.goto("https://wikipedia.org").await?;
    let elem_form = driver.find(By::Id("search-form")).await?;

    // Always explicitly close the browser. There are no async destructors.
    driver.quit().await?;

    Ok(())
}

21.云原生开发

// 处理 Kubernetes API 的:kube-rs

22.WebAssembly 开发

// 用 wasm-pack 和 wasm-bindgen,不但生成 wasm,同时还生成 ts/js 调用 wasm 的代码

23.html解析

nipper = "0.1.9"
tendril = "0.4.2"

24.正则

regex = "1.5.6"
/// 正则处理内容,多空格自动替换
pub async fn content_handle(content: &str) -> String {
    let r = Regex::new(r"\s+").unwrap();
    let res = r.replace_all(content, " ");
    res.to_string()
}

25.递归调用

// 引入依赖:async-recursion = "1.0.0"
// 递归方法增加:#[async_recursion(?Send)]

26.hash code

let mut hasher = DefaultHasher::new();
url.hash(&mut hasher);
let id = hasher.finish();

27.json

# serde: required if you are going to use documents
serde = { version="1.0",   features = ["derive"] }
# serde_json: required in some parts of this guide
serde_json = "1.0"
#[derive(Serialize, Deserialize)]

28.环境变量

dotenv = "0.15.0"
use dotenv::dotenv;
use std::env;

fn main() {
    dotenv().ok();

    for (key, value) in env::vars() {
        println!("{}: {}", key, value);
    }
}

29.Email开发

[dependencies]
# 邮件发送 smtp协议
lettre = "0.9"
lettre_email = "0.9"

# 邮件接收pop3协议
pop3 = "1.0.6"

# 邮件接收协议
imap = "2.4.1"

# email解析
# lettre下的MimeMessage::parse() 目前不稳定
# email-parser待验证
email-parser = "0.5.0"

案例:https://www.codeleading.com/article/3341645849/

邮件发送乱码问题,在Message::builder()中添加.header(ContentType::TEXT_PLAIN),这将指定ContentTypetext/plain; charset=utf-8

github的issues中有提到:https://github.com/lettre/lettre/pull/841

源码ContentType文件中也要有相关文档描述:

/// A `ContentType` of type `text/plain; charset=utf-8`
///
/// Indicates that the body is in utf-8 encoded plain text.
pub const TEXT_PLAIN: ContentType = Self::from_mime(mime::TEXT_PLAIN_UTF_8);

/// A `ContentType` of type `text/html; charset=utf-8`
///
/// Indicates that the body is in utf-8 encoded html.
pub const TEXT_HTML: ContentType = Self::from_mime(mime::TEXT_HTML_UTF_8);

30.windows openssl rust

可自行编译安装,较为繁琐。

或vcpkg进行安装(未验证成功)

# 指定vendored特征,可免安装,但依赖perl进行编译,下载:https://strawberryperl.com/
openssl = { version = "0.10.41", features = ["vendored"] }
Ubuntu安装openssl
sudo apt-get install openssl
sudo apt-get install libssl-dev

31.Linux系统监控

  • crate:https://crates.io/crates/procfs

  • doc:https://docs.rs/procfs/0.13.2/procfs/

  • github:https://github.com/eminence/procfs

# 该库为读取/proc下的文件解析
procfs = "0.13.2"
# 跨平台,获取操作系统信息,包括:kernel/cpu/memory/disk/load/hostname。
sys-info = "0.9.1"
31.1 sys-info提供方法:
  • boottime:获取系统启动时间
  • cpu_num:获取 cpu num 数量。
  • cpu_speed:获取 cpu 速度(频率)。
  • disk_info:获取磁盘信息。
  • hostname:获取主机名。
  • linux_os_release:获取Linux的操作系统发行说明
  • loadavg:获取系统负载平均值。
  • mem_info:获取内存信息。
  • os_release:获取操作系统发布版本。
  • os_type:获取操作系统类型。
  • proc_total:获取当前进程数量。

依赖glib2.0:

# error:Package glib-2.0 was not found in the pkg-config search path
# ubuntu
sudo apt-get install libglib2.0-dev
# centos
sudo yum install glib2-devel

32.使用sqlite数据库

rusqlite = { version = "0.28.0", features = ["bundled"] }
# on OpenSUSE
sudo zypper install sqlite3-devel libsqlite3-0 sqlite3

# on Ubuntu
sudo apt-get install libsqlite3-dev sqlite3

# on Fedora
sudo dnf install libsqlite3x-devel sqlite3x

# on macOS (using homebrew)
brew install sqlite3

基于ubuntu构建包含sqlite3的docker镜像:

FROM ubuntu:20.04
WORKDIR /app
copy ./api-server ./api-server

RUN apt-get install -y update \
    && apt-get install -y upgrade \
    && apt-get install -y sqlite3 libsqlite3-dev

ENV LANG=C.UTF-8

VOLUME /app/data
EXPOSE ${PORT}

RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo 'Asia/Shanghai' >/etc/timezone
ENTRYPOINT /app/api-server
# The default when using vcpkg is to dynamically link, which must be enabled by setting # # 设置环境变量 VCPKGRS_DYNAMIC=1
# environment variable before build. vcpkg install sqlite3:x64-windows will install the required library.
vcpkg install sqlite3:x64-windows

33.vcpkg_cli

cargo install vcpkg_cli
# 用vcpkg_cli检验编译好后的sqlite3是否能被rust识别
vcpkg_cli probe sqlite3

34.获取系统包路径

例如:home_dir,cache_dir,config_dir,data_dir,desktop_dir,download_dir等等

可以使用的库:dirs 或者 directories

dirs = "4.0.0"
directories = "4.0.1"

35.Rust守护进程

https://crates.io/crates/daemonize

https://crates.io/crates/fork

daemonize = "0.4.1"
fork = "0.1.19"

36.获取用户和组信息

users: Library for accessing Unix users and groups

这是一个用于访问 Unix 用户和组的库。 它支持获取系统用户和组,将它们存储在缓存中,并创建您自己的模拟表。

users = "0.11.0"

打印出当前用户名的完整示例:

use users::{get_user_by_uid, get_current_uid};

let user = get_user_by_uid(get_current_uid()).unwrap();
let username = user.name().to_str().unwrap().to_string();
let group_id = user.primary_group_id() as i32;
println!("Hello, {}!", username);

37.定时任务

# crontab,已经多年没维护了,不可用

# 新框架:delay-timer,试用有一些bug,github作者称比较忙
delay_timer = "0.11.3"

# 中文社区,群友作者,简单实用
rcron = "1.1.3"

38.Docker监控

# Bollard: an asynchronous rust client library for the docker API
bollard = "0.13.0"

39.uuid

// uuid
// uuid = { version = "1.1.2", features = ["v4"] }

/// uuid
pub fn generate_uuid() -> String {
    let id = Uuid::new_v4();
    id.to_string()
}

40.递归问题

Rust中编写递归比较麻烦,该类问题更多的可以采用loop结构进行处理。

41.切换nightly

rustup toolchain list

rustup toolchain install nightly

# 更改当前系统默认的channel
rustup default nightly

# 使用rustup run指定channel
rustup run nightly cargo build

# 使用rustup overwrite设置当前项目使用的channel 
rustup override set nightly

42.在资源管理器打开目录

// mac
use std::process::Command;

fn main( ) {
    println!( "Opening" );
    Command::new( "open" )
        .arg( "." ) // <- Specify the directory you'd like to open.
        .spawn( )
        .unwrap( );
}

// windows
use std::process::Command;

fn main( ) {
    println!( "Opening" );
    Command::new( "explorer" )
        .arg( "." ) // <- Specify the directory you'd like to open.
        .spawn( )
        .unwrap( );
}

//linux
use std::process::Command;

fn main( ) {
    println!( "Opening" );
    Command::new( "xdg-open" )
        .arg( "." ) // <- Specify the directory you'd like to open.
        .spawn( )
        .unwrap( );
}

43.环境变量读取

dotenv = "0.15.0"
dotenv().ok();
let secret = env::var("JWT_SECRET").expect("请设置JWT_SECRET环境变量");

44.tracing日志

tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tracing_subscriber::registry()
        .with(
            tracing_subscriber::EnvFilter::try_from_default_env()
                .unwrap_or_else(|_| "rust-demo=info,tower_http=info".into()),
        )
        .with(tracing_subscriber::fmt::layer())
        .init();
/// 注意:rust-demo为项目名时,配置处需使用rust_demo,转为下划线

use tracing::{error, info, warn};
info!("entry....");

45.reqwest使用rustls-tls

# 默认使用的是openssl,安装麻烦,可使用上文的内置静态包,也可以使用rustls-tls
reqwest = { version = "0.11.9", features = ["json", "rustls-tls"], default-features = false }

46.sha1等加密算法

rust-crypto = "^0.2"
use crypto::digest::Digest;
use crypto::sha1::Sha1;

/// sha1 encrypt
pub fn get_sha1(s: &str) -> String {
    let mut hasher = Sha1::new();
    hasher.input_str(s);
    let hex = hasher.result_str();
    hex
}

47.jwttoken解决方案

# 可参考axum官方jwt例子
jsonwebtoken = "8.0"

48.url-encode

urlencoding = "2.1.2"
use urlencoding::encode;

let encoded = encode("This string will be URL encoded.");
println!("{}", encoded);

49.Rust本地搜索引擎

Tantivy是Rust实现的本地搜索库,功能对标lucene

 类似资料: