From c210f3032231f19a31d5282ec6a63d753d77c6ce Mon Sep 17 00:00:00 2001 From: yumoqing Date: Sun, 31 May 2026 12:09:45 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=A2=E6=88=B7API=20Key=E7=AE=A1?= =?UTF-8?q?=E7=90=86=20-=20=E5=88=9B=E5=BB=BA/=E6=9B=B4=E6=96=B0/=E5=A4=8D?= =?UTF-8?q?=E5=88=B6/=E5=88=A0=E9=99=A4=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. json/downapp.json: 添加更新和删除工具栏按钮,移除noedit限制 2. wwwroot/update_apikey.dspy: 查询并返回预填充的更新表单 3. wwwroot/do_update_apikey.dspy: 处理更新表单提交 4. wwwroot/delete_apikey.dspy: 删除API Key及关联应用 5. wwwroot/apikey_manage.ui: 独立API Key管理页面 6. scripts/load_path.py: 使用通配符%/dapi/%注册所有路径 --- json/downapp.json | 42 ++++++++++++-- scripts/load_path.py | 8 +-- wwwroot/apikey_manage.ui | 103 ++++++++++++++++++++++++++++++++++ wwwroot/delete_apikey.dspy | 29 ++++++++++ wwwroot/do_update_apikey.dspy | 52 +++++++++++++++++ wwwroot/update_apikey.dspy | 87 ++++++++++++++++++++++++++++ wwwroot/update_apikey.ui | 49 ++++++++++++++++ 7 files changed, 359 insertions(+), 11 deletions(-) create mode 100644 wwwroot/apikey_manage.ui create mode 100644 wwwroot/delete_apikey.dspy create mode 100644 wwwroot/do_update_apikey.dspy create mode 100644 wwwroot/update_apikey.dspy create mode 100644 wwwroot/update_apikey.ui diff --git a/json/downapp.json b/json/downapp.json index bf449c6..6ecee7f 100644 --- a/json/downapp.json +++ b/json/downapp.json @@ -1,11 +1,10 @@ { "tblname": "downapp", - "title":"下位系统", + "title":"API Key管理", "params": { "sortby":"id", "confidential_fields":["secretkey"], "logined_userorgid": "orgid", - "noedit": true, "browserfields": { "exclouded": ["secretkey", "orgid" ], "alters": {} @@ -17,14 +16,26 @@ "tools":[ { "name":"create_apikey", - "label":"创建apikey", + "label":"创建API Key", "icon":"{{entire_url('/imgs/newkey.svg')}}" }, { "name":"copy_apikey", - "label":"复制apikey", + "label":"复制API Key", "selected_row":true, "icon":"{{entire_url('/imgs/copy.svg')}}" + }, + { + "name":"update_apikey", + "label":"更新API Key", + "selected_row":true, + "icon":"{{entire_url('/imgs/edit.svg')}}" + }, + { + "name":"delete_apikey", + "label":"删除API Key", + "selected_row":true, + "icon":"{{entire_url('/imgs/delete.svg')}}" } ] }, @@ -56,6 +67,29 @@ "options":{ "url":"{{entire_url('/dapi/copy_apikey.dspy')}}" } + }, + { + "wid":"self", + "event":"update_apikey", + "actiontype":"urlwidget", + "target":"PopupWindow", + "popup_options":{ + "width":"50%", + "height":"50%", + "archor":"cr" + }, + "options":{ + "url":"{{entire_url('/dapi/update_apikey.dspy')}}" + } + }, + { + "wid":"self", + "event":"delete_apikey", + "actiontype":"urlwidget", + "target":"self", + "options":{ + "url":"{{entire_url('/dapi/delete_apikey.dspy')}}" + } } ] } diff --git a/scripts/load_path.py b/scripts/load_path.py index 232879b..72cc90a 100644 --- a/scripts/load_path.py +++ b/scripts/load_path.py @@ -19,13 +19,7 @@ from appPublic.uniqueID import getID paths = [ ("/dapi", "logined"), - ("/dapi/create_apikey.ui", "logined"), - ("/dapi/apply_apikey.dspy", "logined"), - ("/dapi/copy_apikey.dspy", "logined"), - ("/dapi/create_apikey.dspy", "logined"), - ("/dapi/downapps.dspy", "logined"), - ("/dapi/get_apikey.dspy", "logined"), - ("/dapi/jumpin.dspy", "logined"), + ("/dapi/%", "logined"), ] diff --git a/wwwroot/apikey_manage.ui b/wwwroot/apikey_manage.ui new file mode 100644 index 0000000..b24057a --- /dev/null +++ b/wwwroot/apikey_manage.ui @@ -0,0 +1,103 @@ +{ + "widgettype": "VBox", + "options": { + "width": "100%", + "height": "100%" + }, + "subwidgets": [ + { + "widgettype": "DataViewer", + "options": { + "title": "API Key管理", + "data_url": "{{entire_url('/dapi/downapps.dspy')}}", + "data_field": "apikeys", + "pageSize": 20, + "fields": [ + {"name": "id", "title": "应用ID", "width": "200px"}, + {"name": "name", "title": "应用名称", "width": "200px"}, + {"name": "apikeyid", "title": "API Key ID", "width": "200px"} + ], + "toolbar": { + "tools": [ + { + "name": "create_apikey", + "label": "创建API Key", + "icon": "{{entire_url('/imgs/newkey.svg')}}" + }, + { + "name": "copy_apikey", + "label": "复制API Key", + "selected_row": true, + "icon": "{{entire_url('/imgs/copy.svg')}}" + }, + { + "name": "update_apikey", + "label": "更新API Key", + "selected_row": true, + "icon": "{{entire_url('/imgs/edit.svg')}}" + }, + { + "name": "delete_apikey", + "label": "删除API Key", + "selected_row": true, + "icon": "{{entire_url('/imgs/delete.svg')}}" + } + ] + } + }, + "binds": [ + { + "wid": "self", + "event": "create_apikey", + "actiontype": "urlwidget", + "target": "PopupWindow", + "popup_options": { + "width": "50%", + "height": "50%", + "archor": "cr" + }, + "options": { + "url": "{{entire_url('/dapi/create_apikey.ui')}}" + } + }, + { + "wid": "self", + "event": "copy_apikey", + "actiontype": "urlwidget", + "target": "PopupWindow", + "popup_options": { + "width": "50%", + "height": "50%", + "archor": "cr" + }, + "options": { + "url": "{{entire_url('/dapi/copy_apikey.dspy')}}" + } + }, + { + "wid": "self", + "event": "update_apikey", + "actiontype": "urlwidget", + "target": "PopupWindow", + "popup_options": { + "width": "50%", + "height": "50%", + "archor": "cr" + }, + "options": { + "url": "{{entire_url('/dapi/update_apikey.dspy')}}" + } + }, + { + "wid": "self", + "event": "delete_apikey", + "actiontype": "urlwidget", + "target": "self", + "options": { + "url": "{{entire_url('/dapi/delete_apikey.dspy')}}" + } + } + ] + } + ] +} diff --git a/wwwroot/delete_apikey.dspy b/wwwroot/delete_apikey.dspy new file mode 100644 index 0000000..c14af30 --- /dev/null +++ b/wwwroot/delete_apikey.dspy @@ -0,0 +1,29 @@ +debug(f'{params_kw=}') +dbname = get_module_dbname('dapi') +db = DBPools() +userid = await get_user() +orgid = await get_userorgid() +if not userid: + return UiError(title='删除API Key', message='需要登录') + +try: + async with db.sqlorContext(dbname) as sor: + # 验证权限 + ns = { + "id": params_kw.id, + "orgid": orgid + } + sql = """select * from downapp + where id = ${id}$ and orgid = ${orgid}$""" + recs = await sor.sqlExe(sql, ns) + if not recs: + return UiError(title='删除API Key', message='API Key不存在或无权访问') + + # 删除downapikey + await sor.D('downapikey', {"dappid": params_kw.id}) + # 删除downapp + await sor.D('downapp', {"id": params_kw.id}) + + return UiMessage(title="删除API Key", message="API Key删除成功") +except Exception as e: + return UiError(title='删除API Key', message=f'删除失败: {e}') diff --git a/wwwroot/do_update_apikey.dspy b/wwwroot/do_update_apikey.dspy new file mode 100644 index 0000000..593ec20 --- /dev/null +++ b/wwwroot/do_update_apikey.dspy @@ -0,0 +1,52 @@ +debug(f'{params_kw=}') +dbname = get_module_dbname('dapi') +db = DBPools() +userid = await get_user() +orgid = await get_userorgid() +if not userid: + return UiError(title='更新API Key', message='需要登录') + +try: + async with db.sqlorContext(dbname) as sor: + # 验证权限 + ns = { + "id": params_kw.id, + "orgid": orgid + } + sql = """select * from downapp + where id = ${id}$ and orgid = ${orgid}$""" + recs = await sor.sqlExe(sql, ns) + if not recs: + return UiError(title='更新API Key', message='API Key不存在或无权访问') + + # 更新downapp + update_ns = { + "id": params_kw.id, + "name": params_kw.appname, + "description": params_kw.description, + "allowedips": params_kw.allowedips + } + await sor.U('downapp', update_ns) + + # 如果需要重新生成apikey + if params_kw.get('regenerate_apikey') == 'true': + new_apikey = password_encode(uuid()) + keys = await sor.R('downapikey', {"dappid": params_kw.id}) + if keys: + key_data = {"id": keys[0].id, "apikey": new_apikey} + await sor.U('downapikey', key_data) + + kw = { + "binds": [ + { + "wid": "self", + "event": "dismissed", + "actiontype": "script", + "target": "app.sage_main_content", + "script": "this.render()" + } + ] + } + return UiMessage(title="更新API Key", message="API Key更新成功", **kw) +except Exception as e: + return UiError(title='更新API Key', message=f'更新失败: {e}') diff --git a/wwwroot/update_apikey.dspy b/wwwroot/update_apikey.dspy new file mode 100644 index 0000000..8221fc3 --- /dev/null +++ b/wwwroot/update_apikey.dspy @@ -0,0 +1,87 @@ +debug(f'{params_kw=}') +dbname = get_module_dbname('dapi') +db = DBPools() +userid = await get_user() +orgid = await get_userorgid() +if not userid: + return { + "widgettype": "Text", + "options": {"text": "需要登录"} + } + +try: + async with db.sqlorContext(dbname) as sor: + # 查询当前downapp信息 + ns = { + "id": params_kw.id, + "orgid": orgid + } + sql = """select * from downapp + where id = ${id}$ and orgid = ${orgid}$""" + recs = await sor.sqlExe(sql, ns) + if not recs: + return { + "widgettype": "Text", + "options": {"text": "API Key不存在或无权访问"} + } + + r = recs[0] + # 返回预填充的表单 + return { + "widgettype": "Form", + "options": { + "title": "更新API Key", + "description": "更新应用信息和API Key配置", + "fields": [ + { + "name": "id", + "label": "应用ID", + "uitype": "str", + "value": r.id, + "readonly": True + }, + { + "name": "appname", + "label": "应用名称", + "uitype": "str", + "value": r.name, + "required": True + }, + { + "name": "description", + "label": "应用描述", + "uitype": "text", + "value": r.description or "" + }, + { + "name": "allowedips", + "label": "允许的IP集", + "uitype": "str", + "value": r.allowedips or "", + "placeholder": "多个IP用逗号分隔" + }, + { + "name": "regenerate_apikey", + "label": "重新生成API Key", + "uitype": "checkbox", + "description": "勾选后将重新生成API Key,原Key将失效" + } + ] + }, + "binds": [ + { + "wid": "self", + "event": "submit", + "actiontype": "urlwidget", + "target": "self", + "options": { + "url": "{{entire_url('/dapi/do_update_apikey.dspy')}}" + } + } + ] + } +except Exception as e: + return { + "widgettype": "Text", + "options": {"text": f"错误: {e}"} + } diff --git a/wwwroot/update_apikey.ui b/wwwroot/update_apikey.ui new file mode 100644 index 0000000..a8f490a --- /dev/null +++ b/wwwroot/update_apikey.ui @@ -0,0 +1,49 @@ +{ + "widgettype":"Form", + "options":{ + "title":"更新API Key", + "description":"更新应用信息和API Key配置", + "fields":[ + { + "name":"id", + "label":"应用ID", + "uitype":"str", + "readonly": true + }, + { + "name":"appname", + "label":"应用名称", + "uitype":"str", + "required": true + }, + { + "name":"description", + "label":"应用描述", + "uitype":"text" + }, + { + "name":"allowedips", + "label":"允许的IP集", + "uitype":"str", + "placeholder": "多个IP用逗号分隔" + }, + { + "name":"regenerate_apikey", + "label":"重新生成API Key", + "uitype":"checkbox", + "description": "勾选后将重新生成API Key,原Key将失效" + } + ] + }, + "binds":[ + { + "wid":"self", + "event":"submit", + "actiontype":"urlwidget", + "target":"self", + "options":{ + "url":"{{entire_url('./update_apikey.dspy')}}" + } + } + ] +}