新增app_manager.py文件负责app的安装卸载注册等生命周期管理
This commit is contained in:
parent
e06566c3fc
commit
33dee5c329
@ -24,6 +24,7 @@ from jingrow.utils.jingrow_api import get_jingrow_api_headers
|
|||||||
from jingrow.utils.auth import get_jingrow_cloud_url, get_jingrow_cloud_api_headers, get_jingrow_cloud_api_url, get_jingrow_api_headers
|
from jingrow.utils.auth import get_jingrow_cloud_url, get_jingrow_cloud_api_headers, get_jingrow_cloud_api_url, get_jingrow_api_headers
|
||||||
from jingrow.utils.export_app_package import export_app_package_from_local
|
from jingrow.utils.export_app_package import export_app_package_from_local
|
||||||
from jingrow.config import Config
|
from jingrow.config import Config
|
||||||
|
from jingrow.utils.app_manager import update_apps_txt
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
@ -151,6 +152,9 @@ async def install_app_from_upload(
|
|||||||
# 额外:确保创建 Package 与 Module Def(custom)记录(上传安装)
|
# 额外:确保创建 Package 与 Module Def(custom)记录(上传安装)
|
||||||
ensure_package_and_module(app_name_result)
|
ensure_package_and_module(app_name_result)
|
||||||
|
|
||||||
|
# 更新 apps.txt,确保路由生效
|
||||||
|
update_apps_txt(app_name_result, add=True)
|
||||||
|
|
||||||
# 2. 调用 sync_app_files 同步文件到数据库
|
# 2. 调用 sync_app_files 同步文件到数据库
|
||||||
if app_dir:
|
if app_dir:
|
||||||
current = Path(__file__).resolve()
|
current = Path(__file__).resolve()
|
||||||
@ -396,6 +400,9 @@ async def install_local_app(request: Request, app_name: str):
|
|||||||
# 确保创建 Package 与 Module Def(custom)记录(本地扫描安装)
|
# 确保创建 Package 与 Module Def(custom)记录(本地扫描安装)
|
||||||
ensure_package_and_module(app_name)
|
ensure_package_and_module(app_name)
|
||||||
|
|
||||||
|
# 更新 apps.txt,确保路由生效
|
||||||
|
update_apps_txt(app_name, add=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
api_url = f"{Config.jingrow_server_url}/api/action/jingrow.ai.utils.jlocal.sync_app_files"
|
api_url = f"{Config.jingrow_server_url}/api/action/jingrow.ai.utils.jlocal.sync_app_files"
|
||||||
requests.post(
|
requests.post(
|
||||||
@ -599,6 +606,9 @@ async def install_from_git(repo_url: str = Form(...)):
|
|||||||
|
|
||||||
# 确保创建 Package 与 Module Def(custom)记录
|
# 确保创建 Package 与 Module Def(custom)记录
|
||||||
ensure_package_and_module(app_name)
|
ensure_package_and_module(app_name)
|
||||||
|
|
||||||
|
# 更新 apps.txt,确保路由生效
|
||||||
|
update_apps_txt(app_name, add=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to register app: {e}")
|
logger.error(f"Failed to register app: {e}")
|
||||||
|
|
||||||
@ -690,6 +700,9 @@ async def install_from_url(url: str = Form(...)):
|
|||||||
# 额外:确保创建 Package 与 Module Def(custom)记录(URL安装)
|
# 额外:确保创建 Package 与 Module Def(custom)记录(URL安装)
|
||||||
ensure_package_and_module(app_name_result)
|
ensure_package_and_module(app_name_result)
|
||||||
|
|
||||||
|
# 更新 apps.txt,确保路由生效
|
||||||
|
update_apps_txt(app_name_result, add=True)
|
||||||
|
|
||||||
# 2. 调用 sync_app_files 同步文件到数据库
|
# 2. 调用 sync_app_files 同步文件到数据库
|
||||||
if app_dir:
|
if app_dir:
|
||||||
current = Path(__file__).resolve()
|
current = Path(__file__).resolve()
|
||||||
@ -820,6 +833,9 @@ async def uninstall_app(request: Request, app_name: str):
|
|||||||
shutil.rmtree(app_dir)
|
shutil.rmtree(app_dir)
|
||||||
await _remove_from_database(app_name)
|
await _remove_from_database(app_name)
|
||||||
|
|
||||||
|
# 更新 apps.txt,确保路由生效
|
||||||
|
update_apps_txt(app_name, add=False)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'success': True,
|
'success': True,
|
||||||
'message': f'应用 {app_name} 卸载成功'
|
'message': f'应用 {app_name} 卸载成功'
|
||||||
|
|||||||
@ -8,44 +8,16 @@ Jingrow 白名单路由服务
|
|||||||
from fastapi import APIRouter, Request, HTTPException
|
from fastapi import APIRouter, Request, HTTPException
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
import importlib
|
import importlib
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
import logging
|
import logging
|
||||||
from jingrow import get_whitelisted_function, is_whitelisted
|
from jingrow import get_whitelisted_function, is_whitelisted
|
||||||
from jingrow.utils.auth import get_jingrow_api_headers, get_session_api_headers
|
from jingrow.utils.auth import get_jingrow_api_headers, get_session_api_headers
|
||||||
from jingrow.utils.jingrow_api import get_logged_user
|
from jingrow.utils.jingrow_api import get_logged_user
|
||||||
|
from jingrow.utils.app_manager import ensure_apps_on_sys_path
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
# 确保各 app 根目录在 sys.path 中(仅初始化一次)
|
|
||||||
_apps_path_initialized = False
|
|
||||||
|
|
||||||
def _ensure_apps_on_sys_path():
|
|
||||||
"""确保各 app 根目录在 sys.path 中,支持跨 app 导入"""
|
|
||||||
global _apps_path_initialized
|
|
||||||
if _apps_path_initialized:
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
project_root = Path(__file__).resolve().parents[4]
|
|
||||||
apps_dir = project_root / "apps"
|
|
||||||
|
|
||||||
# 读取 apps.txt,添加各 app 的根目录(apps/<app>)
|
|
||||||
apps_txt = apps_dir / "apps.txt"
|
|
||||||
if apps_txt.exists():
|
|
||||||
for app_name in apps_txt.read_text().splitlines():
|
|
||||||
app_name = app_name.strip()
|
|
||||||
if app_name:
|
|
||||||
app_root_dir = apps_dir / app_name
|
|
||||||
if app_root_dir.exists() and str(app_root_dir) not in sys.path:
|
|
||||||
sys.path.insert(0, str(app_root_dir))
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
finally:
|
|
||||||
_apps_path_initialized = True
|
|
||||||
|
|
||||||
async def authenticate_request(request: Request, allow_guest: bool) -> bool:
|
async def authenticate_request(request: Request, allow_guest: bool) -> bool:
|
||||||
"""
|
"""
|
||||||
认证请求,支持两种认证方式:
|
认证请求,支持两种认证方式:
|
||||||
@ -98,7 +70,7 @@ async def _process_whitelist_call(request: Request, full_module_path: str):
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
# 确保 apps 目录在 sys.path 中(支持跨 app 导入)
|
# 确保 apps 目录在 sys.path 中(支持跨 app 导入)
|
||||||
_ensure_apps_on_sys_path()
|
ensure_apps_on_sys_path()
|
||||||
|
|
||||||
# 解析路径并导入
|
# 解析路径并导入
|
||||||
modulename = ".".join(full_module_path.split('.')[:-1])
|
modulename = ".".join(full_module_path.split('.')[:-1])
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import logging
|
|||||||
from jingrow.config import Config
|
from jingrow.config import Config
|
||||||
from jingrow.utils.jingrow_api import get_jingrow_api_headers
|
from jingrow.utils.jingrow_api import get_jingrow_api_headers
|
||||||
from jingrow.utils.jingrow_api import get_record_id, create_record
|
from jingrow.utils.jingrow_api import get_record_id, create_record
|
||||||
|
from jingrow.utils.app_manager import update_apps_txt
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -349,6 +350,9 @@ def install_app(uploaded_file_path: str, app_name: str = None) -> Dict[str, Any]
|
|||||||
# 忽略该步骤错误,不影响安装完成
|
# 忽略该步骤错误,不影响安装完成
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# 更新 apps.txt,确保路由生效
|
||||||
|
update_apps_txt(app_name, add=True)
|
||||||
|
|
||||||
# 清理临时文件
|
# 清理临时文件
|
||||||
cleanup_temp_dir(temp_dir)
|
cleanup_temp_dir(temp_dir)
|
||||||
return {
|
return {
|
||||||
|
|||||||
148
apps/jingrow/jingrow/utils/app_manager.py
Normal file
148
apps/jingrow/jingrow/utils/app_manager.py
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
# Copyright (c) 2025, JINGROW and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
"""
|
||||||
|
Jingrow App 生命周期管理
|
||||||
|
统一管理 app 的安装、卸载、路由注册等生命周期操作
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Set
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# 确保各 app 根目录在 sys.path 中(仅初始化一次)
|
||||||
|
_apps_path_initialized = False
|
||||||
|
|
||||||
|
|
||||||
|
def get_project_root() -> Path:
|
||||||
|
"""获取项目根目录路径"""
|
||||||
|
# 从当前文件位置计算:apps/jingrow/jingrow/utils/app_manager.py -> 向上4级
|
||||||
|
return Path(__file__).resolve().parents[4]
|
||||||
|
|
||||||
|
|
||||||
|
def get_apps_dir() -> Path:
|
||||||
|
"""获取 apps 目录路径"""
|
||||||
|
return get_project_root() / "apps"
|
||||||
|
|
||||||
|
|
||||||
|
def get_apps_txt_path() -> Path:
|
||||||
|
"""获取 apps.txt 文件路径"""
|
||||||
|
return get_apps_dir() / "apps.txt"
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_apps_on_sys_path():
|
||||||
|
"""
|
||||||
|
确保各 app 根目录在 sys.path 中,支持跨 app 导入
|
||||||
|
在路由处理时自动调用,确保新安装的 app 路由可以生效
|
||||||
|
"""
|
||||||
|
global _apps_path_initialized
|
||||||
|
if _apps_path_initialized:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
apps_dir = get_apps_dir()
|
||||||
|
apps_txt = get_apps_txt_path()
|
||||||
|
|
||||||
|
# 读取 apps.txt,添加各 app 的根目录(apps/<app>)
|
||||||
|
if apps_txt.exists():
|
||||||
|
for app_name in apps_txt.read_text(encoding='utf-8').splitlines():
|
||||||
|
app_name = app_name.strip()
|
||||||
|
if app_name:
|
||||||
|
app_root_dir = apps_dir / app_name
|
||||||
|
if app_root_dir.exists() and str(app_root_dir) not in sys.path:
|
||||||
|
sys.path.insert(0, str(app_root_dir))
|
||||||
|
logger.debug(f"已添加 app 路径到 sys.path: {app_root_dir}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"初始化 app 路径失败: {e}")
|
||||||
|
finally:
|
||||||
|
_apps_path_initialized = True
|
||||||
|
|
||||||
|
|
||||||
|
def reset_apps_path_cache():
|
||||||
|
"""
|
||||||
|
重置 apps path 缓存,强制下次请求时重新初始化
|
||||||
|
在安装/卸载 app 后调用,确保路由立即生效
|
||||||
|
"""
|
||||||
|
global _apps_path_initialized
|
||||||
|
_apps_path_initialized = False
|
||||||
|
logger.debug("已重置 apps path 缓存")
|
||||||
|
|
||||||
|
|
||||||
|
def get_apps_from_txt() -> Set[str]:
|
||||||
|
"""
|
||||||
|
从 apps.txt 读取已注册的 app 列表
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Set[str]: app 名称集合
|
||||||
|
"""
|
||||||
|
apps_txt = get_apps_txt_path()
|
||||||
|
apps = set()
|
||||||
|
|
||||||
|
if apps_txt.exists():
|
||||||
|
for line in apps_txt.read_text(encoding='utf-8').splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
if line:
|
||||||
|
apps.add(line)
|
||||||
|
|
||||||
|
return apps
|
||||||
|
|
||||||
|
|
||||||
|
def update_apps_txt(app_name: str, add: bool = True) -> bool:
|
||||||
|
"""
|
||||||
|
更新 apps.txt 文件(保持原有顺序;新增仅在末尾追加)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
app_name: 应用名称
|
||||||
|
add: True 为添加,False 为删除
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 是否成功更新
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
apps_txt = get_apps_txt_path()
|
||||||
|
apps_txt.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# 读取现有的 app 列表(保持顺序)
|
||||||
|
if apps_txt.exists():
|
||||||
|
lines = [ln.strip() for ln in apps_txt.read_text(encoding='utf-8').splitlines()]
|
||||||
|
lines = [ln for ln in lines if ln]
|
||||||
|
else:
|
||||||
|
lines = []
|
||||||
|
|
||||||
|
if add:
|
||||||
|
if app_name not in lines:
|
||||||
|
lines.append(app_name)
|
||||||
|
logger.info(f"添加应用到 apps.txt: {app_name}")
|
||||||
|
else:
|
||||||
|
new_lines = [ln for ln in lines if ln != app_name]
|
||||||
|
if len(new_lines) != len(lines):
|
||||||
|
logger.info(f"从 apps.txt 移除应用: {app_name}")
|
||||||
|
lines = new_lines
|
||||||
|
|
||||||
|
# 写回文件(保持顺序,统一以单个换行分隔,并以换行结尾)
|
||||||
|
apps_txt.write_text('\n'.join(lines) + ('\n' if lines else ''), encoding='utf-8')
|
||||||
|
|
||||||
|
# 重置缓存,确保下次请求时重新加载
|
||||||
|
reset_apps_path_cache()
|
||||||
|
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"更新 apps.txt 失败: {e}", exc_info=True)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def is_app_registered(app_name: str) -> bool:
|
||||||
|
"""
|
||||||
|
检查 app 是否已注册到 apps.txt
|
||||||
|
|
||||||
|
Args:
|
||||||
|
app_name: 应用名称
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 是否已注册
|
||||||
|
"""
|
||||||
|
return app_name in get_apps_from_txt()
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user