From 63cd095f1b80a7e6758939e3b59ce7ece248b812 Mon Sep 17 00:00:00 2001 From: ninemine <1371605831@qq.com> Date: Thu, 6 Nov 2025 10:55:39 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B7=B2=E7=BB=8F=E5=8F=AF=E4=BB=A5=E9=A1=BA?= =?UTF-8?q?=E5=88=A9=E8=BF=90=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Application/web.py | 17 +++++++--- Convention | 2 +- CoreModules/plugin_interface.py | 60 +++++++++++++++++++++------------ CoreRouters/callback.py | 24 ++++++++----- README.md | 24 ++++++------- 5 files changed, 78 insertions(+), 49 deletions(-) diff --git a/Application/web.py b/Application/web.py index 1ec7f06..4b8cb0d 100644 --- a/Application/web.py +++ b/Application/web.py @@ -9,6 +9,9 @@ from ..Convention.Runtime.GlobalConfig import * from ..Convention.Runtime.Architecture import Architecture config = ProjectConfig() +APP_CONFIG = { + "docs_url": "/docs", +} @asynccontextmanager async def lifespan(app: FastAPI): @@ -34,11 +37,12 @@ async def lifespan(app: FastAPI): config.Log("Info", "关闭应用完成...") # db.close() -def generate_app(APP_CONFIG: dict) -> FastAPI: +def generate_app(kwargs: dict) -> FastAPI: ''' 生成FastAPI应用 ''' - app = FastAPI(**APP_CONFIG, lifespan=lifespan) + kwargs.update(**APP_CONFIG) + app = FastAPI(**kwargs, lifespan=lifespan) # 添加并发限制中间件 app.add_middleware(ConcurrencyLimitMiddleware) @@ -46,7 +50,7 @@ def generate_app(APP_CONFIG: dict) -> FastAPI: # 注册路由 app.include_router(callback.router, prefix="/api", tags=["callback"]) app.include_router(health.router, tags=["health"]) - ImportPlugins(app, config.FindItem("plugin_dir", "Plugins/")) + ImportPlugins(app, config.FindItem("plugin_dir", "Plugins")) # 注册至框架中 Architecture.Register(FastAPI, app, lambda: None) @@ -60,9 +64,12 @@ app: FastAPI = generate_app(config.FindItem("app_config", {})) @app.get("/") async def root(): """根路径""" + app_name = config.FindItem("app_name", "Application") + app_version = config.FindItem("app_version", "0.0.0") + config.SaveProperties() return JSONResponse({ - "message": config.FindItem("app_name", "Application"), - "version": config.FindItem("app_version", "0.0.0"), + "message": app_name, + "version": app_version, "status": "running" }) diff --git a/Convention b/Convention index a79ba69..8121899 160000 --- a/Convention +++ b/Convention @@ -1 +1 @@ -Subproject commit a79ba694439cfae175583f918042a06b587c22ca +Subproject commit 8121899d32f631c9a8d75bf8dcc5b3b43a5d7995 diff --git a/CoreModules/plugin_interface.py b/CoreModules/plugin_interface.py index 4941e23..3249e12 100644 --- a/CoreModules/plugin_interface.py +++ b/CoreModules/plugin_interface.py @@ -1,3 +1,6 @@ +from pickle import NONE + +from ..Convention.Runtime.Config import * from ..Convention.Runtime.GlobalConfig import ProjectConfig from ..Convention.Runtime.Architecture import Architecture from ..Convention.Runtime.File import ToolFile @@ -19,7 +22,7 @@ class DatabaseModel(BaseModel): class PluginInterface(ABC): plugin_instances: Dict[str, "PluginInterface"] = {} - def callback(self, message: str, chat_id: int, user_id: int) -> str: + async def callback(self, message: str, chat_id: int, user_id: int) -> str|None: ''' 继承后重写该方法接受消息并返回消息 返回空字符串代表不进行反馈 @@ -27,9 +30,8 @@ class PluginInterface(ABC): message: 消息内容 chat_id: 会话ID user_id: 用户ID - Returns: - str: 消息内容 ''' + config.Log("Warning", f"插件{self.__class__.__name__}未实现callback方法") return "" def execute(self, path:str) -> Optional[APIRouter]: @@ -39,7 +41,7 @@ class PluginInterface(ABC): ''' Architecture.Register(self.__class__, self, self.wake_up, *self.dependencies()) router = APIRouter() - router.get(path)(self.generate_router_callback()) + router.post(path)(self.generate_router_callback()) # 在数据库保证必要的表和列存在 db = get_db() db_model = self.register_db_model() @@ -54,8 +56,8 @@ class PluginInterface(ABC): ''' 继承后重写该方法生成路由回调函数 ''' - async def callback(*args: Any, **kwargs: Any) -> Any: - pass + async def callback(message: str, chat_id: int, user_id: int) -> Any: + return await self.callback(message, chat_id, user_id) return callback def dependencies(self) -> List[Type]: @@ -83,7 +85,16 @@ class PluginInterface(ABC): ''' return DatabaseModel() -def ImportPlugins(app: FastAPI, plugin_dir:str = "Plugins/") -> None: + def is_enable_plugin(self) -> bool: + ''' + 继承后重写该方法判断是否启用该插件 + ''' + return False + + def get_plugin_tag(self) -> str: + return "plugin" + +def ImportPlugins(app: FastAPI, plugin_dir:str = "Plugins") -> None: ''' 导入插件 @@ -91,26 +102,31 @@ def ImportPlugins(app: FastAPI, plugin_dir:str = "Plugins/") -> None: app: FastAPI应用 plugin_dir: 插件目录 ''' - plugin_tool_dir = ToolFile(plugin_dir) + plugin_tool_dir = ToolFile(plugin_dir)|None if plugin_tool_dir.Exists() == False: plugin_tool_dir.MustExistsPath() return if plugin_tool_dir.IsDir() == False: config.Log("Error", f"插件目录不是目录: {plugin_tool_dir.GetFullPath()}") return - for file in plugin_tool_dir.DirIter(): - 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}") + for dir_name, sub_dirs, files in plugin_tool_dir.DirWalk(): + for file_name in files: + module_file = ToolFile(dir_name)|file_name + if file_name.endswith(".py") and not file_name.startswith("__"): + try: + module = importlib.import_module(f"{module_file.GetFullPath().replace(".py", '').replace('/', '.').replace('\\', '.')}") + for class_name in dir(module): + plugin_class = getattr(module, class_name) + if not isinstance(plugin_class, type): + continue + if issubclass(plugin_class, PluginInterface): + plugin = plugin_class() + if plugin.is_enable_plugin() == False: + continue + router = plugin.execute(f"/{module_file}") + if router: + app.include_router(router, prefix=f"/api", tags=[plugin.get_plugin_tag()]) + except Exception as e: + config.Log("Error", f"{ConsoleFrontColor.RED}加载插件 {module_file} 失败: {e}{ConsoleFrontColor.RESET}") __all__ = ["ImportPlugins", "PluginInterface", "DatabaseModel"] \ No newline at end of file diff --git a/CoreRouters/callback.py b/CoreRouters/callback.py index be2f055..d3820cf 100644 --- a/CoreRouters/callback.py +++ b/CoreRouters/callback.py @@ -40,36 +40,42 @@ async def callback_receive(request: Request): # 解析指令 content = callback_data.content command = content.split(" ")[0] + message = content[len(command):].strip() config.Log("Info", f"识别指令: command={command}") - # TODO: 处理指令 - return await handle_command(command, content, callback_data.chatid, callback_data.creator) + # 处理指令 + result = await handle_command(command, message, callback_data.chatid, callback_data.creator) + if result: + return JSONResponse({"result": "ok", "message": result}) + else: + return JSONResponse({"result": "ok"}) except Exception as e: - config.Log("Error", f"处理Callback异常: {e}", exc_info=True) + config.Log("Error", f"处理Callback异常: {e}") if ALWAYS_RETURN_OK: return JSONResponse({"result": "ok"}) else: return JSONResponse({"result": "error", "message": str(e)}) -async def handle_command(command: str, content: str, - chat_id: int, user_id: int) -> str: - """处理游戏指令 +async def handle_command(command: str, message: str, + chat_id: int, user_id: int) -> str|None: + """处理指令 Args: command: 指令 - content: 消息内容 + message: 消息内容 chat_id: 会话ID user_id: 用户ID Returns: - 回复文本 + 回复文本或None """ try: plugin = PluginInterface.plugin_instances.get(command, None) if plugin: - return plugin.callback(content, chat_id, user_id) + config.Log("Info", f"已找到插件注册指令: {command}, class: {plugin.__class__.__name__}") + return await plugin.callback(message, chat_id, user_id) else: return f"❌ 未识别指令: {command}" except Exception as e: diff --git a/README.md b/README.md index f8395fd..bb75495 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,21 @@ -# README +# PWF + +Plugin-based Web Framework ## Clone -use recursive +add as submodule ```bash -git clone --recursive +git init +git submodule add PWF ``` or ```bash -git clone -cd Convention +git clone PWF +cd PWF/Convention git submodule update --init --recursive ``` @@ -22,6 +25,8 @@ use ```bash python app.py +# from PWF.Application.app import main +# main() ``` to start and generate **Assets** Folder(generate by [ProjectConfig](Convention/Runtime/GlobalConfig.py)) @@ -38,7 +43,6 @@ just because program not running to the place where argument been referenced ### Commandline and Config -- **--main-webhook-url** main target of the message will be send, **needed** - **--host** default: 0.0.0.0 - **--port** default: 8000 - **--verbose** default: false @@ -46,7 +50,7 @@ just because program not running to the place where argument been referenced ### Only Config - **max_concurrent_requests** default: 100 -- **database_path** file on [Assets](Assets), default: db.db +- **database_path** file on Assets, default: db.db - **plugin_dir** where plugins load, default: Plugins - **always_return_ok** default: true @@ -58,13 +62,11 @@ from CoreModules.plugin_interface import PluginInterface, DatabaseModel class MyPlugin(PluginInterface): def generate_router_callback(self): - """生成路由回调函数,必需实现""" async def callback(): return {"message": "Hello from MyPlugin"} return callback def register_db_model(self): - """注册数据库模型,可选实现""" return DatabaseModel( table_name="my_plugin_table", column_names=["id", "data", "created_at"], @@ -76,10 +78,8 @@ class MyPlugin(PluginInterface): ) def dependencies(self): - """定义依赖的插件类,可选实现""" - return [] # 返回空列表表示无依赖 + return [] def wake_up(self): - """依赖插件注册完成后调用,可选实现""" pass ``` \ No newline at end of file