Rust使用config加载Toml配置文件

news2024/10/23 19:40:03

前面提到用dotenvy读取配置文件到环境变量:https://juejin.cn/post/7411407565357449225

这里从配置文件中读取配置

添加依赖(这里使用yaml配置)

# 异步运行时
tokio = { version = "1", features = ["full"] }
# 序列化和反序列化数据
serde = { version = "1.0.127", features = ["derive"] }
# 动态修改配置
config = "0.14.0"
# 枚举处理
strum = { version = "0.26", features = ["derive"] }
# 假数据,用于测试
fake = { version = "2.9.2", features = ["derive", "uuid", "chrono"] }
# 异步锁
once_cell = "1.20.2"
# 错误处理
anyhow = "1.0.86"
# 序列化JSON
serde_json = "1.0.128"
# 自定义错误
thiserror = "1.0.64"
# 读取env
dotenvy = "0.15.7"
# 分布式跟踪的 SDK,用于采集监控数据,这里用其日志功能
tracing = "0.1.40"
# 日志
log = "0.4.22"
# 日志派生
log-derive = "0.4.1"
# 日志过滤器
tracing-subscriber = { version = "0.3", default-features = true, features = [
    "std",
    "env-filter",
    "registry",
    "local-time",
    "fmt",
] }
# 日志记录器
tracing-appender = "0.2.3"
# redis 客户端
redis = { version = "0.27.4", features = ["aio", "tokio-comp"] }
# 使用tokio实现的连接池,支持postgres、redis、redis cluster、rsmq等
bb8 = "1.11.0"
bb8-redis = "0.17.0"
# 异步 WebSocket
tokio-tungstenite = "0.24.0"

新建四个配置文件

  • default.toml:默认配置
  • development.toml:开发配置
  • production.toml:生产配置
  • test.toml:测试配置

settings\default.toml

debug = true
[network]
# 这里的配置会被development中的配置覆盖
host = "0.0.0.0"
port = 8080
[database]
url = "postgres://postgres:root123456@localhost:5432/postgres"
[redis]
url = "redis://127.0.0.1:6379"

settings\development.toml

debug = true
[network]
host = "127.0.0.1"
port = 8080
[database]
url = "postgres://postgres:root123456@localhost:5432/postgres"
[redis]
url = "redis://127.0.0.1:6379"

config\settings.rs配置信息

use std::env;
use config::{ Config, ConfigError, Environment, File };
use serde::Deserialize;

#[derive(Debug, Deserialize)]
#[allow(unused)]
pub struct Network {
    pub host: String,
    pub port: u16,
}
#[derive(Debug, Deserialize)]
#[allow(unused)]
pub struct Database {
    pub url: String,
}
#[derive(Debug, Deserialize)]
#[allow(unused)]
pub struct Redis {
    pub url: String,
}
// 配置
#[derive(Debug, Deserialize)]
#[allow(unused)]
pub struct Settings {
    pub debug: bool,
    pub network: Network,
    pub database: Database,
    pub redis: Redis,
}

impl Settings {
    pub fn new() -> Result<Self, ConfigError> {
        // 从环境变量中获取运行模式development、production、test
        let run_mode = env::var("RUN_MODE").unwrap_or_else(|_| "development".into());
        let s = Config::builder()
            //从“default”配置文件开始合并,后来合并的配置会替换“default”中的配置
            .add_source(File::with_name("settings/development"))
            //添加到当前环境文件中
            //默认为'development'
            //注意这个功能是可选的,所以可以使用.required(false)来忽略它
            .add_source(File::with_name(&format!("settings/{}", run_mode)).required(false))
            // 添加本地配置文件
            // 不应该加入git
            .add_source(File::with_name("settings/local").required(false))
            // 从环境中添加配置(带有APP前缀)
            // 如. .' APP_DEBUG=1 ./target/app '将设置' debug '键
            .add_source(Environment::with_prefix("app"))
            // 您也可以通过编程方式覆盖配置,database.url将覆盖配置文件中的值
            // .set_override("database.url", "postgres://")?
            .build()?;

        // 访问配置
        println!("debug: {:?}", s.get_bool("debug"));
        println!("database: {:?}", s.get::<String>("database.url"));

        // 反序列化
        s.try_deserialize()
    }

