feat(gotify-ws): 实现 SSH 连接信息解析和存储功能

- 新增 SSHConnection 结构体用于解析 Gotify 消息中的 SSH 连接信息
- 实现数据库连接和 SSH 连接信息存储功能
- 添加正则表达式匹配 IPv4 地址
- 移除不必要的测试代码和学生相关模型
- 优化项目结构,增加时间工具模块
This commit is contained in:
2025-08-12 22:34:56 +08:00
parent bb56a17f26
commit 372237ba89
17 changed files with 179 additions and 159 deletions

2
.gitignore vendored
View File

@@ -1,2 +1,4 @@
/target
Cargo.lock
*.db
*.sqlite

33
.idea/dataSources.xml generated
View File

@@ -8,40 +8,11 @@
<jdbc-url>jdbc:postgresql://home.hzer.xyz:5432/postgres</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
<data-source source="LOCAL" name="database" uuid="f7f808d9-ca1a-4e93-912f-baf449326472">
<data-source source="LOCAL" name="gotify" uuid="bf0d9667-0cf1-4470-bd69-6a15c4a18b78">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:D:\dev\code\mine\home-api\database.sqlite</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
<data-source source="LOCAL" name="test" uuid="ad5ecc9e-100e-44c4-85b1-6c01457609e4">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:D:\code\mine\rust\home-api\packages\gotify-ws\test.sqlite</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
<data-source source="LOCAL" name="database [2]" uuid="c8e748c5-b9c8-4754-93eb-af293ce1bbdc">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:D:\code\mine\rust\home-api\database.sqlite</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
<libraries>
<library>
<url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/xerial/sqlite-jdbc/3.45.1.0/sqlite-jdbc-3.45.1.0.jar</url>
</library>
<library>
<url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar</url>
</library>
</libraries>
</data-source>
<data-source source="LOCAL" name="db" uuid="e5f6bceb-da4b-4064-a6b3-f34e6193e88c">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:D:\dev\code\mine\home-api\db.sqlite</jdbc-url>
<jdbc-url>jdbc:sqlite:D:\dev\code\mine\home-api\gotify.db</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
<libraries>
<library>

View File

@@ -18,4 +18,5 @@ serde_json = "1.0.142"
tokio = { version = "1.47.1", features = ["full"] }
tokio-tungstenite = { version = "0.27.0", features = ["native-tls"] }
regex = "1.11.1"
sea-orm = "1.1.14"
sea-orm = { version = "1.1.14", features = ["sqlx-sqlite", "runtime-tokio-rustls", "macros"] }
chrono = "0.4.41"

View File

