Rust实战(四):数据持久化、告警配置与Web API —— 构建监控系统的功能闭环

关于本Rust实战教程系列:

  • 定位:一份从Rust基础迈向项目实战的练习记录。
  • 内容:聚焦实战中遇到的具体问题、解决思路与心得总结。
  • 读者 :适合有Rust基础,但急需项目经验巩固知识的开发者。资深玩家可忽略
  • 计划:本期为系列第四篇,将根据我的学习进度持续更新。
  • 导航 :往期文章可以直接查看专栏:Rust实践课程

简要说明

在前两篇关于监控系统的文章中,我们实现了从配置文件中读取监控目标、调度监控引擎执行检查,并通过 println! 直接输出结果的基础流程。而在本篇中,我们将进一步推进系统的完整性与实用性,重点实现以下三个企业级功能:

  • 数据持久化:将监控结果存储至数据库,实现历史记录查询与分析;
  • 告警配置:建立灵活的告警规则机制,及时通知异常状态;
  • Web API:提供标准化的接口,便于前端或其他服务调用数据。

这也意味着,我们的监控系统将初步具备可视化与可集成的能力。至于前端页面本身,考虑到其内容较为独立且篇幅较大,本次将不展开实现,留给有兴趣深入前端实践的读者来自行设计与开发。

需求拆解&&设计方案:

数据持久化

  • 对接数据库,实现数据持久化存储:使用SQLite作为存储后端
  • 使用 Diesel 建立数据库连接和模型
  • 实现对数据的CRUD操作

告警配置

  • 基于规则引擎对每条监控结果进行实时评估
  • 支持规则类型:
    • status:当结果status=false
    • response_time: 当响应/总耗时大于阈值
    • status_code: 当响应码属于某个集合
    • body_not_contains: 当响应体不包含关键字
  • 规则和告警记录均存入数据库,可扩展外部通知(email、webhook)

Web API

  • 使用Axum提供REST接口:
    • /api/monitors: 监控配置CRUD
    • /api/results: 结果查询(分页/按monitor_id)
    • /api/alerts: 告警规则 CRUD
    • /api/alert-events: 告警事件查询

实现步骤

1.数据持久化

在数据持久化部分,我们首先需要设计合理的表结构。基于此前已实现的监控逻辑,系统主要涉及以下四张核心数据表:

  • monitors_config :监控配置表,用于存储从 monitor_list.json 中读取并标准化后的监控对象配置;
  • monitor_results :监控结果记录表,其结构对应于代码中 monitor::types::CheckResult 的映射;
  • alert_rules:告警规则配置表,用于定义各类监控指标的异常触发条件;
  • alert_events:告警事件记录表,实现对告警触发过程的完整留痕。

在数据库交互方面,我们将采用 Diesel 作为 ORM 框架。该选择使我们能够更专注于业务逻辑的实现,而非底层 SQL 的编写与维护,从而有效降低数据层交互的复杂度,提升开发效率与代码可维护性。

1.1 数据库表设计

  • monitors_config 监控配置表