    /// 如果你想封装数据,仅将Settings设置为pub,其他的修改为private,通过自定义函数使用
    /// ```
    /// let settings = Settings::new().expect("Failed to load settings");
    /// let db_url = settings.get_db_url();
    /// println!("Database URL: {:?}", db_url);
    /// ```
    pub fn get_newtwork(&self) -> String {
        format!("{}:{}", &self.network.host, &self.network.port).parse().unwrap()
    }
}

使用

let settings = Settings::new().expect("Failed to load settings");
println!("{:?}", settings);

结果

database: Database { url:"postgres://postgres:root123456@localhost:5432/postgres" }, 
redis: Redis { url: "redis://127.0.0.1:6379" } }

在Axum中使用

在路由上使用Extension,任何实现了clone的结构体都可以被提供路由

    let settings = Settings::new().expect("Failed to load settings");
    println!("{:?}", settings);
    // 这里将default中的0.0.0.0替换为了127.0.0.1
    println!("{:?}", settings.network.host);
    println!("{:?}", settings.database.url);
    // 将读取的配置格式化为我们需要的字符串
    let server_url = format!("{}:{}", settings.network.host, settings.network.port);
    // 从配置文件中读取
    let listener = tokio::net::TcpListener::bind(server_url).await.unwrap();

从环境变量中选择开发测试配置

1、设置配置

.env

# 指定当前配置文件
RUN_MODE=development
# 开启调试模式
# APP__开头的配置会被自动注入到环境变量中替换配置文件中的配置
# APP__DATABASE_URL=your_database_url
RUST_BACKTRACE=1
# 此配置仅仅用于seaormcli
DATABASE_URL=postgres://postgres:root123456@localhost:5432/postgres
LOG_LEVEL=TRACE

设置默认配置文件settings\default.toml

debug = true

[server]
host = "0.0.0.0"
port = 8080

设置开发配置文件settings\development.toml

debug = true
# 指定开发环境配置
profile = "development"
[tracing]
log_level = "debug"
[server]
host = "127.0.0.1"
port = 9090
[db]
password = "root123456"
host = "127.0.0.1"
port = 5_432
max_connections = 100
database_name = "postgres"

[redis]
host = "127.0.0.1"
port = 6_379

读取环境变量config\env.rs

use std::str::FromStr;
use config::ConfigError;
use super::profile::Profile;
// 获取环境变量配置,例如本例中prefix为"APP",环境变量前缀分隔符和环境变量分隔符都设置为"__"
// 使用,则将环境变量中的DATABASE_URL作为配置项
pub fn get_env_source(prefix: &str) -> config::Environment {
    // 创建新的环境变量配置
  config::Environment::with_prefix(prefix)
  // 设置环境变量前缀分隔符和环境变量分隔符
    .prefix_separator("__")
    .separator("__")
}
// 从环境变量中获取profile,开发环境还是测试环境
pub fn get_profile() -> Result<Profile, config::ConfigError> {
  std::env::var("RUN_MODE")
    .map(|env| Profile::from_str(&env).map_err(|e| ConfigError::Message(e.to_string())))
    .unwrap_or_else(|_e| Ok(Profile::Dev))
}

数据库配置config\database.rs

use serde::Deserialize;
#[derive(Debug, Deserialize, Clone)]
pub struct DatabaseConfig {
    pub username: String,
    pub password: String,
    pub port: u16,
    pub host: String,
    pub max_connections: u32,
    pub database_name: String,
}

impl DatabaseConfig {
    pub fn get_url(&self) -> String {
        Self::create_url(&self.username, &self.password, &self.host, self.port, &self.database_name)
    }
    // 创建数据库连接字符串
    pub fn create_url(
        username: &str,
        password: &str,
        host: &str,
        port: u16,
        database_name: &str
    ) -> String {
        format!("postgres://{username}:{password}@{host}:{port}/{database_name}")
    }
}

redis配置config\redis.rs

pub use redis::Client;
use serde::Deserialize;

#[derive(Debug, Deserialize, Clone)]
pub struct RedisConfig {
    pub port: u16,
    pub host: String,
}

impl RedisConfig {
    // 获取redis连接地址
    pub fn get_url(&self) -> String {
        format!(
            "redis://{host}:{port}",
            host = self.host,
            port = self.port,
        )
    }
}

服务端配置config\server.rs

use std::net::{AddrParseError, SocketAddr};
use serde::Deserialize;
#[derive(Debug, Deserialize, Clone)]
pub struct ServerConfig {
    pub host: String,
    pub port: u16,
}