@@ -2,9 +2,11 @@ mod model;
mod tests;
mod utils;
use crate::model::ws::WsMessage;
use crate::model::ssh_connection::{ActiveModel, SSHConnection};
use crate::utils::sql::sqlite;
use futures::StreamExt;
use log::{info, warn};
use regex::Regex;
use tokio_tungstenite::connect_async;
use tokio_tungstenite::tungstenite::client::IntoClientRequest;
use utils::logger;
@@ -12,25 +14,42 @@ use utils::logger;
#[tokio::main]
async fn main() {
logger::init_logger();
let addr = "wss://home.hzer.xyz/gotify/stream?token=CDIwYlYJuxWxVr5".into_client_request().unwrap();
let addr = "wss://home.hzer.xyz/gotify/stream?token=CDIwYlYJuxWxVr5"
.into_client_request()
.unwrap();
let (stream, _) = connect_async(addr.clone()).await.unwrap();
// info!("Connected to Gotify server {addr}");
info!("Connected to Gotify server {addr:?}");
sqlite::init("./gotify.db".to_string()).await;
sqlite::create_ssh_connection_table().await;
let (_, mut read) = stream.split();
let ipv4_re: Regex = Regex::new(r"\b[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\b").expect("正则表达式错误");
while let Some(msg) = read.next().await {
match msg {
Ok(msg) => {
let str = msg.to_text().unwrap();
info!("Got message: {}", str);
// info!("Got message: {}", str);
if str.trim().is_empty() {
info!("监听到心跳{}", &msg);
continue;
}
match serde_json::from_str::<WsMessage>(str) {
Ok(ws) => {
info!("Got {} from Gotify", ws.message);
match serde_json::from_str::<SSHConnection>(str) {
Ok(mut conn) => {
// info!("Got {ws:?} from Gotify");
match ipv4_re.find(conn.message.as_str()) {
Some(ipv4) => {
info!("已记录ipv4地址{:?}", ipv4.as_str());
conn.ip = Some(ipv4.as_str().to_string());
sqlite::insert_into_ssh_connection(ActiveModel::from(conn)).await;
}
None => {
warn!("未检测到ipv4地址");
}
}
}
Err(e) => {
warn!("转换json失败{} -- {e}", &msg)

View File

@@ -1,3 +1 @@
pub mod ws;
pub mod student;
mod test;
pub mod ssh_connection;

View File

@@ -0,0 +1,29 @@
use crate::utils::time::get_current_time;
use sea_orm::prelude::DeriveEntityModel;
use sea_orm::{ActiveModelBehavior, DeriveRelation, EnumIter};
use sea_orm::{DerivePrimaryKey, PrimaryKeyTrait};
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "ssh_connection")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: u32,
pub ip: Option<String>,
#[sea_orm(not_null)]
pub appid: i64,
pub message: String,
pub title: String,
pub priority: i64,
pub date: String,
#[serde(default = "get_current_time")]
pub create_at: String,
#[serde(default = "get_current_time")]
pub update_at: String,
}
pub type SSHConnection = Model;
#[derive(Debug, DeriveRelation, EnumIter)]
pub enum Relation {}
impl ActiveModelBehavior for ActiveModel {}

View File

@@ -1,26 +0,0 @@
use sea_orm::entity::prelude::*;
#[derive(Debug, Clone, DeriveEntityModel)]
#[sea_orm(table_name = "student")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: u32,
pub name: String,
pub age: i32,
pub sex: Sex,
pub class: u8,
pub score: f32,
}
#[derive(EnumIter, Copy, Clone, Debug, DeriveRelation)]
pub enum Relation {}
#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum)]
#[sea_orm(rs_type = "String", db_type = "Text")]
pub enum Sex {
#[sea_orm(string_value = "male")]
Male,
#[sea_orm(string_value = "female")]
Female,
}
impl ActiveModelBehavior for ActiveModel {}

View File

@@ -1 +0,0 @@
mod student;

View File

@@ -1,32 +0,0 @@
#[cfg(test)]
mod student {
use crate::model::student;
use log::info;
use sea_orm::{Database, DbBackend, Schema, sea_query::Table};
#[tokio::test]
async fn db_test() {
db_connect().await;
println!("-----------");
}
async fn db_connect() {
if let Ok(db) = Database::connect("test.sqlite").await {
println!("DB connected {db:?}");
let stmt = Table::create().table(student::Entity).if_not_exists();
println!("{}",1);
}
}
#[test]
fn print_sql() {
let db = DbBackend::Sqlite;
let schema = Schema::new(db);
let stmt = schema.create_table_from_entity(student::Entity);
let sql = db.build(&stmt);
println!("sql print {}", sql.sql);
println!("-------------------------------------------------")
}
}

View File

@@ -1,11 +0,0 @@
use serde::Deserialize;
#[derive(Debug, Deserialize, Clone, PartialEq)]
pub struct WsMessage {
id: u64,
appid: u64,
pub message: String,
title: String,
priority: u64,
date: String,
}

View File

@@ -1,2 +1,3 @@
pub mod logger;
pub mod sql;
pub mod time;

View File

@@ -1,8 +1,60 @@
// use sea_orm::Database;
//
// const ADDR: &str = "db.sqlite";
// static mut db = async || {
// Database::connect(ADDR).await
// };
//
//
use crate::model::ssh_connection::{ActiveModel, Entity};
use log::{info, warn};
use sea_orm::{
ActiveModelTrait, ConnectionTrait, Database, DatabaseBackend, DatabaseConnection, Schema,
};
use std::sync::OnceLock;
static DB: OnceLock<Option<DatabaseConnection>> = OnceLock::new();
pub async fn init(addr: String) {
match Database::connect(format!("sqlite://{addr}?mode=rwc")).await {
Ok(db) => {
info!("连接数据库成功!");
DB.get_or_init(|| Some(db));
}
Err(e) => {
info!("连接数据库出错! {e:?}");
}
}
}
pub fn get_connection() -> DatabaseConnection {
let db = DB.get().expect("数据库未连接");
db.clone().unwrap()
}
/**
* 创建ssh_connection表
*/
pub async fn create_ssh_connection_table() -> bool {
let db = get_connection();
let statement = Schema::new(DatabaseBackend::Sqlite).create_table_from_entity(Entity);
let sql = db.get_database_backend().build(&statement);
match db.execute(sql).await {
Ok(_) => {
info!("创建表成功!");
true
}
Err(e) => {
warn!("创建表失败! {e:?}");
false
}
}
}
/**
* 插入数据
*/
pub async fn insert_into_ssh_connection(model: ActiveModel) {
let db = get_connection();
match model.insert(&db).await {
Ok(_) => {
info!("插入数据成功!");
}
Err(e) => {
warn!("插入数据失败! {e:?}");
}
}
}

View File

@@ -1,32 +1,43 @@
// #[cfg(test)]
// mod sqlite_test {
// use std::fs::remove_file;
// use std::path::Path;
//
// use crate::utils::sql::sqlite::SqliteDB;
//
// #[test]
// fn new_database() {
// let file_path: &str = "test.sqlite";
// SqliteDB::new(&file_path).unwrap();
//
// let file = Path::new(&file_path);
// assert!(file.exists());
//
// if file.exists() {
// remove_file(file).unwrap();
// }
// }
//
// #[test]
// fn create_table() {
// let file_path: &str = "test.sqlite";
// let db = SqliteDB::new(&file_path).unwrap();
// db.create_table();
// drop(db.connection_pool);
//
// if Path::new(&file_path).exists() {
// remove_file(file_path).unwrap();
// }
// }
// }
#[cfg(test)]
mod sqlite_test {
use crate::utils::logger::init_logger;
use crate::utils::sql::sqlite;
use crate::utils::sql::sqlite::create_ssh_connection_table;
use std::fs::remove_file;
use std::path::Path;
static FILE_PATH: &str = "test.sqlite";
#[test]
fn create_table() {
before_detail();
create_ssh_connection_table();
}
#[test]
fn insert_data() {
create_table();
// insert_into_ssh_connection(ActiveModel {
// id: ActiveValue::Set(2),
// appid: ActiveValue::Set(1),
// message: ActiveValue::Set("message".to_string()),
// title: ActiveValue::Set("title".to_string()),
// priority: ActiveValue::Set(1),
// data: ActiveValue::Set("{}".to_string()),
// create_at: ActiveValue::Set("2025-08-12T16:55:44+08:00".to_string()),
// update_at: ActiveValue::Set("2025-08-12T16:55:44+08:00".to_string()),
// });
}
fn before_detail() {
init_logger();
sqlite::init(format!("sqlite://{}?mode=rwc", &FILE_PATH));
}
#[test]
pub fn after_detail() {
let file = Path::new(&FILE_PATH);
if file.exists() {
remove_file(file).unwrap();
}
}
}

View File

@@ -0,0 +1,6 @@
use chrono::Local;
pub fn get_current_time() -> String {
let time = Local::now();
time.format("%Y-%m-%d %H:%M:%S").to_string()
}