sql 复制代码
--监控配置表
CREATE TABLE monitor_config (
    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
    name TEXT UNIQUE,
    target TEXT NOT NULL,
    method TEXT,
    monitor_type TEXT NOT NULL CHECK (monitor_type IN ('HTTP', 'TCP', 'ICMP', 'DNS','CPU', 'DISK', 'MEMORY', 'PROCESS')),
    interval_ms INTEGER,
    timeout_ms INTEGER NOT NULL DEFAULT 5000,
    config_json TEXT,
    enabled INTEGER NOT NULL DEFAULT 1,
    tag TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
  • monitor_results 监控结果记录表
sql 复制代码
-- 检查结果表
CREATE TABLE check_result (
    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
    monitor_id INTEGER NOT NULL REFERENCES monitor_config(id) ON DELETE CASCADE,
    monitor_type TEXT NOT NULL,
    status INTEGER NOT NULL,
    response_time INTEGER NOT NULL,
    metadata_json TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

1.2 Diesel集成

我们参考 Diesel 官方教程 中提供的指导,按照其推荐的步骤逐一实现数据库相关功能。

首先,让我们将 Diesel 添加到我们的依赖项中。我们还将使用一个名为 .env 的工具来管理我们的环境变量。
rust 复制代码
// cargo.toml
# 数据库操作
diesel = { version = "2.3.3", features = ["chrono", "serde_json", "sqlite"] }

// .env环境配置文件
DATABASE_URL=./db/monitor.db 
安装 Diesel 命令 (CLI)
rust 复制代码
// 设置只支持sqlite数据库
cargo install diesel_cli --no-default-features --features sqlite  
执行 diesel setup进行初始化数据库操作

因为我们创建了一个环境变量文件 .env,所以这个操作将会按照我们在这个环境变量文件中定义的路径生成monitor.db数据库

执行diesel migration generate create_monitor_tables生成数据库迁移文件,可以版本化我们数据库的操作,方便团队协作和生产环境部署操作

此时会在src/migrations/目录下面创建两个sql空文件:

rust 复制代码
|-src
 |-migrations
   |-2025-11-17-051840-0000_create_monitor_tables
     |-up.sql
     |-down.sql       

这样我们就可以把我们的创建表的SQL语句放到up.sql文件中,然后数据库表会滚的操作放到down.sql中就可以了

rust 复制代码
// up.sql
-- Your SQL goes here
--监控配置表
CREATE TABLE monitor_config (
    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
    name TEXT UNIQUE,
    target TEXT NOT NULL,
    method TEXT,
    monitor_type TEXT NOT NULL CHECK (monitor_type IN ('HTTP', 'TCP', 'ICMP', 'DNS','CPU', 'DISK', 'MEMORY', 'PROCESS')),
    interval_ms INTEGER,
    timeout_ms INTEGER NOT NULL DEFAULT 5000,
    config_json TEXT,
    enabled INTEGER NOT NULL DEFAULT 1,
    tag TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 检查结果表
CREATE TABLE check_result (
    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
    monitor_id INTEGER NOT NULL REFERENCES monitor_config(id) ON DELETE CASCADE,
    monitor_type TEXT NOT NULL,
    status INTEGER NOT NULL,
    response_time INTEGER NOT NULL,
    metadata_json TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);





-- 索引创建
CREATE INDEX idx_monitor_config_enabled ON monitor_config(enabled); -- SELECT * FROM monitor_config WHERE enabled = 1;
-- 启用/禁用筛选 + 按 id 逆序分页
CREATE INDEX IF NOT EXISTS idx_monitor_config_enabled_id ON monitor_config(enabled, id DESC); -- SELECT * FROM monitor_config WHERE enabled = 1 ORDER BY id DESC LIMIT 10 OFFSET 20;
-- 目标精确/前缀搜索
CREATE INDEX IF NOT EXISTS idx_monitor_config_target ON monitor_config(target); -- SELECT * FROM monitor_config WHERE target LIKE 'http%';
-- 标签过滤
CREATE INDEX IF NOT EXISTS idx_monitor_config_tag ON monitor_config(tag); -- SELECT * FROM monitor_config WHERE tag = 'production';
CREATE INDEX idx_check_result_monitor_id ON check_result(monitor_id); -- SELECT * FROM check_result WHERE monitor_id = 1;

-- 触发器创建
CREATE TRIGGER trg_monitor_config_updated_at
AFTER UPDATE ON monitor_config
WHEN NEW.updated_at = OLD.updated_at
BEGIN
  UPDATE monitor_config SET updated_at = CURRENT_TIMESTAMP WHERE id = OLD.id;
END;

CREATE TRIGGER trg_check_result_updated_at
AFTER UPDATE ON check_result
WHEN NEW.updated_at = OLD.updated_at
BEGIN
  UPDATE check_result SET updated_at = CURRENT_TIMESTAMP WHERE id = OLD.id;
END;
rust 复制代码
// down.sql
-- 数据库回滚
DROP TABLE check_result;
DROP TABLE monitor_config;

最后执行 diesel migration run即可执行up.sql中的数据库中建表操作,也可以通过执行diesel migration redo来查看执行down.sql的效果

1.3 数据库交互逻辑

在完成数据库表结构设计并通过 Diesel 建立数据库之后,我们接下来将正式开始业务逻辑的编写。第一步是实现数据库连接------我们将采用连接池(Connection Pool) 的方式来管理数据库连接,以实现连接的复用与性能优化,从而提升系统在高并发场景下的处理能力。

1.3.1 连接数据库:

我们将采用连接池来管理数据库连接,并结合 Arc(原子引用计数)实现在多线程环境下的安全共享。在此基础上,还将引入相应的重试机制,共同构建稳定可靠的底层数据库连接层,确保系统在高并发场景下的数据访问稳定性。

rust 复制代码
src/database/connect_db.rs
// 数据库链接方法 
// 定义链接池返回的数据类型
pub type SqlitePool = Pool<ConnectionManager<SqliteConnection>>;
pub type SqlitePooledConnection = PooledConnection<ConnectionManager<SqliteConnection>>;
// 使用diesel_migrations 方便进行数据的迁移
pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!(); // 默认 查找与cargo.toml同级目录下的migrations文件夹

pub fn establish_connection() ->Result<SqlitePool, Box<dyn std::error::Error>> {
    let database_url = env::var("DATABASE_URL").unwrap_or_else(|_| "./db/monitor.db".to_string());
    let manager = ConnectionManager::<SqliteConnection>::new(database_url);
    let pool  = Pool::builder()
        .max_size(8) // 设置最大链接数
        .connection_timeout(std::time::Duration::from_secs(30)) // 设置超时时间
        .build(manager)
        .map_err(|e| format!("Failed to create DB pool: {}", e))?;
    let mut conn =   pool.get().map_err(|e| format!("Failed to get DB connection from pool: {}", e))?;
    conn.run_pending_migrations(MIGRATIONS).map_err(|e| format!("Failed to run database migrations: {}", e))?;
    Ok(pool)
}

// 定义一个重试的方法
pub async fn establish_database_connection() -> Result<Arc<SqlitePool>, Box<dyn std::error::Error>> {
    // 引入 backon 库 来实现链接方法重试的逻辑  都是通用的逻辑 
    let pool = (|| async { establish_connection() }) // 把需要重试的内容 封装成一个闭包函数
        .retry(default_retry_policy()) // 使用默认的重试函数
        .await
        .map(Arc::new) // 使用Arc进行封装 方便进行线程间共享
        .map_err(|e| {
            eprintln!("数据库连接重试失败: {}", e);
            e
        })?;
    Ok(pool)
}

// 定义获取链接方法 方便业务层获取统一的链接池
pub fn get_connection(pool: &SqlitePool) -> SqlitePooledConnection {
    pool.get().expect("Failed to get connection from pool.")
}
1.3.2 定义数据库对应的Model层结构体

在实现数据库操作之前,我们需要先定义与数据库表字段一一对应的结构体。这种映射关系确保了数据在Rust程序与数据库之间的正确转换,为后续的数据插入、查询等操作奠定基础。

rust 复制代码
#[derive(Queryable, Debug, Clone, Serialize, Deserialize)]
#[diesel(table_name = monitor_config)]
pub struct MonitorConfigModel {
    pub id: i32,
    pub name: Option<String>,
    pub target: String,
    pub method: Option<String>,
    pub monitor_type: String,
    pub interval_ms: Option<i32>,
    pub timeout_ms: i32,
    pub config_json: Option<String>,
    pub enabled: i32,
    pub tag: Option<String>,
    pub created_at: Option<NaiveDateTime>,
    pub updated_at: Option<NaiveDateTime>,
}

// 更新用结构(不含 id / created_at / updated_at)
#[derive(Debug, Clone, Serialize, Deserialize, AsChangeset)]
#[diesel(table_name = monitor_config)]
pub struct MonitorConfigUpdate {
    pub name: Option<String>,
    pub target: Option<String>,
    pub method: Option<String>,
    pub monitor_type: Option<String>,
    pub interval_ms: Option<i32>,
    pub timeout_ms: Option<i32>,
    pub config_json: Option<String>,
    pub enabled: Option<i32>,
    pub tag: Option<String>,
}
// 新增用结构体 (不包含ID ,updated_at) )
#[derive(Insertable, Debug, Clone, Serialize, Deserialize)]
#[diesel(table_name = monitor_config)]
pub struct MonitorConfigInsert {
    pub name: Option<String>,
    pub target: String,
    pub method: Option<String>,
    pub monitor_type: String,
    pub interval_ms: Option<i32>,
    pub timeout_ms: i32,
    pub config_json: Option<String>,
    pub enabled: i32,
    pub tag: Option<String>,
}

#[derive(Insertable, Debug, Clone, Serialize, Deserialize, Selectable, Queryable)]
#[diesel(table_name = check_result)]
pub struct CheckResultModel {
    pub id: i32,
    pub monitor_id: i32,
    pub monitor_type: String,
    pub status: i32,
    pub response_time: i32,
    pub metadata_json: Option<String>,
    pub created_at: Option<NaiveDateTime>,
    pub updated_at: Option<NaiveDateTime>,
}

// 插入监控结果数据
#[derive(Insertable, Debug, Clone, Serialize, Deserialize)]
#[diesel(table_name = check_result)]
pub struct CheckResultModelInsert {
    pub monitor_id: i32,
    pub monitor_type: String,
    pub status: i32,
    pub response_time: i32,
    pub metadata_json: Option<String>,
}

同时还需要使用diesel工具来生成对应的schema,来供上面的结构体使用:

rust 复制代码
src/database/schema.rs

diesel::table! {
    check_result (id) {
        id -> Integer,
        monitor_id -> Integer,
        monitor_type -> Text,
        status -> Integer,
        response_time -> Integer,
        metadata_json -> Nullable<Text>,
        created_at -> Nullable<Timestamp>,
        updated_at -> Nullable<Timestamp>,
    }
}

diesel::table! {
    monitor_config (id) {
        id -> Integer,
        name -> Nullable<Text>,
        target -> Text,
        method -> Nullable<Text>,
        monitor_type -> Text,
        interval_ms -> Nullable<Integer>,
        timeout_ms -> Integer,
        config_json -> Nullable<Text>,
        enabled -> Integer,
        tag -> Nullable<Text>,
        created_at -> Nullable<Timestamp>,
        updated_at -> Nullable<Timestamp>,
    }
}

我们采用分层架构来组织数据库操作,整体设计如下:

API层Service层Repository层数据库(SQLite)

其中:

  • Repository层 封装所有数据访问细节,提供统一的数据库操作接口
  • Service层 负责组合各类数据操作,实现具体的业务逻辑流程
  • API层 专注于HTTP请求的接收与响应,保持边界的清晰性

这种分层设计确保了各层级职责分离,既提升了代码的可维护性,也为后续功能扩展奠定了良好基础。

1.3.3 Repository层 && 和Service层逻辑实现
rust 复制代码
// src/database/repositories/monitor_repo.rs
use crate::database::connect_db::{SqlitePool, get_connection};
use crate::database::models::{MonitorConfigModel};
use crate::database::schema::{monitor_config};

pub struct MonitorRepository {
    pool: Arc<SqlitePool>,
}

impl MonitorRepository{
    pub fn new(pool: Arc<SqlitePool>) -> Self {
        MonitorRepository { pool }
    }
    pub fn get_monitors_by_enabled(&self, enabled_flag:bool, page: i64, page_size: i64)-> Result<(Vec<MonitorConfigModel>, i64), diesel::result::Error> {
        // 获取当前对数据库的链接
        let mut conn = get_connection(&self.pool);
        let page_no = page.max(1);
        let page_sz = page_size.max(1);
        let offset = (page_no -1) * page_sz;
        let enabled_v = if enabled_flag {1} else {0};

        let total:i64 = monitor_config::table
            .filter(monitor_config::enabled.eq(enabled_v))
            .count()
            .get_result(&mut conn)?;
        // 执行数据库操作
       let results =  monitor_config::table
            .filter(monitor_config::enabled.eq(enabled_v))
            .order(monitor_config::id.desc())
            .limit(page_sz)
            .offset(offset)
            .load::<MonitorConfigModel>(&mut conn)?;
        Ok((results, total))
    }
}

我们再看下Service层中的逻辑,其实是类似的结构,只不过是提供不同的实现对象而已

rust 复制代码
// src/database/services/monitor_service.rs
pub struct MonitorService {
    // 存储对Repo层对象的引用
    repo: Arc<MonitorRepository>,
}
impl MonitorService {
    pub fn new(repo: MonitorRepository) -> Self {
        MonitorService {
            repo: Arc::new(repo),

        }
    }
    // 这里的话 我们没有特殊的逻辑 所以比较简单
    // 如果后续 我们把告警规则、告警配置单独拎出来的话 对应的业务逻辑就需要在这里进行处理
    pub fn get_monitors_by_enabled(&self, enabled_flag:bool, page: i64, page_size: i64)-> Result<(Vec<MonitorConfigModel>, i64), diesel::result::Error> {
        self.repo.get_monitors_by_enabled(enabled_flag, page, page_size)
    }
}

其他的表check_result的逻辑基本上是一样的,我们就不再这里写了。这样的的话我们就已经实现了Repository层以及Service层的逻辑了,下面的话我们来看下最上层 - API层的实现

1.3.4 API层的设计与实现

我们将通过 actix_web 框架来实现面向 Web 前端的 API 接口层。具体实施分为两步:首先完成路由的系统化配置,明确各端点与处理函数的映射关系;随后基于此启动并运行相应的 HTTP 服务。

rust 复制代码
use actix_web::web;
// 定义最基础的增删改查的逻辑即可
pub fn config(cfg: &mut web::ServiceConfig) {
    print!("配置 API 路由...");
    cfg.service(
        web::scope("/api")
            .route("/monitors", web::get().to(crate::api::handlers::monitor_handlers::get_all_monitors))
            .route("/monitors", web::post().to(crate::api::handlers::monitor_handlers::insert_monitors))
            .route("/monitors/{id}", web::get().to(crate::api::handlers::monitor_handlers::get_monitors_by_id))
            .route("/monitors/{id}", web::put().to(crate::api::handlers::monitor_handlers::update_monitors_by_id))
            .route("/monitors/{id}", web::delete().to(crate::api::handlers::monitor_handlers::delete_monitors_by_id))
            .route("/monitors/{id}/results", web::get().to(crate::api::handlers::check_result_handlers::get_check_results_by_monitor_id))
            .route("/", web::get().to(crate::api::handlers::default_handler))
    );
}

我们定义好了路由文件之后,还需要实现具体的每个路由的处理函数:

rust 复制代码
// src/api/handles/monitor_handlers.rs

// 定义统一的返回数据结构
#[derive(Debug, Serialize, Deserialize)]
pub struct DefaultResponseObj<T> {
    code: usize,
    message: String,
    data: T,
}
#[derive(Debug, Deserialize)]
pub struct PaginationParams {
    pub page_no: Option<i64>,
    pub page_size: Option<i64>,
}
// 获取全部的监控配置处理函数
pub async  fn get_all_monitors(
    monitor_service: web::Data<MonitorService>, // 设置统一的业务层的对象,进行相关的数据操作
    query: web::Query<PaginationParams>, // 分页参数对象
) -> Result<HttpResponse, actix_web::Error> {
    let no = query.page_no.unwrap_or(1);
    let size = query.page_size.unwrap_or(20);
    let monitors  = monitor_service.get_monitors_by_enabled(true,no,size)
        .map_err(|e| {
            actix_web::error::ErrorInternalServerError(format!("Failed to get monitors: {}", e))
        })?;
    Ok(HttpResponse::Ok().json(DefaultResponseObj {
        code: 200,
        message: "OK".to_string(),
        data: monitors.0,
    }))
}
// 其他的handler函数也是类似的就不在这里一一展示了,大家可以自行补充
...

2.告警配置

我们的告警引擎将优先实现飞书机器人消息通知功能,其他告警渠道的实现逻辑与此类似。在规则设计方面,目前主要支持针对接口返回状态码 RESPONSE_CODE 的三种匹配规则:

  • contains(包含)
  • no_contains(不包含)
  • regex(正则匹配)

为简化实现,我们将告警规则配置以序列化形式存储在 monitor_config 表的 config_json 字段中。尽管更规范的做法是将告警规则独立为单独的数据表,但当前方案通过直接序列化配置内容,已能满足基础需求。

以下是所定义的告警规则配置的数据结构:

json 复制代码
// 其实就是我们上一篇中使用到的monitor_list.json中的数据结构 只不过是我们来增加了几个字段而已
{
        "target": "https://www.google.com",
        "monitor_type": "HTTP",
        "method": "GET",
        "params": {},
        "headers": {},
        "content_evaluation_rules": [
            {
                "rule_type": "contains",
                "rule_content": "Google",
                "rule_description": "是否包含自定义内容"
            }
        ],
        "alert_rules": { // 定义监控规则配置项 
            "notify_type": "FEISHU", // 告警类型 FEISHU等
            "notify_config": { // 飞书告警的相关配置
                "webhook_url": "https://open.feishu.cn/open-apis/you/to/path" 
            },
            "rules": [ // 告警规则配置 目前只支持 响应码 告警
                {
                    "rule_type": "RESPONSE_CODE", 
                    "condition": { // 告警条件配置
                        "no_contains": [200, 301, 302],  // 不包含哪些code就告警
                        "contains": [],  // 包含哪些code就告警
                        "regex": "" // 正则匹配哪些code就告警
                    }
                }
            ]

        },
        "timeout": 5000
    },

定义一个告警引擎,提供一个check方法,跟我们的监控引擎类似的逻辑:

rust 复制代码
// src/alerts/mod.rs

pub struct AlertsEngine{
    notify: NotifyEngine, //告警引擎 
    alert_rules: AlertVerificationRules,
}
impl AlertsEngine {
    pub fn new(alert_rule: AlertVerificationRules) -> Self {
        AlertsEngine {
            alert_rules: alert_rule.clone(),
            notify: NotifyEngine::new(alert_rule.notify_type, alert_rule.notify_config),
        }
    }

    // 这里可以添加一些方法,比如添加告警规则、触发告警等
    // 定义一个 告警规则check事件 当监控结果产生时,调用该方法,检查是否需要告警通知
    pub async fn check(&self,  check_result: CheckResult) -> Result<(), String> {
        // 只对成功的监控结果进行告警检查
        if !check_result.status{
            return Ok(());
        }
        // 这里可以根据具体的告警规则进行检查
        match check_result.monitor_type {
            MonitorType::Http => {
                // 调用HTTP监控的检查方法
                Ok(self.check_http(check_result).await)
            },
            _ => {
                // 其他监控类型的检查方法

                Ok(())
            }
        }

    }

    pub async fn check_http(&self, check_result: CheckResult)  {
        // 获取当前config 下面的具体的告警规则
        let alerts_rules= self.alert_rules.rules.clone();
        if alerts_rules.is_empty() {
            return;
        }
        // 遍历规则列表 根据不同的类型 进行不同的提醒
        for single_rule in alerts_rules.iter() {
            match single_rule.rule_type {
                AlertRuleTypes::ResponseCode => {
                    // 检查响应码是否符合告警条件
                    if let CheckResultDetail::Http(ref http_result) = check_result.details {
                        let NotifyCondition{contains, no_contains, regex} = &single_rule.condition;
                        if contains.contains(&http_result.basic_avaliable.res_status_code.map(|code| code as u16).unwrap_or(0)) {
                            // 触发告警
                            self.notify.send_alert_message(format!("Response code {} triggered alert, in contains list: {:?}", http_result.basic_avaliable.res_status_code.map(|code| code as u16).unwrap_or(0), contains)).await;
                            return ;
                        }
                        if !no_contains.contains(&http_result.basic_avaliable.res_status_code.map(|code| code as u16).unwrap_or(0)) {
                            self.notify.send_alert_message(format!("Response code {} triggered alert, not in no_contains list: {:?}", http_result.basic_avaliable.res_status_code.map(|code| code as u16).unwrap_or(0), no_contains)).await;
                            return ;
                        }
                        // 正则匹配
                        let regex_r = Regex::new(&regex).unwrap();
                        // 判断 regex 不为 ""
                        if regex.len() > 0 && regex_r.is_match(&http_result.basic_avaliable.res_status_code.map(|code| code.to_string()).unwrap_or("".to_string())) {
                            // 触发告警
                           self.notify.send_alert_message(format!("Response code {} triggered alert, regex matched: {:?}", http_result.basic_avaliable.res_status_code.map(|code| code as u16).unwrap_or(0), regex)).await;
                            return ;
                        }
                    }
                    return;
                },
                _ => {
                    // 其他规则类型的检查方法

                }
            }

        }

    }
}
pub struct NotifyEngine{
    // 这里可以添加一些配置参数,比如通知方式、接收人等
    notify_type: String, // 通知类型 email sms webhook 等
    notify_config: NotifyConfig, // 通知配置内容 比如邮箱地址 电话号码等
}

impl NotifyEngine {
    pub fn new(notify_type: String, notify_config: NotifyConfig) -> Self {
        NotifyEngine {
            notify_type,
            notify_config,
        }
    }

    // 这里可以添加一些方法,比如发送通知等
    pub async fn send_alert_message(&self, message: String) {
        // 这里可以根据具体的通知方式进行发送
        match self.notify_type.as_str() {
            "EMAIL" => {
                // 发送邮件通知
                println!("Sending email alert...");
            },
            "SMS" => {
                // 发送短信通知
                println!("Sending SMS alert...");
            },
            "FEISHU" => {
                // 发送飞书通知
                println!("Sending Feishu alert... {}", self.notify_config.webhook_url);

                // 发动post 请求 到 webhook_url
                let response = Client::new()
                    .post(self.notify_config.webhook_url.clone())
                    .json(&serde_json::json!({
                        "msg_type": "text",
                        "content": {
                            "text": message
                        }
                    }))
                    .send();
                match response.await {
                    Ok(resp) => {
                        println!("Feishu alert sent successfully: {:?}", resp);
                        match resp.text().await {
                            Ok(_) => println!("Response text: FEISHU" ),
                            Err(e) => println!("Failed to get response text: {:?}", e),
                        }
                    },
                    Err(e) => {
                        println!("Failed to send Feishu alert: {:?}", e);
                    }
                }
            },
            _ => {
                println!("Unknown notify type");
            }
        }
    }
}

至此,我们已经完成了监控系统所有核心模块的实现。尽管部分模块在功能和细节上仍较为基础,可能存在进一步完善的空间,但整体系统已形成功能闭环,具备了完整的监控能力。

现在,我们只需在 main 函数中将各个模块整合起来,启动整个系统即可。

...长舒一口气~~~

3.整合main.rs中的逻辑

话不多说,直接上代码~

rust 复制代码
src/main.rs
#[tokio::main]
async fn main() -> std::io::Result<()> {
    env_logger::init();
    println!("网络监控器 启动...");
    // 初始化数据库 提供重试操作
    let pool = match connect_db::establish_database_connection().await {
        Ok(p) => p,
        Err(e) => {
            eprintln!("Failed to establish database connection: {}", e);
            return Err(std::io::Error::new(std::io::ErrorKind::Other, "Database connection failed"));
        }
    };
    // 初始化 监控配置表
    let monitor_repo: repositories::monitor_repo::MonitorRepository = repositories::monitor_repo::MonitorRepository::new(Arc::clone(&pool));
    let monitor_service = services::monitor_service::MonitorService::new(monitor_repo);

    // 初始化 监控结果
    let check_result_repo = repositories::check_result_repo::CheckResultRepository::new(Arc::clone(&pool));
    let check_result_service = services::check_result_service::CheckResultService::new(check_result_repo);

    // 读取监控配置表的数据
    let monitor_website_list = (|| async { monitor_service.get_monitors_by_enabled(true, 1, 1000) })
    .retry(&default_retry_policy())
    .when(|e| {
        println!("读取监控配置表数据失败: {}", e);
        true
    }).await
    .map_err(|e| {
        eprintln!("Failed to read monitor list after retries: {}", e);
        std::io::Error::new(std::io::ErrorKind::Other, "Read monitor list failed")
    })?;
    
    // let monitor_website_list = read_json_file("monitor_list.json");
    let check_result_service_clone = check_result_service.clone();
    tokio::spawn(async move {
        //上一篇中的创建相关的轮询监控引擎 还是单次监控引擎
        let _ = schedule_monitoring_task(monitor_website_list.0, check_result_service_clone).await;
    });

    // 启动 HTTP服务
    print!("启动 HTTP服务 在0.0.0.0:8080...");
    // 创建HTTP服务,提供对应的接口给用户
    HttpServer::new( move || {
        App::new()
            .app_data(web::Data::new(monitor_service.clone()))
            .app_data(web::Data::new(check_result_service.clone()))
            .configure(api::routes::config)
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

总结

经过各模块的逐步实现,我们的监控系统现已形成完整闭环,主要包含以下功能模块:

🔍 核心监控模块

  • 多协议支持:已实现 HTTP/HTTPS 监控,支持状态码、响应时间等关键指标检测
  • 可扩展架构:基于策略模式设计,预留 TCP、DNS、ICMP 等监控类型的接入能力
  • 异步执行引擎:采用异步任务调度,支持高并发监控任务处理

⚙️ 配置管理模块

  • 配置文件解析:支持从 JSON 格式文件加载监控任务配置
  • 动态配置更新:监控目标与规则可通过配置文件灵活调整
  • 标准化配置结构:统一配置格式,便于批量管理与维护

🗄️ 数据持久化模块

  • 监控结果存储:将每次检查结果持久化至 SQLite 数据库
  • 历史记录查询:支持监控历史数据的检索与分析
  • 数据表结构:包含监控配置、结果记录、告警规则等多张数据表

🚨 告警通知模块

  • 飞书机器人集成:实现飞书群聊告警消息推送
  • 多条件告警规则:支持包含、不包含、正则匹配等多种匹配方式
  • 告警事件记录:完整记录告警触发过程,便于追溯与分析

🌐 API 服务模块

  • RESTful API:基于 actix-web 框架提供标准化 Web 接口
  • 数据查询接口:为前端界面提供监控数据查询能力
  • 分层架构设计:采用 API-Service-Repository 清晰分层

🔄 调度引擎模块

  • 任务调度器:统一调度各类监控任务的执行
  • 连接池管理:使用数据库连接池优化资源利用
  • 多线程安全:通过 Arc 实现多线程环境下的安全共享

📊 基础功能闭环

  • 完整监控流程:从配置加载 → 任务调度 → 监控执行 → 结果存储 → 告警判断 → 通知发送
  • 基础可视化支持:通过 API 层为前端界面提供数据支撑
  • 系统可运行:所有模块已整合,系统可正常启动并执行监控任务

过程问题&&解决方案

  1. cargo install diesel_cli --no-default-features --features sqlite ,中的两个参数代表什么意思?

    答: --no-default-features这个参数表示不安装默认特性diesel_cli 默认会包含对多种数据库的支持: PostgreSQL/MySQL/SQLite 安装所有数据库支持会增加编译时间和依赖项数量。通过使用 --no-default-features,我们只安装明确指定的特性,避免不必要的依赖。
    --features sqlite这个参数表示启用 SQLite 特性 。它告诉 Cargo 在编译 diesel_cli 时只包含对 SQLite 数据库的支持。

  2. 数据Schema 要跟Models结构体一一对应,包括是否可为空、数据类型等等,否则将无法进行赋值操作

  3. Arc 是 Rust 标准库中的一个智能指针类型,全称是 "Atomically Reference Counted"(原子引用计数)。它用于在多线程环境中安全地共享数据。

写在最后

从一行代码到一个系统,这场"填空"之旅的精彩,远非三篇文章所能尽述。愿你我始终亲手探索,方知前路别有洞天。

相关推荐
sino爱学习2 小时前
FastUtil 高性能集合最佳实践:让你的 Java 程序真正“快”起来
java·后端
java水泥工2 小时前
基于Echarts+HTML5可视化数据大屏展示-物流大数据展示
大数据·前端·echarts·html5·可视化大屏
U***e632 小时前
Vue自然语言
前端·javascript·vue.js
用户761736354012 小时前
浏览器渲染原理
前端·浏览器
拉不动的猪2 小时前
Vue 跨组件通信底层:provide/inject 原理与实战指南
前端·javascript·面试
得物技术2 小时前
从数字到版面:得物数据产品里数字格式化的那些事
前端·数据结构·数据分析
百***86462 小时前
Spring Boot应用关闭分析
java·spring boot·后端
00后程序员2 小时前
WebApp 上架 iOS 的可行性分析,审查机制、技术载体与工程落地方案的全流程说明
后端
Java水解2 小时前
从零开始打造高性能数据结构——手把手教你实现环形缓冲
后端