impl ServerConfig {
    // 获取地址
    pub fn get_addr(&self) -> String {
        format!("{}:{}", self.host, self.port)
    }
    // 获取http地址
    pub fn get_http_addr(&self) -> String {
        format!("http://{}:{}", self.host, self.port)
    }
    // 获取socket地址
    pub fn get_socket_addr(&self) -> Result<SocketAddr, AddrParseError> {
        self.get_addr().parse()
    }
}

#[cfg(test)]
pub mod tests {

    use super::*;

    #[test]
    pub fn app_config_http_addr_test() {
        let config = ServerConfig {
            host: "127.0.0.1".to_string(),
            port: 8080,
        };
        assert_eq!(config.get_http_addr(), "http://127.0.0.1:8080");
    }
    #[test]
    pub fn app_config_socket_addr_test() {
        let config = ServerConfig {
            host: "127.0.0.1".to_string(),
            port: 8080,
        };
        assert_eq!(config.get_socket_addr().unwrap().to_string(), "127.0.0.1:8080");
    }
}

日志配置config\tracing.rs

use serde::Deserialize;
#[derive(Debug, Deserialize, Clone)]
pub struct TracingConfig {
    log_level: String,
}
impl TracingConfig {
    // 获取地址
    pub fn get_log_level(&self) -> String {
        format!("{}", self.log_level)
    }
}
#[cfg(test)]
pub mod tests {

    use super::*;

    #[test]
    pub fn app_config_http_addr_test() {
        let config = TracingConfig {
            log_level: "debug".to_string(),
        };
        assert_eq!(config.get_log_level(), "debug");
    }
}

读取配置类型config\profile.rs

use serde::Deserialize;

#[derive(
    Debug,
    strum::Display,
    strum::EnumString,
    Deserialize,
    PartialEq,
    Eq,
    PartialOrd,
    Ord,
    Clone,
    Copy,
)]
pub enum Profile {
    #[serde(rename = "test")]// 序列化、反序列化重命名
    #[strum(serialize = "test")]// 枚举序列化、反序列化重命名
    Test,
    #[serde(rename = "development")]
    #[strum(serialize = "development")]
    Dev,
    #[serde(rename = "production")]
    #[strum(serialize = "production")]
    Prod,
}

构建配置config\mod.rs


pub mod database;
pub mod env;
pub mod redis;
pub mod server;
pub mod profile;
pub mod tracing;
use std::str::FromStr;
use database::DatabaseConfig;
use profile::Profile;
use redis::RedisConfig;
use server::ServerConfig;
use ::tracing::info;
use config::{ConfigError, Environment};
use serde::Deserialize;
use tracing::TracingConfig;
use utils::dir::get_project_root;
use crate::utils;

#[derive(Debug, Deserialize, Clone)]
pub struct AppConfig {
    pub profile: Profile,
    pub tracing: TracingConfig,
    pub server: ServerConfig,
    pub db: DatabaseConfig,
    pub redis: RedisConfig,
}

impl AppConfig {
    pub fn read(env_src: Environment) -> Result<Self, config::ConfigError> {
        // 获取配置文件目录
        let config_dir = get_settings_dir()?;
        info!("config_dir: {:#?}", config_dir);
        // 获取配置文件环境
        let run_mode = std::env::var("RUN_MODE")
            .map(|env| Profile::from_str(&env).map_err(|e| ConfigError::Message(e.to_string())))
            .unwrap_or_else(|_e| Ok(Profile::Dev))?;
        // 当前配置文件名
        let profile_filename = format!("{run_mode}.toml");
        // 获取配置
        let config = config::Config::builder()
            // 添加默认配置
            .add_source(config::File::from(config_dir.join("default.toml")))
            // 添加自定义前缀配置
            .add_source(config::File::from(config_dir.join(profile_filename)))
            // 添加环境变量
            .add_source(env_src)
            .build()?;
        info!("Successfully read config profile: {run_mode}.");
        // 反序列化
        config.try_deserialize()
    }
}
// 获取配置文件目录
pub fn get_settings_dir() -> Result<std::path::PathBuf, ConfigError> {
    Ok(get_project_root()
        .map_err(|e| ConfigError::Message(e.to_string()))?
        .join("settings"))
}
// // 获取静态文件目录
// pub fn get_static_dir() -> Result<std::path::PathBuf, ConfigError> {
//     Ok(get_project_root()
//         .map_err(|e| ConfigError::Message(e.to_string()))?
//         .join("static"))
// }

#[cfg(test)]
mod tests {
    use crate::config::profile::Profile;

    use self::env::get_env_source;

