关于本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(®ex).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 层为前端界面提供数据支撑
- 系统可运行:所有模块已整合,系统可正常启动并执行监控任务
过程问题&&解决方案
-
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 数据库的支持。 -
数据
Schema要跟Models结构体一一对应,包括是否可为空、数据类型等等,否则将无法进行赋值操作 -
Arc是 Rust 标准库中的一个智能指针类型,全称是 "Atomically Reference Counted"(原子引用计数)。它用于在多线程环境中安全地共享数据。
写在最后
从一行代码到一个系统,这场"填空"之旅的精彩,远非三篇文章所能尽述。愿你我始终亲手探索,方知前路别有洞天。