add: 数据监听功能

This commit is contained in:
2025-08-11 16:55:55 +08:00
parent 32e22aee18
commit 149855b867
19 changed files with 247 additions and 12 deletions
+2 -3
View File
@@ -2,6 +2,5 @@ from enum import Enum
class MessageType(Enum):
NOTIFICATION = "notification"
SYSTEM = "system"
TASK = "task"
TASK = "task" # 任务
LISTEN = "listen" # 监听
+1 -1
View File
@@ -1,4 +1,4 @@
from ..task_cycle import TaskMounter
from ..mounter import TaskMounter
from typing import Callable, List
from ..constant import MessageStatus
View File
@@ -1,5 +1,3 @@
from .task_mounter import TaskMounter
__all__ = [
"TaskMounter"
]
__all__ = ["TaskMounter"]
+42
View File
@@ -0,0 +1,42 @@
from typing import Callable, List
class BaseMounter:
@classmethod
def mount_(cls, function: Callable):
setattr(cls, function.__name__, function)
@staticmethod
def mount(meta: dict = {}):
"""带参数的装饰器"""
def decorator(function: Callable):
# 使用自定义名称或函数原名
function.meta = meta # type: ignore
setattr(BaseMounter, function.__name__, function)
return function
return decorator
@classmethod
def get_function(cls, name: str):
return getattr(cls, name, None)
@classmethod
def get_task_list(cls) -> List[str]:
"""获取所有挂载的任务函数名称列表"""
task_list = []
# 遍历类属性字典
for attr_name, attr_value in vars(cls).items():
# 过滤条件:
# 1. 必须是可调用对象(函数)
# 2. 不是类自带的特殊方法(非双下划线开头)
# 3. 不是类方法本身(如 mount_, get_task_list 等)
if (
callable(attr_value)
and not attr_name.startswith("__")
and attr_name not in ["mount_", "mount", "get_function", "get_task_list"]
):
task_list.append(attr_name)
return task_list
@@ -0,0 +1,41 @@
from typing import Callable, List
class ListenMounter:
@classmethod
def mount_Listener(cls, function: Callable):
setattr(cls, function.__name__, function)
@staticmethod
def listener():
"""带参数的装饰器"""
def decorator(function: Callable):
# 使用自定义名称或函数原名
setattr(ListenMounter, function.__name__, function)
return function
return decorator
@classmethod
def get_Listener_function(cls, name: str):
return getattr(cls, name, None)
@classmethod
def get_Listener_list(cls) -> List[str]:
"""获取所有挂载的监听函数名称列表"""
listener_list = []
# 遍历类属性字典
for attr_name, attr_value in vars(cls).items():
# 过滤条件:
# 1. 必须是可调用对象(函数)
# 2. 不是类自带的特殊方法(非双下划线开头)
# 3. 不是类方法本身(如 mount_Listener, listener 等)
if (
callable(attr_value)
and not attr_name.startswith("__")
and attr_name not in ["mount_Listener", "listener", "get_Listener_function", "get_Listener_list"]
):
listener_list.append(attr_name)
return listener_list
@@ -0,0 +1,71 @@
from ..mounter.listen_mounter import ListenMounter
import sqlite3
import threading
class ListenOperation:
def __init__(self, db_dir):
self.db_dir = db_dir
self.listen_fields = ListenMounter.get_Listener_list()
self.create_table()
def create_table(self):
if len(self.listen_fields) == 0:
return
sql = f"""
CREATE TABLE IF NOT EXISTS listen_table (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key Text,
value Text
);
"""
self.conn = sqlite3.connect(
self.db_dir,
check_same_thread = False,
)
self.conn.execute(sql)
self.conn.execute("PRAGMA journal_mode=WAL;")
self.conn.execute("PRAGMA synchronous=NORMAL;")
self.conn.execute("PRAGMA cache_size=-20000;")
self.conn.execute("PRAGMA mmap_size=1073741824;")
self.conn.execute("PRAGMA temp_store=MEMORY;")
self.conn.execute("PRAGMA busy_timeout=5000;")
self.conn.commit()
# 删除原有数据
sql = f"""
DELETE FROM listen_table
"""
self.conn.execute(sql)
self.conn.commit()
for listen_field in self.listen_fields:
sql = f"""
INSERT INTO
listen_table (key, value)
VALUES
(?, ?)
"""
self.conn.execute(sql, (listen_field, "null"))
self.conn.commit()
def listen_data(self, key, value) -> tuple[bool, str]:
sql = f"""
Select value from listen_table where key = '{key}'
"""
cursor = self.conn.execute(sql)
result = cursor.fetchone()
if result is None:
return False, "key not found"
if result[0] == value:
return False, "value not changed"
else:
return True, result[0]
def update_listen_data(self, key, value):
sql = f"""
UPDATE listen_table SET value = '{value}' WHERE key = '{key}'
"""
self.conn.execute(sql)
self.conn.commit()
@@ -8,7 +8,6 @@ from ..core import core
from ..constant import MessageStatus
class QueueOperation():
def __init__(self, shard_num: int = 4, queue_name: str = "default"):
self.shard_num = shard_num
+12
View File
@@ -1,13 +1,20 @@
from .receive_scheduler import ReceiveScheduler
from .task_scheduler import TaskScheduler
from .listen_data_scheduler import ListenDataScheduler
from ..model import MessageItem
from typing import Callable
from ..queue_operation import QueueOperation
from ..queue_operation.listen_operation import ListenOperation
from .cleanup_scheduler import CleanupScheduler
import os
class QueueScheduler:
def __init__(self, receive_thread_num=1, task_thread_num=1, shard_num=4):
self.queue_operation = QueueOperation(shard_num)
self.listen_operation = ListenOperation(os.path.join(self.queue_operation.db_dir, "listen.db"))
self.listen_operation.create_table()
self.listen_scheduler = ListenDataScheduler(self.listen_operation)
self.receive_scheduler = ReceiveScheduler(self.queue_operation, receive_thread_num)
self.task_scheduler = TaskScheduler(self.queue_operation, task_thread_num)
self.cleanup_scheduler = CleanupScheduler(self.queue_operation)
@@ -15,13 +22,18 @@ class QueueScheduler:
def send_message(self, message: MessageItem, callback: Callable):
self.receive_scheduler.send_message(message, callback)
def update_listen_data(self, key, value):
self.listen_operation.update_listen_data(key, value)
def start_queue_scheduler(self):
self.receive_scheduler.start_receive_thread()
self.task_scheduler.start_task_thread()
self.cleanup_scheduler.start_cleanup()
self.listen_scheduler.start_listen_data()
def stop_queue_scheduler(self):
self.receive_scheduler.stop_receive_thread()
self.listen_scheduler.stop_listen_data()
self.task_scheduler.stop_task_thread()
self.cleanup_scheduler.stop_cleanup()
# self.queue_operation.close_all_connections()
@@ -0,0 +1,46 @@
from ..queue_operation.listen_operation import ListenOperation
from concurrent.futures import ThreadPoolExecutor
from ..mounter.listen_mounter import ListenMounter
import threading
class ListenDataScheduler:
def __init__(self, listen_operation: ListenOperation):
self.listen_operation = listen_operation
self.is_running = False
self.executor = ThreadPoolExecutor(max_workers=1)
self.listen_thread = None
self.listen_data = {field: "null" for field in ListenMounter.get_Listener_list()}
def _process_listen_data(self, key, value):
listen_function = ListenMounter.get_Listener_function(key)
if listen_function:
try:
listen_function(value)
except Exception as e:
ValueError(f"Error in {key} listener function: {e}")
def listen(self):
while self.is_running:
for key, value in self.listen_data.items():
status, new_value = self.listen_operation.listen_data(key, value)
if status:
self.listen_data[key] = new_value
self.executor.submit(self._process_listen_data, key, new_value)
def start_listen_data(self):
if self.is_running:
return
self.is_running = True
self.listen_thread = threading.Thread(target=self.listen)
self.listen_thread.start()
def stop_listen_data(self):
if not self.is_running:
return
self.is_running = False
if self.listen_thread and self.listen_thread.is_alive():
self.listen_thread.join(timeout=2)
self.executor.shutdown(wait=True)
+2 -2
View File
@@ -1,8 +1,8 @@
from ..queue_operation import QueueOperation
from ..model import MessageItem
from ..constant import MessageStatus
from ..task_cycle.task_cycle import TaskCycle
from ..task_cycle.task_mounter import TaskMounter
from ..cycle.task_cycle import TaskCycle
from ..mounter.task_mounter import TaskMounter
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime
import time