    pub use super::*;
    #[test]
    pub fn test_profile_to_string() {
        // 设置dev模式
        let profile: Profile = Profile::try_from("development").unwrap();
        println!("profile: {:#?}", profile);
        assert_eq!(profile, Profile::Dev)
    }
    #[test]
    pub fn test_read_app_config_prefix() {
        // 读取配置
        let config = AppConfig::read(get_env_source("APP")).unwrap();
        println!("config: {:#?}", config);
    }
}

2、设置客户端

InfraError是自定义的错误,你可以改成标准库的错误

redis客户端client\redis.rs

use redis::{Client, RedisError};
use std::time::Duration;
use tracing::log::info;
use test_context::AsyncTestContext;
use crate::{config::{AppConfig,redis::RedisConfig}, constant::CONFIG};
use super::builder::ClientBuilder;
// 类型别名
pub type RedisClient = redis::Client;

pub trait RedisClientExt: ClientBuilder {
    fn ping(&self) -> impl std::future::Future<Output = Result<Option<String>, RedisError>>;
    fn set(
        &self,
        key: &str,
        value: &str,
        expire: Duration,
    ) -> impl std::future::Future<Output = Result<(), RedisError>>;
    fn exist(&self, key: &str) -> impl std::future::Future<Output = Result<bool, RedisError>>;
    fn get(
        &self,
        key: &str,
    ) -> impl std::future::Future<Output = Result<Option<String>, RedisError>>;
    fn del(&self, key: &str) -> impl std::future::Future<Output = Result<bool, RedisError>>;
    fn ttl(&self, key: &str) -> impl std::future::Future<Output = Result<i64, RedisError>>;
}

impl ClientBuilder for RedisClient {
    fn build_from_config(config: &AppConfig) -> Result<RedisClient,InfraError> {
        Ok(redis::Client::open(config.redis.get_url())?)
    }
}

pub struct RedisTestContext {
    pub config: RedisConfig,
    pub redis: RedisClient,
}

impl AsyncTestContext for RedisTestContext {
    async fn setup() -> Self {
        info!("setup redis config for the test");
        // let database_name = util::string::generate_random_string_with_prefix("test_db");
        let redis = RedisClient::build_from_config(&CONFIG).unwrap();
        Self {
            config: CONFIG.redis.clone(),
            redis,
        }
    }
}

impl RedisClientExt for Client {
    async fn ping(&self) -> Result<Option<String>, RedisError> {
        let mut conn = self.get_multiplexed_async_connection().await?;
        let value: Option<String> = redis::cmd("PING").query_async(&mut conn).await?;
        info!("ping redis server");
        Ok(value)
    }

    async fn set(&self, key: &str, value: &str, expire: Duration) -> Result<(), RedisError> {
        let mut conn = self.get_multiplexed_async_connection().await?;
        let msg: String = redis::cmd("SET")
            .arg(&[key, value])
            .query_async(&mut conn)
            .await?;
        info!("set key redis: {msg}");
        let msg: i32 = redis::cmd("EXPIRE")
            .arg(&[key, &expire.as_secs().to_string()])
            .query_async(&mut conn)
            .await?;
        info!("set expire time redis: {msg}");
        Ok(())
    }

    async fn exist(&self, key: &str) -> Result<bool, RedisError> {
        let mut conn = self.get_multiplexed_async_connection().await?;
        let value: bool = redis::cmd("EXISTS").arg(key).query_async(&mut conn).await?;
        info!("check key exists: {key}");
        Ok(value)
    }

    async fn get(&self, key: &str) -> Result<Option<String>, RedisError> {
        let mut conn = self.get_multiplexed_async_connection().await?;
        let value: Option<String> = redis::cmd("GET").arg(key).query_async(&mut conn).await?;
        info!("get value: {key}");
        Ok(value)
    }
    async fn del(&self, key: &str) -> Result<bool, RedisError> {
        let mut conn = self.get_multiplexed_async_connection().await?;
        let value: i32 = redis::cmd("DEL").arg(key).query_async(&mut conn).await?;
        info!("delete value: {key}");
        Ok(value == 1)
    }
    async fn ttl(&self, key: &str) -> Result<i64, RedisError> {
        let mut conn = self.get_multiplexed_async_connection().await?;
        let value: i64 = redis::cmd("TTL").arg(key).query_async(&mut conn).await?;
        info!("get TTL value: {key}");
        Ok(value)
    }
}

#[cfg(test)]
mod tests {
    use crate::constant::REDIS;

    use super::*;

