初步集成消息通道,支持Telegram + Lark机器人

This commit is contained in:
xintaofei
2026-03-30 22:51:49 +08:00
parent 544abbd15d
commit d18cec33bf
44 changed files with 4106 additions and 11 deletions

View File

@@ -0,0 +1,31 @@
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "chat_channel")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub name: String,
pub channel_type: String,
pub enabled: bool,
pub config_json: String,
pub event_filter_json: Option<String>,
pub daily_report_enabled: bool,
pub daily_report_time: Option<String>,
pub created_at: DateTimeUtc,
pub updated_at: DateTimeUtc,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(has_many = "super::chat_channel_message_log::Entity")]
MessageLogs,
}
impl Related<super::chat_channel_message_log::Entity> for Entity {
fn to() -> RelationDef {
Relation::MessageLogs.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@@ -0,0 +1,33 @@
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "chat_channel_message_log")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub channel_id: i32,
pub direction: String,
pub message_type: String,
pub content_preview: String,
pub status: String,
pub error_detail: Option<String>,
pub created_at: DateTimeUtc,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::chat_channel::Entity",
from = "Column::ChannelId",
to = "super::chat_channel::Column::Id"
)]
ChatChannel,
}
impl Related<super::chat_channel::Entity> for Entity {
fn to() -> RelationDef {
Relation::ChatChannel.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@@ -1,5 +1,7 @@
pub mod agent_setting;
pub mod app_metadata;
pub mod chat_channel;
pub mod chat_channel_message_log;
pub mod conversation;
pub mod folder;
pub mod folder_command;

View File

@@ -2,6 +2,8 @@
pub use super::agent_setting::Entity as AgentSetting;
pub use super::app_metadata::Entity as AppMetadata;
pub use super::chat_channel::Entity as ChatChannel;
pub use super::chat_channel_message_log::Entity as ChatChannelMessageLog;
pub use super::conversation::Entity as Conversation;
pub use super::folder::Entity as Folder;
pub use super::folder_command::Entity as FolderCommand;

View File

@@ -0,0 +1,179 @@
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// ─── chat_channel ───
manager
.create_table(
Table::create()
.table(ChatChannel::Table)
.if_not_exists()
.col(
ColumnDef::new(ChatChannel::Id)
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
.col(ColumnDef::new(ChatChannel::Name).string().not_null())
.col(
ColumnDef::new(ChatChannel::ChannelType)
.string()
.not_null(),
)
.col(
ColumnDef::new(ChatChannel::Enabled)
.boolean()
.not_null()
.default(true),
)
.col(
ColumnDef::new(ChatChannel::ConfigJson)
.text()
.not_null(),
)
.col(ColumnDef::new(ChatChannel::EventFilterJson).text().null())
.col(
ColumnDef::new(ChatChannel::DailyReportEnabled)
.boolean()
.not_null()
.default(false),
)
.col(
ColumnDef::new(ChatChannel::DailyReportTime)
.string()
.null(),
)
.col(
ColumnDef::new(ChatChannel::CreatedAt)
.timestamp_with_time_zone()
.not_null(),
)
.col(
ColumnDef::new(ChatChannel::UpdatedAt)
.timestamp_with_time_zone()
.not_null(),
)
.to_owned(),
)
.await?;
// ─── chat_channel_message_log ───
manager
.create_table(
Table::create()
.table(ChatChannelMessageLog::Table)
.if_not_exists()
.col(
ColumnDef::new(ChatChannelMessageLog::Id)
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
.col(
ColumnDef::new(ChatChannelMessageLog::ChannelId)
.integer()
.not_null(),
)
.col(
ColumnDef::new(ChatChannelMessageLog::Direction)
.string()
.not_null(),
)
.col(
ColumnDef::new(ChatChannelMessageLog::MessageType)
.string()
.not_null(),
)
.col(
ColumnDef::new(ChatChannelMessageLog::ContentPreview)
.text()
.not_null(),
)
.col(
ColumnDef::new(ChatChannelMessageLog::Status)
.string()
.not_null(),
)
.col(
ColumnDef::new(ChatChannelMessageLog::ErrorDetail)
.text()
.null(),
)
.col(
ColumnDef::new(ChatChannelMessageLog::CreatedAt)
.timestamp_with_time_zone()
.not_null(),
)
.foreign_key(
ForeignKey::create()
.name("fk_ccml_channel_id")
.from(
ChatChannelMessageLog::Table,
ChatChannelMessageLog::ChannelId,
)
.to(ChatChannel::Table, ChatChannel::Id)
.on_delete(ForeignKeyAction::Cascade),
)
.to_owned(),
)
.await?;
manager
.create_index(
Index::create()
.name("idx_ccml_channel_created")
.table(ChatChannelMessageLog::Table)
.col(ChatChannelMessageLog::ChannelId)
.col(ChatChannelMessageLog::CreatedAt)
.to_owned(),
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_table(
Table::drop()
.table(ChatChannelMessageLog::Table)
.to_owned(),
)
.await?;
manager
.drop_table(Table::drop().table(ChatChannel::Table).to_owned())
.await
}
}
#[derive(DeriveIden)]
enum ChatChannel {
Table,
Id,
Name,
ChannelType,
Enabled,
ConfigJson,
EventFilterJson,
DailyReportEnabled,
DailyReportTime,
CreatedAt,
UpdatedAt,
}
#[derive(DeriveIden)]
enum ChatChannelMessageLog {
Table,
Id,
ChannelId,
Direction,
MessageType,
ContentPreview,
Status,
ErrorDetail,
CreatedAt,
}

View File

@@ -5,6 +5,7 @@ mod m20260219_000001_folder_command;
mod m20260221_000001_folder_is_open;
mod m20260226_000001_agent_setting;
mod m20260227_000001_folder_parent_branch;
mod m20260330_000001_chat_channel;
pub struct Migrator;
#[async_trait::async_trait]
@@ -16,6 +17,7 @@ impl MigratorTrait for Migrator {
Box::new(m20260221_000001_folder_is_open::Migration),
Box::new(m20260226_000001_agent_setting::Migration),
Box::new(m20260227_000001_folder_parent_branch::Migration),
Box::new(m20260330_000001_chat_channel::Migration),
]
}
}

View File

@@ -0,0 +1,70 @@
use chrono::Utc;
use sea_orm::prelude::DateTimeUtc;
use sea_orm::{
ActiveModelTrait, ActiveValue::NotSet, ColumnTrait, DatabaseConnection, EntityTrait,
QueryFilter, QueryOrder, Set,
};
use crate::db::entities::chat_channel_message_log;
use crate::db::error::DbError;
pub async fn create_log(
conn: &DatabaseConnection,
channel_id: i32,
direction: &str,
message_type: &str,
content_preview: &str,
status: &str,
error_detail: Option<String>,
) -> Result<(), DbError> {
let active = chat_channel_message_log::ActiveModel {
id: NotSet,
channel_id: Set(channel_id),
direction: Set(direction.to_string()),
message_type: Set(message_type.to_string()),
content_preview: Set(truncate_preview(content_preview)),
status: Set(status.to_string()),
error_detail: Set(error_detail),
created_at: Set(Utc::now()),
};
active.insert(conn).await?;
Ok(())
}
pub async fn list_by_channel(
conn: &DatabaseConnection,
channel_id: i32,
limit: u64,
offset: u64,
) -> Result<Vec<chat_channel_message_log::Model>, DbError> {
use sea_orm::PaginatorTrait;
Ok(chat_channel_message_log::Entity::find()
.filter(chat_channel_message_log::Column::ChannelId.eq(channel_id))
.order_by_desc(chat_channel_message_log::Column::CreatedAt)
.paginate(conn, limit)
.fetch_page(offset / limit)
.await?)
}
pub async fn cleanup_old_logs(
conn: &DatabaseConnection,
older_than: DateTimeUtc,
) -> Result<u64, DbError> {
let result = chat_channel_message_log::Entity::delete_many()
.filter(chat_channel_message_log::Column::CreatedAt.lt(older_than))
.exec(conn)
.await?;
Ok(result.rows_affected)
}
fn truncate_preview(s: &str) -> String {
if s.len() <= 200 {
s.to_string()
} else {
let mut end = 200;
while !s.is_char_boundary(end) && end > 0 {
end -= 1;
}
format!("{}...", &s[..end])
}
}

View File

@@ -0,0 +1,98 @@
use chrono::Utc;
use sea_orm::{
ActiveModelTrait, ActiveValue::NotSet, ColumnTrait, DatabaseConnection, EntityTrait,
IntoActiveModel, QueryFilter, QueryOrder, Set,
};
use crate::db::entities::chat_channel;
use crate::db::error::DbError;
pub async fn create(
conn: &DatabaseConnection,
name: String,
channel_type: String,
config_json: String,
enabled: bool,
daily_report_enabled: bool,
daily_report_time: Option<String>,
) -> Result<chat_channel::Model, DbError> {
let now = Utc::now();
let active = chat_channel::ActiveModel {
id: NotSet,
name: Set(name),
channel_type: Set(channel_type),
enabled: Set(enabled),
config_json: Set(config_json),
event_filter_json: Set(None),
daily_report_enabled: Set(daily_report_enabled),
daily_report_time: Set(daily_report_time),
created_at: Set(now),
updated_at: Set(now),
};
Ok(active.insert(conn).await?)
}
pub async fn update(
conn: &DatabaseConnection,
id: i32,
name: Option<String>,
enabled: Option<bool>,
config_json: Option<String>,
event_filter_json: Option<Option<String>>,
daily_report_enabled: Option<bool>,
daily_report_time: Option<Option<String>>,
) -> Result<chat_channel::Model, DbError> {
let model = chat_channel::Entity::find_by_id(id)
.one(conn)
.await?
.ok_or_else(|| DbError::Migration(format!("chat channel not found: {id}")))?;
let mut active = model.into_active_model();
if let Some(v) = name {
active.name = Set(v);
}
if let Some(v) = enabled {
active.enabled = Set(v);
}
if let Some(v) = config_json {
active.config_json = Set(v);
}
if let Some(v) = event_filter_json {
active.event_filter_json = Set(v);
}
if let Some(v) = daily_report_enabled {
active.daily_report_enabled = Set(v);
}
if let Some(v) = daily_report_time {
active.daily_report_time = Set(v);
}
active.updated_at = Set(Utc::now());
Ok(active.update(conn).await?)
}
pub async fn delete(conn: &DatabaseConnection, id: i32) -> Result<(), DbError> {
chat_channel::Entity::delete_by_id(id).exec(conn).await?;
Ok(())
}
pub async fn get_by_id(
conn: &DatabaseConnection,
id: i32,
) -> Result<Option<chat_channel::Model>, DbError> {
Ok(chat_channel::Entity::find_by_id(id).one(conn).await?)
}
pub async fn list_all(conn: &DatabaseConnection) -> Result<Vec<chat_channel::Model>, DbError> {
Ok(chat_channel::Entity::find()
.order_by_asc(chat_channel::Column::Id)
.all(conn)
.await?)
}
pub async fn list_enabled(conn: &DatabaseConnection) -> Result<Vec<chat_channel::Model>, DbError> {
Ok(chat_channel::Entity::find()
.filter(chat_channel::Column::Enabled.eq(true))
.order_by_asc(chat_channel::Column::Id)
.all(conn)
.await?)
}

View File

@@ -1,5 +1,7 @@
pub mod agent_setting_service;
pub mod app_metadata_service;
pub mod chat_channel_message_log_service;
pub mod chat_channel_service;
pub mod conversation_service;
pub mod folder_command_service;
pub mod folder_service;