1+ import inspect
12import json
23import os
34import warnings
89import anyio
910import rtoml
1011
11- from fastapi import APIRouter , Depends , Request
12+ from fastapi import APIRouter , Depends , FastAPI , Request
1213
1314from backend .common .enums import DataBaseType , PluginLevelType , PrimaryKeyType , StatusType
1415from backend .common .exception import errors
16+ from backend .common .lifespan import lifespan_manager
1517from backend .common .log import log
1618from backend .core .conf import settings
1719from backend .core .path_conf import PLUGIN_DIR
@@ -132,53 +134,130 @@ def load_plugin_config(plugin: str) -> dict[str, Any]:
132134 return rtoml .load (f )
133135
134136
137+ def get_plugin_enable (plugin_info : str | None , default_status : int ) -> str :
138+ """
139+ 解析插件启用状态
140+
141+ :param plugin_info: 插件缓存信息
142+ :param default_status: 默认状态值
143+ :return:
144+ """
145+ if not plugin_info :
146+ return str (default_status )
147+
148+ try :
149+ return json .loads (plugin_info )['plugin' ]['enable' ]
150+ except Exception :
151+ return str (default_status )
152+
153+
154+ def get_enabled_plugins (plugins : tuple [str , ...] | None = None ) -> set [str ]:
155+ """
156+ 获取已启用的插件列表
157+
158+ :param plugins: 插件名称列表
159+ :return:
160+ """
161+ plugin_names = plugins or get_plugins ()
162+ enabled_plugins = set (plugin_names )
163+
164+ current_redis_client = RedisCli ()
165+ run_await (current_redis_client .init )()
166+
167+ try :
168+ for plugin in plugin_names :
169+ plugin_info = run_await (current_redis_client .get )(f'{ settings .PLUGIN_REDIS_PREFIX } :{ plugin } ' )
170+ if get_plugin_enable (plugin_info , StatusType .enable .value ) != str (StatusType .enable .value ):
171+ enabled_plugins .discard (plugin )
172+ finally :
173+ run_await (current_redis_client .aclose )()
174+
175+ return enabled_plugins
176+
177+
178+ def register_plugin_lifespan_hook (plugin : str , module : Any ) -> None :
179+ """
180+ 注册插件 lifespan hook
181+
182+ :param plugin: 插件名称
183+ :param module: 插件 hooks 模块
184+ :return:
185+ """
186+ lifespan_hook = getattr (module , 'lifespan' , None )
187+ if lifespan_hook is None :
188+ return
189+
190+ if not callable (lifespan_hook ):
191+ log .warning (f'插件 { plugin } 的 lifespan 不是可调用对象,已跳过' )
192+ return
193+
194+ lifespan_manager .register (lifespan_hook )
195+ log .info (f'插件 { plugin } lifespan hook 注册成功' )
196+
197+
198+ def run_plugin_startup_hook (plugin : str , module : Any , app : FastAPI ) -> None :
199+ """
200+ 执行插件 startup hook
201+
202+ :param plugin: 插件名称
203+ :param module: 插件 hooks 模块
204+ :param app: FastAPI 应用实例
205+ :return:
206+ """
207+ setup_hook = getattr (module , 'setup' , None )
208+ if setup_hook is None :
209+ return
210+
211+ if not callable (setup_hook ):
212+ log .warning (f'插件 { plugin } 的 setup 不是可调用对象,已跳过' )
213+ return
214+
215+ setup_result = setup_hook (app )
216+ if inspect .isawaitable (setup_result ):
217+ run_await (lambda : setup_result )() # type: ignore
218+ log .info (f'插件 { plugin } startup hook 执行成功' )
219+
220+
135221def parse_plugin_config () -> tuple [list [dict [str , Any ]], list [dict [str , Any ]]]:
136222 """解析插件配置"""
137223 extend_plugins = []
138224 app_plugins = []
139-
140225 plugins = get_plugins ()
141226
142227 # 使用独立连接
143228 current_redis_client = RedisCli ()
144229 run_await (current_redis_client .init )()
145230
146- # 清理未知插件信息
147- exclude_keys = [f'{ settings .PLUGIN_REDIS_PREFIX } :{ key } ' for key in plugins ]
148- run_await (current_redis_client .delete_prefix )(
149- settings .PLUGIN_REDIS_PREFIX ,
150- exclude = exclude_keys ,
151- )
152-
153- for plugin in plugins :
154- data = load_plugin_config (plugin )
155- plugin_type = validate_plugin_config (plugin , data )
156-
157- if plugin_type == PluginLevelType .extend :
158- extend_plugins .append (data )
159- else :
160- app_plugins .append (data )
161-
162- # 补充插件信息
163- data ['plugin' ]['name' ] = plugin
164- plugin_cache_key = f'{ settings .PLUGIN_REDIS_PREFIX } :{ plugin } '
165- plugin_cache_info = run_await (current_redis_client .get )(plugin_cache_key )
166- if plugin_cache_info :
167- try :
168- data ['plugin' ]['enable' ] = json .loads (plugin_cache_info )['plugin' ]['enable' ]
169- except Exception :
170- data ['plugin' ]['enable' ] = str (StatusType .enable .value )
171- else :
172- data ['plugin' ]['enable' ] = str (StatusType .enable .value )
173-
174- # 缓存最新插件信息
175- run_await (current_redis_client .set )(plugin_cache_key , json .dumps (data , ensure_ascii = False ))
176-
177- # 重置插件变更状态
178- run_await (current_redis_client .delete )(f'{ settings .PLUGIN_REDIS_PREFIX } :changed' )
179-
180- # 关闭连接
181- run_await (current_redis_client .aclose )()
231+ try :
232+ # 清理未知插件信息
233+ exclude_keys = [f'{ settings .PLUGIN_REDIS_PREFIX } :{ key } ' for key in plugins ]
234+ run_await (current_redis_client .delete_prefix )(
235+ settings .PLUGIN_REDIS_PREFIX ,
236+ exclude = exclude_keys ,
237+ )
238+
239+ for plugin in plugins :
240+ plugin_config = load_plugin_config (plugin )
241+ plugin_type = validate_plugin_config (plugin , plugin_config )
242+
243+ if plugin_type == PluginLevelType .extend :
244+ extend_plugins .append (plugin_config )
245+ else :
246+ app_plugins .append (plugin_config )
247+
248+ # 补充插件信息
249+ plugin_config ['plugin' ]['name' ] = plugin
250+ plugin_cache_key = f'{ settings .PLUGIN_REDIS_PREFIX } :{ plugin } '
251+ plugin_cache_info = run_await (current_redis_client .get )(plugin_cache_key )
252+ plugin_config ['plugin' ]['enable' ] = get_plugin_enable (plugin_cache_info , StatusType .enable .value )
253+
254+ # 缓存最新插件信息
255+ run_await (current_redis_client .set )(plugin_cache_key , json .dumps (plugin_config , ensure_ascii = False ))
256+
257+ # 重置插件变更状态
258+ run_await (current_redis_client .delete )(f'{ settings .PLUGIN_REDIS_PREFIX } :changed' )
259+ finally :
260+ run_await (current_redis_client .aclose )()
182261
183262 return extend_plugins , app_plugins
184263
@@ -288,6 +367,41 @@ def build_final_router() -> APIRouter:
288367 return main_router
289368
290369
370+ def setup_plugins (app : FastAPI ) -> None :
371+ """
372+ 注册并执行插件 hooks
373+
374+ :param app: FastAPI 应用实例
375+ :return:
376+ """
377+ plugins = get_plugins ()
378+ enabled_plugins = get_enabled_plugins (plugins )
379+
380+ for plugin in plugins :
381+ if plugin not in enabled_plugins :
382+ log .info (f'插件 { plugin } 未启用,已跳过 hooks 注册与执行' )
383+ continue
384+
385+ module_path = f'backend.plugin.{ plugin } .hooks'
386+ try :
387+ module = import_module_cached (module_path )
388+ except ModuleNotFoundError as e :
389+ if e .name == module_path :
390+ # 未定义 hooks.py
391+ continue
392+ log .warning (f'插件 { plugin } hooks 模块加载失败: { e } ' )
393+ continue
394+ except Exception as e :
395+ log .warning (f'插件 { plugin } hooks 模块加载失败: { e } ' )
396+ continue
397+
398+ try :
399+ register_plugin_lifespan_hook (plugin , module )
400+ run_plugin_startup_hook (plugin , module , app )
401+ except Exception as e :
402+ log .error (f'插件 { plugin } hooks 执行失败: { e } ' )
403+
404+
291405class PluginStatusChecker :
292406 """插件状态检查器"""
293407
@@ -312,10 +426,5 @@ async def __call__(self, request: Request) -> None:
312426 log .error ('插件状态未初始化或丢失,需重启服务自动修复' )
313427 raise PluginInjectError ('插件状态未初始化或丢失,请联系系统管理员' )
314428
315- try :
316- is_enabled = int (json .loads (plugin_info )['plugin' ]['enable' ])
317- except Exception :
318- is_enabled = 0
319-
320- if not is_enabled :
429+ if get_plugin_enable (plugin_info , StatusType .disable .value ) != str (StatusType .enable .value ):
321430 raise errors .ServerError (msg = f'插件 { self .plugin } 未启用,请联系系统管理员' )
0 commit comments