    use fake::{Fake, Faker};
    use uuid::Uuid;

    #[tokio::test]
    async fn test_ping_redis_server() {
        let resp = REDIS.ping().await.unwrap();
        let pong = "PONG";
        assert!(matches!(resp, Some(p) if p == pong));
    }

    #[tokio::test]
    async fn test_set_key_redis() {
        let key: String = Faker.fake();
        let value = Uuid::new_v4().to_string();
        REDIS
            .set(&key, &value, Duration::from_secs(5))
            .await
            .unwrap();
        let resp = REDIS.get(&key).await.unwrap();
        assert!(matches!(resp, Some(v) if v == value));
        let resp = REDIS.ttl(&key).await.unwrap();
        assert!(resp > 0);
    }

    #[tokio::test]
    async fn test_exist_key_redis() {
        let key: String = Faker.fake();
        let value = Uuid::new_v4().to_string();
        REDIS
            .set(&key, &value, Duration::from_secs(4))
            .await
            .unwrap();
        let resp = REDIS.get(&key).await.unwrap();
        assert!(matches!(resp, Some(v) if v == value));
        let resp = REDIS.exist(&key).await.unwrap();
        assert!(resp);
        let key: String = Faker.fake();
        let resp = REDIS.exist(&key).await.unwrap();
        assert!(!resp);
    }

    #[tokio::test]
    async fn test_del_key_redis() {
        let key: String = Faker.fake();
        let value = Uuid::new_v4().to_string();
        REDIS
            .set(&key, &value, Duration::from_secs(4))
            .await
            .unwrap();
        let resp = REDIS.get(&key).await.unwrap();
        assert!(matches!(resp, Some(v) if v == value));
        let resp = REDIS.exist(&key).await.unwrap();
        assert!(resp);
        REDIS.del(&key).await.unwrap();
        let resp = REDIS.exist(&key).await.unwrap();
        assert!(!resp);
    }

    #[tokio::test]
    async fn test_key_ttl_redis() {
        let key: String = Faker.fake();
        let ttl = 4;
        let value = Uuid::new_v4().to_string();
        REDIS
            .set(&key, &value, Duration::from_secs(ttl))
            .await
            .unwrap();
        let resp = REDIS.get(&key).await.unwrap();
        assert!(matches!(resp, Some(v) if v == value));
        let resp = REDIS.ttl(&key).await.unwrap();
        assert!(resp <= ttl as i64 && resp > 0);
        REDIS.del(&key).await.unwrap();
        let resp = REDIS.ttl(&key).await.unwrap();
        assert!(resp < 0);
    }
}

数据库客户端client\database.rs

use std::time::Duration;
use common::error::InfraError;
use sea_orm::{ConnectOptions, Database, DatabaseConnection};
use tracing::info;
use crate::config::AppConfig;

// 类型别名
pub type DatabaseClient = DatabaseConnection;

pub trait DatabaseClientExt: Sized {
    fn build_from_config(config: &AppConfig) -> impl std::future::Future<Output = Result<Self,InfraError>>;
}

