11#!/usr/bin/env python3
22# -*- coding: utf-8 -*-
3- import io
4- import os .path
5- import zipfile
6-
7- from typing import Annotated
3+ from typing import Annotated , Any
84
95from fastapi import APIRouter , Depends , File , UploadFile
106from fastapi .params import Query
117from starlette .responses import StreamingResponse
128
13- from backend .common .exception import errors
14- from backend .common .response .response_schema import ResponseModel , response_base
9+ from backend .app .admin .service .plugin_service import plugin_service
10+ from backend .common .response .response_code import CustomResponseCode
11+ from backend .common .response .response_schema import ResponseModel , ResponseSchemaModel , response_base
12+ from backend .common .security .jwt import DependsJwtAuth
1513from backend .common .security .permission import RequestPermission
1614from backend .common .security .rbac import DependsRBAC
17- from backend .core .path_conf import PLUGIN_DIR
18- from backend .plugin .tools import install_requirements_async
1915
2016router = APIRouter ()
2117
2218
19+ @router .get ('' , summary = '获取所有插件' , dependencies = [DependsJwtAuth ])
20+ async def get_all_plugins () -> ResponseSchemaModel [list [dict [str , Any ]]]:
21+ plugins = await plugin_service .get_all ()
22+ return response_base .success (data = plugins )
23+
24+
25+ @router .post (
26+ '/install/zip' ,
27+ summary = '安装 zip 插件' ,
28+ description = '使用插件 zip 压缩包进行安装' ,
29+ dependencies = [
30+ Depends (RequestPermission ('sys:plugin:install' )),
31+ DependsRBAC ,
32+ ],
33+ )
34+ async def install_zip_plugin (file : Annotated [UploadFile , File ()]) -> ResponseModel :
35+ await plugin_service .install_zip (file = file )
36+ return response_base .success (res = CustomResponseCode .PLUGIN_INSTALL_SUCCESS )
37+
38+
2339@router .post (
24- '/install' ,
25- summary = '安装插件 ' ,
26- description = '需使用插件 zip 压缩包进行安装 ' ,
40+ '/install/git ' ,
41+ summary = '安装 git 插件 ' ,
42+ description = '使用插件 git 仓库地址进行安装,不限制平台;如果需要凭证,需在 git 仓库地址中添加凭证信息 ' ,
2743 dependencies = [
2844 Depends (RequestPermission ('sys:plugin:install' )),
2945 DependsRBAC ,
3046 ],
3147)
32- async def install_plugin (file : Annotated [UploadFile , File ()]) -> ResponseModel :
33- contents = await file .read ()
34- file_bytes = io .BytesIO (contents )
35- if not zipfile .is_zipfile (file_bytes ):
36- raise errors .ForbiddenError (msg = '插件压缩包格式非法' )
37- with zipfile .ZipFile (file_bytes ) as zf :
38- # 校验压缩包
39- plugin_dir_in_zip = f'{ file .filename [:- 4 ]} /backend/plugin/'
40- members_in_plugin_dir = [name for name in zf .namelist () if name .startswith (plugin_dir_in_zip )]
41- if not members_in_plugin_dir :
42- raise errors .ForbiddenError (msg = '插件压缩包内容非法' )
43- plugin_name = members_in_plugin_dir [1 ].replace (plugin_dir_in_zip , '' ).replace ('/' , '' )
44- if (
45- len (members_in_plugin_dir ) <= 3
46- or f'{ plugin_dir_in_zip } { plugin_name } /plugin.toml' not in members_in_plugin_dir
47- or f'{ plugin_dir_in_zip } { plugin_name } /README.md' not in members_in_plugin_dir
48- ):
49- raise errors .ForbiddenError (msg = '插件压缩包内缺少必要文件' )
48+ async def install_git_plugin (repo_url : Annotated [str , Query (description = '插件 git 仓库地址' )]) -> ResponseModel :
49+ await plugin_service .install_git (repo_url = repo_url )
50+ return response_base .success (res = CustomResponseCode .PLUGIN_INSTALL_SUCCESS )
51+
5052
51- # 插件是否可安装
52- full_plugin_path = os .path .join (PLUGIN_DIR , plugin_name )
53- if os .path .exists (full_plugin_path ):
54- raise errors .ForbiddenError (msg = '此插件已安装' )
55- else :
56- os .makedirs (full_plugin_path , exist_ok = True )
53+ @router .post (
54+ '/uninstall' ,
55+ summary = '卸载插件' ,
56+ description = '此操作会直接删除插件依赖,但不会直接删除插件,而是将插件移动到备份目录' ,
57+ dependencies = [
58+ Depends (RequestPermission ('sys:plugin:uninstall' )),
59+ DependsRBAC ,
60+ ],
61+ )
62+ async def uninstall_plugin (plugin : Annotated [str , Query (description = '插件名称' )]) -> ResponseModel :
63+ await plugin_service .uninstall (plugin = plugin )
64+ return response_base .success (res = CustomResponseCode .PLUGIN_UNINSTALL_SUCCESS )
5765
58- # 解压(安装)
59- members = []
60- for member in zf .infolist ():
61- if member .filename .startswith (plugin_dir_in_zip ):
62- new_filename = member .filename .replace (plugin_dir_in_zip , '' )
63- if new_filename :
64- member .filename = new_filename
65- members .append (member )
66- zf .extractall (PLUGIN_DIR , members )
67- if os .path .exists (os .path .join (full_plugin_path , 'requirements.txt' )):
68- await install_requirements_async ()
6966
67+ @router .post (
68+ '/status' ,
69+ summary = '更新插件状态' ,
70+ dependencies = [
71+ Depends (RequestPermission ('sys:plugin:status' )),
72+ DependsRBAC ,
73+ ],
74+ )
75+ async def update_plugin_status (plugin : Annotated [str , Query (description = '插件名称' )]) -> ResponseModel :
76+ await plugin_service .update_status (plugin = plugin )
7077 return response_base .success ()
7178
7279
@@ -79,19 +86,7 @@ async def install_plugin(file: Annotated[UploadFile, File()]) -> ResponseModel:
7986 ],
8087)
8188async def build_plugin (plugin : Annotated [str , Query (description = '插件名称' )]) -> StreamingResponse :
82- plugin_dir = os .path .join (PLUGIN_DIR , plugin )
83- if not os .path .exists (plugin_dir ):
84- raise errors .ForbiddenError (msg = '插件不存在' )
85-
86- bio = io .BytesIO ()
87- with zipfile .ZipFile (bio , 'w' ) as zf :
88- for root , dirs , files in os .walk (plugin_dir ):
89- dirs [:] = [d for d in dirs if d != '__pycache__' ]
90- for file in files :
91- file_path = os .path .join (root , file )
92- arcname = os .path .relpath (file_path , start = plugin_dir )
93- zf .write (file_path , arcname )
94-
89+ bio = await plugin_service .build (plugin = plugin )
9590 bio .seek (0 )
9691 return StreamingResponse (
9792 bio ,
0 commit comments