feat: 客户API Key管理 - 创建/更新/复制/删除功能

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/%注册所有路径
This commit is contained in:
yumoqing 2026-05-31 12:09:45 +08:00
parent 0c184fd5fe
commit c210f30322
7 changed files with 359 additions and 11 deletions

View File

@ -1,11 +1,10 @@
{ {
"tblname": "downapp", "tblname": "downapp",
"title":"下位系统", "title":"API Key管理",
"params": { "params": {
"sortby":"id", "sortby":"id",
"confidential_fields":["secretkey"], "confidential_fields":["secretkey"],
"logined_userorgid": "orgid", "logined_userorgid": "orgid",
"noedit": true,
"browserfields": { "browserfields": {
"exclouded": ["secretkey", "orgid" ], "exclouded": ["secretkey", "orgid" ],
"alters": {} "alters": {}
@ -17,14 +16,26 @@
"tools":[ "tools":[
{ {
"name":"create_apikey", "name":"create_apikey",
"label":"创建apikey", "label":"创建API Key",
"icon":"{{entire_url('/imgs/newkey.svg')}}" "icon":"{{entire_url('/imgs/newkey.svg')}}"
}, },
{ {
"name":"copy_apikey", "name":"copy_apikey",
"label":"复制apikey", "label":"复制API Key",
"selected_row":true, "selected_row":true,
"icon":"{{entire_url('/imgs/copy.svg')}}" "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":{ "options":{
"url":"{{entire_url('/dapi/copy_apikey.dspy')}}" "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')}}"
}
} }
] ]
} }

View File

@ -19,13 +19,7 @@ from appPublic.uniqueID import getID
paths = [ paths = [
("/dapi", "logined"), ("/dapi", "logined"),
("/dapi/create_apikey.ui", "logined"), ("/dapi/%", "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"),
] ]

103
wwwroot/apikey_manage.ui Normal file
View File

@ -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')}}"
}
}
]
}
]
}

View File

@ -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}')

View File

@ -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}')

View File

@ -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}"}
}

49
wwwroot/update_apikey.ui Normal file
View File

@ -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')}}"
}
}
]
}