impl DatabaseClientExt for DatabaseClient {
    async fn build_from_config(config: &AppConfig) -> Result<Self,InfraError> {
        let mut opt = ConnectOptions::new(config.db.get_url());
        opt.max_connections(100)
            .min_connections(5)
            .connect_timeout(Duration::from_secs(8))
            .acquire_timeout(Duration::from_secs(8))
            .idle_timeout(Duration::from_secs(8))
            .max_lifetime(Duration::from_secs(8))
            .sqlx_logging(false)
            .sqlx_logging_level(log::LevelFilter::Info);
        let db = Database::connect(opt).await?;
        info!("Database connected");
        Ok(db)
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    use crate::constant::CONFIG;

    #[tokio::test]
    async fn test_ping_database() {
        DatabaseClient::build_from_config(&CONFIG).await
            .unwrap()
            .ping().await
            .expect("Database ping failed.")
    }
}

日志客户端logger\log.rs

use std::env;
use tracing::dispatcher::set_global_default;
use tracing_appender::rolling::daily;
use tracing_subscriber::{ fmt::{ self, time::UtcTime }, layer::SubscriberExt, EnvFilter, Registry };
pub struct LogGuard(pub std::sync::Arc<tracing_appender::non_blocking::WorkerGuard>);
pub async fn setup_logs(log_level: Option<String>) -> LogGuard {
    // 读取日志级别
    let log_level = log_level.unwrap_or_else(|| "debug".to_string());
    // 设置日志级别过滤器
    let env_filter = EnvFilter::try_from_default_env()
        .or_else(|_| EnvFilter::try_new(log_level))
        .unwrap_or_else(|_| format!("{}=debug", env!("CARGO_CRATE_NAME")).into());
    // 创建每日滚动的日志文件写入器
    let file_appender = daily("logs", "app.log");
    let (non_blocking_appender, guard) = tracing_appender::non_blocking(file_appender);
    // 初始化日志订阅者,同时输出到控制台和文件
    let subscriber = Registry::default()
        .with(env_filter)
        // 控制台输出层
        .with(fmt::layer().with_ansi(true).with_target(false))
        // 文件输出层
        .with(fmt::Layer::new().with_writer(non_blocking_appender).with_timer(UtcTime::rfc_3339()));
    // 设置为全局日志订阅者
    set_global_default(subscriber.into()).expect("setting default subscriber failed");
    LogGuard(std::sync::Arc::new(guard))
}

构建客户端client\builder.rs

use crate::config::AppConfig;
// 传输配置文件到客户端
pub trait ClientBuilder: Sized {
    fn build_from_config(config: &AppConfig) -> Result<Self,InfraError>;
}

3、使用AppState存储配置和数据库链接

AppStatestate\mod.rs

use std::sync::Arc;
use anyhow::Error;
use infrastructure::{client::{builder::ClientBuilder, database::{DatabaseClient, DatabaseClientExt}, redis::RedisClient}, config::AppConfig};
// 使用Arc来共享数据,避免数据的复制和所有权的转移
#[derive(Clone)]
pub struct AppState {
  pub config: Arc<AppConfig>,
  pub redis: Arc<RedisClient>,
  pub db: Arc<DatabaseClient>,
}

impl AppState {
  pub async fn new(config: AppConfig) -> Result<Self,Error> {
    let redis = Arc::new(RedisClient::build_from_config(&config)?);
    let db = Arc::new(DatabaseClient::build_from_config(&config).await?);
    Ok(Self {
      config: Arc::new(config),
      db,
      redis,
    })
  }
}

使用AppState

use application::dto::response_dto::{ EmptyData, Res };
use axum::{ http::{ HeaderValue, Method }, response::IntoResponse, Router };
use infrastructure::{config::{env::get_env_source, AppConfig}, logger::log};
use tokio::signal;
use tower_http::cors::CorsLayer;
use tracing::info;
use crate::state::AppState;

pub async fn start() -> anyhow::Result<()> {
    // 加载.env 环境配置文件,成功返回包含的值,失败返回None
    dotenvy::dotenv().ok();
    // 加载AppState
    let config = AppConfig::read(get_env_source("APP"))?;
    let state = AppState::new(config.clone()).await?;
    info!("The initialization of Settings was successful");
    // 初始化日志
    let guard = log::setup_logs(Some(config.tracing.get_log_level())).await;
    info!("The initialization of Tracing was successful");
    // 路由以及后备处理
    let app = setup_routes().await.fallback(handler_404).with_state(state);
    // 端口绑定
    let listener = tokio::net::TcpListener::bind(config.server.get_socket_addr()?).await.unwrap();
    // 调用 `tracing` 包的 `info!`,放在启动服务之前,因为会被move
    info!("🚀 listening on {}", &listener.local_addr().unwrap());
    // 启动服务
    axum::serve(listener, app.into_make_service())
        .with_graceful_shutdown(shutdown_signal()).await
        .unwrap();
    // 在程序结束前释放资源
    drop(guard);
    Ok(())
}

/// 嵌套路由
pub async fn setup_routes() -> Router<AppState> {
    Router::new()
        // .nest("/users", setup_user_routes().await)
        //请注意,对于某些请求类型,例如发布content-style:app/json
        //需要添加“.allow_heads([http::header::CONTENT_GROUP])”
        .layer(
            CorsLayer::new()
                .allow_origin("http://localhost:3000".parse::<HeaderValue>().unwrap())
                .allow_methods([Method::GET, Method::POST, Method::PUT, Method::DELETE])
        )
}

/// 404处理
async fn handler_404() -> impl IntoResponse {
    Res::<EmptyData>::with_not_found()
}
/// 优雅关闭
async fn shutdown_signal() {
    let ctrl_c = async {
        signal::ctrl_c().await.expect("failed to install Ctrl+C handler");
    };

    #[cfg(unix)]
    let terminate = async {
        signal::unix
            ::signal(signal::unix::SignalKind::terminate())
            .expect("failed to install signal handler")
            .recv().await;
    };

    #[cfg(not(unix))]
    let terminate = std::future::pending::<()>();

    tokio::select! {
        _ = ctrl_c => {},
        _ = terminate => {},
    }

    println!("signal received, starting graceful shutdown");
}

项目地址:https://github.com/VCCICCV/MGR/tree/main/auth

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2221821.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

每天练打字8:今日状况——常用字后五百击键4.5第1遍进行中,赛文速度105.75

今日跟打&#xff1a;738字 总跟打&#xff1a;125701字 记录天数&#xff1a;2459天 &#xff08;实际没有这么多天&#xff0c;这个是注册账号的天数&#xff09; 平均每天&#xff1a;50字 本周目标完成进度&#xff1a; 练习常用单字后500&#xff0c;击键3.5&#xff0c;…

kernel32.dll的功能、作用,教大家几种修复kernel32.dll错误的办法

当这个文件出现问题时&#xff0c;用户可能会遇到各种错误消息&#xff0c;例如“缺失kernel32.dll”或“kernel32.dll发生错误”。这些错误不仅令人困扰&#xff0c;还可能威胁到您的数据安全和系统性能。接下来&#xff0c;本文将教大家几种修复kernel32.dll错误的有效方法&a…

群控系统服务端开发模式-业务流程图补充

进天有读者给我反馈&#xff0c;业务流程图看的不是很明确&#xff0c;所以我把未画完的业务流程图补充完毕。也希望以后更多的读者给我评论及意见。 一、业务流程梳理 1、非业务流程 a、添加部门、添加级别、添加执行方式。因为这些参数都是要被其他地方调用的&#xff0c;更…

word中的内容旋转90度

在vsto、Aspose.Words 中&#xff0c;默认没有直接的 API 可以让表格整体旋转 90 度。然而&#xff0c;我们可以通过一些方式来实现类似的效果&#xff0c;具体思路如下&#xff1a; 将表格插入到一个形状&#xff08;Shape&#xff09;或文本框中&#xff0c;然后旋转该形状。…

影刀RPA实战番外:excel函数应用指南

Excel函数是用于执行特定计算、分析和数据处理任务的预定义公式。它们可处理数学计算、文本处理、逻辑判断、日期和时间运算、查找和引用数据等。例如&#xff0c;SUM函数可以计算一系列数字的总和&#xff0c;IF函数进行逻辑测试&#xff0c;VLOOKUP函数在表格中查找数据&…

Oracle CONNECT BY、PRIOR和START WITH关键字详解

Oracle CONNECT BY、PRIOR和START WITH关键字详解 1. 基本概念2. 数据示例3. SQL示例3.1. 查询所有员工及其上级3.2. 显示层次结构3.3. 查询特定员工的子级 4. 结论 在Oracle数据库中&#xff0c;CONNECT BY、PRIOR和START WITH关键字主要用于处理层次结构数据&#xff0c;例如…

我悟了,华为FreeBuds 6i这样戴更稳了!

谁懂啊&#xff0c;才知道华为FreeBuds 6i标配多种尺寸的耳塞&#xff08;如S、M、L&#xff09;&#xff0c;方便大家根据自己耳道大小自由选择。如果耳机戴上后感到耳朵不适或松动&#xff0c;可能是耳塞尺寸不匹配。 小提示&#xff1a; 耳塞过大 可能会造成压迫感&#xf…

kafka自定义配置信息踩坑

org.apache.kafka.common.config.ConfigException: Invalid value 0 for configuration acks: Expected value to be a string, but it was a java.lang.Integer 场景描述&#xff1a; 单个kafka使用springboot框架自带的 yml 配置完全OK&#xff08;因为底层会帮我们处理好类…

CSS 设置网页的背景图片

背景 最近正好在写一个个人博客网站“小石潭记”&#xff0c;需要一张有水&#xff0c;有鱼的图片。正好玩原神遇到了类似场景&#xff0c;于是截图保存&#xff0c;添加到网站里面。以下是效果图&#xff1a; css 写个class&#xff0c;加到整个网页的body上 .bodyBg {ba…

接口测试 —— 如何测试加密接口?

接口加密是指在网络传输过程中&#xff0c;将数据进行加密&#xff0c;以保护数据的安全性。接口加密可以采用多种加密算法&#xff0c;如AES、DES、RSA等。测试接口加密的目的是验证接口加密算法的正确性和安全性。以下是一些详细的测试方法和注意事项&#xff1a; 接口加密字…

A-【项目开发知识管理】Android AIDL跨进程通信

Android AIDL跨进程通信 文章目录 Android AIDL跨进程通信0.我为啥要写这篇文章1.AIDL是干啥的&#xff1f;1.1简述1.2官方话 2.在AndroidStudio中怎么干&#xff1f;2.1准备工作2.2在项目A中创建AIDL文件夹2.3在项目A中创建一个aidl文件2.4将项目A进行一次Rebuild操作2.5在项目…

计算机专业大学四年的学习路线(非常详细),零基础入门到精通,看这一篇就够了

前言 许多学子选择踏上计算机这条充满挑战与机遇的道路。但在大学四年中&#xff0c;如何规划自己的学习路线&#xff0c;才能在毕业时脱颖而出&#xff0c;成为行业的佼佼者呢&#xff1f; 第一学年&#xff1a;基础知识的奠基 1.1 课程安排 在大学的第一年&#xff0c;重…

超详解C++类与对象(下)

目录 1. 初始化列表 1.1. 定义 2.2. 注意 2. 隐式类型转换 2.1. 内置类型 2.2. 自定义类型 2.3. explicit关键字 3.类的静态成员 2.1. 定义 2.2. 注意 4.const成员函数 5. 友元 5. 1友元函数 5.2. 友元类 6. 内部类 6.1. 定义 6.2. 注意 7. 匿名对象 7…

手撕布隆过滤器:原理解析与面试心得

前言 说来话长&#xff0c;话来说长。前些天我投了一些日常实习的简历&#xff0c;结果足足等了两个礼拜才收到面试通知&#xff0c;看来如今的行情确实是挺紧张的。当时我是满怀信心去的&#xff0c;心想这次一定要好好拷打面试官一番&#xff0c;结果没想到&#xff0c;自我…

一、python基础

python基础 认识Python1. Python介绍1.1 为什么学习Python1.2 Python发展历史 2. 语言分类简介2.1 编译型2.2 解释型 Python环境搭建1. Python 解释器1.1 Python解释器下载1.2 Python解释器安装 2. 解释器运行Python脚本2.1 演练步骤 PyCharm1. PyCharm介绍2. PyCharm安装3. Py…

15分钟学Go 第6天:变量与常量

第6天&#xff1a;变量与常量 在Go语言中&#xff0c;变量和常量是编程的基础概念。理解如何定义和使用它们不仅能帮助我们管理数据&#xff0c;还能增强代码的可读性和可维护性。在本章中&#xff0c;我们将详细探讨Go语言中的变量和常量&#xff0c;涵盖它们的定义、使用、作…

机器学习建模分析

机器学习 5.1 机器学习概述5.1.1 机器学习与人工智能5.1.2 python机器学习方法库 5.2 回归分析5.2.1 回归分析原理5.2.2 回归分析实现 5.3 分类分析5.3.1 分类学习原理5.3.2 决策树5.5.3 支持向量机 5.4 聚类分析5.4.1 聚类任务5.4.2 K-means算法 5.5 神经网络和深度学习5.5.1神…

python配合yolov11开发分类训练软件

上一篇文件写了用yolo分类模型开发分类软件&#xff0c;这边文章在上个分类软件的基础上加入训练功能环境配置:pycharm&#xff0c;PySide6 6.6.1 &#xff0c;PySide6-Addons 6.6.1&#xff0c;PySide6-Essentials 6.6.1&#xff0c;torch 2.3.1cu121&#xff0c;torchaudio 2…

dynadot设置域名动态DNS(DDNS)

需求&#xff1a;本地测试代理&#xff0c;代理需要绑定IP或者域名&#xff0c;本地IP是动态变化的&#xff0c;解决办法就是给域名设置动态DNS 1.dynadot设置 开启动态DNS选项会显示动态DNS密码&#xff0c;该密码后续将会用在DDNS-GO工具上 2.DDNS-GO设置 GitHub介绍页面&a…

WIFI、NBIOT、4G模块调试AT指令连接华为云物联网服务器(MQTT协议)

一、前言 随着物联网&#xff08;IoT&#xff09;技术的飞速发展&#xff0c;越来越多的设备开始连接到互联网&#xff0c;形成了一个万物互联的世界。在这个背景下&#xff0c;设备与云端之间的通讯变得尤为重要。 本文将探讨几种常见的无线通信模块——EC20-4G、Air724ug-4…