Init
This commit is contained in:
2
CoreModules/__init__.py
Normal file
2
CoreModules/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
"""核心模块"""
|
||||
|
||||
152
CoreModules/database.py
Normal file
152
CoreModules/database.py
Normal file
@@ -0,0 +1,152 @@
|
||||
"""SQLite数据库操作模块 - 使用标准库sqlite3"""
|
||||
import sqlite3
|
||||
import json
|
||||
import time
|
||||
from typing import *
|
||||
from Convention.Runtime.GlobalConfig import ProjectConfig, ConsoleFrontColor
|
||||
from Convention.Runtime.Architecture import Architecture
|
||||
from Convention.Runtime.File import ToolFile
|
||||
|
||||
config = ProjectConfig()
|
||||
DATABASE_PATH = config.GetFile(config.FindItem("database_path", "db.db"), False).GetFullPath()
|
||||
|
||||
class Database:
|
||||
"""数据库管理类"""
|
||||
|
||||
def __init__(self, db_path: str = DATABASE_PATH):
|
||||
"""初始化数据库连接
|
||||
|
||||
Args:
|
||||
db_path: 数据库文件路径
|
||||
"""
|
||||
self.db_path = db_path
|
||||
self._conn: Optional[sqlite3.Connection] = None
|
||||
self._ensure_db_exists()
|
||||
self.init_tables()
|
||||
Architecture.Register(Database, self, lambda: None)
|
||||
|
||||
def _ensure_db_exists(self):
|
||||
"""确保数据库目录存在"""
|
||||
db_dir = ToolFile(self.db_path).BackToParentDir()
|
||||
db_dir.MustExistsPath()
|
||||
|
||||
@property
|
||||
def conn(self) -> sqlite3.Connection:
|
||||
"""获取数据库连接(懒加载)"""
|
||||
if self._conn is None:
|
||||
try:
|
||||
self._conn = sqlite3.connect(
|
||||
self.db_path,
|
||||
check_same_thread=False, # 允许多线程访问
|
||||
isolation_level=None, # 自动提交
|
||||
timeout=30.0 # 增加超时时间
|
||||
)
|
||||
self._conn.row_factory = sqlite3.Row # 支持字典式访问
|
||||
|
||||
# 启用WAL模式以提高并发性能
|
||||
self._conn.execute("PRAGMA journal_mode=WAL")
|
||||
self._conn.execute("PRAGMA synchronous=NORMAL")
|
||||
self._conn.execute("PRAGMA cache_size=1000")
|
||||
self._conn.execute("PRAGMA temp_store=MEMORY")
|
||||
|
||||
config.Log("Info", f"{ConsoleFrontColor.GREEN}数据库连接成功: {self.db_path}{ConsoleFrontColor.RESET}")
|
||||
except Exception as e:
|
||||
config.Log("Error", f"{ConsoleFrontColor.RED}数据库连接失败: {e}{ConsoleFrontColor.RESET}", exc_info=True)
|
||||
raise
|
||||
return self._conn
|
||||
|
||||
def _table_exists(self, table_name: str) -> bool:
|
||||
"""检查表是否存在
|
||||
|
||||
Args:
|
||||
table_name: 表名
|
||||
|
||||
Returns:
|
||||
是否存在
|
||||
"""
|
||||
cursor = self.conn.cursor()
|
||||
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name=?", (table_name,))
|
||||
return cursor.fetchone() is not None
|
||||
|
||||
def define_table(self, table_name: str):
|
||||
"""定义表
|
||||
|
||||
Args:
|
||||
table_name: 表名
|
||||
"""
|
||||
if not self._table_exists(table_name):
|
||||
cursor = self.conn.cursor()
|
||||
cursor.execute(f"CREATE TABLE IF NOT EXISTS {table_name} (id INTEGER PRIMARY KEY AUTOINCREMENT)")
|
||||
config.Log("Info", f"{ConsoleFrontColor.GREEN}为表 {table_name} 创建{ConsoleFrontColor.RESET}")
|
||||
return self
|
||||
|
||||
def _column_exists(self, table_name: str, column_name: str) -> bool:
|
||||
"""检查表中列是否存在
|
||||
|
||||
Args:
|
||||
table_name: 表名
|
||||
column_name: 列名
|
||||
|
||||
Returns:
|
||||
是否存在
|
||||
"""
|
||||
cursor = self.conn.cursor()
|
||||
cursor.execute(f"PRAGMA table_info({table_name})")
|
||||
columns = [row[1] for row in cursor.fetchall()]
|
||||
return column_name in columns
|
||||
|
||||
def _add_column_if_not_exists(self, table_name: str, column_name: str, column_def: str):
|
||||
"""安全地添加列(如果不存在)
|
||||
|
||||
Args:
|
||||
table_name: 表名
|
||||
column_name: 列名
|
||||
column_def: 列定义(如 "INTEGER" 或 "TEXT DEFAULT ''")
|
||||
"""
|
||||
if not self._column_exists(table_name, column_name):
|
||||
try:
|
||||
cursor = self.conn.cursor()
|
||||
cursor.execute(f"ALTER TABLE {table_name} ADD COLUMN {column_name} {column_def}")
|
||||
config.Log("Info", f"{ConsoleFrontColor.GREEN}为表 {table_name} 添加列 {column_name}{ConsoleFrontColor.RESET}")
|
||||
except Exception as e:
|
||||
config.Log("Warning", f"{ConsoleFrontColor.YELLOW}添加列失败: {e}{ConsoleFrontColor.RESET}")
|
||||
|
||||
def define_column(self, table_name: str, column_name: str, column_def: str):
|
||||
"""定义列
|
||||
|
||||
Args:
|
||||
table_name: 表名
|
||||
column_name: 列名
|
||||
column_def: 列定义(如 "INTEGER" 或 "TEXT DEFAULT ''")
|
||||
"""
|
||||
self._add_column_if_not_exists(table_name, column_name, column_def)
|
||||
return self
|
||||
|
||||
def init_tables(self):
|
||||
"""初始化数据库表"""
|
||||
cursor = self.conn.cursor()
|
||||
|
||||
# 用户表
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
user_id INTEGER PRIMARY KEY,
|
||||
username TEXT,
|
||||
created_at INTEGER NOT NULL,
|
||||
last_active INTEGER NOT NULL
|
||||
)
|
||||
""")
|
||||
|
||||
def close(self):
|
||||
"""关闭数据库连接"""
|
||||
if self._conn:
|
||||
self._conn.close()
|
||||
self._conn = None
|
||||
config.Log("Info", f"{ConsoleFrontColor.GREEN}数据库连接已关闭{ConsoleFrontColor.RESET}")
|
||||
|
||||
def get_db() -> Database:
|
||||
"""获取全局数据库实例(单例模式)"""
|
||||
if not Architecture.Contains(Database):
|
||||
return Database()
|
||||
return Architecture.Get(Database)
|
||||
|
||||
__all__ = ["get_db"]
|
||||
23
CoreModules/flags.py
Normal file
23
CoreModules/flags.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from ..Convention.Runtime.Architecture import *
|
||||
from pydantic import *
|
||||
|
||||
class DebugFlags(BaseModel):
|
||||
debug: bool = Field(default=False)
|
||||
|
||||
class VerboseFlags(BaseModel):
|
||||
verbose: bool = Field(default=False)
|
||||
|
||||
Architecture.Register(DebugFlags, DebugFlags(debug=False), lambda: None)
|
||||
Architecture.Register(VerboseFlags, VerboseFlags(verbose=False), lambda: None)
|
||||
|
||||
def set_internal_debug(debug:bool) -> None:
|
||||
Architecture.Get(DebugFlags).debug = debug
|
||||
def get_internal_debug() -> bool:
|
||||
return Architecture.Get(DebugFlags).debug
|
||||
|
||||
def set_internal_verbose(verbose:bool) -> None:
|
||||
Architecture.Get(VerboseFlags).verbose = verbose
|
||||
def get_internal_verbose() -> bool:
|
||||
return Architecture.Get(VerboseFlags).verbose
|
||||
|
||||
__all__ = ["set_internal_debug", "get_internal_debug", "set_internal_verbose", "get_internal_verbose"]
|
||||
34
CoreModules/middleware.py
Normal file
34
CoreModules/middleware.py
Normal file
@@ -0,0 +1,34 @@
|
||||
"""中间件模块"""
|
||||
import asyncio
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
from starlette.requests import Request
|
||||
from starlette.responses import Response
|
||||
from ..Convention.Runtime.GlobalConfig import ProjectConfig
|
||||
|
||||
config = ProjectConfig()
|
||||
MAX_CONCURRENT_REQUESTS = config.FindItem("max_concurrent_requests", 100)
|
||||
|
||||
class ConcurrencyLimitMiddleware(BaseHTTPMiddleware):
|
||||
"""并发限制中间件 - 防止内存爆炸"""
|
||||
|
||||
def __init__(self, app, max_concurrent: int = MAX_CONCURRENT_REQUESTS):
|
||||
super().__init__(app)
|
||||
self.semaphore = asyncio.Semaphore(max_concurrent)
|
||||
self.max_concurrent = max_concurrent
|
||||
config.Log("Info", f"并发限制中间件已启用,最大并发数:{max_concurrent}")
|
||||
|
||||
async def dispatch(self, request: Request, call_next) -> Response:
|
||||
"""处理请求"""
|
||||
async with self.semaphore:
|
||||
try:
|
||||
response = await call_next(request)
|
||||
return response
|
||||
except Exception as e:
|
||||
config.Log("Error", f"请求处理错误: {e}", exc_info=True)
|
||||
return Response(
|
||||
content='{"error": "Internal Server Error"}',
|
||||
status_code=500,
|
||||
media_type="application/json"
|
||||
)
|
||||
|
||||
__all__ = ["ConcurrencyLimitMiddleware"]
|
||||
95
CoreModules/models.py
Normal file
95
CoreModules/models.py
Normal file
@@ -0,0 +1,95 @@
|
||||
"""数据模型定义"""
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional, Dict, Any, List
|
||||
|
||||
|
||||
class CallbackRequest(BaseModel):
|
||||
"""WPS Callback请求模型"""
|
||||
chatid: int = Field(..., description="会话ID")
|
||||
creator: int = Field(..., description="发送者ID")
|
||||
content: str = Field(..., description="消息内容")
|
||||
reply: Optional[Dict[str, Any]] = Field(None, description="回复内容")
|
||||
robot_key: str = Field(..., description="机器人key")
|
||||
url: str = Field(..., description="callback地址")
|
||||
ctime: int = Field(..., description="发送时间")
|
||||
|
||||
|
||||
class TextMessage(BaseModel):
|
||||
"""文本消息"""
|
||||
msgtype: str = "text"
|
||||
text: Dict[str, str]
|
||||
|
||||
@classmethod
|
||||
def create(cls, content: str):
|
||||
"""创建文本消息"""
|
||||
return cls(text={"content": content})
|
||||
|
||||
|
||||
class MarkdownMessage(BaseModel):
|
||||
"""Markdown消息"""
|
||||
msgtype: str = "markdown"
|
||||
markdown: Dict[str, str]
|
||||
|
||||
@classmethod
|
||||
def create(cls, text: str):
|
||||
"""创建Markdown消息"""
|
||||
return cls(markdown={"text": text})
|
||||
|
||||
|
||||
class LinkMessage(BaseModel):
|
||||
"""链接消息"""
|
||||
msgtype: str = "link"
|
||||
link: Dict[str, str]
|
||||
|
||||
@classmethod
|
||||
def create(cls, title: str, text: str, message_url: str = "", btn_title: str = "查看详情"):
|
||||
"""创建链接消息"""
|
||||
return cls(link={
|
||||
"title": title,
|
||||
"text": text,
|
||||
"messageUrl": message_url,
|
||||
"btnTitle": btn_title
|
||||
})
|
||||
|
||||
|
||||
class GameState(BaseModel):
|
||||
"""游戏状态基类"""
|
||||
game_type: str
|
||||
created_at: int
|
||||
updated_at: int
|
||||
|
||||
|
||||
class GuessGameState(GameState):
|
||||
"""猜数字游戏状态"""
|
||||
game_type: str = "guess"
|
||||
target: int = Field(..., description="目标数字")
|
||||
attempts: int = Field(0, description="尝试次数")
|
||||
guesses: list[int] = Field(default_factory=list, description="历史猜测")
|
||||
max_attempts: int = Field(10, description="最大尝试次数")
|
||||
|
||||
|
||||
class QuizGameState(GameState):
|
||||
"""问答游戏状态"""
|
||||
game_type: str = "quiz"
|
||||
question_id: int = Field(..., description="问题ID")
|
||||
question: str = Field(..., description="问题内容")
|
||||
attempts: int = Field(0, description="尝试次数")
|
||||
max_attempts: int = Field(3, description="最大尝试次数")
|
||||
|
||||
|
||||
class PrivateMessageRequest(BaseModel):
|
||||
"""私聊消息请求模型"""
|
||||
user_id: int = Field(..., description="目标用户ID")
|
||||
content: str = Field(..., description="消息内容")
|
||||
msg_type: str = Field(default="text", description="消息类型: text 或 markdown")
|
||||
|
||||
|
||||
class CheckBatchRequest(BaseModel):
|
||||
"""批量检查请求模型"""
|
||||
user_ids: List[int] = Field(..., description="用户ID列表")
|
||||
|
||||
|
||||
class CheckBatchResponse(BaseModel):
|
||||
"""批量检查响应模型"""
|
||||
results: Dict[int, bool] = Field(..., description="用户ID到是否有URL的映射")
|
||||
|
||||
87
CoreModules/plugin_interface.py
Normal file
87
CoreModules/plugin_interface.py
Normal file
@@ -0,0 +1,87 @@
|
||||
from ..Convention.Runtime.GlobalConfig import ProjectConfig
|
||||
from ..Convention.Runtime.Architecture import Architecture
|
||||
from ..CoreModules.database import get_db
|
||||
from fastapi import APIRouter, FastAPI
|
||||
from typing import *
|
||||
from pydantic import *
|
||||
from abc import ABC
|
||||
import importlib
|
||||
import os
|
||||
|
||||
config = ProjectConfig()
|
||||
|
||||
class DatabaseModel(BaseModel):
|
||||
table_name: str = Field(default="main_table")
|
||||
column_names: List[str] = Field(default=[])
|
||||
column_defs: Dict[str, str] = Field(default={})
|
||||
|
||||
class PluginInterface(ABC):
|
||||
def execute(self, path:str) -> Optional[APIRouter]:
|
||||
'''
|
||||
继承后是否返回路由决定是否启动该插件
|
||||
若返回None, 则不启动该插件
|
||||
'''
|
||||
Architecture.Register(self.__class__, self, self.wake_up, *self.dependencies())
|
||||
router = APIRouter()
|
||||
router.get(path)(self.generate_router_callback())
|
||||
# 在数据库保证必要的表和列存在
|
||||
db = get_db()
|
||||
db_model = self.register_db_model()
|
||||
if db_model:
|
||||
db.define_table(db_model.table_name)
|
||||
for field in db_model.column_names:
|
||||
db.define_column(db_model.table_name, field, db_model.column_defs[field])
|
||||
|
||||
return router
|
||||
|
||||
def generate_router_callback(self) -> Callable|Coroutine:
|
||||
'''
|
||||
继承后重写该方法生成路由回调函数
|
||||
'''
|
||||
async def callback(*args: Any, **kwargs: Any) -> Any:
|
||||
pass
|
||||
return callback
|
||||
|
||||
def dependencies(self) -> List[Type]:
|
||||
'''
|
||||
继承后重写该方法注册依赖插件
|
||||
若返回[], 则不需要依赖插件
|
||||
'''
|
||||
return []
|
||||
|
||||
def wake_up(self) -> None:
|
||||
'''
|
||||
依赖插件全部注册后被调用, 用于通知插件实例依赖项已完全注册
|
||||
'''
|
||||
pass
|
||||
|
||||
def register_db_model(self) -> DatabaseModel:
|
||||
'''
|
||||
继承后重写该方法注册数据库模型
|
||||
'''
|
||||
return DatabaseModel()
|
||||
|
||||
def ImportPlugins(app: FastAPI, plugin_dir:str = "Plugins") -> None:
|
||||
'''
|
||||
导入插件
|
||||
|
||||
Args:
|
||||
app: FastAPI应用
|
||||
plugin_dir: 插件目录
|
||||
'''
|
||||
for file in os.listdir(plugin_dir):
|
||||
if file.endswith(".py") and not file.startswith("__"):
|
||||
module_name = file[:-3]
|
||||
try:
|
||||
module = importlib.import_module(module_name)
|
||||
for class_name in dir(module):
|
||||
plugin_class = getattr(module, class_name)
|
||||
if issubclass(plugin_class, PluginInterface):
|
||||
plugin = plugin_class()
|
||||
router = plugin.execute(f"/{module_name}")
|
||||
if router:
|
||||
app.include_router(router, prefix=f"/api", tags=[module_name])
|
||||
except Exception as e:
|
||||
config.Log("Error", f"加载插件{module_name}失败: {e}")
|
||||
|
||||
__all__ = ["ImportPlugins", "PluginInterface", "DatabaseModel"]
|
||||
Reference in New Issue
Block a user