1
1
#!/usr/bin/env python3
2
2
# -*- 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
8
4
9
5
from fastapi import APIRouter , Depends , File , UploadFile
10
6
from fastapi .params import Query
11
7
from starlette .responses import StreamingResponse
12
8
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
15
13
from backend .common .security .permission import RequestPermission
16
14
from backend .common .security .rbac import DependsRBAC
17
- from backend .core .path_conf import PLUGIN_DIR
18
- from backend .plugin .tools import install_requirements_async
19
15
20
16
router = APIRouter ()
21
17
22
18
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
+
23
39
@router .post (
24
- '/install' ,
25
- summary = '安装插件 ' ,
26
- description = '需使用插件 zip 压缩包进行安装 ' ,
40
+ '/install/git ' ,
41
+ summary = '安装 git 插件 ' ,
42
+ description = '使用插件 git 仓库地址进行安装,不限制平台;如果需要凭证,需在 git 仓库地址中添加凭证信息 ' ,
27
43
dependencies = [
28
44
Depends (RequestPermission ('sys:plugin:install' )),
29
45
DependsRBAC ,
30
46
],
31
47
)
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
+
50
52
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 )
57
65
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 ()
69
66
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 )
70
77
return response_base .success ()
71
78
72
79
@@ -79,19 +86,7 @@ async def install_plugin(file: Annotated[UploadFile, File()]) -> ResponseModel:
79
86
],
80
87
)
81
88
async 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 )
95
90
bio .seek (0 )
96
91
return StreamingResponse (
97
92
bio ,
0 commit comments